Skip to content

Commit

Permalink
feat: paych CLI - lotus paych status <from> <to>
Browse files Browse the repository at this point in the history
  • Loading branch information
dirkmc committed Sep 3, 2020
1 parent 8423325 commit 930be3d
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 1 deletion.
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

0 comments on commit 930be3d

Please sign in to comment.