Skip to content
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

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
227 changes: 227 additions & 0 deletions cmd/lncli/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -866,3 +868,228 @@ func printRTAsJSON(r *rt.RoutingTable) {
}
printRespJson(channels)
}

var SendMultihopPayment = cli.Command{
Copy link
Member

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".

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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the latest version of master, when sending payments compressed public keys are used, so this should check for a length of 33 instead.

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",
Copy link
Member

Choose a reason for hiding this comment

The 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:

"attempts to find a path to the target destination node"

Usage: "findpath --destination <LightningID> --maxpath <MaxPath> --validate --amount <Satoshis>",
Flags: []cli.Flag{
cli.StringFlag{
Name: "destination",
Usage: "lightningID of destination",
Copy link
Member

Choose a reason for hiding this comment

The 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",
Copy link
Member

Choose a reason for hiding this comment

The 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 maxpathlength.

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",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about:

"total amount in satoshis to send to the final destination"

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 {
Copy link
Member

Choose a reason for hiding this comment

The 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++{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Go's built-in for _, _ := range construct can be used here to make the loop a bit more readable.

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{
Copy link
Member

@Roasbeef Roasbeef Sep 22, 2016

Choose a reason for hiding this comment

The 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 sendmultihop command.

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 stdout.

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 {
Copy link
Member

Choose a reason for hiding this comment

The 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
}
3 changes: 3 additions & 0 deletions cmd/lncli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ func main() {
PendingChannelsCommand,
SendPaymentCommand,
ShowRoutingTableCommand,
SendMultihopPayment,
FindPathCommand,
PayMultihopCommand,
}

if err := app.Run(os.Args); err != nil {
Expand Down
18 changes: 9 additions & 9 deletions glide.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion glide.yaml
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
Copy link
Member

Choose a reason for hiding this comment

The 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
Expand Down
Loading