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

paych: Create lotus paych status command #3523

Merged
merged 1 commit into from
Sep 4, 2020
Merged
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
87 changes: 87 additions & 0 deletions cli/paych.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
"sort"
"strings"

"github.com/filecoin-project/lotus/paychmgr"

Expand Down Expand Up @@ -79,6 +80,92 @@ var paychGetCmd = &cli.Command{
},
}

var paychStatusCmd = &cli.Command{
Name: "status",
Usage: "Show the status of an outbound payment channel between fromAddress and toAddress",
ArgsUsage: "[fromAddress toAddress]",
Action: func(cctx *cli.Context) error {
if cctx.Args().Len() != 2 {
return ShowHelp(cctx, fmt.Errorf("must pass two arguments: <from> <to>"))
}

from, err := address.NewFromString(cctx.Args().Get(0))
if err != nil {
return ShowHelp(cctx, fmt.Errorf("failed to parse from address: %s", err))
}

to, err := address.NewFromString(cctx.Args().Get(1))
if err != nil {
return ShowHelp(cctx, fmt.Errorf("failed to parse to address: %s", err))
}

api, closer, err := GetFullNodeAPI(cctx)
if err != nil {
return err
}
defer closer()

avail, err := api.PaychAvailableFunds(from, to)
if err != nil {
return err
}

if avail.Channel == nil {
if avail.PendingWaitSentinel != nil {
fmt.Fprint(cctx.App.Writer, "Creating channel\n")
fmt.Fprintf(cctx.App.Writer, " From: %s\n", from)
fmt.Fprintf(cctx.App.Writer, " To: %s\n", to)
fmt.Fprintf(cctx.App.Writer, " Pending Amt: %d\n", avail.PendingAmt)
fmt.Fprintf(cctx.App.Writer, " Wait Sentinel: %s\n", avail.PendingWaitSentinel)
return nil
}
fmt.Fprint(cctx.App.Writer, "Channel does not exist\n")
fmt.Fprintf(cctx.App.Writer, " From: %s\n", from)
fmt.Fprintf(cctx.App.Writer, " To: %s\n", to)
return nil
}

if avail.PendingWaitSentinel != nil {
fmt.Fprint(cctx.App.Writer, "Adding Funds to channel\n")
} else {
fmt.Fprint(cctx.App.Writer, "Channel exists\n")
}

nameValues := [][]string{
{"Channel", avail.Channel.String()},
{"From", from.String()},
{"To", to.String()},
{"Confirmed Amt", fmt.Sprintf("%d", avail.ConfirmedAmt)},
{"Pending Amt", fmt.Sprintf("%d", avail.PendingAmt)},
{"Queued Amt", fmt.Sprintf("%d", avail.QueuedAmt)},
{"Voucher Redeemed Amt", fmt.Sprintf("%d", avail.VoucherReedeemedAmt)},
}
if avail.PendingWaitSentinel != nil {
nameValues = append(nameValues, []string{
"Add Funds Wait Sentinel",
avail.PendingWaitSentinel.String(),
})
}
fmt.Fprint(cctx.App.Writer, formatNameValues(nameValues))
return nil
},
}

func formatNameValues(nameValues [][]string) string {
maxLen := 0
for _, nv := range nameValues {
if len(nv[0]) > maxLen {
maxLen = len(nv[0])
}
}
out := make([]string, len(nameValues))
for i, nv := range nameValues {
namePad := strings.Repeat(" ", maxLen-len(nv[0]))
out[i] = " " + nv[0] + ": " + namePad + nv[1]
}
return strings.Join(out, "\n") + "\n"
}

var paychListCmd = &cli.Command{
Name: "list",
Usage: "List all locally registered payment channels",
Expand Down
81 changes: 80 additions & 1 deletion cli/paych_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"testing"
"time"

"github.com/filecoin-project/lotus/build"

"github.com/filecoin-project/specs-actors/actors/abi/big"
saminer "github.com/filecoin-project/specs-actors/actors/builtin/miner"
"github.com/filecoin-project/specs-actors/actors/builtin/power"
Expand Down Expand Up @@ -53,7 +55,7 @@ func TestPaymentChannels(t *testing.T) {
ctx := context.Background()
nodes, addrs := startTwoNodesOneMiner(ctx, t, blocktime)
paymentCreator := nodes[0]
paymentReceiver := nodes[0]
paymentReceiver := nodes[1]
creatorAddr := addrs[0]
receiverAddr := addrs[1]

Expand Down Expand Up @@ -99,6 +101,83 @@ type voucherSpec struct {
lane int
}

// TestPaymentChannelStatus tests the payment channel status CLI command
func TestPaymentChannelStatus(t *testing.T) {
_ = os.Setenv("BELLMAN_NO_GPU", "1")

blocktime := 5 * time.Millisecond
ctx := context.Background()
nodes, addrs := startTwoNodesOneMiner(ctx, t, blocktime)
paymentCreator := nodes[0]
creatorAddr := addrs[0]
receiverAddr := addrs[1]

// Create mock CLI
mockCLI := newMockCLI(t)
creatorCLI := mockCLI.client(paymentCreator.ListenAddr)

cmd := []string{creatorAddr.String(), receiverAddr.String()}
out := creatorCLI.runCmd(paychStatusCmd, cmd)
fmt.Println(out)
noChannelState := "Channel does not exist"
require.Regexp(t, regexp.MustCompile(noChannelState), out)

channelAmt := uint64(100)
create := make(chan string)
go func() {
// creator: paych get <creator> <receiver> <amount>
cmd = []string{creatorAddr.String(), receiverAddr.String(), fmt.Sprintf("%d", channelAmt)}
create <- creatorCLI.runCmd(paychGetCmd, cmd)
}()

// Wait for the output to stop being "Channel does not exist"
for regexp.MustCompile(noChannelState).MatchString(out) {
cmd = []string{creatorAddr.String(), receiverAddr.String()}
out = creatorCLI.runCmd(paychStatusCmd, cmd)
}
fmt.Println(out)

// The next state should be creating channel or channel created, depending
// on timing
stateCreating := regexp.MustCompile("Creating channel").MatchString(out)
stateCreated := regexp.MustCompile("Channel exists").MatchString(out)
require.True(t, stateCreating || stateCreated)

channelAmtAtto := types.BigMul(types.NewInt(channelAmt), types.NewInt(build.FilecoinPrecision))
channelAmtStr := fmt.Sprintf("%d", channelAmtAtto)
if stateCreating {
// If we're in the creating state (most likely) the amount should be pending
require.Regexp(t, regexp.MustCompile("Pending.*"+channelAmtStr), out)
}

// Wait for create channel to complete
chstr := <-create

cmd = []string{creatorAddr.String(), receiverAddr.String()}
out = creatorCLI.runCmd(paychStatusCmd, cmd)
fmt.Println(out)
// Output should have the channel address
require.Regexp(t, regexp.MustCompile("Channel.*"+chstr), out)
// Output should have confirmed amount
require.Regexp(t, regexp.MustCompile("Confirmed.*"+channelAmtStr), out)

chAddr, err := address.NewFromString(chstr)
require.NoError(t, err)

// creator: paych voucher create <channel> <amount>
voucherAmt := uint64(10)
cmd = []string{chAddr.String(), fmt.Sprintf("%d", voucherAmt)}
creatorCLI.runCmd(paychVoucherCreateCmd, cmd)

cmd = []string{creatorAddr.String(), receiverAddr.String()}
out = creatorCLI.runCmd(paychStatusCmd, cmd)
fmt.Println(out)
voucherAmtAtto := types.BigMul(types.NewInt(voucherAmt), types.NewInt(build.FilecoinPrecision))
voucherAmtStr := fmt.Sprintf("%d", voucherAmtAtto)
// Output should include voucher amount
require.Regexp(t, regexp.MustCompile("Voucher.*"+voucherAmtStr), out)
}

// TestPaymentChannelVouchers does a basic test to exercise some payment
// channel voucher commands
func TestPaymentChannelVouchers(t *testing.T) {
Expand Down