mirror of
https://github.com/lightninglabs/pool.git
synced 2026-08-13 12:33:04 +02:00
cmd/pool: add sidecar commands for offer/register/expect
With the RPC methods now in place, we add a new sidecar subcommand to our CLI and add verbs for interacting with sidecars to it. Because the ticket is in an encoded form we also add a printticket subcommand for easy inspection.
This commit is contained in:
parent
15eeee2a3e
commit
14da89be07
3 changed files with 385 additions and 5 deletions
|
|
@ -4,7 +4,6 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
|
@ -17,6 +16,7 @@ import (
|
|||
"github.com/lightninglabs/lndclient"
|
||||
"github.com/lightninglabs/pool"
|
||||
"github.com/lightninglabs/pool/poolrpc"
|
||||
"github.com/lightninglabs/protobuf-hex-display/json"
|
||||
"github.com/lightninglabs/protobuf-hex-display/jsonpb"
|
||||
"github.com/lightninglabs/protobuf-hex-display/proto"
|
||||
"github.com/lightningnetwork/lnd/lncfg"
|
||||
|
|
@ -133,6 +133,7 @@ func main() {
|
|||
}
|
||||
app.Commands = append(app.Commands, accountsCommands...)
|
||||
app.Commands = append(app.Commands, ordersCommands...)
|
||||
app.Commands = append(app.Commands, sidecarCommands...)
|
||||
app.Commands = append(app.Commands, auctionCommands...)
|
||||
app.Commands = append(app.Commands, listAuthCommand)
|
||||
app.Commands = append(app.Commands, getInfoCommand)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
|
@ -283,7 +284,7 @@ func ordersSubmitAsk(ctx *cli.Context) error { // nolint: dupl
|
|||
ask.LeaseDurationBlocks,
|
||||
chainfee.SatPerKWeight(
|
||||
ask.Details.MaxBatchFeeRateSatPerKw,
|
||||
), true,
|
||||
), true, nil,
|
||||
); err != nil {
|
||||
return fmt.Errorf("unable to print order details: %v", err)
|
||||
}
|
||||
|
|
@ -313,7 +314,7 @@ func ordersSubmitAsk(ctx *cli.Context) error { // nolint: dupl
|
|||
func printOrderDetails(client poolrpc.TraderClient, amt,
|
||||
minChanAmt, selfChanBalance btcutil.Amount, rate order.FixedRatePremium,
|
||||
leaseDuration uint32, maxBatchFeeRate chainfee.SatPerKWeight,
|
||||
isAsk bool) error {
|
||||
isAsk bool, sidecarTicket *sidecar.Ticket) error {
|
||||
|
||||
auctionFee, err := client.AuctionFee(
|
||||
context.Background(), &poolrpc.AuctionFeeRequest{},
|
||||
|
|
@ -356,6 +357,12 @@ func printOrderDetails(client poolrpc.TraderClient, amt,
|
|||
fmt.Printf("Self channel balance: %v\n", selfChanBalance)
|
||||
}
|
||||
|
||||
if sidecarTicket != nil {
|
||||
fmt.Println("Sidecar order: ")
|
||||
fmt.Printf(" Recipient node: %x\n",
|
||||
sidecarTicket.Recipient.NodePubKey.SerializeCompressed())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -413,6 +420,17 @@ var ordersSubmitBidCommand = cli.Command{
|
|||
"from our account into the channel; can be " +
|
||||
"used to create up to 50/50 balanced channels",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "sidecar_ticket",
|
||||
Usage: "instead of leasing a channel for the node " +
|
||||
"connected to this pool instance, lease a " +
|
||||
"channel for another node; use the " +
|
||||
"information within the ticket to identify " +
|
||||
"the receiver of the sidecar channel; using " +
|
||||
"a sidecar ticket will also overwrite the " +
|
||||
"amt, min_chan_amt, lease_duration_blocks " +
|
||||
"and self_chan_balance fields",
|
||||
},
|
||||
}, sharedFlags...),
|
||||
Action: ordersSubmitBid,
|
||||
}
|
||||
|
|
@ -444,10 +462,54 @@ func ordersSubmitBid(ctx *cli.Context) error { // nolint: dupl
|
|||
|
||||
bid := &poolrpc.Bid{
|
||||
LeaseDurationBlocks: uint32(ctx.Uint64("lease_duration_blocks")),
|
||||
Version: uint32(order.VersionSelfChanBalance),
|
||||
Version: uint32(order.VersionSidecarChannel),
|
||||
MinNodeTier: nodeTier,
|
||||
}
|
||||
|
||||
// Let's find out if this is an order for a sidecar channel because if
|
||||
// it is, we can take some of the information out of the ticket and
|
||||
// don't require the user to enter them manually again.
|
||||
var ticket *sidecar.Ticket
|
||||
if ctx.IsSet("sidecar_ticket") {
|
||||
// The ticket is expected in the string encoded version which
|
||||
// has a prefix and a checksum. We're supposed to send it to
|
||||
// the daemon in its raw format though. So let's decode and
|
||||
// check it in the process.
|
||||
ticket, err = sidecar.DecodeString(ctx.String("sidecar_ticket"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse sidecar ticket: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
// Let's make sure the ticket is in the correct state. This will
|
||||
// be checked by the server as well but we want to make sure we
|
||||
// don't run into a nil reference when printing the order
|
||||
// details below.
|
||||
if ticket.State != sidecar.StateRegistered ||
|
||||
ticket.Recipient == nil {
|
||||
|
||||
return fmt.Errorf("unexpected sidecar ticket state "+
|
||||
"%d, possibly not registered with recipient "+
|
||||
"node yet", ticket.State)
|
||||
}
|
||||
|
||||
// With the ticket parsed and formally checked, we can now pre-
|
||||
// fill the amount and min channel amount. Those values must
|
||||
// match the offered capacity, otherwise the push amount won't
|
||||
// work as expected and the protocol would get more complex as
|
||||
// well.
|
||||
amtStr := fmt.Sprintf("%d", ticket.Offer.Capacity)
|
||||
pushAmtStr := fmt.Sprintf("%d", ticket.Offer.PushAmt)
|
||||
_ = ctx.Set("amt", amtStr)
|
||||
_ = ctx.Set("min_chan_amt", amtStr)
|
||||
_ = ctx.Set("self_chan_balance", pushAmtStr)
|
||||
bid.LeaseDurationBlocks = ticket.Offer.LeaseDurationBlocks
|
||||
|
||||
// Looks good so far. The rest will be checked server side. For
|
||||
// now we can just add the ticket to the order.
|
||||
bid.SidecarTicket = ctx.String("sidecar_ticket")
|
||||
}
|
||||
|
||||
params, err := parseCommonParams(ctx, bid.LeaseDurationBlocks)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse order params: %v", err)
|
||||
|
|
@ -495,7 +557,7 @@ func ordersSubmitBid(ctx *cli.Context) error { // nolint: dupl
|
|||
bid.LeaseDurationBlocks,
|
||||
chainfee.SatPerKWeight(
|
||||
bid.Details.MaxBatchFeeRateSatPerKw,
|
||||
), false,
|
||||
), false, ticket,
|
||||
); err != nil {
|
||||
return fmt.Errorf("unable to print order details: %v", err)
|
||||
}
|
||||
|
|
@ -517,8 +579,38 @@ func ordersSubmitBid(ctx *cli.Context) error { // nolint: dupl
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printRespJSON(resp)
|
||||
|
||||
// If there was a sidecar ticket, we now also need to display the
|
||||
// updated ticket that contains the order nonce (and a signature over
|
||||
// that). This ticket needs to be given to the recipient for them to
|
||||
// initiate the last step of the sidecar channel protocol.
|
||||
// The signed ticket is available in the output of the ListOrders call.
|
||||
if ticket != nil {
|
||||
newBidNonce := resp.GetAcceptedOrderNonce()
|
||||
allOrders, err := client.ListOrders(
|
||||
context.Background(), &poolrpc.ListOrdersRequest{
|
||||
ActiveOnly: true,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing orders to print "+
|
||||
"updated sidecar ticket: %v", err)
|
||||
}
|
||||
|
||||
// Find the order we just created.
|
||||
for _, bid := range allOrders.Bids {
|
||||
if bytes.Equal(newBidNonce, bid.Details.OrderNonce) {
|
||||
printJSON(struct {
|
||||
Ticket string `json:"ticket"`
|
||||
}{
|
||||
Ticket: bid.SidecarTicket,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
287
cmd/pool/sidecar.go
Normal file
287
cmd/pool/sidecar.go
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/lightninglabs/pool/order"
|
||||
"github.com/lightninglabs/pool/poolrpc"
|
||||
"github.com/lightninglabs/pool/sidecar"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var sidecarCommands = []cli.Command{
|
||||
{
|
||||
Name: "sidecar",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "Manage sidecar channels.",
|
||||
Category: "Orders",
|
||||
Subcommands: []cli.Command{
|
||||
sidecarOfferCommand,
|
||||
sidecarPrintTicketCommand,
|
||||
sidecarRegisterCommand,
|
||||
sidecarExpectChannelCommand,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var sidecarOfferCommand = cli.Command{
|
||||
Name: "offer",
|
||||
Aliases: []string{"o"},
|
||||
Usage: "offer a sidecar channel",
|
||||
ArgsUsage: "capacity self_chan_balance lease_duration_blocks",
|
||||
Description: `
|
||||
Creates an offer for providing a sidecar channel to another node.`,
|
||||
Flags: []cli.Flag{
|
||||
cli.Uint64Flag{
|
||||
Name: "capacity",
|
||||
Usage: "the total channel capacity of the sidecar " +
|
||||
"channel to offer",
|
||||
},
|
||||
cli.Uint64Flag{
|
||||
Name: "self_chan_balance",
|
||||
Usage: "the number of satoshis that should be pushed " +
|
||||
"to the recipient of the sidecar channel as " +
|
||||
"initial outbound channel balance; amount " +
|
||||
"will be deducted from account that submits " +
|
||||
"bid order, reimbursement must happen out of " +
|
||||
"band, not part of the sidecar protocol",
|
||||
},
|
||||
cli.Uint64Flag{
|
||||
Name: "lease_duration_blocks",
|
||||
Usage: "the number of blocks the resulting leased " +
|
||||
"channel should be open for",
|
||||
Value: uint64(order.LegacyLeaseDurationBucket),
|
||||
},
|
||||
},
|
||||
Action: sidecarOffer,
|
||||
}
|
||||
|
||||
func sidecarOffer(ctx *cli.Context) error {
|
||||
// Show help if no arguments or flags are provided.
|
||||
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
|
||||
_ = cli.ShowCommandHelp(ctx, "offer")
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
args = ctx.Args()
|
||||
capacity, pushAmt uint64
|
||||
duration uint32
|
||||
)
|
||||
|
||||
switch {
|
||||
case ctx.IsSet("capacity"):
|
||||
capacity = ctx.Uint64("capacity")
|
||||
case args.Present():
|
||||
parsed, err := parseAmt(args.First())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to decode capacity: %v", err)
|
||||
}
|
||||
capacity = uint64(parsed)
|
||||
args = args.Tail()
|
||||
}
|
||||
|
||||
switch {
|
||||
case ctx.IsSet("self_chan_balance"):
|
||||
pushAmt = ctx.Uint64("self_chan_balance")
|
||||
case args.Present():
|
||||
parsed, err := parseAmt(args.First())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to decode self channel "+
|
||||
"balance: %v", err)
|
||||
}
|
||||
pushAmt = uint64(parsed)
|
||||
args = args.Tail()
|
||||
}
|
||||
|
||||
switch {
|
||||
case ctx.IsSet("lease_duration_blocks"):
|
||||
duration = uint32(ctx.Uint64("lease_duration_blocks"))
|
||||
case args.Present():
|
||||
duration64, err := strconv.ParseInt(args.First(), 10, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse lease duration "+
|
||||
"blocks: %v", err)
|
||||
}
|
||||
duration = uint32(duration64)
|
||||
args = args.Tail()
|
||||
}
|
||||
|
||||
client, cleanup, err := getClient(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
resp, err := client.OfferSidecar(
|
||||
context.Background(), &poolrpc.OfferSidecarRequest{
|
||||
ChannelCapacitySat: capacity,
|
||||
SelfChanBalance: pushAmt,
|
||||
LeaseDurationBlocks: duration,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printRespJSON(resp)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type jsonTicket struct {
|
||||
ID string
|
||||
Version uint8
|
||||
State string
|
||||
Capacity uint64
|
||||
PushAmount uint64
|
||||
LeaseDurationBlocks uint32
|
||||
OfferSigningPubKey string
|
||||
RecipientNodePubKey string
|
||||
RecipientMultiSigPubKey string
|
||||
RecipientMultiSigKeyIndex uint32
|
||||
OrderNonce string
|
||||
}
|
||||
|
||||
var sidecarPrintTicketCommand = cli.Command{
|
||||
Name: "printticket",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "decode and print the content of a sidecar ticket",
|
||||
ArgsUsage: "ticket",
|
||||
Description: `
|
||||
Tries to decode the given ticket from the human readable (prefixed)
|
||||
base64 encoded version.`,
|
||||
Action: sidecarPrintTicket,
|
||||
}
|
||||
|
||||
func sidecarPrintTicket(ctx *cli.Context) error {
|
||||
// Show help if no arguments or flags are provided.
|
||||
if ctx.NArg() != 1 || ctx.NumFlags() != 0 {
|
||||
_ = cli.ShowCommandHelp(ctx, "printticket")
|
||||
return nil
|
||||
}
|
||||
|
||||
ticketStr := ctx.Args().First()
|
||||
|
||||
ticket, err := sidecar.DecodeString(ticketStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error decoding base64 ticket: %v", err)
|
||||
}
|
||||
|
||||
jsonTicket := &jsonTicket{
|
||||
ID: hex.EncodeToString(ticket.ID[:]),
|
||||
Version: uint8(ticket.Version),
|
||||
State: ticket.State.String(),
|
||||
Capacity: uint64(ticket.Offer.Capacity),
|
||||
PushAmount: uint64(ticket.Offer.PushAmt),
|
||||
LeaseDurationBlocks: ticket.Offer.LeaseDurationBlocks,
|
||||
}
|
||||
|
||||
if ticket.Offer.SignPubKey != nil {
|
||||
jsonTicket.OfferSigningPubKey = hex.EncodeToString(
|
||||
ticket.Offer.SignPubKey.SerializeCompressed(),
|
||||
)
|
||||
}
|
||||
if ticket.Recipient != nil {
|
||||
if ticket.Recipient.NodePubKey != nil {
|
||||
jsonTicket.RecipientNodePubKey = hex.EncodeToString(
|
||||
ticket.Recipient.NodePubKey.SerializeCompressed(),
|
||||
)
|
||||
}
|
||||
if ticket.Recipient.MultiSigPubKey != nil {
|
||||
jsonTicket.RecipientMultiSigPubKey = hex.EncodeToString(
|
||||
ticket.Recipient.MultiSigPubKey.SerializeCompressed(),
|
||||
)
|
||||
}
|
||||
jsonTicket.RecipientMultiSigKeyIndex = ticket.Recipient.MultiSigKeyIndex
|
||||
}
|
||||
if ticket.Order != nil {
|
||||
jsonTicket.OrderNonce = hex.EncodeToString(
|
||||
ticket.Order.BidNonce[:],
|
||||
)
|
||||
}
|
||||
|
||||
printJSON(jsonTicket)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var sidecarRegisterCommand = cli.Command{
|
||||
Name: "register",
|
||||
Aliases: []string{"r"},
|
||||
Usage: "register an incoming sidecar channel and add node info to " +
|
||||
"ticket",
|
||||
ArgsUsage: "ticket",
|
||||
Description: `
|
||||
Registers a sidecar ticket for an incoming sidecar channel with the node
|
||||
and adds its recipient information to it, resulting in an updated ticket
|
||||
that needs to be handed back to the provider.`,
|
||||
Action: sidecarRegister,
|
||||
}
|
||||
|
||||
func sidecarRegister(ctx *cli.Context) error {
|
||||
// Show help if no arguments or flags are provided.
|
||||
if ctx.NArg() != 1 || ctx.NumFlags() != 0 {
|
||||
_ = cli.ShowCommandHelp(ctx, "register")
|
||||
return nil
|
||||
}
|
||||
|
||||
client, cleanup, err := getClient(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
resp, err := client.RegisterSidecar(
|
||||
context.Background(), &poolrpc.RegisterSidecarRequest{
|
||||
Ticket: ctx.Args().First(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printRespJSON(resp)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var sidecarExpectChannelCommand = cli.Command{
|
||||
Name: "expectchannel",
|
||||
Aliases: []string{"e"},
|
||||
Usage: "start waiting for sidecar channel",
|
||||
ArgsUsage: "ticket",
|
||||
Description: `
|
||||
Connect to the auctioneer and wait for a sidecar order to be matched and
|
||||
a channel being opened to us.`,
|
||||
Action: sidecarExpectChannel,
|
||||
}
|
||||
|
||||
func sidecarExpectChannel(ctx *cli.Context) error {
|
||||
// Show help if no arguments or flags are provided.
|
||||
if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
|
||||
_ = cli.ShowCommandHelp(ctx, "expectchannel")
|
||||
return nil
|
||||
}
|
||||
|
||||
client, cleanup, err := getClient(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
resp, err := client.ExpectSidecarChannel(
|
||||
context.Background(), &poolrpc.ExpectSidecarChannelRequest{
|
||||
Ticket: ctx.Args().First(),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printRespJSON(resp)
|
||||
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue