Skip to content

Commit cfa9b3a

Browse files
authored
paych module + client + cli (textileio#437)
* Wip on a paych module Signed-off-by: Aaron Sutula <hi@asutula.com> * added create impl, updated interface Signed-off-by: Aaron Sutula <hi@asutula.com> * adding more to the interface Signed-off-by: Aaron Sutula <hi@asutula.com> * Implemet paych create Signed-off-by: Aaron Sutula <hi@asutula.com> * updates and implementing ffs paych rpc Signed-off-by: Aaron Sutula <hi@asutula.com> * paych client code Signed-off-by: Aaron Sutula <hi@asutula.com> * cli Signed-off-by: Aaron Sutula <hi@asutula.com> * pr feedback Signed-off-by: Aaron Sutula <hi@asutula.com>
1 parent 65a8d94 commit cfa9b3a

18 files changed

Lines changed: 1823 additions & 555 deletions

File tree

api/client/ffs.go

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ func (f *FFS) WatchJobs(ctx context.Context, ch chan<- JobEvent, jids ...ffs.Job
340340
close(ch)
341341
break
342342
}
343-
dealErrors, err := protoToDealError(reply.Job.DealErrors)
343+
dealErrors, err := fromRPCDealErrors(reply.Job.DealErrors)
344344
if err != nil {
345345
ch <- JobEvent{Err: err}
346346
close(ch)
@@ -536,6 +536,44 @@ func (f *FFS) AddToHot(ctx context.Context, data io.Reader) (*cid.Cid, error) {
536536
return &cid, nil
537537
}
538538

539+
// ListPayChannels returns a list of payment channels
540+
func (f *FFS) ListPayChannels(ctx context.Context) ([]ffs.PaychInfo, error) {
541+
resp, err := f.client.ListPayChannels(ctx, &rpc.ListPayChannelsRequest{})
542+
if err != nil {
543+
return []ffs.PaychInfo{}, err
544+
}
545+
infos := make([]ffs.PaychInfo, len(resp.PayChannels))
546+
for i, info := range resp.PayChannels {
547+
infos[i] = fromRPCPaychInfo(info)
548+
}
549+
return infos, nil
550+
}
551+
552+
// CreatePayChannel creates a new payment channel
553+
func (f *FFS) CreatePayChannel(ctx context.Context, from string, to string, amount uint64) (ffs.PaychInfo, cid.Cid, error) {
554+
req := &rpc.CreatePayChannelRequest{
555+
From: from,
556+
To: to,
557+
Amount: amount,
558+
}
559+
resp, err := f.client.CreatePayChannel(ctx, req)
560+
if err != nil {
561+
return ffs.PaychInfo{}, cid.Undef, err
562+
}
563+
messageCid, err := cid.Decode(resp.ChannelMessageCid)
564+
if err != nil {
565+
return ffs.PaychInfo{}, cid.Undef, err
566+
}
567+
return fromRPCPaychInfo(resp.PayChannel), messageCid, nil
568+
}
569+
570+
// RedeemPayChannel redeems a payment channel
571+
func (f *FFS) RedeemPayChannel(ctx context.Context, addr string) error {
572+
req := &rpc.RedeemPayChannelRequest{PayChannelAddr: addr}
573+
_, err := f.client.RedeemPayChannel(ctx, req)
574+
return err
575+
}
576+
539577
func toRPCHotConfig(config ffs.HotConfig) *rpc.HotConfig {
540578
return &rpc.HotConfig{
541579
Enabled: config.Enabled,
@@ -564,7 +602,7 @@ func toRPCColdConfig(config ffs.ColdConfig) *rpc.ColdConfig {
564602
}
565603
}
566604

567-
func protoToDealError(des []*rpc.DealError) ([]ffs.DealError, error) {
605+
func fromRPCDealErrors(des []*rpc.DealError) ([]ffs.DealError, error) {
568606
res := make([]ffs.DealError, len(des))
569607
for i, de := range des {
570608
var propCid cid.Cid
@@ -583,3 +621,20 @@ func protoToDealError(des []*rpc.DealError) ([]ffs.DealError, error) {
583621
}
584622
return res, nil
585623
}
624+
625+
func fromRPCPaychInfo(info *rpc.PaychInfo) ffs.PaychInfo {
626+
var direction ffs.PaychDir
627+
switch info.Direction {
628+
case rpc.Direction_DIRECTION_INBOUND:
629+
direction = ffs.PaychDirInbound
630+
case rpc.Direction_DIRECTION_OUTBOUND:
631+
direction = ffs.PaychDirOutbound
632+
default:
633+
direction = ffs.PaychDirUnspecified
634+
}
635+
return ffs.PaychInfo{
636+
CtlAddr: info.CtlAddr,
637+
Addr: info.Addr,
638+
Direction: direction,
639+
}
640+
}

api/server/server.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import (
4444
pgnet "github.com/textileio/powergate/net"
4545
pgnetlotus "github.com/textileio/powergate/net/lotus"
4646
pgnetRpc "github.com/textileio/powergate/net/rpc"
47+
paychLotus "github.com/textileio/powergate/paych/lotus"
4748
"github.com/textileio/powergate/reputation"
4849
reputationRpc "github.com/textileio/powergate/reputation/rpc"
4950
txndstr "github.com/textileio/powergate/txndstransform"
@@ -170,6 +171,7 @@ func NewServer(conf Config) (*Server, error) {
170171
if err != nil {
171172
return nil, fmt.Errorf("creating wallet module: %s", err)
172173
}
174+
pm := paychLotus.New(c)
173175
rm := reputation.New(txndstr.Wrap(ds, "reputation"), mi, si, ai)
174176
nm := pgnetlotus.New(c, ip2l)
175177
hm := health.New(nm)
@@ -190,7 +192,7 @@ func NewServer(conf Config) (*Server, error) {
190192
return nil, fmt.Errorf("creating scheduler: %s", err)
191193
}
192194

193-
ffsManager, err := manager.New(txndstr.Wrap(ds, "ffs/manager"), wm, sched)
195+
ffsManager, err := manager.New(txndstr.Wrap(ds, "ffs/manager"), wm, pm, sched)
194196
if err != nil {
195197
return nil, fmt.Errorf("creating ffs instance: %s", err)
196198
}

cmd/pow/cmd/ffs_paych.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package cmd
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
)
6+
7+
func init() {
8+
ffsCmd.AddCommand(ffsPaychCmd)
9+
}
10+
11+
var ffsPaychCmd = &cobra.Command{
12+
Use: "paych",
13+
Short: "Provides commands to manage payment channels",
14+
Long: `Provides commands to manage payment channels`,
15+
}

cmd/pow/cmd/ffs_paych_create.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"strconv"
6+
7+
"github.com/caarlos0/spin"
8+
"github.com/spf13/cobra"
9+
"github.com/spf13/viper"
10+
)
11+
12+
func init() {
13+
ffsPaychCreateCmd.Flags().StringP("token", "t", "", "token of the request")
14+
15+
ffsPaychCmd.AddCommand(ffsPaychCreateCmd)
16+
}
17+
18+
var ffsPaychCreateCmd = &cobra.Command{
19+
Use: "create [from] [to] [amount]",
20+
Short: "Create a payment channel",
21+
Long: `Create a payment channel`,
22+
Args: cobra.ExactArgs(3),
23+
PreRun: func(cmd *cobra.Command, args []string) {
24+
err := viper.BindPFlags(cmd.Flags())
25+
checkErr(err)
26+
},
27+
Run: func(cmd *cobra.Command, args []string) {
28+
ctx, cancel := context.WithTimeout(context.Background(), cmdTimeout)
29+
defer cancel()
30+
31+
from := args[0]
32+
to := args[1]
33+
amt, err := strconv.ParseInt(args[2], 10, 64)
34+
checkErr(err)
35+
36+
s := spin.New("%s Creating payment channel...")
37+
s.Start()
38+
chInfo, msgCid, err := fcClient.FFS.CreatePayChannel(authCtx(ctx), from, to, uint64(amt))
39+
s.Stop()
40+
checkErr(err)
41+
42+
Success("Created payment channel with address %v and message cid %v", chInfo.Addr, msgCid.String())
43+
},
44+
}

cmd/pow/cmd/ffs_paych_list.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"os"
6+
7+
"github.com/caarlos0/spin"
8+
"github.com/spf13/cobra"
9+
"github.com/spf13/viper"
10+
"github.com/textileio/powergate/ffs"
11+
)
12+
13+
func init() {
14+
ffsPaychListCmd.Flags().StringP("token", "t", "", "token of the request")
15+
16+
ffsPaychCmd.AddCommand(ffsPaychListCmd)
17+
}
18+
19+
var ffsPaychListCmd = &cobra.Command{
20+
Use: "list",
21+
Short: "List the payment channels for the ffs instance",
22+
Long: `List the payment channels for the ffs instance`,
23+
PreRun: func(cmd *cobra.Command, args []string) {
24+
err := viper.BindPFlags(cmd.Flags())
25+
checkErr(err)
26+
},
27+
Run: func(cmd *cobra.Command, args []string) {
28+
ctx, cancel := context.WithTimeout(context.Background(), cmdTimeout)
29+
defer cancel()
30+
31+
s := spin.New("%s Retrieving payment channels...")
32+
s.Start()
33+
infos, err := fcClient.FFS.ListPayChannels(authCtx(ctx))
34+
checkErr(err)
35+
s.Stop()
36+
37+
data := make([][]string, len(infos))
38+
for i, info := range infos {
39+
data[i] = []string{info.CtlAddr, info.Addr, ffs.PaychDirStr[info.Direction]}
40+
}
41+
Message("Payment channels:")
42+
RenderTable(os.Stdout, []string{"Ctrl Address", "Address", "Direction"}, data)
43+
},
44+
}

cmd/pow/cmd/ffs_paych_redeem.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
6+
"github.com/caarlos0/spin"
7+
"github.com/spf13/cobra"
8+
"github.com/spf13/viper"
9+
)
10+
11+
func init() {
12+
ffsPaychRedeemCmd.Flags().StringP("token", "t", "", "token of the request")
13+
14+
ffsPaychCmd.AddCommand(ffsPaychRedeemCmd)
15+
}
16+
17+
var ffsPaychRedeemCmd = &cobra.Command{
18+
Use: "redeem [from] [to] [amount]",
19+
Short: "Redeem a payment channel",
20+
Long: `Redeem a payment channel`,
21+
Args: cobra.ExactArgs(1),
22+
PreRun: func(cmd *cobra.Command, args []string) {
23+
err := viper.BindPFlags(cmd.Flags())
24+
checkErr(err)
25+
},
26+
Run: func(cmd *cobra.Command, args []string) {
27+
ctx, cancel := context.WithTimeout(context.Background(), cmdTimeout)
28+
defer cancel()
29+
30+
s := spin.New("%s Redeeming payment channel...")
31+
s.Start()
32+
err := fcClient.FFS.RedeemPayChannel(authCtx(ctx), args[0])
33+
s.Stop()
34+
checkErr(err)
35+
36+
Success("Redeemed payment channel %v", args[0])
37+
},
38+
}

ffs/api/api.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ var (
3838
type API struct {
3939
is *instanceStore
4040
wm ffs.WalletManager
41+
pm ffs.PaychManager
4142

4243
sched *scheduler.Scheduler
4344

@@ -49,7 +50,7 @@ type API struct {
4950
}
5051

5152
// New returns a new Api instance.
52-
func New(ctx context.Context, ds datastore.Datastore, iid ffs.APIID, sch *scheduler.Scheduler, wm ffs.WalletManager, dc ffs.DefaultConfig) (*API, error) {
53+
func New(ctx context.Context, ds datastore.Datastore, iid ffs.APIID, sch *scheduler.Scheduler, wm ffs.WalletManager, pm ffs.PaychManager, dc ffs.DefaultConfig) (*API, error) {
5354
is := newInstanceStore(namespace.Wrap(ds, datastore.NewKey("istore")))
5455

5556
addr, err := wm.NewAddress(ctx, defaultAddressType)
@@ -76,28 +77,29 @@ func New(ctx context.Context, ds datastore.Datastore, iid ffs.APIID, sch *schedu
7677
}
7778

7879
ctx, cancel := context.WithCancel(context.Background())
79-
i := new(ctx, is, wm, config, sch, cancel)
80+
i := new(ctx, is, wm, pm, config, sch, cancel)
8081
if err := i.is.putConfig(config); err != nil {
8182
return nil, fmt.Errorf("saving new instance %s: %s", i.cfg.ID, err)
8283
}
8384
return i, nil
8485
}
8586

8687
// Load loads a saved Api instance from its ConfigStore.
87-
func Load(ds datastore.Datastore, iid ffs.APIID, sched *scheduler.Scheduler, wm ffs.WalletManager) (*API, error) {
88+
func Load(ds datastore.Datastore, iid ffs.APIID, sched *scheduler.Scheduler, wm ffs.WalletManager, pm ffs.PaychManager) (*API, error) {
8889
is := newInstanceStore(namespace.Wrap(ds, datastore.NewKey("istore")))
8990
c, err := is.getConfig()
9091
if err != nil {
9192
return nil, fmt.Errorf("loading instance: %s", err)
9293
}
9394
ctx, cancel := context.WithCancel(context.Background())
94-
return new(ctx, is, wm, c, sched, cancel), nil
95+
return new(ctx, is, wm, pm, c, sched, cancel), nil
9596
}
9697

97-
func new(ctx context.Context, is *instanceStore, wm ffs.WalletManager, config Config, sch *scheduler.Scheduler, cancel context.CancelFunc) *API {
98+
func new(ctx context.Context, is *instanceStore, wm ffs.WalletManager, pm ffs.PaychManager, config Config, sch *scheduler.Scheduler, cancel context.CancelFunc) *API {
9899
i := &API{
99100
is: is,
100101
wm: wm,
102+
pm: pm,
101103
cfg: config,
102104
sched: sch,
103105
cancel: cancel,

ffs/api/api_paych.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package api
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/ipfs/go-cid"
8+
"github.com/textileio/powergate/ffs"
9+
)
10+
11+
// ListPayChannels lists all payment channels associated with this FFS
12+
func (i *API) ListPayChannels(ctx context.Context) ([]ffs.PaychInfo, error) {
13+
addrInfos := i.Addrs()
14+
addrs := make([]string, len(addrInfos))
15+
for i, info := range addrInfos {
16+
addrs[i] = info.Addr
17+
}
18+
res, err := i.pm.List(ctx, addrs...)
19+
if err != nil {
20+
return nil, err
21+
}
22+
return res, nil
23+
}
24+
25+
// CreatePayChannel creates a new payment channel
26+
func (i *API) CreatePayChannel(ctx context.Context, from string, to string, amount uint64) (ffs.PaychInfo, cid.Cid, error) {
27+
if !i.isManagedAddress(from) {
28+
return ffs.PaychInfo{}, cid.Undef, fmt.Errorf("%v is not managed by ffs instance", from)
29+
}
30+
return i.pm.Create(ctx, from, to, amount)
31+
}
32+
33+
// RedeemPayChannel redeems a payment channel
34+
func (i *API) RedeemPayChannel(ctx context.Context, addr string) error {
35+
channels, err := i.ListPayChannels(ctx)
36+
if err != nil {
37+
return err
38+
}
39+
managed := false
40+
for _, channel := range channels {
41+
if channel.Addr == addr {
42+
managed = true
43+
break
44+
}
45+
}
46+
if !managed {
47+
return fmt.Errorf("paych %v is not managed by ffs instance", addr)
48+
}
49+
return i.pm.Redeem(ctx, addr)
50+
}

ffs/integrationtest/integration_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"github.com/textileio/powergate/ffs/minerselector/fixed"
3232
"github.com/textileio/powergate/ffs/scheduler"
3333
"github.com/textileio/powergate/filchain"
34+
paych "github.com/textileio/powergate/paych/lotus"
3435
"github.com/textileio/powergate/tests"
3536
txndstr "github.com/textileio/powergate/txndstransform"
3637
"github.com/textileio/powergate/util"
@@ -1334,7 +1335,9 @@ func newFFSManager(t *testing.T, ds datastore.TxnDatastore, lotusClient *apistru
13341335
wm, err := wallet.New(lotusClient, masterAddr, *big.NewInt(iWalletBal))
13351336
require.Nil(t, err)
13361337

1337-
manager, err := manager.New(ds, wm, sched)
1338+
pm := paych.New(lotusClient)
1339+
1340+
manager, err := manager.New(ds, wm, pm, sched)
13381341
require.NoError(t, err)
13391342
err = manager.SetDefaultConfig(ffs.DefaultConfig{
13401343
Hot: ffs.HotConfig{

ffs/interfaces.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,17 @@ type WalletManager interface {
1616
// Balance returns the current balance for an address.
1717
Balance(context.Context, string) (uint64, error)
1818
// SendFil sends fil from one address to another
19-
SendFil(ctx context.Context, from string, to string, amount *big.Int) error
19+
SendFil(context.Context, string, string, *big.Int) error
20+
}
21+
22+
// PaychManager provides access to payment channels
23+
type PaychManager interface {
24+
// List lists all payment channels involving the specified addresses
25+
List(ctx context.Context, addrs ...string) ([]PaychInfo, error)
26+
// Create creates a new payment channel
27+
Create(ctx context.Context, from string, to string, amount uint64) (PaychInfo, cid.Cid, error)
28+
// Redeem redeems a payment channel
29+
Redeem(ctx context.Context, ch string) error
2030
}
2131

2232
// HotStorage is a fast storage layer for Cid data.

0 commit comments

Comments
 (0)