Merge pull request #3197 from filecoin-project/fix/paych-spendable-submit-voucher
paych: fix issue where `voucher spendable` fails for submitted vouchers
This commit is contained in:
commit
4bcc71c68f
@ -427,7 +427,7 @@ type FullNode interface {
|
|||||||
PaychVoucherCreate(context.Context, address.Address, types.BigInt, uint64) (*paych.SignedVoucher, error)
|
PaychVoucherCreate(context.Context, address.Address, types.BigInt, uint64) (*paych.SignedVoucher, error)
|
||||||
PaychVoucherAdd(context.Context, address.Address, *paych.SignedVoucher, []byte, types.BigInt) (types.BigInt, error)
|
PaychVoucherAdd(context.Context, address.Address, *paych.SignedVoucher, []byte, types.BigInt) (types.BigInt, error)
|
||||||
PaychVoucherList(context.Context, address.Address) ([]*paych.SignedVoucher, error)
|
PaychVoucherList(context.Context, address.Address) ([]*paych.SignedVoucher, error)
|
||||||
PaychVoucherSubmit(context.Context, address.Address, *paych.SignedVoucher) (cid.Cid, error)
|
PaychVoucherSubmit(context.Context, address.Address, *paych.SignedVoucher, []byte, []byte) (cid.Cid, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileRef struct {
|
type FileRef struct {
|
||||||
|
@ -214,7 +214,7 @@ type FullNodeStruct struct {
|
|||||||
PaychVoucherAdd func(context.Context, address.Address, *paych.SignedVoucher, []byte, types.BigInt) (types.BigInt, error) `perm:"write"`
|
PaychVoucherAdd func(context.Context, address.Address, *paych.SignedVoucher, []byte, types.BigInt) (types.BigInt, error) `perm:"write"`
|
||||||
PaychVoucherCreate func(context.Context, address.Address, big.Int, uint64) (*paych.SignedVoucher, error) `perm:"sign"`
|
PaychVoucherCreate func(context.Context, address.Address, big.Int, uint64) (*paych.SignedVoucher, error) `perm:"sign"`
|
||||||
PaychVoucherList func(context.Context, address.Address) ([]*paych.SignedVoucher, error) `perm:"write"`
|
PaychVoucherList func(context.Context, address.Address) ([]*paych.SignedVoucher, error) `perm:"write"`
|
||||||
PaychVoucherSubmit func(context.Context, address.Address, *paych.SignedVoucher) (cid.Cid, error) `perm:"sign"`
|
PaychVoucherSubmit func(context.Context, address.Address, *paych.SignedVoucher, []byte, []byte) (cid.Cid, error) `perm:"sign"`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -920,8 +920,8 @@ func (c *FullNodeStruct) PaychNewPayment(ctx context.Context, from, to address.A
|
|||||||
return c.Internal.PaychNewPayment(ctx, from, to, vouchers)
|
return c.Internal.PaychNewPayment(ctx, from, to, vouchers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *FullNodeStruct) PaychVoucherSubmit(ctx context.Context, ch address.Address, sv *paych.SignedVoucher) (cid.Cid, error) {
|
func (c *FullNodeStruct) PaychVoucherSubmit(ctx context.Context, ch address.Address, sv *paych.SignedVoucher, secret []byte, proof []byte) (cid.Cid, error) {
|
||||||
return c.Internal.PaychVoucherSubmit(ctx, ch, sv)
|
return c.Internal.PaychVoucherSubmit(ctx, ch, sv, secret, proof)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StorageMinerStruct
|
// StorageMinerStruct
|
||||||
|
@ -468,7 +468,7 @@ var paychVoucherSubmitCmd = &cli.Command{
|
|||||||
|
|
||||||
ctx := ReqContext(cctx)
|
ctx := ReqContext(cctx)
|
||||||
|
|
||||||
mcid, err := api.PaychVoucherSubmit(ctx, ch, sv)
|
mcid, err := api.PaychVoucherSubmit(ctx, ch, sv, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -107,12 +107,14 @@ func TestPaymentChannelVouchers(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
nodes, addrs := startTwoNodesOneMiner(ctx, t, blocktime)
|
nodes, addrs := startTwoNodesOneMiner(ctx, t, blocktime)
|
||||||
paymentCreator := nodes[0]
|
paymentCreator := nodes[0]
|
||||||
|
paymentReceiver := nodes[1]
|
||||||
creatorAddr := addrs[0]
|
creatorAddr := addrs[0]
|
||||||
receiverAddr := addrs[1]
|
receiverAddr := addrs[1]
|
||||||
|
|
||||||
// Create mock CLI
|
// Create mock CLI
|
||||||
mockCLI := newMockCLI(t)
|
mockCLI := newMockCLI(t)
|
||||||
creatorCLI := mockCLI.client(paymentCreator.ListenAddr)
|
creatorCLI := mockCLI.client(paymentCreator.ListenAddr)
|
||||||
|
receiverCLI := mockCLI.client(paymentReceiver.ListenAddr)
|
||||||
|
|
||||||
// creator: paych get <creator> <receiver> <amount>
|
// creator: paych get <creator> <receiver> <amount>
|
||||||
channelAmt := "100000"
|
channelAmt := "100000"
|
||||||
@ -127,43 +129,123 @@ func TestPaymentChannelVouchers(t *testing.T) {
|
|||||||
// creator: paych voucher create <channel> <amount>
|
// creator: paych voucher create <channel> <amount>
|
||||||
// Note: implied --lane=0
|
// Note: implied --lane=0
|
||||||
voucherAmt1 := 100
|
voucherAmt1 := 100
|
||||||
vamt1 := strconv.Itoa(voucherAmt1)
|
cmd = []string{chAddr.String(), strconv.Itoa(voucherAmt1)}
|
||||||
cmd = []string{chAddr.String(), vamt1}
|
|
||||||
voucher1 := creatorCLI.runCmd(paychVoucherCreateCmd, cmd)
|
voucher1 := creatorCLI.runCmd(paychVoucherCreateCmd, cmd)
|
||||||
vouchers = append(vouchers, voucherSpec{serialized: voucher1, lane: 0, amt: voucherAmt1})
|
vouchers = append(vouchers, voucherSpec{serialized: voucher1, lane: 0, amt: voucherAmt1})
|
||||||
|
|
||||||
// creator: paych voucher create <channel> <amount> --lane=5
|
// creator: paych voucher create <channel> <amount> --lane=5
|
||||||
lane5 := "--lane=5"
|
lane5 := "--lane=5"
|
||||||
voucherAmt2 := 50
|
voucherAmt2 := 50
|
||||||
vamt2 := strconv.Itoa(voucherAmt2)
|
cmd = []string{lane5, chAddr.String(), strconv.Itoa(voucherAmt2)}
|
||||||
cmd = []string{lane5, chAddr.String(), vamt2}
|
|
||||||
voucher2 := creatorCLI.runCmd(paychVoucherCreateCmd, cmd)
|
voucher2 := creatorCLI.runCmd(paychVoucherCreateCmd, cmd)
|
||||||
vouchers = append(vouchers, voucherSpec{serialized: voucher2, lane: 5, amt: voucherAmt2})
|
vouchers = append(vouchers, voucherSpec{serialized: voucher2, lane: 5, amt: voucherAmt2})
|
||||||
|
|
||||||
// creator: paych voucher create <channel> <amount> --lane=5
|
// creator: paych voucher create <channel> <amount> --lane=5
|
||||||
voucherAmt3 := 70
|
voucherAmt3 := 70
|
||||||
vamt3 := strconv.Itoa(voucherAmt3)
|
cmd = []string{lane5, chAddr.String(), strconv.Itoa(voucherAmt3)}
|
||||||
cmd = []string{lane5, chAddr.String(), vamt3}
|
|
||||||
voucher3 := creatorCLI.runCmd(paychVoucherCreateCmd, cmd)
|
voucher3 := creatorCLI.runCmd(paychVoucherCreateCmd, cmd)
|
||||||
vouchers = append(vouchers, voucherSpec{serialized: voucher3, lane: 5, amt: voucherAmt3})
|
vouchers = append(vouchers, voucherSpec{serialized: voucher3, lane: 5, amt: voucherAmt3})
|
||||||
|
|
||||||
|
// creator: paych voucher create <channel> <amount> --lane=5
|
||||||
|
voucherAmt4 := 80
|
||||||
|
cmd = []string{lane5, chAddr.String(), strconv.Itoa(voucherAmt4)}
|
||||||
|
voucher4 := creatorCLI.runCmd(paychVoucherCreateCmd, cmd)
|
||||||
|
vouchers = append(vouchers, voucherSpec{serialized: voucher4, lane: 5, amt: voucherAmt4})
|
||||||
|
|
||||||
// creator: paych voucher list <channel> --export
|
// creator: paych voucher list <channel> --export
|
||||||
cmd = []string{"--export", chAddr.String()}
|
cmd = []string{"--export", chAddr.String()}
|
||||||
list := creatorCLI.runCmd(paychVoucherListCmd, cmd)
|
list := creatorCLI.runCmd(paychVoucherListCmd, cmd)
|
||||||
|
|
||||||
// Check that voucher list output is correct
|
// Check that voucher list output is correct on creator
|
||||||
checkVoucherOutput(t, list, vouchers)
|
checkVoucherOutput(t, list, vouchers)
|
||||||
|
|
||||||
// creator: paych voucher best-spendable <channel>
|
// creator: paych voucher best-spendable <channel>
|
||||||
cmd = []string{"--export", chAddr.String()}
|
cmd = []string{"--export", chAddr.String()}
|
||||||
bestSpendable := creatorCLI.runCmd(paychVoucherBestSpendableCmd, cmd)
|
bestSpendable := creatorCLI.runCmd(paychVoucherBestSpendableCmd, cmd)
|
||||||
|
|
||||||
// Check that best spendable output is correct
|
// Check that best spendable output is correct on creator
|
||||||
bestVouchers := []voucherSpec{
|
bestVouchers := []voucherSpec{
|
||||||
{serialized: voucher1, lane: 0, amt: voucherAmt1},
|
{serialized: voucher1, lane: 0, amt: voucherAmt1},
|
||||||
{serialized: voucher3, lane: 5, amt: voucherAmt3},
|
{serialized: voucher4, lane: 5, amt: voucherAmt4},
|
||||||
}
|
}
|
||||||
checkVoucherOutput(t, bestSpendable, bestVouchers)
|
checkVoucherOutput(t, bestSpendable, bestVouchers)
|
||||||
|
|
||||||
|
// receiver: paych voucher add <voucher>
|
||||||
|
cmd = []string{chAddr.String(), voucher1}
|
||||||
|
receiverCLI.runCmd(paychVoucherAddCmd, cmd)
|
||||||
|
|
||||||
|
// receiver: paych voucher add <voucher>
|
||||||
|
cmd = []string{chAddr.String(), voucher2}
|
||||||
|
receiverCLI.runCmd(paychVoucherAddCmd, cmd)
|
||||||
|
|
||||||
|
// receiver: paych voucher add <voucher>
|
||||||
|
cmd = []string{chAddr.String(), voucher3}
|
||||||
|
receiverCLI.runCmd(paychVoucherAddCmd, cmd)
|
||||||
|
|
||||||
|
// receiver: paych voucher add <voucher>
|
||||||
|
cmd = []string{chAddr.String(), voucher4}
|
||||||
|
receiverCLI.runCmd(paychVoucherAddCmd, cmd)
|
||||||
|
|
||||||
|
// receiver: paych voucher list <channel> --export
|
||||||
|
cmd = []string{"--export", chAddr.String()}
|
||||||
|
list = receiverCLI.runCmd(paychVoucherListCmd, cmd)
|
||||||
|
|
||||||
|
// Check that voucher list output is correct on receiver
|
||||||
|
checkVoucherOutput(t, list, vouchers)
|
||||||
|
|
||||||
|
// receiver: paych voucher best-spendable <channel>
|
||||||
|
cmd = []string{"--export", chAddr.String()}
|
||||||
|
bestSpendable = receiverCLI.runCmd(paychVoucherBestSpendableCmd, cmd)
|
||||||
|
|
||||||
|
// Check that best spendable output is correct on receiver
|
||||||
|
bestVouchers = []voucherSpec{
|
||||||
|
{serialized: voucher1, lane: 0, amt: voucherAmt1},
|
||||||
|
{serialized: voucher4, lane: 5, amt: voucherAmt4},
|
||||||
|
}
|
||||||
|
checkVoucherOutput(t, bestSpendable, bestVouchers)
|
||||||
|
|
||||||
|
// receiver: paych voucher submit <channel> <voucher>
|
||||||
|
cmd = []string{chAddr.String(), voucher1}
|
||||||
|
receiverCLI.runCmd(paychVoucherSubmitCmd, cmd)
|
||||||
|
|
||||||
|
// receiver: paych voucher best-spendable <channel>
|
||||||
|
cmd = []string{"--export", chAddr.String()}
|
||||||
|
bestSpendable = receiverCLI.runCmd(paychVoucherBestSpendableCmd, cmd)
|
||||||
|
|
||||||
|
// Check that best spendable output no longer includes submitted voucher
|
||||||
|
bestVouchers = []voucherSpec{
|
||||||
|
{serialized: voucher4, lane: 5, amt: voucherAmt4},
|
||||||
|
}
|
||||||
|
checkVoucherOutput(t, bestSpendable, bestVouchers)
|
||||||
|
|
||||||
|
// There are three vouchers in lane 5: 50, 70, 80
|
||||||
|
// Submit the voucher for 50. Best spendable should still be 80.
|
||||||
|
// receiver: paych voucher submit <channel> <voucher>
|
||||||
|
cmd = []string{chAddr.String(), voucher2}
|
||||||
|
receiverCLI.runCmd(paychVoucherSubmitCmd, cmd)
|
||||||
|
|
||||||
|
// receiver: paych voucher best-spendable <channel>
|
||||||
|
cmd = []string{"--export", chAddr.String()}
|
||||||
|
bestSpendable = receiverCLI.runCmd(paychVoucherBestSpendableCmd, cmd)
|
||||||
|
|
||||||
|
// Check that best spendable output still includes the voucher for 80
|
||||||
|
bestVouchers = []voucherSpec{
|
||||||
|
{serialized: voucher4, lane: 5, amt: voucherAmt4},
|
||||||
|
}
|
||||||
|
checkVoucherOutput(t, bestSpendable, bestVouchers)
|
||||||
|
|
||||||
|
// Submit the voucher for 80
|
||||||
|
// receiver: paych voucher submit <channel> <voucher>
|
||||||
|
cmd = []string{chAddr.String(), voucher4}
|
||||||
|
receiverCLI.runCmd(paychVoucherSubmitCmd, cmd)
|
||||||
|
|
||||||
|
// receiver: paych voucher best-spendable <channel>
|
||||||
|
cmd = []string{"--export", chAddr.String()}
|
||||||
|
bestSpendable = receiverCLI.runCmd(paychVoucherBestSpendableCmd, cmd)
|
||||||
|
|
||||||
|
// Check that best spendable output no longer includes submitted voucher
|
||||||
|
bestVouchers = []voucherSpec{}
|
||||||
|
checkVoucherOutput(t, bestSpendable, bestVouchers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkVoucherOutput(t *testing.T, list string, vouchers []voucherSpec) {
|
func checkVoucherOutput(t *testing.T, list string, vouchers []voucherSpec) {
|
||||||
@ -171,14 +253,20 @@ func checkVoucherOutput(t *testing.T, list string, vouchers []voucherSpec) {
|
|||||||
listVouchers := make(map[string]string)
|
listVouchers := make(map[string]string)
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
parts := strings.Split(line, ";")
|
parts := strings.Split(line, ";")
|
||||||
serialized := strings.TrimSpace(parts[1])
|
if len(parts) == 2 {
|
||||||
listVouchers[serialized] = strings.TrimSpace(parts[0])
|
serialized := strings.TrimSpace(parts[1])
|
||||||
|
listVouchers[serialized] = strings.TrimSpace(parts[0])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for _, vchr := range vouchers {
|
for _, vchr := range vouchers {
|
||||||
res, ok := listVouchers[vchr.serialized]
|
res, ok := listVouchers[vchr.serialized]
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
require.Regexp(t, fmt.Sprintf("Lane %d", vchr.lane), res)
|
require.Regexp(t, fmt.Sprintf("Lane %d", vchr.lane), res)
|
||||||
require.Regexp(t, fmt.Sprintf("%d", vchr.amt), res)
|
require.Regexp(t, fmt.Sprintf("%d", vchr.amt), res)
|
||||||
|
delete(listVouchers, vchr.serialized)
|
||||||
|
}
|
||||||
|
for _, vchr := range listVouchers {
|
||||||
|
require.Fail(t, "Extra voucher "+vchr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2437,7 +2437,9 @@ Inputs:
|
|||||||
"Type": 2,
|
"Type": 2,
|
||||||
"Data": "Ynl0ZSBhcnJheQ=="
|
"Data": "Ynl0ZSBhcnJheQ=="
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"Ynl0ZSBhcnJheQ==",
|
||||||
|
"Ynl0ZSBhcnJheQ=="
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -2,18 +2,15 @@ package paych
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/ipfs/go-cid"
|
"github.com/ipfs/go-cid"
|
||||||
"go.uber.org/fx"
|
"go.uber.org/fx"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/filecoin-project/go-address"
|
"github.com/filecoin-project/go-address"
|
||||||
"github.com/filecoin-project/specs-actors/actors/builtin"
|
|
||||||
"github.com/filecoin-project/specs-actors/actors/builtin/paych"
|
"github.com/filecoin-project/specs-actors/actors/builtin/paych"
|
||||||
|
|
||||||
"github.com/filecoin-project/lotus/api"
|
"github.com/filecoin-project/lotus/api"
|
||||||
"github.com/filecoin-project/lotus/chain/actors"
|
|
||||||
"github.com/filecoin-project/lotus/chain/types"
|
"github.com/filecoin-project/lotus/chain/types"
|
||||||
full "github.com/filecoin-project/lotus/node/impl/full"
|
full "github.com/filecoin-project/lotus/node/impl/full"
|
||||||
"github.com/filecoin-project/lotus/paychmgr"
|
"github.com/filecoin-project/lotus/paychmgr"
|
||||||
@ -183,35 +180,6 @@ func (a *PaychAPI) PaychVoucherList(ctx context.Context, pch address.Address) ([
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *PaychAPI) PaychVoucherSubmit(ctx context.Context, ch address.Address, sv *paych.SignedVoucher) (cid.Cid, error) {
|
func (a *PaychAPI) PaychVoucherSubmit(ctx context.Context, ch address.Address, sv *paych.SignedVoucher, secret []byte, proof []byte) (cid.Cid, error) {
|
||||||
ci, err := a.PaychMgr.GetChannelInfo(ch)
|
return a.PaychMgr.SubmitVoucher(ctx, ch, sv, secret, proof)
|
||||||
if err != nil {
|
|
||||||
return cid.Undef, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if sv.Extra != nil || len(sv.SecretPreimage) > 0 {
|
|
||||||
return cid.Undef, fmt.Errorf("cant handle more advanced payment channel stuff yet")
|
|
||||||
}
|
|
||||||
|
|
||||||
enc, err := actors.SerializeParams(&paych.UpdateChannelStateParams{
|
|
||||||
Sv: *sv,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return cid.Undef, err
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := &types.Message{
|
|
||||||
From: ci.Control,
|
|
||||||
To: ch,
|
|
||||||
Value: types.NewInt(0),
|
|
||||||
Method: builtin.MethodsPaych.UpdateChannelState,
|
|
||||||
Params: enc,
|
|
||||||
}
|
|
||||||
|
|
||||||
smsg, err := a.MpoolPushMessage(ctx, msg, nil)
|
|
||||||
if err != nil {
|
|
||||||
return cid.Undef, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return smsg.Cid(), nil
|
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ func (t *VoucherInfo) MarshalCBOR(w io.Writer) error {
|
|||||||
_, err := w.Write(cbg.CborNull)
|
_, err := w.Write(cbg.CborNull)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := w.Write([]byte{162}); err != nil {
|
if _, err := w.Write([]byte{163}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,6 +64,22 @@ func (t *VoucherInfo) MarshalCBOR(w io.Writer) error {
|
|||||||
if _, err := w.Write(t.Proof[:]); err != nil {
|
if _, err := w.Write(t.Proof[:]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// t.Submitted (bool) (bool)
|
||||||
|
if len("Submitted") > cbg.MaxLength {
|
||||||
|
return xerrors.Errorf("Value in field \"Submitted\" was too long")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Submitted"))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := io.WriteString(w, string("Submitted")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cbg.WriteBool(w, t.Submitted); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,6 +158,24 @@ func (t *VoucherInfo) UnmarshalCBOR(r io.Reader) error {
|
|||||||
if _, err := io.ReadFull(br, t.Proof[:]); err != nil {
|
if _, err := io.ReadFull(br, t.Proof[:]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// t.Submitted (bool) (bool)
|
||||||
|
case "Submitted":
|
||||||
|
|
||||||
|
maj, extra, err = cbg.CborReadHeaderBuf(br, scratch)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if maj != cbg.MajOther {
|
||||||
|
return fmt.Errorf("booleans must be major type 7")
|
||||||
|
}
|
||||||
|
switch extra {
|
||||||
|
case 20:
|
||||||
|
t.Submitted = false
|
||||||
|
case 21:
|
||||||
|
t.Submitted = true
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("booleans are either major type 7, value 20 or 21 (got %d)", extra)
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown struct field %d: '%s'", i, name)
|
return fmt.Errorf("unknown struct field %d: '%s'", i, name)
|
||||||
|
@ -285,6 +285,14 @@ func (pm *Manager) trackInboundChannel(ctx context.Context, ch address.Address)
|
|||||||
return pm.store.TrackChannel(stateCi)
|
return pm.store.TrackChannel(stateCi)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pm *Manager) SubmitVoucher(ctx context.Context, ch address.Address, sv *paych.SignedVoucher, secret []byte, proof []byte) (cid.Cid, error) {
|
||||||
|
ca, err := pm.accessorByAddress(ch)
|
||||||
|
if err != nil {
|
||||||
|
return cid.Undef, err
|
||||||
|
}
|
||||||
|
return ca.submitVoucher(ctx, ch, sv, secret, proof)
|
||||||
|
}
|
||||||
|
|
||||||
func (pm *Manager) AllocateLane(ch address.Address) (uint64, error) {
|
func (pm *Manager) AllocateLane(ch address.Address) (uint64, error) {
|
||||||
ca, err := pm.accessorByAddress(ch)
|
ca, err := pm.accessorByAddress(ch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -40,6 +40,7 @@ type mockStateManager struct {
|
|||||||
paychState map[address.Address]mockPchState
|
paychState map[address.Address]mockPchState
|
||||||
store adt.Store
|
store adt.Store
|
||||||
response *api.InvocResult
|
response *api.InvocResult
|
||||||
|
lastCall *types.Message
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMockStateManager() *mockStateManager {
|
func newMockStateManager() *mockStateManager {
|
||||||
@ -93,7 +94,26 @@ func (sm *mockStateManager) LoadActorState(ctx context.Context, a address.Addres
|
|||||||
panic(fmt.Sprintf("unexpected state type %v", out))
|
panic(fmt.Sprintf("unexpected state type %v", out))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sm *mockStateManager) setCallResponse(response *api.InvocResult) {
|
||||||
|
sm.lk.Lock()
|
||||||
|
defer sm.lk.Unlock()
|
||||||
|
|
||||||
|
sm.response = response
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sm *mockStateManager) getLastCall() *types.Message {
|
||||||
|
sm.lk.Lock()
|
||||||
|
defer sm.lk.Unlock()
|
||||||
|
|
||||||
|
return sm.lastCall
|
||||||
|
}
|
||||||
|
|
||||||
func (sm *mockStateManager) Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error) {
|
func (sm *mockStateManager) Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error) {
|
||||||
|
sm.lk.Lock()
|
||||||
|
defer sm.lk.Unlock()
|
||||||
|
|
||||||
|
sm.lastCall = msg
|
||||||
|
|
||||||
return sm.response, nil
|
return sm.response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,24 +152,32 @@ func (ca *channelAccessor) checkVoucherSpendable(ctx context.Context, ch address
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ci, err := ca.store.ByAddress(ch)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if voucher has already been submitted
|
||||||
|
submitted, err := ci.wasVoucherSubmitted(sv)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if submitted {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If proof is needed and wasn't supplied as a parameter, get it from the
|
||||||
|
// datastore
|
||||||
if sv.Extra != nil && proof == nil {
|
if sv.Extra != nil && proof == nil {
|
||||||
known, err := ca.store.VouchersForPaych(ch)
|
vi, err := ci.infoForVoucher(sv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range known {
|
if vi.Proof != nil {
|
||||||
eq, err := cborutil.Equals(v.Voucher, sv)
|
log.Info("CheckVoucherSpendable: using stored proof")
|
||||||
if err != nil {
|
proof = vi.Proof
|
||||||
return false, err
|
} else {
|
||||||
}
|
|
||||||
if v.Proof != nil && eq {
|
|
||||||
log.Info("CheckVoucherSpendable: using stored proof")
|
|
||||||
proof = v.Proof
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if proof == nil {
|
|
||||||
log.Warn("CheckVoucherSpendable: nil proof for voucher with validation")
|
log.Warn("CheckVoucherSpendable: nil proof for voucher with validation")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -276,6 +284,87 @@ func (ca *channelAccessor) addVoucher(ctx context.Context, ch address.Address, s
|
|||||||
return delta, ca.store.putChannelInfo(ci)
|
return delta, ca.store.putChannelInfo(ci)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ca *channelAccessor) submitVoucher(ctx context.Context, ch address.Address, sv *paych.SignedVoucher, secret []byte, proof []byte) (cid.Cid, error) {
|
||||||
|
ca.lk.Lock()
|
||||||
|
defer ca.lk.Unlock()
|
||||||
|
|
||||||
|
ci, err := ca.store.ByAddress(ch)
|
||||||
|
if err != nil {
|
||||||
|
return cid.Undef, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If voucher needs proof, and none was supplied, check datastore for proof
|
||||||
|
if sv.Extra != nil && proof == nil {
|
||||||
|
vi, err := ci.infoForVoucher(sv)
|
||||||
|
if err != nil {
|
||||||
|
return cid.Undef, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if vi.Proof != nil {
|
||||||
|
log.Info("SubmitVoucher: using stored proof")
|
||||||
|
proof = vi.Proof
|
||||||
|
} else {
|
||||||
|
log.Warn("SubmitVoucher: nil proof for voucher with validation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
has, err := ci.hasVoucher(sv)
|
||||||
|
if err != nil {
|
||||||
|
return cid.Undef, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the channel has the voucher
|
||||||
|
if has {
|
||||||
|
// Check that the voucher hasn't already been submitted
|
||||||
|
submitted, err := ci.wasVoucherSubmitted(sv)
|
||||||
|
if err != nil {
|
||||||
|
return cid.Undef, err
|
||||||
|
}
|
||||||
|
if submitted {
|
||||||
|
return cid.Undef, xerrors.Errorf("cannot submit voucher that has already been submitted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enc, err := actors.SerializeParams(&paych.UpdateChannelStateParams{
|
||||||
|
Sv: *sv,
|
||||||
|
Secret: secret,
|
||||||
|
Proof: proof,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return cid.Undef, err
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := &types.Message{
|
||||||
|
From: ci.Control,
|
||||||
|
To: ch,
|
||||||
|
Value: types.NewInt(0),
|
||||||
|
Method: builtin.MethodsPaych.UpdateChannelState,
|
||||||
|
Params: enc,
|
||||||
|
}
|
||||||
|
|
||||||
|
smsg, err := ca.api.MpoolPushMessage(ctx, msg, nil)
|
||||||
|
if err != nil {
|
||||||
|
return cid.Undef, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the channel didn't already have the voucher
|
||||||
|
if !has {
|
||||||
|
// Add the voucher to the channel
|
||||||
|
ci.Vouchers = append(ci.Vouchers, &VoucherInfo{
|
||||||
|
Voucher: sv,
|
||||||
|
Proof: proof,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark the voucher and any lower-nonce vouchers as having been submitted
|
||||||
|
err = ca.store.MarkVoucherSubmitted(ci, sv)
|
||||||
|
if err != nil {
|
||||||
|
return cid.Undef, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return smsg.Cid(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (ca *channelAccessor) allocateLane(ch address.Address) (uint64, error) {
|
func (ca *channelAccessor) allocateLane(ch address.Address) (uint64, error) {
|
||||||
ca.lk.Lock()
|
ca.lk.Lock()
|
||||||
defer ca.lk.Unlock()
|
defer ca.lk.Unlock()
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package paychmgr
|
package paychmgr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/lotus/api"
|
||||||
|
|
||||||
"github.com/filecoin-project/specs-actors/actors/builtin"
|
"github.com/filecoin-project/specs-actors/actors/builtin"
|
||||||
"github.com/filecoin-project/specs-actors/actors/util/adt"
|
"github.com/filecoin-project/specs-actors/actors/util/adt"
|
||||||
"github.com/ipfs/go-cid"
|
"github.com/ipfs/go-cid"
|
||||||
@ -382,7 +385,7 @@ func TestAddVoucherDelta(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// Set up a manager with a single payment channel
|
// Set up a manager with a single payment channel
|
||||||
mgr, ch, fromKeyPrivate := testSetupMgrWithChannel(ctx, t)
|
mgr, _, ch, fromKeyPrivate := testSetupMgrWithChannel(ctx, t)
|
||||||
|
|
||||||
voucherLane := uint64(1)
|
voucherLane := uint64(1)
|
||||||
|
|
||||||
@ -424,7 +427,7 @@ func TestAddVoucherNextLane(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// Set up a manager with a single payment channel
|
// Set up a manager with a single payment channel
|
||||||
mgr, ch, fromKeyPrivate := testSetupMgrWithChannel(ctx, t)
|
mgr, _, ch, fromKeyPrivate := testSetupMgrWithChannel(ctx, t)
|
||||||
|
|
||||||
minDelta := big.NewInt(0)
|
minDelta := big.NewInt(0)
|
||||||
voucherAmount := big.NewInt(2)
|
voucherAmount := big.NewInt(2)
|
||||||
@ -474,7 +477,7 @@ func TestAllocateLane(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// Set up a manager with a single payment channel
|
// Set up a manager with a single payment channel
|
||||||
mgr, ch, _ := testSetupMgrWithChannel(ctx, t)
|
mgr, _, ch, _ := testSetupMgrWithChannel(ctx, t)
|
||||||
|
|
||||||
// First lane should be 0
|
// First lane should be 0
|
||||||
lane, err := mgr.AllocateLane(ch)
|
lane, err := mgr.AllocateLane(ch)
|
||||||
@ -550,7 +553,7 @@ func TestAddVoucherProof(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// Set up a manager with a single payment channel
|
// Set up a manager with a single payment channel
|
||||||
mgr, ch, fromKeyPrivate := testSetupMgrWithChannel(ctx, t)
|
mgr, _, ch, fromKeyPrivate := testSetupMgrWithChannel(ctx, t)
|
||||||
|
|
||||||
nonce := uint64(1)
|
nonce := uint64(1)
|
||||||
voucherAmount := big.NewInt(1)
|
voucherAmount := big.NewInt(1)
|
||||||
@ -656,11 +659,254 @@ func TestAddVoucherInboundWalletKey(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBestSpendable(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Set up a manager with a single payment channel
|
||||||
|
mgr, mock, ch, fromKeyPrivate := testSetupMgrWithChannel(ctx, t)
|
||||||
|
|
||||||
|
// Add vouchers to lane 1 with amounts: [1, 2, 3]
|
||||||
|
voucherLane := uint64(1)
|
||||||
|
minDelta := big.NewInt(0)
|
||||||
|
nonce := uint64(1)
|
||||||
|
voucherAmount := big.NewInt(1)
|
||||||
|
svL1V1 := createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate)
|
||||||
|
_, err := mgr.AddVoucherInbound(ctx, ch, svL1V1, nil, minDelta)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
nonce++
|
||||||
|
voucherAmount = big.NewInt(2)
|
||||||
|
svL1V2 := createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate)
|
||||||
|
_, err = mgr.AddVoucherInbound(ctx, ch, svL1V2, nil, minDelta)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
nonce++
|
||||||
|
voucherAmount = big.NewInt(3)
|
||||||
|
svL1V3 := createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate)
|
||||||
|
_, err = mgr.AddVoucherInbound(ctx, ch, svL1V3, nil, minDelta)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Add voucher to lane 2 with amounts: [2]
|
||||||
|
voucherLane = uint64(2)
|
||||||
|
nonce = uint64(1)
|
||||||
|
voucherAmount = big.NewInt(2)
|
||||||
|
svL2V1 := createTestVoucher(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate)
|
||||||
|
_, err = mgr.AddVoucherInbound(ctx, ch, svL2V1, nil, minDelta)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Return success exit code from calls to check if voucher is spendable
|
||||||
|
bsapi := newMockBestSpendableAPI(mgr)
|
||||||
|
mock.setCallResponse(&api.InvocResult{
|
||||||
|
MsgRct: &types.MessageReceipt{
|
||||||
|
ExitCode: 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Verify best spendable vouchers on each lane
|
||||||
|
vouchers, err := BestSpendableByLane(ctx, bsapi, ch)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, vouchers, 2)
|
||||||
|
|
||||||
|
vchr, ok := vouchers[1]
|
||||||
|
require.True(t, ok)
|
||||||
|
require.EqualValues(t, 3, vchr.Amount.Int64())
|
||||||
|
|
||||||
|
vchr, ok = vouchers[2]
|
||||||
|
require.True(t, ok)
|
||||||
|
require.EqualValues(t, 2, vchr.Amount.Int64())
|
||||||
|
|
||||||
|
// Submit voucher from lane 2
|
||||||
|
_, err = mgr.SubmitVoucher(ctx, ch, svL2V1, nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Best spendable voucher should no longer include lane 2
|
||||||
|
// (because voucher has not been submitted)
|
||||||
|
vouchers, err = BestSpendableByLane(ctx, bsapi, ch)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, vouchers, 1)
|
||||||
|
|
||||||
|
// Submit first voucher from lane 1
|
||||||
|
_, err = mgr.SubmitVoucher(ctx, ch, svL1V1, nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Best spendable voucher for lane 1 should still be highest value voucher
|
||||||
|
vouchers, err = BestSpendableByLane(ctx, bsapi, ch)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, vouchers, 1)
|
||||||
|
|
||||||
|
vchr, ok = vouchers[1]
|
||||||
|
require.True(t, ok)
|
||||||
|
require.EqualValues(t, 3, vchr.Amount.Int64())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckSpendable(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Set up a manager with a single payment channel
|
||||||
|
mgr, mock, ch, fromKeyPrivate := testSetupMgrWithChannel(ctx, t)
|
||||||
|
|
||||||
|
// Create voucher with Extra
|
||||||
|
voucherLane := uint64(1)
|
||||||
|
nonce := uint64(1)
|
||||||
|
voucherAmount := big.NewInt(1)
|
||||||
|
voucher := createTestVoucherWithExtra(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate)
|
||||||
|
|
||||||
|
// Add voucher with proof
|
||||||
|
minDelta := big.NewInt(0)
|
||||||
|
proof := []byte("proof")
|
||||||
|
_, err := mgr.AddVoucherInbound(ctx, ch, voucher, proof, minDelta)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Return success exit code from VM call, which indicates that voucher is
|
||||||
|
// spendable
|
||||||
|
successResponse := &api.InvocResult{
|
||||||
|
MsgRct: &types.MessageReceipt{
|
||||||
|
ExitCode: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mock.setCallResponse(successResponse)
|
||||||
|
|
||||||
|
// Check that spendable is true
|
||||||
|
secret := []byte("secret")
|
||||||
|
otherProof := []byte("other proof")
|
||||||
|
spendable, err := mgr.CheckVoucherSpendable(ctx, ch, voucher, secret, otherProof)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, spendable)
|
||||||
|
|
||||||
|
// Check that the secret and proof were passed through correctly
|
||||||
|
lastCall := mock.getLastCall()
|
||||||
|
var p paych.UpdateChannelStateParams
|
||||||
|
err = p.UnmarshalCBOR(bytes.NewReader(lastCall.Params))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, otherProof, p.Proof)
|
||||||
|
require.Equal(t, secret, p.Secret)
|
||||||
|
|
||||||
|
// Check that if no proof is supplied, the proof supplied to add voucher
|
||||||
|
// above is used
|
||||||
|
secret2 := []byte("secret2")
|
||||||
|
spendable, err = mgr.CheckVoucherSpendable(ctx, ch, voucher, secret2, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, spendable)
|
||||||
|
|
||||||
|
lastCall = mock.getLastCall()
|
||||||
|
var p2 paych.UpdateChannelStateParams
|
||||||
|
err = p2.UnmarshalCBOR(bytes.NewReader(lastCall.Params))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, proof, p2.Proof)
|
||||||
|
require.Equal(t, secret2, p2.Secret)
|
||||||
|
|
||||||
|
// Check that if VM call returns non-success exit code, spendable is false
|
||||||
|
mock.setCallResponse(&api.InvocResult{
|
||||||
|
MsgRct: &types.MessageReceipt{
|
||||||
|
ExitCode: 1,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
spendable, err = mgr.CheckVoucherSpendable(ctx, ch, voucher, secret, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.False(t, spendable)
|
||||||
|
|
||||||
|
// Return success exit code (indicating voucher is spendable)
|
||||||
|
mock.setCallResponse(successResponse)
|
||||||
|
spendable, err = mgr.CheckVoucherSpendable(ctx, ch, voucher, secret, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, spendable)
|
||||||
|
|
||||||
|
// Check that voucher is no longer spendable once it has been submitted
|
||||||
|
_, err = mgr.SubmitVoucher(ctx, ch, voucher, nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
spendable, err = mgr.CheckVoucherSpendable(ctx, ch, voucher, secret, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.False(t, spendable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSubmitVoucher(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Set up a manager with a single payment channel
|
||||||
|
mgr, mock, ch, fromKeyPrivate := testSetupMgrWithChannel(ctx, t)
|
||||||
|
|
||||||
|
// Create voucher with Extra
|
||||||
|
voucherLane := uint64(1)
|
||||||
|
nonce := uint64(1)
|
||||||
|
voucherAmount := big.NewInt(1)
|
||||||
|
voucher := createTestVoucherWithExtra(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate)
|
||||||
|
|
||||||
|
// Add voucher with proof
|
||||||
|
minDelta := big.NewInt(0)
|
||||||
|
addVoucherProof := []byte("proof")
|
||||||
|
_, err := mgr.AddVoucherInbound(ctx, ch, voucher, addVoucherProof, minDelta)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Submit voucher
|
||||||
|
secret := []byte("secret")
|
||||||
|
submitProof := []byte("submit proof")
|
||||||
|
submitCid, err := mgr.SubmitVoucher(ctx, ch, voucher, secret, submitProof)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Check that the secret and proof were passed through correctly
|
||||||
|
msg := mock.pushedMessages(submitCid)
|
||||||
|
var p paych.UpdateChannelStateParams
|
||||||
|
err = p.UnmarshalCBOR(bytes.NewReader(msg.Message.Params))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, submitProof, p.Proof)
|
||||||
|
require.Equal(t, secret, p.Secret)
|
||||||
|
|
||||||
|
// Check that if no proof is supplied to submit voucher, the proof supplied
|
||||||
|
// to add voucher is used
|
||||||
|
nonce++
|
||||||
|
voucherAmount = big.NewInt(2)
|
||||||
|
addVoucherProof2 := []byte("proof2")
|
||||||
|
secret2 := []byte("secret2")
|
||||||
|
voucher = createTestVoucherWithExtra(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate)
|
||||||
|
_, err = mgr.AddVoucherInbound(ctx, ch, voucher, addVoucherProof2, minDelta)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
submitCid, err = mgr.SubmitVoucher(ctx, ch, voucher, secret2, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
msg = mock.pushedMessages(submitCid)
|
||||||
|
var p2 paych.UpdateChannelStateParams
|
||||||
|
err = p2.UnmarshalCBOR(bytes.NewReader(msg.Message.Params))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, addVoucherProof2, p2.Proof)
|
||||||
|
require.Equal(t, secret2, p2.Secret)
|
||||||
|
|
||||||
|
// Submit a voucher without first adding it
|
||||||
|
nonce++
|
||||||
|
voucherAmount = big.NewInt(3)
|
||||||
|
secret3 := []byte("secret2")
|
||||||
|
proof3 := []byte("proof3")
|
||||||
|
voucher = createTestVoucherWithExtra(t, ch, voucherLane, nonce, voucherAmount, fromKeyPrivate)
|
||||||
|
submitCid, err = mgr.SubmitVoucher(ctx, ch, voucher, secret3, proof3)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
msg = mock.pushedMessages(submitCid)
|
||||||
|
var p3 paych.UpdateChannelStateParams
|
||||||
|
err = p3.UnmarshalCBOR(bytes.NewReader(msg.Message.Params))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, proof3, p3.Proof)
|
||||||
|
require.Equal(t, secret3, p3.Secret)
|
||||||
|
|
||||||
|
// Verify that vouchers are marked as submitted
|
||||||
|
vis, err := mgr.ListVouchers(ctx, ch)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, vis, 3)
|
||||||
|
|
||||||
|
for _, vi := range vis {
|
||||||
|
require.True(t, vi.Submitted)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempting to submit the same voucher again should fail
|
||||||
|
_, err = mgr.SubmitVoucher(ctx, ch, voucher, secret2, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestNextNonceForLane(t *testing.T) {
|
func TestNextNonceForLane(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// Set up a manager with a single payment channel
|
// Set up a manager with a single payment channel
|
||||||
mgr, ch, key := testSetupMgrWithChannel(ctx, t)
|
mgr, _, ch, key := testSetupMgrWithChannel(ctx, t)
|
||||||
|
|
||||||
// Expect next nonce for non-existent lane to be 1
|
// Expect next nonce for non-existent lane to be 1
|
||||||
next, err := mgr.NextNonceForLane(ctx, ch, 1)
|
next, err := mgr.NextNonceForLane(ctx, ch, 1)
|
||||||
@ -700,7 +946,7 @@ func TestNextNonceForLane(t *testing.T) {
|
|||||||
require.EqualValues(t, next, 8)
|
require.EqualValues(t, next, 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSetupMgrWithChannel(ctx context.Context, t *testing.T) (*Manager, address.Address, []byte) {
|
func testSetupMgrWithChannel(ctx context.Context, t *testing.T) (*Manager, *mockManagerAPI, address.Address, []byte) {
|
||||||
fromKeyPrivate, fromKeyPublic := testGenerateKeyPair(t)
|
fromKeyPrivate, fromKeyPublic := testGenerateKeyPair(t)
|
||||||
|
|
||||||
ch := tutils.NewIDAddr(t, 100)
|
ch := tutils.NewIDAddr(t, 100)
|
||||||
@ -745,7 +991,7 @@ func testSetupMgrWithChannel(ctx context.Context, t *testing.T) (*Manager, addre
|
|||||||
err = mgr.store.putChannelInfo(ci)
|
err = mgr.store.putChannelInfo(ci)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
return mgr, ch, fromKeyPrivate
|
return mgr, mock, ch, fromKeyPrivate
|
||||||
}
|
}
|
||||||
|
|
||||||
func testGenerateKeyPair(t *testing.T) ([]byte, []byte) {
|
func testGenerateKeyPair(t *testing.T) ([]byte, []byte) {
|
||||||
@ -771,3 +1017,49 @@ func createTestVoucher(t *testing.T, ch address.Address, voucherLane uint64, non
|
|||||||
sv.Signature = sig
|
sv.Signature = sig
|
||||||
return sv
|
return sv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createTestVoucherWithExtra(t *testing.T, ch address.Address, voucherLane uint64, nonce uint64, voucherAmount big.Int, key []byte) *paych.SignedVoucher {
|
||||||
|
sv := &paych.SignedVoucher{
|
||||||
|
ChannelAddr: ch,
|
||||||
|
Lane: voucherLane,
|
||||||
|
Nonce: nonce,
|
||||||
|
Amount: voucherAmount,
|
||||||
|
Extra: &paych.ModVerifyParams{
|
||||||
|
Actor: tutils.NewActorAddr(t, "act"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
signingBytes, err := sv.SigningBytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
sig, err := sigs.Sign(crypto.SigTypeSecp256k1, key, signingBytes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
sv.Signature = sig
|
||||||
|
|
||||||
|
return sv
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockBestSpendableAPI struct {
|
||||||
|
mgr *Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockBestSpendableAPI) PaychVoucherList(ctx context.Context, ch address.Address) ([]*paych.SignedVoucher, error) {
|
||||||
|
vi, err := m.mgr.ListVouchers(ctx, ch)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make([]*paych.SignedVoucher, len(vi))
|
||||||
|
for k, v := range vi {
|
||||||
|
out[k] = v.Voucher
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockBestSpendableAPI) PaychVoucherCheckSpendable(ctx context.Context, ch address.Address, voucher *paych.SignedVoucher, secret []byte, proof []byte) (bool, error) {
|
||||||
|
return m.mgr.CheckVoucherSpendable(ctx, ch, voucher, secret, proof)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockBestSpendableAPI(mgr *Manager) BestSpendableAPI {
|
||||||
|
return &mockBestSpendableAPI{mgr: mgr}
|
||||||
|
}
|
||||||
|
@ -40,7 +40,7 @@ type settlerAPI interface {
|
|||||||
PaychStatus(context.Context, address.Address) (*api.PaychStatus, error)
|
PaychStatus(context.Context, address.Address) (*api.PaychStatus, error)
|
||||||
PaychVoucherCheckSpendable(context.Context, address.Address, *paych.SignedVoucher, []byte, []byte) (bool, error)
|
PaychVoucherCheckSpendable(context.Context, address.Address, *paych.SignedVoucher, []byte, []byte) (bool, error)
|
||||||
PaychVoucherList(context.Context, address.Address) ([]*paych.SignedVoucher, error)
|
PaychVoucherList(context.Context, address.Address) ([]*paych.SignedVoucher, error)
|
||||||
PaychVoucherSubmit(context.Context, address.Address, *paych.SignedVoucher) (cid.Cid, error)
|
PaychVoucherSubmit(context.Context, address.Address, *paych.SignedVoucher, []byte, []byte) (cid.Cid, error)
|
||||||
StateWaitMsg(ctx context.Context, cid cid.Cid, confidence uint64) (*api.MsgLookup, error)
|
StateWaitMsg(ctx context.Context, cid cid.Cid, confidence uint64) (*api.MsgLookup, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ func (pcs *paymentChannelSettler) messageHandler(msg *types.Message, rec *types.
|
|||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(len(bestByLane))
|
wg.Add(len(bestByLane))
|
||||||
for _, voucher := range bestByLane {
|
for _, voucher := range bestByLane {
|
||||||
submitMessageCID, err := pcs.api.PaychVoucherSubmit(pcs.ctx, msg.To, voucher)
|
submitMessageCID, err := pcs.api.PaychVoucherSubmit(pcs.ctx, msg.To, voucher, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,13 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"github.com/filecoin-project/lotus/chain/types"
|
"github.com/filecoin-project/lotus/chain/types"
|
||||||
|
|
||||||
|
cborutil "github.com/filecoin-project/go-cbor-util"
|
||||||
"github.com/filecoin-project/specs-actors/actors/builtin/paych"
|
"github.com/filecoin-project/specs-actors/actors/builtin/paych"
|
||||||
"github.com/ipfs/go-cid"
|
"github.com/ipfs/go-cid"
|
||||||
"github.com/ipfs/go-datastore"
|
"github.com/ipfs/go-datastore"
|
||||||
@ -45,8 +48,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type VoucherInfo struct {
|
type VoucherInfo struct {
|
||||||
Voucher *paych.SignedVoucher
|
Voucher *paych.SignedVoucher
|
||||||
Proof []byte
|
Proof []byte
|
||||||
|
Submitted bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChannelInfo keeps track of information about a channel
|
// ChannelInfo keeps track of information about a channel
|
||||||
@ -82,6 +86,64 @@ type ChannelInfo struct {
|
|||||||
Settling bool
|
Settling bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// infoForVoucher gets the VoucherInfo for the given voucher.
|
||||||
|
// returns nil if the channel doesn't have the voucher.
|
||||||
|
func (ci *ChannelInfo) infoForVoucher(sv *paych.SignedVoucher) (*VoucherInfo, error) {
|
||||||
|
for _, v := range ci.Vouchers {
|
||||||
|
eq, err := cborutil.Equals(sv, v.Voucher)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if eq {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ci *ChannelInfo) hasVoucher(sv *paych.SignedVoucher) (bool, error) {
|
||||||
|
vi, err := ci.infoForVoucher(sv)
|
||||||
|
return vi != nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// markVoucherSubmitted marks the voucher, and any vouchers of lower nonce
|
||||||
|
// in the same lane, as being submitted.
|
||||||
|
// Note: This method doesn't write anything to the store.
|
||||||
|
func (ci *ChannelInfo) markVoucherSubmitted(sv *paych.SignedVoucher) error {
|
||||||
|
vi, err := ci.infoForVoucher(sv)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if vi == nil {
|
||||||
|
return xerrors.Errorf("cannot submit voucher that has not been added to channel")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark the voucher as submitted
|
||||||
|
vi.Submitted = true
|
||||||
|
|
||||||
|
// Mark lower-nonce vouchers in the same lane as submitted (lower-nonce
|
||||||
|
// vouchers are superseded by the submitted voucher)
|
||||||
|
for _, vi := range ci.Vouchers {
|
||||||
|
if vi.Voucher.Lane == sv.Lane && vi.Voucher.Nonce < sv.Nonce {
|
||||||
|
vi.Submitted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// wasVoucherSubmitted returns true if the voucher has been submitted
|
||||||
|
func (ci *ChannelInfo) wasVoucherSubmitted(sv *paych.SignedVoucher) (bool, error) {
|
||||||
|
vi, err := ci.infoForVoucher(sv)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if vi == nil {
|
||||||
|
return false, xerrors.Errorf("cannot submit voucher that has not been added to channel")
|
||||||
|
}
|
||||||
|
return vi.Submitted, nil
|
||||||
|
}
|
||||||
|
|
||||||
// TrackChannel stores a channel, returning an error if the channel was already
|
// TrackChannel stores a channel, returning an error if the channel was already
|
||||||
// being tracked
|
// being tracked
|
||||||
func (ps *Store) TrackChannel(ci *ChannelInfo) (*ChannelInfo, error) {
|
func (ps *Store) TrackChannel(ci *ChannelInfo) (*ChannelInfo, error) {
|
||||||
@ -200,6 +262,14 @@ func (ps *Store) VouchersForPaych(ch address.Address) ([]*VoucherInfo, error) {
|
|||||||
return ci.Vouchers, nil
|
return ci.Vouchers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ps *Store) MarkVoucherSubmitted(ci *ChannelInfo, sv *paych.SignedVoucher) error {
|
||||||
|
err := ci.markVoucherSubmitted(sv)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ps.putChannelInfo(ci)
|
||||||
|
}
|
||||||
|
|
||||||
// ByAddress gets the channel that matches the given address
|
// ByAddress gets the channel that matches the given address
|
||||||
func (ps *Store) ByAddress(addr address.Address) (*ChannelInfo, error) {
|
func (ps *Store) ByAddress(addr address.Address) (*ChannelInfo, error) {
|
||||||
return ps.findChan(func(ci *ChannelInfo) bool {
|
return ps.findChan(func(ci *ChannelInfo) bool {
|
||||||
|
Loading…
Reference in New Issue
Block a user