feat: paych cli voucher tests
This commit is contained in:
parent
043b63d8d8
commit
d1592b01c3
79
cli/paych.go
79
cli/paych.go
@ -4,6 +4,10 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/lotus/paychmgr"
|
||||||
|
|
||||||
"github.com/filecoin-project/go-address"
|
"github.com/filecoin-project/go-address"
|
||||||
"github.com/filecoin-project/lotus/build"
|
"github.com/filecoin-project/lotus/build"
|
||||||
@ -322,7 +326,7 @@ var paychVoucherListCmd = &cli.Command{
|
|||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.BoolFlag{
|
&cli.BoolFlag{
|
||||||
Name: "export",
|
Name: "export",
|
||||||
Usage: "Print export strings",
|
Usage: "Print voucher as serialized string",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Action: func(cctx *cli.Context) error {
|
Action: func(cctx *cli.Context) error {
|
||||||
@ -348,16 +352,11 @@ var paychVoucherListCmd = &cli.Command{
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range vouchers {
|
for _, v := range sortVouchers(vouchers) {
|
||||||
if cctx.Bool("export") {
|
export := cctx.Bool("export")
|
||||||
enc, err := EncodedString(v)
|
err := outputVoucher(cctx.App.Writer, v, export)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(cctx.App.Writer, "Lane %d, Nonce %d: %s; %s\n", v.Lane, v.Nonce, v.Amount.String(), enc)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(cctx.App.Writer, "Lane %d, Nonce %d: %s\n", v.Lane, v.Nonce, v.Amount.String())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -367,8 +366,14 @@ var paychVoucherListCmd = &cli.Command{
|
|||||||
|
|
||||||
var paychVoucherBestSpendableCmd = &cli.Command{
|
var paychVoucherBestSpendableCmd = &cli.Command{
|
||||||
Name: "best-spendable",
|
Name: "best-spendable",
|
||||||
Usage: "Print voucher with highest value that is currently spendable",
|
Usage: "Print vouchers with highest value that is currently spendable for each lane",
|
||||||
ArgsUsage: "[channelAddress]",
|
ArgsUsage: "[channelAddress]",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "export",
|
||||||
|
Usage: "Print voucher as serialized string",
|
||||||
|
},
|
||||||
|
},
|
||||||
Action: func(cctx *cli.Context) error {
|
Action: func(cctx *cli.Context) error {
|
||||||
if cctx.Args().Len() != 1 {
|
if cctx.Args().Len() != 1 {
|
||||||
return ShowHelp(cctx, fmt.Errorf("must pass payment channel address"))
|
return ShowHelp(cctx, fmt.Errorf("must pass payment channel address"))
|
||||||
@ -387,37 +392,53 @@ var paychVoucherBestSpendableCmd = &cli.Command{
|
|||||||
|
|
||||||
ctx := ReqContext(cctx)
|
ctx := ReqContext(cctx)
|
||||||
|
|
||||||
vouchers, err := api.PaychVoucherList(ctx, ch)
|
vouchersByLane, err := paychmgr.BestSpendableByLane(ctx, api, ch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var best *paych.SignedVoucher
|
var vouchers []*paych.SignedVoucher
|
||||||
for _, v := range vouchers {
|
for _, vchr := range vouchersByLane {
|
||||||
spendable, err := api.PaychVoucherCheckSpendable(ctx, ch, v, nil, nil)
|
vouchers = append(vouchers, vchr)
|
||||||
|
}
|
||||||
|
for _, best := range sortVouchers(vouchers) {
|
||||||
|
export := cctx.Bool("export")
|
||||||
|
err := outputVoucher(cctx.App.Writer, best, export)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if spendable {
|
|
||||||
if best == nil || v.Amount.GreaterThan(best.Amount) {
|
|
||||||
best = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if best == nil {
|
return nil
|
||||||
return fmt.Errorf("No spendable vouchers for that channel")
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
enc, err := EncodedString(best)
|
func sortVouchers(vouchers []*paych.SignedVoucher) []*paych.SignedVoucher {
|
||||||
|
sort.Slice(vouchers, func(i, j int) bool {
|
||||||
|
if vouchers[i].Lane == vouchers[j].Lane {
|
||||||
|
return vouchers[i].Nonce < vouchers[j].Nonce
|
||||||
|
}
|
||||||
|
return vouchers[i].Lane < vouchers[j].Lane
|
||||||
|
})
|
||||||
|
return vouchers
|
||||||
|
}
|
||||||
|
|
||||||
|
func outputVoucher(w io.Writer, v *paych.SignedVoucher, export bool) error {
|
||||||
|
var enc string
|
||||||
|
if export {
|
||||||
|
var err error
|
||||||
|
enc, err = EncodedString(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Fprintln(cctx.App.Writer, enc)
|
fmt.Fprintf(w, "Lane %d, Nonce %d: %s", v.Lane, v.Nonce, v.Amount.String())
|
||||||
fmt.Fprintf(cctx.App.Writer, "Amount: %s\n", best.Amount)
|
if export {
|
||||||
return nil
|
fmt.Fprintf(w, "; %s", enc)
|
||||||
},
|
}
|
||||||
|
fmt.Fprintln(w)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var paychVoucherSubmitCmd = &cli.Command{
|
var paychVoucherSubmitCmd = &cli.Command{
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -49,6 +50,139 @@ func TestPaymentChannels(t *testing.T) {
|
|||||||
|
|
||||||
blocktime := 5 * time.Millisecond
|
blocktime := 5 * time.Millisecond
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
nodes, addrs := startTwoNodesOneMiner(ctx, t, blocktime)
|
||||||
|
paymentCreator := nodes[0]
|
||||||
|
paymentReceiver := nodes[0]
|
||||||
|
creatorAddr := addrs[0]
|
||||||
|
receiverAddr := addrs[1]
|
||||||
|
|
||||||
|
// Create mock CLI
|
||||||
|
mockCLI := newMockCLI(t)
|
||||||
|
creatorCLI := mockCLI.client(paymentCreator.ListenAddr)
|
||||||
|
receiverCLI := mockCLI.client(paymentReceiver.ListenAddr)
|
||||||
|
|
||||||
|
// creator: paych get <creator> <receiver> <amount>
|
||||||
|
channelAmt := "100000"
|
||||||
|
cmd := []string{creatorAddr.String(), receiverAddr.String(), channelAmt}
|
||||||
|
chstr := creatorCLI.runCmd(paychGetCmd, cmd)
|
||||||
|
|
||||||
|
chAddr, err := address.NewFromString(chstr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// creator: paych voucher create <channel> <amount>
|
||||||
|
voucherAmt := 100
|
||||||
|
vamt := strconv.Itoa(voucherAmt)
|
||||||
|
cmd = []string{chAddr.String(), vamt}
|
||||||
|
voucher := creatorCLI.runCmd(paychVoucherCreateCmd, cmd)
|
||||||
|
|
||||||
|
// receiver: paych voucher add <channel> <voucher>
|
||||||
|
cmd = []string{chAddr.String(), voucher}
|
||||||
|
receiverCLI.runCmd(paychVoucherAddCmd, cmd)
|
||||||
|
|
||||||
|
// creator: paych settle <channel>
|
||||||
|
cmd = []string{chAddr.String()}
|
||||||
|
creatorCLI.runCmd(paychSettleCmd, cmd)
|
||||||
|
|
||||||
|
// Wait for the chain to reach the settle height
|
||||||
|
chState := getPaychState(ctx, t, paymentReceiver, chAddr)
|
||||||
|
waitForHeight(ctx, t, paymentReceiver, chState.SettlingAt)
|
||||||
|
|
||||||
|
// receiver: paych collect <channel>
|
||||||
|
cmd = []string{chAddr.String()}
|
||||||
|
receiverCLI.runCmd(paychCloseCmd, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
type voucherSpec struct {
|
||||||
|
serialized string
|
||||||
|
amt int
|
||||||
|
lane int
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPaymentChannelVouchers does a basic test to exercise some payment
|
||||||
|
// channel voucher commands
|
||||||
|
func TestPaymentChannelVouchers(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)
|
||||||
|
|
||||||
|
// creator: paych get <creator> <receiver> <amount>
|
||||||
|
channelAmt := "100000"
|
||||||
|
cmd := []string{creatorAddr.String(), receiverAddr.String(), channelAmt}
|
||||||
|
chstr := creatorCLI.runCmd(paychGetCmd, cmd)
|
||||||
|
|
||||||
|
chAddr, err := address.NewFromString(chstr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var vouchers []voucherSpec
|
||||||
|
|
||||||
|
// creator: paych voucher create <channel> <amount>
|
||||||
|
// Note: implied --lane=0
|
||||||
|
voucherAmt1 := 100
|
||||||
|
vamt1 := strconv.Itoa(voucherAmt1)
|
||||||
|
cmd = []string{chAddr.String(), vamt1}
|
||||||
|
voucher1 := creatorCLI.runCmd(paychVoucherCreateCmd, cmd)
|
||||||
|
vouchers = append(vouchers, voucherSpec{serialized: voucher1, lane: 0, amt: voucherAmt1})
|
||||||
|
|
||||||
|
// creator: paych voucher create <channel> <amount> --lane=5
|
||||||
|
lane5 := "--lane=5"
|
||||||
|
voucherAmt2 := 50
|
||||||
|
vamt2 := strconv.Itoa(voucherAmt2)
|
||||||
|
cmd = []string{lane5, chAddr.String(), vamt2}
|
||||||
|
voucher2 := creatorCLI.runCmd(paychVoucherCreateCmd, cmd)
|
||||||
|
vouchers = append(vouchers, voucherSpec{serialized: voucher2, lane: 5, amt: voucherAmt2})
|
||||||
|
|
||||||
|
// creator: paych voucher create <channel> <amount> --lane=5
|
||||||
|
voucherAmt3 := 70
|
||||||
|
vamt3 := strconv.Itoa(voucherAmt3)
|
||||||
|
cmd = []string{lane5, chAddr.String(), vamt3}
|
||||||
|
voucher3 := creatorCLI.runCmd(paychVoucherCreateCmd, cmd)
|
||||||
|
vouchers = append(vouchers, voucherSpec{serialized: voucher3, lane: 5, amt: voucherAmt3})
|
||||||
|
|
||||||
|
// creator: paych voucher list <channel> --export
|
||||||
|
cmd = []string{"--export", chAddr.String()}
|
||||||
|
list := creatorCLI.runCmd(paychVoucherListCmd, cmd)
|
||||||
|
|
||||||
|
// Check that voucher list output is correct
|
||||||
|
checkVoucherOutput(t, list, vouchers)
|
||||||
|
|
||||||
|
// creator: paych voucher best-spendable <channel>
|
||||||
|
cmd = []string{"--export", chAddr.String()}
|
||||||
|
bestSpendable := creatorCLI.runCmd(paychVoucherBestSpendableCmd, cmd)
|
||||||
|
|
||||||
|
// Check that best spendable output is correct
|
||||||
|
bestVouchers := []voucherSpec{
|
||||||
|
{serialized: voucher1, lane: 0, amt: voucherAmt1},
|
||||||
|
{serialized: voucher3, lane: 5, amt: voucherAmt3},
|
||||||
|
}
|
||||||
|
checkVoucherOutput(t, bestSpendable, bestVouchers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkVoucherOutput(t *testing.T, list string, vouchers []voucherSpec) {
|
||||||
|
lines := strings.Split(list, "\n")
|
||||||
|
listVouchers := make(map[string]string)
|
||||||
|
for _, line := range lines {
|
||||||
|
parts := strings.Split(line, ";")
|
||||||
|
serialized := strings.TrimSpace(parts[1])
|
||||||
|
listVouchers[serialized] = strings.TrimSpace(parts[0])
|
||||||
|
}
|
||||||
|
for _, vchr := range vouchers {
|
||||||
|
res, ok := listVouchers[vchr.serialized]
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Regexp(t, fmt.Sprintf("Lane %d", vchr.lane), res)
|
||||||
|
require.Regexp(t, fmt.Sprintf("%d", vchr.amt), res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func startTwoNodesOneMiner(ctx context.Context, t *testing.T, blocktime time.Duration) ([]test.TestNode, []address.Address) {
|
||||||
n, sn := builder.RPCMockSbBuilder(t, 2, test.OneMiner)
|
n, sn := builder.RPCMockSbBuilder(t, 2, test.OneMiner)
|
||||||
|
|
||||||
paymentCreator := n[0]
|
paymentCreator := n[0]
|
||||||
@ -88,39 +222,7 @@ func TestPaymentChannels(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create mock CLI
|
// Create mock CLI
|
||||||
mockCLI := newMockCLI(t)
|
return n, []address.Address{creatorAddr, receiverAddr}
|
||||||
creatorCLI := mockCLI.client(paymentCreator.ListenAddr)
|
|
||||||
receiverCLI := mockCLI.client(paymentReceiver.ListenAddr)
|
|
||||||
|
|
||||||
// creator: paych get <creator> <receiver> <amount>
|
|
||||||
channelAmt := "100000"
|
|
||||||
cmd := []string{creatorAddr.String(), receiverAddr.String(), channelAmt}
|
|
||||||
chstr := creatorCLI.runCmd(paychGetCmd, cmd)
|
|
||||||
|
|
||||||
chAddr, err := address.NewFromString(chstr)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// creator: paych voucher create <channel> <amount>
|
|
||||||
voucherAmt := 100
|
|
||||||
vamt := strconv.Itoa(voucherAmt)
|
|
||||||
cmd = []string{chAddr.String(), vamt}
|
|
||||||
voucher := creatorCLI.runCmd(paychVoucherCreateCmd, cmd)
|
|
||||||
|
|
||||||
// receiver: paych voucher add <channel> <voucher>
|
|
||||||
cmd = []string{chAddr.String(), voucher}
|
|
||||||
receiverCLI.runCmd(paychVoucherAddCmd, cmd)
|
|
||||||
|
|
||||||
// creator: paych settle <channel>
|
|
||||||
cmd = []string{chAddr.String()}
|
|
||||||
creatorCLI.runCmd(paychSettleCmd, cmd)
|
|
||||||
|
|
||||||
// Wait for the chain to reach the settle height
|
|
||||||
chState := getPaychState(ctx, t, paymentReceiver, chAddr)
|
|
||||||
waitForHeight(ctx, t, paymentReceiver, chState.SettlingAt)
|
|
||||||
|
|
||||||
// receiver: paych collect <channel>
|
|
||||||
cmd = []string{chAddr.String()}
|
|
||||||
receiverCLI.runCmd(paychCloseCmd, cmd)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type mockCLI struct {
|
type mockCLI struct {
|
||||||
|
@ -4,6 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/lotus/paychmgr"
|
||||||
|
|
||||||
"go.uber.org/fx"
|
"go.uber.org/fx"
|
||||||
|
|
||||||
"github.com/ipfs/go-cid"
|
"github.com/ipfs/go-cid"
|
||||||
@ -72,23 +74,10 @@ func (pcs *paymentChannelSettler) check(ts *types.TipSet) (done bool, more bool,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pcs *paymentChannelSettler) messageHandler(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (more bool, err error) {
|
func (pcs *paymentChannelSettler) messageHandler(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (more bool, err error) {
|
||||||
vouchers, err := pcs.api.PaychVoucherList(pcs.ctx, msg.To)
|
bestByLane, err := paychmgr.BestSpendableByLane(pcs.ctx, pcs.api, msg.To)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
|
|
||||||
bestByLane := make(map[uint64]*paych.SignedVoucher)
|
|
||||||
for _, voucher := range vouchers {
|
|
||||||
spendable, err := pcs.api.PaychVoucherCheckSpendable(pcs.ctx, msg.To, voucher, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
if spendable {
|
|
||||||
if bestByLane[voucher.Lane] == nil || voucher.Amount.GreaterThan(bestByLane[voucher.Lane].Amount) {
|
|
||||||
bestByLane[voucher.Lane] = voucher
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(len(bestByLane))
|
wg.Add(len(bestByLane))
|
||||||
for _, voucher := range bestByLane {
|
for _, voucher := range bestByLane {
|
||||||
|
34
paychmgr/util.go
Normal file
34
paychmgr/util.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package paychmgr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/go-address"
|
||||||
|
"github.com/filecoin-project/specs-actors/actors/builtin/paych"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BestSpendableAPI interface {
|
||||||
|
PaychVoucherList(context.Context, address.Address) ([]*paych.SignedVoucher, error)
|
||||||
|
PaychVoucherCheckSpendable(context.Context, address.Address, *paych.SignedVoucher, []byte, []byte) (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BestSpendableByLane(ctx context.Context, api BestSpendableAPI, ch address.Address) (map[uint64]*paych.SignedVoucher, error) {
|
||||||
|
vouchers, err := api.PaychVoucherList(ctx, ch)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bestByLane := make(map[uint64]*paych.SignedVoucher)
|
||||||
|
for _, voucher := range vouchers {
|
||||||
|
spendable, err := api.PaychVoucherCheckSpendable(ctx, ch, voucher, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if spendable {
|
||||||
|
if bestByLane[voucher.Lane] == nil || voucher.Amount.GreaterThan(bestByLane[voucher.Lane].Amount) {
|
||||||
|
bestByLane[voucher.Lane] = voucher
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestByLane, nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user