diff --git a/api/api_full.go b/api/api_full.go index cf58a3cc6ee..4c4d6ebf936 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -689,7 +689,17 @@ type FullNode interface { // MethodGroup: Paych // The Paych methods are for interacting with and managing payment channels - PaychGet(ctx context.Context, from, to address.Address, amt types.BigInt) (*ChannelInfo, error) //perm:sign + // PaychGet gets or creates a payment channel between address pair + // The specified amount will be reserved for use. If there aren't enough non-reserved funds + // available, funds will be added through an on-chain message. + // - When opts.OffChain is true, this call will not cause any messages to be sent to the chain (no automatic + // channel creation/funds adding). If the operation can't be performed without sending a message an error will be + // returned. Note that even when this option is specified, this call can be blocked by previous operations on the + // channel waiting for on-chain operations. + PaychGet(ctx context.Context, from, to address.Address, amt types.BigInt, opts PaychGetOpts) (*ChannelInfo, error) //perm:sign + // PaychFund gets or creates a payment channel between address pair. + // The specified amount will be added to the channel through on-chain send for future use + PaychFund(ctx context.Context, from, to address.Address, amt types.BigInt) (*ChannelInfo, error) //perm:sign PaychGetWaitReady(context.Context, cid.Cid) (address.Address, error) //perm:sign PaychAvailableFunds(ctx context.Context, ch address.Address) (*ChannelAvailableFunds, error) //perm:sign PaychAvailableFundsByFromTo(ctx context.Context, from, to address.Address) (*ChannelAvailableFunds, error) //perm:sign @@ -828,6 +838,10 @@ const ( PCHOutbound ) +type PaychGetOpts struct { + OffChain bool +} + type PaychStatus struct { ControlAddr address.Address Direction PCHDir @@ -845,16 +859,23 @@ type ChannelAvailableFunds struct { From address.Address // To is the to address of the channel To address.Address - // ConfirmedAmt is the amount of funds that have been confirmed on-chain - // for the channel + + // ConfirmedAmt is the total amount of funds that have been confirmed on-chain for the channel ConfirmedAmt types.BigInt // PendingAmt is the amount of funds that are pending confirmation on-chain PendingAmt types.BigInt + + // NonReservedAmt is part of ConfirmedAmt that is available for use (e.g. when the payment channel was pre-funded) + NonReservedAmt types.BigInt + // PendingAvailableAmt is the amount of funds that are pending confirmation on-chain that will become available once confirmed + PendingAvailableAmt types.BigInt + // PendingWaitSentinel can be used with PaychGetWaitReady to wait for // confirmation of pending funds PendingWaitSentinel *cid.Cid // QueuedAmt is the amount that is queued up behind a pending request QueuedAmt types.BigInt + // VoucherRedeemedAmt is the amount that is redeemed by vouchers on-chain // and in the local datastore VoucherReedeemedAmt types.BigInt diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index e985a794d0c..ab120abdd22 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -1975,19 +1975,34 @@ func (mr *MockFullNodeMockRecorder) PaychCollect(arg0, arg1 interface{}) *gomock return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychCollect", reflect.TypeOf((*MockFullNode)(nil).PaychCollect), arg0, arg1) } +// PaychFund mocks base method. +func (m *MockFullNode) PaychFund(arg0 context.Context, arg1, arg2 address.Address, arg3 big.Int) (*api.ChannelInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychFund", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*api.ChannelInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychFund indicates an expected call of PaychFund. +func (mr *MockFullNodeMockRecorder) PaychFund(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychFund", reflect.TypeOf((*MockFullNode)(nil).PaychFund), arg0, arg1, arg2, arg3) +} + // PaychGet mocks base method. -func (m *MockFullNode) PaychGet(arg0 context.Context, arg1, arg2 address.Address, arg3 big.Int) (*api.ChannelInfo, error) { +func (m *MockFullNode) PaychGet(arg0 context.Context, arg1, arg2 address.Address, arg3 big.Int, arg4 api.PaychGetOpts) (*api.ChannelInfo, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "PaychGet", arg0, arg1, arg2, arg3) + ret := m.ctrl.Call(m, "PaychGet", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].(*api.ChannelInfo) ret1, _ := ret[1].(error) return ret0, ret1 } // PaychGet indicates an expected call of PaychGet. -func (mr *MockFullNodeMockRecorder) PaychGet(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockFullNodeMockRecorder) PaychGet(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychGet", reflect.TypeOf((*MockFullNode)(nil).PaychGet), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychGet", reflect.TypeOf((*MockFullNode)(nil).PaychGet), arg0, arg1, arg2, arg3, arg4) } // PaychGetWaitReady mocks base method. diff --git a/api/proxy_gen.go b/api/proxy_gen.go index cd3e1ad3533..5e27b0117a4 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -306,7 +306,9 @@ type FullNodeStruct struct { PaychCollect func(p0 context.Context, p1 address.Address) (cid.Cid, error) `perm:"sign"` - PaychGet func(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (*ChannelInfo, error) `perm:"sign"` + PaychFund func(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (*ChannelInfo, error) `perm:"sign"` + + PaychGet func(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt, p4 PaychGetOpts) (*ChannelInfo, error) `perm:"sign"` PaychGetWaitReady func(p0 context.Context, p1 cid.Cid) (address.Address, error) `perm:"sign"` @@ -2187,14 +2189,25 @@ func (s *FullNodeStub) PaychCollect(p0 context.Context, p1 address.Address) (cid return *new(cid.Cid), ErrNotSupported } -func (s *FullNodeStruct) PaychGet(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (*ChannelInfo, error) { +func (s *FullNodeStruct) PaychFund(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (*ChannelInfo, error) { + if s.Internal.PaychFund == nil { + return nil, ErrNotSupported + } + return s.Internal.PaychFund(p0, p1, p2, p3) +} + +func (s *FullNodeStub) PaychFund(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (*ChannelInfo, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) PaychGet(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt, p4 PaychGetOpts) (*ChannelInfo, error) { if s.Internal.PaychGet == nil { return nil, ErrNotSupported } - return s.Internal.PaychGet(p0, p1, p2, p3) + return s.Internal.PaychGet(p0, p1, p2, p3, p4) } -func (s *FullNodeStub) PaychGet(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (*ChannelInfo, error) { +func (s *FullNodeStub) PaychGet(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt, p4 PaychGetOpts) (*ChannelInfo, error) { return nil, ErrNotSupported } diff --git a/api/v0api/v1_wrapper.go b/api/v0api/v1_wrapper.go index 7e0d7a94ab6..3f2dd837391 100644 --- a/api/v0api/v1_wrapper.go +++ b/api/v0api/v1_wrapper.go @@ -337,4 +337,8 @@ func (w *WrapperV1Full) clientRetrieve(ctx context.Context, order RetrievalOrder finish(w.ClientExport(ctx, eref, *ref)) } +func (w *WrapperV1Full) PaychGet(ctx context.Context, from, to address.Address, amt types.BigInt) (*api.ChannelInfo, error) { + return w.FullNode.PaychFund(ctx, from, to, amt) +} + var _ FullNode = &WrapperV1Full{} diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index b5d1d4e4464..3ea46be45ad 100644 Binary files a/build/openrpc/full.json.gz and b/build/openrpc/full.json.gz differ diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index 303444e9921..81756c8297c 100644 Binary files a/build/openrpc/miner.json.gz and b/build/openrpc/miner.json.gz differ diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index 397ee7423a8..5fa5f4ba3f9 100644 Binary files a/build/openrpc/worker.json.gz and b/build/openrpc/worker.json.gz differ diff --git a/cli/paych.go b/cli/paych.go index 1d5e304c383..92c1a13e31f 100644 --- a/cli/paych.go +++ b/cli/paych.go @@ -8,7 +8,7 @@ import ( "sort" "strings" - "github.com/filecoin-project/lotus/api" + lapi "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/paychmgr" @@ -39,12 +39,15 @@ var paychAddFundsCmd = &cli.Command{ Usage: "Add funds to the payment channel between fromAddress and toAddress. Creates the payment channel if it doesn't already exist.", ArgsUsage: "[fromAddress toAddress amount]", Flags: []cli.Flag{ - &cli.BoolFlag{ Name: "restart-retrievals", Usage: "restart stalled retrieval deals on this payment channel", Value: true, }, + &cli.BoolFlag{ + Name: "reserve", + Usage: "mark funds as reserved", + }, }, Action: func(cctx *cli.Context) error { if cctx.Args().Len() != 3 { @@ -66,7 +69,7 @@ var paychAddFundsCmd = &cli.Command{ return ShowHelp(cctx, fmt.Errorf("parsing amount failed: %s", err)) } - api, closer, err := GetFullNodeAPI(cctx) + api, closer, err := GetFullNodeAPIV1(cctx) if err != nil { return err } @@ -76,7 +79,14 @@ var paychAddFundsCmd = &cli.Command{ // Send a message to chain to create channel / add funds to existing // channel - info, err := api.PaychGet(ctx, from, to, types.BigInt(amt)) + var info *lapi.ChannelInfo + if cctx.Bool("reserve") { + info, err = api.PaychGet(ctx, from, to, types.BigInt(amt), lapi.PaychGetOpts{ + OffChain: false, + }) + } else { + info, err = api.PaychFund(ctx, from, to, types.BigInt(amt)) + } if err != nil { return err } @@ -163,13 +173,13 @@ var paychStatusCmd = &cli.Command{ }, } -func paychStatus(writer io.Writer, avail *api.ChannelAvailableFunds) { +func paychStatus(writer io.Writer, avail *lapi.ChannelAvailableFunds) { if avail.Channel == nil { if avail.PendingWaitSentinel != nil { fmt.Fprint(writer, "Creating channel\n") fmt.Fprintf(writer, " From: %s\n", avail.From) fmt.Fprintf(writer, " To: %s\n", avail.To) - fmt.Fprintf(writer, " Pending Amt: %d\n", avail.PendingAmt) + fmt.Fprintf(writer, " Pending Amt: %s\n", types.FIL(avail.PendingAmt)) fmt.Fprintf(writer, " Wait Sentinel: %s\n", avail.PendingWaitSentinel) return } @@ -189,10 +199,12 @@ func paychStatus(writer io.Writer, avail *api.ChannelAvailableFunds) { {"Channel", avail.Channel.String()}, {"From", avail.From.String()}, {"To", avail.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)}, + {"Confirmed Amt", fmt.Sprintf("%s", types.FIL(avail.ConfirmedAmt))}, + {"Available Amt", fmt.Sprintf("%s", types.FIL(avail.NonReservedAmt))}, + {"Voucher Redeemed Amt", fmt.Sprintf("%s", types.FIL(avail.VoucherReedeemedAmt))}, + {"Pending Amt", fmt.Sprintf("%s", types.FIL(avail.PendingAmt))}, + {"Pending Available Amt", fmt.Sprintf("%s", types.FIL(avail.PendingAvailableAmt))}, + {"Queued Amt", fmt.Sprintf("%s", types.FIL(avail.QueuedAmt))}, } if avail.PendingWaitSentinel != nil { nameValues = append(nameValues, []string{ @@ -576,7 +588,7 @@ func outputVoucher(w io.Writer, v *paych.SignedVoucher, export bool) error { } } - fmt.Fprintf(w, "Lane %d, Nonce %d: %s", v.Lane, v.Nonce, v.Amount.String()) + fmt.Fprintf(w, "Lane %d, Nonce %d: %s", v.Lane, v.Nonce, types.FIL(v.Amount)) if export { fmt.Fprintf(w, "; %s", enc) } diff --git a/documentation/en/api-v0-methods.md b/documentation/en/api-v0-methods.md index 883d4d27499..9444444f569 100644 --- a/documentation/en/api-v0-methods.md +++ b/documentation/en/api-v0-methods.md @@ -4061,6 +4061,8 @@ Response: "To": "f01234", "ConfirmedAmt": "0", "PendingAmt": "0", + "NonReservedAmt": "0", + "PendingAvailableAmt": "0", "PendingWaitSentinel": null, "QueuedAmt": "0", "VoucherReedeemedAmt": "0" @@ -4088,6 +4090,8 @@ Response: "To": "f01234", "ConfirmedAmt": "0", "PendingAmt": "0", + "NonReservedAmt": "0", + "PendingAvailableAmt": "0", "PendingWaitSentinel": null, "QueuedAmt": "0", "VoucherReedeemedAmt": "0" diff --git a/documentation/en/api-v1-unstable-methods.md b/documentation/en/api-v1-unstable-methods.md index a5fdd999429..22e85901c9a 100644 --- a/documentation/en/api-v1-unstable-methods.md +++ b/documentation/en/api-v1-unstable-methods.md @@ -147,6 +147,7 @@ * [PaychAvailableFunds](#PaychAvailableFunds) * [PaychAvailableFundsByFromTo](#PaychAvailableFundsByFromTo) * [PaychCollect](#PaychCollect) + * [PaychFund](#PaychFund) * [PaychGet](#PaychGet) * [PaychGetWaitReady](#PaychGetWaitReady) * [PaychList](#PaychList) @@ -4456,6 +4457,8 @@ Response: "To": "f01234", "ConfirmedAmt": "0", "PendingAmt": "0", + "NonReservedAmt": "0", + "PendingAvailableAmt": "0", "PendingWaitSentinel": null, "QueuedAmt": "0", "VoucherReedeemedAmt": "0" @@ -4483,6 +4486,8 @@ Response: "To": "f01234", "ConfirmedAmt": "0", "PendingAmt": "0", + "NonReservedAmt": "0", + "PendingAvailableAmt": "0", "PendingWaitSentinel": null, "QueuedAmt": "0", "VoucherReedeemedAmt": "0" @@ -4508,8 +4513,10 @@ Response: } ``` -### PaychGet -There are not yet any comments for this method. +### PaychFund +PaychFund gets or creates a payment channel between address pair. +The specified amount will be added to the channel through on-chain send for future use + Perms: sign @@ -4532,6 +4539,40 @@ Response: } ``` +### PaychGet +PaychGet gets or creates a payment channel between address pair + The specified amount will be reserved for use. If there aren't enough non-reserved funds + available, funds will be added through an on-chain message. + - When opts.OffChain is true, this call will not cause any messages to be sent to the chain (no automatic + channel creation/funds adding). If the operation can't be performed without sending a message an error will be + returned. Note that even when this option is specified, this call can be blocked by previous operations on the + channel waiting for on-chain operations. + + +Perms: sign + +Inputs: +```json +[ + "f01234", + "f01234", + "0", + { + "OffChain": true + } +] +``` + +Response: +```json +{ + "Channel": "f01234", + "WaitSentinel": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + } +} +``` + ### PaychGetWaitReady diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index 5499459128a..b5bd8c4d3c6 100644 --- a/documentation/en/cli-lotus.md +++ b/documentation/en/cli-lotus.md @@ -1363,6 +1363,7 @@ USAGE: OPTIONS: --restart-retrievals restart stalled retrieval deals on this payment channel (default: true) + --reserve mark funds as reserved (default: false) --help, -h show help (default: false) ``` diff --git a/documentation/en/default-lotus-config.toml b/documentation/en/default-lotus-config.toml index 0bc22c9d772..ad917814b0d 100644 --- a/documentation/en/default-lotus-config.toml +++ b/documentation/en/default-lotus-config.toml @@ -121,6 +121,14 @@ # env var: LOTUS_CLIENT_SIMULTANEOUSTRANSFERSFORRETRIEVAL #SimultaneousTransfersForRetrieval = 20 + # Require that retrievals perform no on-chain operations. Paid retrievals + # without existing payment channels with available funds will fail instead + # of automatically performing on-chain operations. + # + # type: bool + # env var: LOTUS_CLIENT_OFFCHAINRETRIEVAL + #OffChainRetrieval = false + [Wallet] # type: string diff --git a/go.mod b/go.mod index 344bcd7e275..77fdc21ef30 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/filecoin-project/go-data-transfer v1.14.0 github.com/filecoin-project/go-fil-commcid v0.1.0 github.com/filecoin-project/go-fil-commp-hashhash v0.1.0 - github.com/filecoin-project/go-fil-markets v1.19.0 + github.com/filecoin-project/go-fil-markets v1.19.1 github.com/filecoin-project/go-jsonrpc v0.1.5 github.com/filecoin-project/go-padreader v0.0.1 github.com/filecoin-project/go-paramfetch v0.0.4 diff --git a/go.sum b/go.sum index 15fa2807b00..1597995c7dc 100644 --- a/go.sum +++ b/go.sum @@ -327,8 +327,8 @@ github.com/filecoin-project/go-fil-commcid v0.1.0 h1:3R4ds1A9r6cr8mvZBfMYxTS88Oq github.com/filecoin-project/go-fil-commcid v0.1.0/go.mod h1:Eaox7Hvus1JgPrL5+M3+h7aSPHc0cVqpSxA+TxIEpZQ= github.com/filecoin-project/go-fil-commp-hashhash v0.1.0 h1:imrrpZWEHRnNqqv0tN7LXep5bFEVOVmQWHJvl2mgsGo= github.com/filecoin-project/go-fil-commp-hashhash v0.1.0/go.mod h1:73S8WSEWh9vr0fDJVnKADhfIv/d6dCbAGaAGWbdJEI8= -github.com/filecoin-project/go-fil-markets v1.19.0 h1:kap2q2wTM6tfkVO5gMA5DD9GUeTvkDhMfhjCtEwMDM8= -github.com/filecoin-project/go-fil-markets v1.19.0/go.mod h1:qsb3apmo4RSJYCEq40QxVdU7UZospN6nFJLOBHuaIbc= +github.com/filecoin-project/go-fil-markets v1.19.1 h1:o5sziAp8zCsvIg3KYMgIpwm8gyOl4MDzEKEf0Qq5L3U= +github.com/filecoin-project/go-fil-markets v1.19.1/go.mod h1:qsb3apmo4RSJYCEq40QxVdU7UZospN6nFJLOBHuaIbc= github.com/filecoin-project/go-hamt-ipld v0.1.5 h1:uoXrKbCQZ49OHpsTCkrThPNelC4W3LPEk0OrS/ytIBM= github.com/filecoin-project/go-hamt-ipld v0.1.5/go.mod h1:6Is+ONR5Cd5R6XZoCse1CWaXZc0Hdb/JeX+EQCQzX24= github.com/filecoin-project/go-hamt-ipld/v2 v2.0.0 h1:b3UDemBYN2HNfk3KOXNuxgTTxlWi3xVvbQP0IT38fvM= diff --git a/itests/paych_api_test.go b/itests/paych_api_test.go index 7e135a9bea7..19e18106415 100644 --- a/itests/paych_api_test.go +++ b/itests/paych_api_test.go @@ -65,7 +65,9 @@ func TestPaymentChannelsAPI(t *testing.T) { require.NoError(t, err) channelAmt := int64(7000) - channelInfo, err := paymentCreator.PaychGet(ctx, createrAddr, receiverAddr, abi.NewTokenAmount(channelAmt)) + channelInfo, err := paymentCreator.PaychGet(ctx, createrAddr, receiverAddr, abi.NewTokenAmount(channelAmt), api.PaychGetOpts{ + OffChain: false, + }) require.NoError(t, err) channel, err := paymentCreator.PaychGetWaitReady(ctx, channelInfo.WaitSentinel) diff --git a/itests/paych_cli_test.go b/itests/paych_cli_test.go index c3f9deeba8a..781be80a83b 100644 --- a/itests/paych_cli_test.go +++ b/itests/paych_cli_test.go @@ -144,10 +144,10 @@ func TestPaymentChannelStatus(t *testing.T) { require.True(t, stateCreating || stateCreated) channelAmtAtto := types.BigMul(types.NewInt(channelAmt), types.NewInt(build.FilecoinPrecision)) - channelAmtStr := fmt.Sprintf("%d", channelAmtAtto) + channelAmtStr := fmt.Sprintf("%s", types.FIL(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) + require.Regexp(t, regexp.MustCompile("Pending Amt.*"+channelAmtStr), out) } // Wait for create channel to complete @@ -170,7 +170,7 @@ func TestPaymentChannelStatus(t *testing.T) { out = creatorCLI.RunCmd("paych", "status", chstr) fmt.Println(out) voucherAmtAtto := types.BigMul(types.NewInt(voucherAmt), types.NewInt(build.FilecoinPrecision)) - voucherAmtStr := fmt.Sprintf("%d", voucherAmtAtto) + voucherAmtStr := fmt.Sprintf("%s", types.FIL(voucherAmtAtto)) // Output should include voucher amount require.Regexp(t, regexp.MustCompile("Voucher.*"+voucherAmtStr), out) } diff --git a/markets/retrievaladapter/client.go b/markets/retrievaladapter/client.go index 1bef23e1296..74f02570b9e 100644 --- a/markets/retrievaladapter/client.go +++ b/markets/retrievaladapter/client.go @@ -10,6 +10,7 @@ import ( "github.com/ipfs/go-cid" "github.com/multiformats/go-multiaddr" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors/builtin/paych" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/node/impl/full" @@ -17,6 +18,8 @@ import ( ) type retrievalClientNode struct { + forceOffChain bool + chainAPI full.ChainAPI payAPI payapi.PaychAPI stateAPI full.StateAPI @@ -24,8 +27,13 @@ type retrievalClientNode struct { // NewRetrievalClientNode returns a new node adapter for a retrieval client that talks to the // Lotus Node -func NewRetrievalClientNode(payAPI payapi.PaychAPI, chainAPI full.ChainAPI, stateAPI full.StateAPI) retrievalmarket.RetrievalClientNode { - return &retrievalClientNode{payAPI: payAPI, chainAPI: chainAPI, stateAPI: stateAPI} +func NewRetrievalClientNode(forceOffChain bool, payAPI payapi.PaychAPI, chainAPI full.ChainAPI, stateAPI full.StateAPI) retrievalmarket.RetrievalClientNode { + return &retrievalClientNode{ + forceOffChain: forceOffChain, + chainAPI: chainAPI, + payAPI: payAPI, + stateAPI: stateAPI, + } } // GetOrCreatePaymentChannel sets up a new payment channel if one does not exist @@ -34,10 +42,14 @@ func NewRetrievalClientNode(payAPI payapi.PaychAPI, chainAPI full.ChainAPI, stat func (rcn *retrievalClientNode) GetOrCreatePaymentChannel(ctx context.Context, clientAddress address.Address, minerAddress address.Address, clientFundsAvailable abi.TokenAmount, tok shared.TipSetToken) (address.Address, cid.Cid, error) { // TODO: respect the provided TipSetToken (a serialized TipSetKey) when // querying the chain - ci, err := rcn.payAPI.PaychGet(ctx, clientAddress, minerAddress, clientFundsAvailable) + ci, err := rcn.payAPI.PaychGet(ctx, clientAddress, minerAddress, clientFundsAvailable, api.PaychGetOpts{ + OffChain: rcn.forceOffChain, + }) if err != nil { + log.Errorw("paych get failed", "error", err) return address.Undef, cid.Undef, err } + return ci.Channel, ci.WaitSentinel, nil } diff --git a/node/builder_chain.go b/node/builder_chain.go index 11283ec3a09..0d10dcb9b22 100644 --- a/node/builder_chain.go +++ b/node/builder_chain.go @@ -121,7 +121,7 @@ var ChainNode = Options( // Markets (retrieval) Override(new(discovery.PeerResolver), modules.RetrievalResolver), Override(new(retrievalmarket.BlockstoreAccessor), modules.RetrievalBlockstoreAccessor), - Override(new(retrievalmarket.RetrievalClient), modules.RetrievalClient), + Override(new(retrievalmarket.RetrievalClient), modules.RetrievalClient(false)), Override(new(dtypes.ClientDataTransfer), modules.NewClientGraphsyncDataTransfer), // Markets (storage) @@ -221,6 +221,8 @@ func ConfigFullNode(c interface{}) Option { ), Override(new(dtypes.Graphsync), modules.Graphsync(cfg.Client.SimultaneousTransfersForStorage, cfg.Client.SimultaneousTransfersForRetrieval)), + Override(new(retrievalmarket.RetrievalClient), modules.RetrievalClient(cfg.Client.OffChainRetrieval)), + If(cfg.Wallet.RemoteBackend != "", Override(new(*remotewallet.RemoteWallet), remotewallet.SetupRemoteWallet(cfg.Wallet.RemoteBackend)), ), diff --git a/node/config/doc_gen.go b/node/config/doc_gen.go index 80748d1e6e7..2a7f63180ce 100644 --- a/node/config/doc_gen.go +++ b/node/config/doc_gen.go @@ -105,6 +105,14 @@ and storage providers for storage deals`, Comment: `The maximum number of simultaneous data transfers between the client and storage providers for retrieval deals`, }, + { + Name: "OffChainRetrieval", + Type: "bool", + + Comment: `Require that retrievals perform no on-chain operations. Paid retrievals +without existing payment channels with available funds will fail instead +of automatically performing on-chain operations.`, + }, }, "Common": []DocField{ { diff --git a/node/config/types.go b/node/config/types.go index 93f5459e650..b073735d32e 100644 --- a/node/config/types.go +++ b/node/config/types.go @@ -387,6 +387,11 @@ type Client struct { // The maximum number of simultaneous data transfers between the client // and storage providers for retrieval deals SimultaneousTransfersForRetrieval uint64 + + // Require that retrievals perform no on-chain operations. Paid retrievals + // without existing payment channels with available funds will fail instead + // of automatically performing on-chain operations. + OffChainRetrieval bool } type Wallet struct { diff --git a/node/impl/paych/paych.go b/node/impl/paych/paych.go index df3b1e3e490..d338c6032fd 100644 --- a/node/impl/paych/paych.go +++ b/node/impl/paych/paych.go @@ -22,8 +22,26 @@ type PaychAPI struct { PaychMgr *paychmgr.Manager } -func (a *PaychAPI) PaychGet(ctx context.Context, from, to address.Address, amt types.BigInt) (*api.ChannelInfo, error) { - ch, mcid, err := a.PaychMgr.GetPaych(ctx, from, to, amt) +func (a *PaychAPI) PaychGet(ctx context.Context, from, to address.Address, amt types.BigInt, opts api.PaychGetOpts) (*api.ChannelInfo, error) { + ch, mcid, err := a.PaychMgr.GetPaych(ctx, from, to, amt, paychmgr.GetOpts{ + Reserve: true, + OffChain: opts.OffChain, + }) + if err != nil { + return nil, err + } + + return &api.ChannelInfo{ + Channel: ch, + WaitSentinel: mcid, + }, nil +} + +func (a *PaychAPI) PaychFund(ctx context.Context, from, to address.Address, amt types.BigInt) (*api.ChannelInfo, error) { + ch, mcid, err := a.PaychMgr.GetPaych(ctx, from, to, amt, paychmgr.GetOpts{ + Reserve: false, + OffChain: false, + }) if err != nil { return nil, err } @@ -55,7 +73,7 @@ func (a *PaychAPI) PaychNewPayment(ctx context.Context, from, to address.Address // TODO: Fix free fund tracking in PaychGet // TODO: validate voucher spec before locking funds - ch, err := a.PaychGet(ctx, from, to, amount) + ch, err := a.PaychGet(ctx, from, to, amount, api.PaychGetOpts{OffChain: false}) if err != nil { return nil, err } diff --git a/node/modules/client.go b/node/modules/client.go index 48f9dc3d778..1e74182041d 100644 --- a/node/modules/client.go +++ b/node/modules/client.go @@ -202,26 +202,28 @@ func StorageClient(lc fx.Lifecycle, h host.Host, dataTransfer dtypes.ClientDataT } // RetrievalClient creates a new retrieval client attached to the client blockstore -func RetrievalClient(lc fx.Lifecycle, h host.Host, r repo.LockedRepo, dt dtypes.ClientDataTransfer, payAPI payapi.PaychAPI, resolver discovery.PeerResolver, +func RetrievalClient(forceOffChain bool) func(lc fx.Lifecycle, h host.Host, r repo.LockedRepo, dt dtypes.ClientDataTransfer, payAPI payapi.PaychAPI, resolver discovery.PeerResolver, ds dtypes.MetadataDS, chainAPI full.ChainAPI, stateAPI full.StateAPI, accessor retrievalmarket.BlockstoreAccessor, j journal.Journal) (retrievalmarket.RetrievalClient, error) { - - adapter := retrievaladapter.NewRetrievalClientNode(payAPI, chainAPI, stateAPI) - network := rmnet.NewFromLibp2pHost(h) - ds = namespace.Wrap(ds, datastore.NewKey("/retrievals/client")) - client, err := retrievalimpl.NewClient(network, dt, adapter, resolver, ds, accessor) - if err != nil { - return nil, err + return func(lc fx.Lifecycle, h host.Host, r repo.LockedRepo, dt dtypes.ClientDataTransfer, payAPI payapi.PaychAPI, resolver discovery.PeerResolver, + ds dtypes.MetadataDS, chainAPI full.ChainAPI, stateAPI full.StateAPI, accessor retrievalmarket.BlockstoreAccessor, j journal.Journal) (retrievalmarket.RetrievalClient, error) { + adapter := retrievaladapter.NewRetrievalClientNode(forceOffChain, payAPI, chainAPI, stateAPI) + network := rmnet.NewFromLibp2pHost(h) + ds = namespace.Wrap(ds, datastore.NewKey("/retrievals/client")) + client, err := retrievalimpl.NewClient(network, dt, adapter, resolver, ds, accessor) + if err != nil { + return nil, err + } + client.OnReady(marketevents.ReadyLogger("retrieval client")) + lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + client.SubscribeToEvents(marketevents.RetrievalClientLogger) + + evtType := j.RegisterEventType("markets/retrieval/client", "state_change") + client.SubscribeToEvents(markets.RetrievalClientJournaler(j, evtType)) + + return client.Start(ctx) + }, + }) + return client, nil } - client.OnReady(marketevents.ReadyLogger("retrieval client")) - lc.Append(fx.Hook{ - OnStart: func(ctx context.Context) error { - client.SubscribeToEvents(marketevents.RetrievalClientLogger) - - evtType := j.RegisterEventType("markets/retrieval/client", "state_change") - client.SubscribeToEvents(markets.RetrievalClientJournaler(j, evtType)) - - return client.Start(ctx) - }, - }) - return client, nil } diff --git a/paychmgr/cbor_gen.go b/paychmgr/cbor_gen.go index caa4143a257..428c09a9e05 100644 --- a/paychmgr/cbor_gen.go +++ b/paychmgr/cbor_gen.go @@ -196,7 +196,7 @@ func (t *ChannelInfo) MarshalCBOR(w io.Writer) error { _, err := w.Write(cbg.CborNull) return err } - if _, err := w.Write([]byte{172}); err != nil { + if _, err := w.Write([]byte{174}); err != nil { return err } @@ -346,6 +346,38 @@ func (t *ChannelInfo) MarshalCBOR(w io.Writer) error { return err } + // t.AvailableAmount (big.Int) (struct) + if len("AvailableAmount") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"AvailableAmount\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("AvailableAmount"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("AvailableAmount")); err != nil { + return err + } + + if err := t.AvailableAmount.MarshalCBOR(w); err != nil { + return err + } + + // t.PendingAvailableAmount (big.Int) (struct) + if len("PendingAvailableAmount") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"PendingAvailableAmount\" was too long") + } + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("PendingAvailableAmount"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("PendingAvailableAmount")); err != nil { + return err + } + + if err := t.PendingAvailableAmount.MarshalCBOR(w); err != nil { + return err + } + // t.PendingAmount (big.Int) (struct) if len("PendingAmount") > cbg.MaxLength { return xerrors.Errorf("Value in field \"PendingAmount\" was too long") @@ -577,6 +609,26 @@ func (t *ChannelInfo) UnmarshalCBOR(r io.Reader) error { return xerrors.Errorf("unmarshaling t.Amount: %w", err) } + } + // t.AvailableAmount (big.Int) (struct) + case "AvailableAmount": + + { + + if err := t.AvailableAmount.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling t.AvailableAmount: %w", err) + } + + } + // t.PendingAvailableAmount (big.Int) (struct) + case "PendingAvailableAmount": + + { + + if err := t.PendingAvailableAmount.UnmarshalCBOR(br); err != nil { + return xerrors.Errorf("unmarshaling t.PendingAvailableAmount: %w", err) + } + } // t.PendingAmount (big.Int) (struct) case "PendingAmount": diff --git a/paychmgr/manager.go b/paychmgr/manager.go index e0fcd7a754c..ea77c67efbe 100644 --- a/paychmgr/manager.go +++ b/paychmgr/manager.go @@ -101,13 +101,22 @@ func (pm *Manager) Stop() error { return nil } -func (pm *Manager) GetPaych(ctx context.Context, from, to address.Address, amt types.BigInt) (address.Address, cid.Cid, error) { +type GetOpts struct { + Reserve bool + OffChain bool +} + +func (pm *Manager) GetPaych(ctx context.Context, from, to address.Address, amt types.BigInt, opts GetOpts) (address.Address, cid.Cid, error) { + if !opts.Reserve && opts.OffChain { + return address.Undef, cid.Undef, xerrors.Errorf("can't fund payment channels without on-chain operations") + } + chanAccessor, err := pm.accessorByFromTo(from, to) if err != nil { return address.Undef, cid.Undef, err } - return chanAccessor.getPaych(ctx, amt) + return chanAccessor.getPaych(ctx, amt, opts) } func (pm *Manager) AvailableFunds(ctx context.Context, ch address.Address) (*api.ChannelAvailableFunds, error) { @@ -142,6 +151,8 @@ func (pm *Manager) AvailableFundsByFromTo(ctx context.Context, from address.Addr To: to, ConfirmedAmt: types.NewInt(0), PendingAmt: types.NewInt(0), + NonReservedAmt: types.NewInt(0), + PendingAvailableAmt: types.NewInt(0), PendingWaitSentinel: nil, QueuedAmt: types.NewInt(0), VoucherReedeemedAmt: types.NewInt(0), diff --git a/paychmgr/paych.go b/paychmgr/paych.go index 16c6604c6ca..5fdb4d8842e 100644 --- a/paychmgr/paych.go +++ b/paychmgr/paych.go @@ -106,7 +106,7 @@ func (ca *channelAccessor) outboundActiveByFromTo(ctx context.Context, from, to ca.lk.Lock() defer ca.lk.Unlock() - return ca.store.OutboundActiveByFromTo(ctx, from, to) + return ca.store.OutboundActiveByFromTo(ctx, ca.api, from, to) } // createVoucher creates a voucher with the given specification, setting its diff --git a/paychmgr/paych_test.go b/paychmgr/paych_test.go index e798ba5854d..e485c4e83c8 100644 --- a/paychmgr/paych_test.go +++ b/paychmgr/paych_test.go @@ -475,18 +475,18 @@ func TestAddVoucherInboundWalletKey(t *testing.T) { toAcct := tutils.NewActorAddr(t, "toAct") // Create an actor for the channel in state - act := &types.Actor{ - Code: builtin.AccountActorCodeID, - Head: cid.Cid{}, - Nonce: 0, - Balance: types.NewInt(20), - } mock := newMockManagerAPI() mock.setAccountAddress(fromAcct, from) mock.setAccountAddress(toAcct, to) + act := &types.Actor{ + Code: builtin.AccountActorCodeID, + Head: cid.Cid{}, + Nonce: 0, + Balance: types.NewInt(20), + } mock.setPaychState(ch, act, paychmock.NewMockPayChState(fromAcct, toAcct, abi.ChainEpoch(0), make(map[uint64]paych.LaneState))) // Create a manager diff --git a/paychmgr/paychget_test.go b/paychmgr/paychget_test.go index c9dc48b0519..0688301e883 100644 --- a/paychmgr/paychget_test.go +++ b/paychmgr/paychget_test.go @@ -25,6 +25,23 @@ import ( "github.com/filecoin-project/lotus/chain/types" ) +var onChainReserve = GetOpts{ + Reserve: true, + OffChain: false, +} +var onChainNoReserve = GetOpts{ + Reserve: false, + OffChain: false, +} +var offChainReserve = GetOpts{ + Reserve: true, + OffChain: true, +} +var offChainNoReserve = GetOpts{ + Reserve: false, + OffChain: true, +} + func testChannelResponse(t *testing.T, ch address.Address) types.MessageReceipt { createChannelRet := init2.ExecReturn{ IDAddress: ch, @@ -55,7 +72,7 @@ func TestPaychGetCreateChannelMsg(t *testing.T) { require.NoError(t, err) amt := big.NewInt(10) - ch, mcid, err := mgr.GetPaych(ctx, from, to, amt) + ch, mcid, err := mgr.GetPaych(ctx, from, to, amt, onChainReserve) require.NoError(t, err) require.Equal(t, address.Undef, ch) @@ -65,6 +82,42 @@ func TestPaychGetCreateChannelMsg(t *testing.T) { require.Equal(t, amt, pushedMsg.Message.Value) } +func TestPaychGetOffchainNoReserveFails(t *testing.T) { + ctx := context.Background() + store := NewStore(ds_sync.MutexWrap(ds.NewMapDatastore())) + + from := tutils.NewIDAddr(t, 101) + to := tutils.NewIDAddr(t, 102) + + mock := newMockManagerAPI() + defer mock.close() + + mgr, err := newManager(store, mock) + require.NoError(t, err) + + amt := big.NewInt(10) + _, _, err = mgr.GetPaych(ctx, from, to, amt, offChainNoReserve) + require.Error(t, err) +} + +func TestPaychGetCreateOffchainReserveFails(t *testing.T) { + ctx := context.Background() + store := NewStore(ds_sync.MutexWrap(ds.NewMapDatastore())) + + from := tutils.NewIDAddr(t, 101) + to := tutils.NewIDAddr(t, 102) + + mock := newMockManagerAPI() + defer mock.close() + + mgr, err := newManager(store, mock) + require.NoError(t, err) + + amt := big.NewInt(10) + _, _, err = mgr.GetPaych(ctx, from, to, amt, offChainReserve) + require.Error(t, err) +} + // TestPaychGetCreateChannelThenAddFunds tests creating a channel and then // adding funds to it func TestPaychGetCreateChannelThenAddFunds(t *testing.T) { @@ -79,12 +132,20 @@ func TestPaychGetCreateChannelThenAddFunds(t *testing.T) { mock := newMockManagerAPI() defer mock.close() + act := &types.Actor{ + Code: builtin.AccountActorCodeID, + Head: cid.Cid{}, + Nonce: 0, + Balance: types.NewInt(20), + } + mock.setPaychState(ch, act, paychmock.NewMockPayChState(from, to, abi.ChainEpoch(0), make(map[uint64]paych.LaneState))) + mgr, err := newManager(store, mock) require.NoError(t, err) // Send create message for a channel with value 10 amt := big.NewInt(10) - _, createMsgCid, err := mgr.GetPaych(ctx, from, to, amt) + _, createMsgCid, err := mgr.GetPaych(ctx, from, to, amt, onChainReserve) require.NoError(t, err) // Should have no channels yet (message sent but channel not created) @@ -101,7 +162,7 @@ func TestPaychGetCreateChannelThenAddFunds(t *testing.T) { // 2. Request add funds - should block until create channel has completed amt2 := big.NewInt(5) - ch2, addFundsMsgCid, err := mgr.GetPaych(ctx, from, to, amt2) + ch2, addFundsMsgCid, err := mgr.GetPaych(ctx, from, to, amt2, onChainReserve) // 4. This GetPaych should return after create channel from first // GetPaych completes @@ -155,6 +216,82 @@ func TestPaychGetCreateChannelThenAddFunds(t *testing.T) { <-done } +func TestPaychGetCreatePrefundedChannelThenAddFunds(t *testing.T) { + ctx := context.Background() + store := NewStore(ds_sync.MutexWrap(ds.NewMapDatastore())) + + ch := tutils.NewIDAddr(t, 100) + from := tutils.NewIDAddr(t, 101) + to := tutils.NewIDAddr(t, 102) + + mock := newMockManagerAPI() + defer mock.close() + + act := &types.Actor{ + Code: builtin.AccountActorCodeID, + Head: cid.Cid{}, + Nonce: 0, + Balance: types.NewInt(20), + } + mock.setPaychState(ch, act, paychmock.NewMockPayChState(from, to, abi.ChainEpoch(0), make(map[uint64]paych.LaneState))) + + mgr, err := newManager(store, mock) + require.NoError(t, err) + + // Send create message for a channel with value 10 + amt := big.NewInt(10) + _, createMsgCid, err := mgr.GetPaych(ctx, from, to, amt, onChainNoReserve) + require.NoError(t, err) + + // Should have no channels yet (message sent but channel not created) + cis, err := mgr.ListChannels(ctx) + require.NoError(t, err) + require.Len(t, cis, 0) + + // 1. Set up create channel response (sent in response to WaitForMsg()) + response := testChannelResponse(t, ch) + + done := make(chan struct{}) + go func() { + defer close(done) + + // 2. Request add funds - shouldn't block + amt2 := big.NewInt(3) + ch2, addFundsMsgCid, err := mgr.GetPaych(ctx, from, to, amt2, offChainReserve) + + // 4. This GetPaych should return after create channel from first + // GetPaych completes + require.NoError(t, err) + + // Expect the channel to be the same + require.Equal(t, ch, ch2) + require.Equal(t, cid.Undef, addFundsMsgCid) + + // Should have one channel, whose address is the channel that was created + cis, err := mgr.ListChannels(ctx) + require.NoError(t, err) + require.Len(t, cis, 1) + require.Equal(t, ch, cis[0]) + + // Amount should be amount sent to first GetPaych (to create + // channel). + // PendingAmount should be zero, AvailableAmount should be Amount minus what we requested + + ci, err := mgr.GetChannelInfo(ctx, ch) + require.NoError(t, err) + require.EqualValues(t, 10, ci.Amount.Int64()) + require.EqualValues(t, 0, ci.PendingAmount.Int64()) + require.EqualValues(t, 7, ci.AvailableAmount.Int64()) + require.Nil(t, ci.CreateMsg) + require.Nil(t, ci.AddFundsMsg) + }() + + // 3. Send create channel response + mock.receiveMsgResponse(createMsgCid, response) + + <-done +} + // TestPaychGetCreateChannelWithErrorThenCreateAgain tests that if an // operation is queued up behind a create channel operation, and the create // channel fails, then the waiting operation can succeed. @@ -174,7 +311,7 @@ func TestPaychGetCreateChannelWithErrorThenCreateAgain(t *testing.T) { // Send create message for a channel amt := big.NewInt(10) - _, mcid1, err := mgr.GetPaych(ctx, from, to, amt) + _, mcid1, err := mgr.GetPaych(ctx, from, to, amt, onChainReserve) require.NoError(t, err) // 1. Set up create channel response (sent in response to WaitForMsg()) @@ -192,7 +329,7 @@ func TestPaychGetCreateChannelWithErrorThenCreateAgain(t *testing.T) { // Because first channel create fails, this request // should be for channel create again. amt2 := big.NewInt(5) - ch2, mcid2, err := mgr.GetPaych(ctx, from, to, amt2) + ch2, mcid2, err := mgr.GetPaych(ctx, from, to, amt2, onChainReserve) require.NoError(t, err) require.Equal(t, address.Undef, ch2) @@ -240,7 +377,7 @@ func TestPaychGetRecoverAfterError(t *testing.T) { // Send create message for a channel amt := big.NewInt(10) - _, mcid, err := mgr.GetPaych(ctx, from, to, amt) + _, mcid, err := mgr.GetPaych(ctx, from, to, amt, onChainReserve) require.NoError(t, err) // Send error create channel response @@ -251,7 +388,7 @@ func TestPaychGetRecoverAfterError(t *testing.T) { // Send create message for a channel again amt2 := big.NewInt(7) - _, mcid2, err := mgr.GetPaych(ctx, from, to, amt2) + _, mcid2, err := mgr.GetPaych(ctx, from, to, amt2, onChainReserve) require.NoError(t, err) // Send success create channel response @@ -288,12 +425,20 @@ func TestPaychGetRecoverAfterAddFundsError(t *testing.T) { mock := newMockManagerAPI() defer mock.close() + act := &types.Actor{ + Code: builtin.AccountActorCodeID, + Head: cid.Cid{}, + Nonce: 0, + Balance: types.NewInt(20), + } + mock.setPaychState(ch, act, paychmock.NewMockPayChState(from, to, abi.ChainEpoch(0), make(map[uint64]paych.LaneState))) + mgr, err := newManager(store, mock) require.NoError(t, err) // Send create message for a channel amt := big.NewInt(10) - _, mcid1, err := mgr.GetPaych(ctx, from, to, amt) + _, mcid1, err := mgr.GetPaych(ctx, from, to, amt, onChainReserve) require.NoError(t, err) // Send success create channel response @@ -302,7 +447,7 @@ func TestPaychGetRecoverAfterAddFundsError(t *testing.T) { // Send add funds message for channel amt2 := big.NewInt(5) - _, mcid2, err := mgr.GetPaych(ctx, from, to, amt2) + _, mcid2, err := mgr.GetPaych(ctx, from, to, amt2, onChainReserve) require.NoError(t, err) // Send error add funds response @@ -329,7 +474,7 @@ func TestPaychGetRecoverAfterAddFundsError(t *testing.T) { // Send add funds message for channel again amt3 := big.NewInt(2) - _, mcid3, err := mgr.GetPaych(ctx, from, to, amt3) + _, mcid3, err := mgr.GetPaych(ctx, from, to, amt3, onChainReserve) require.NoError(t, err) // Send success add funds response @@ -375,7 +520,7 @@ func TestPaychGetRestartAfterCreateChannelMsg(t *testing.T) { // Send create message for a channel with value 10 amt := big.NewInt(10) - _, createMsgCid, err := mgr.GetPaych(ctx, from, to, amt) + _, createMsgCid, err := mgr.GetPaych(ctx, from, to, amt, onChainReserve) require.NoError(t, err) // Simulate shutting down system @@ -385,6 +530,14 @@ func TestPaychGetRestartAfterCreateChannelMsg(t *testing.T) { mock2 := newMockManagerAPI() defer mock2.close() + act := &types.Actor{ + Code: builtin.AccountActorCodeID, + Head: cid.Cid{}, + Nonce: 0, + Balance: types.NewInt(20), + } + mock2.setPaychState(ch, act, paychmock.NewMockPayChState(from, to, abi.ChainEpoch(0), make(map[uint64]paych.LaneState))) + mgr2, err := newManager(store, mock2) require.NoError(t, err) @@ -402,7 +555,7 @@ func TestPaychGetRestartAfterCreateChannelMsg(t *testing.T) { // 2. Request add funds - should block until create channel has completed amt2 := big.NewInt(5) - ch2, addFundsMsgCid, err := mgr2.GetPaych(ctx, from, to, amt2) + ch2, addFundsMsgCid, err := mgr2.GetPaych(ctx, from, to, amt2, onChainReserve) // 4. This GetPaych should return after create channel from first // GetPaych completes @@ -450,12 +603,20 @@ func TestPaychGetRestartAfterAddFundsMsg(t *testing.T) { mock := newMockManagerAPI() + act := &types.Actor{ + Code: builtin.AccountActorCodeID, + Head: cid.Cid{}, + Nonce: 0, + Balance: types.NewInt(20), + } + mock.setPaychState(ch, act, paychmock.NewMockPayChState(from, to, abi.ChainEpoch(0), make(map[uint64]paych.LaneState))) + mgr, err := newManager(store, mock) require.NoError(t, err) // Send create message for a channel amt := big.NewInt(10) - _, mcid1, err := mgr.GetPaych(ctx, from, to, amt) + _, mcid1, err := mgr.GetPaych(ctx, from, to, amt, onChainReserve) require.NoError(t, err) // Send success create channel response @@ -464,7 +625,7 @@ func TestPaychGetRestartAfterAddFundsMsg(t *testing.T) { // Send add funds message for channel amt2 := big.NewInt(5) - _, mcid2, err := mgr.GetPaych(ctx, from, to, amt2) + _, mcid2, err := mgr.GetPaych(ctx, from, to, amt2, onChainReserve) require.NoError(t, err) // Simulate shutting down system @@ -474,6 +635,8 @@ func TestPaychGetRestartAfterAddFundsMsg(t *testing.T) { mock2 := newMockManagerAPI() defer mock2.close() + mock2.setPaychState(ch, act, paychmock.NewMockPayChState(from, to, abi.ChainEpoch(0), make(map[uint64]paych.LaneState))) + mgr2, err := newManager(store, mock2) require.NoError(t, err) @@ -510,19 +673,27 @@ func TestPaychGetWait(t *testing.T) { from := tutils.NewIDAddr(t, 101) to := tutils.NewIDAddr(t, 102) + expch := tutils.NewIDAddr(t, 100) mock := newMockManagerAPI() defer mock.close() + act := &types.Actor{ + Code: builtin.AccountActorCodeID, + Head: cid.Cid{}, + Nonce: 0, + Balance: types.NewInt(20), + } + mock.setPaychState(expch, act, paychmock.NewMockPayChState(from, to, abi.ChainEpoch(0), make(map[uint64]paych.LaneState))) + mgr, err := newManager(store, mock) require.NoError(t, err) // 1. Get amt := big.NewInt(10) - _, createMsgCid, err := mgr.GetPaych(ctx, from, to, amt) + _, createMsgCid, err := mgr.GetPaych(ctx, from, to, amt, onChainReserve) require.NoError(t, err) - expch := tutils.NewIDAddr(t, 100) go func() { // 3. Send response response := testChannelResponse(t, expch) @@ -542,7 +713,7 @@ func TestPaychGetWait(t *testing.T) { // Request add funds amt2 := big.NewInt(15) - _, addFundsMsgCid, err := mgr.GetPaych(ctx, from, to, amt2) + _, addFundsMsgCid, err := mgr.GetPaych(ctx, from, to, amt2, onChainReserve) require.NoError(t, err) go func() { @@ -577,7 +748,7 @@ func TestPaychGetWaitErr(t *testing.T) { // 1. Create channel amt := big.NewInt(10) - _, mcid, err := mgr.GetPaych(ctx, from, to, amt) + _, mcid, err := mgr.GetPaych(ctx, from, to, amt, onChainReserve) require.NoError(t, err) done := make(chan address.Address) @@ -624,7 +795,7 @@ func TestPaychGetWaitCtx(t *testing.T) { require.NoError(t, err) amt := big.NewInt(10) - _, mcid, err := mgr.GetPaych(ctx, from, to, amt) + _, mcid, err := mgr.GetPaych(ctx, from, to, amt, onChainReserve) require.NoError(t, err) // When the context is cancelled, should unblock wait @@ -651,12 +822,20 @@ func TestPaychGetMergeAddFunds(t *testing.T) { mock := newMockManagerAPI() defer mock.close() + act := &types.Actor{ + Code: builtin.AccountActorCodeID, + Head: cid.Cid{}, + Nonce: 0, + Balance: types.NewInt(20), + } + mock.setPaychState(ch, act, paychmock.NewMockPayChState(from, to, abi.ChainEpoch(0), make(map[uint64]paych.LaneState))) + mgr, err := newManager(store, mock) require.NoError(t, err) // Send create message for a channel with value 10 createAmt := big.NewInt(10) - _, createMsgCid, err := mgr.GetPaych(ctx, from, to, createAmt) + _, createMsgCid, err := mgr.GetPaych(ctx, from, to, createAmt, onChainReserve) require.NoError(t, err) // Queue up two add funds requests behind create channel @@ -674,7 +853,7 @@ func TestPaychGetMergeAddFunds(t *testing.T) { // Request add funds - should block until create channel has completed var err error - addFundsCh1, addFundsMcid1, err = mgr.GetPaych(ctx, from, to, addFundsAmt1) + addFundsCh1, addFundsMcid1, err = mgr.GetPaych(ctx, from, to, addFundsAmt1, onChainReserve) require.NoError(t, err) }() @@ -683,7 +862,7 @@ func TestPaychGetMergeAddFunds(t *testing.T) { // Request add funds again - should merge with waiting add funds request var err error - addFundsCh2, addFundsMcid2, err = mgr.GetPaych(ctx, from, to, addFundsAmt2) + addFundsCh2, addFundsMcid2, err = mgr.GetPaych(ctx, from, to, addFundsAmt2, onChainReserve) require.NoError(t, err) }() // Wait for add funds requests to be queued up @@ -736,10 +915,111 @@ func TestPaychGetMergeAddFunds(t *testing.T) { require.Equal(t, types.BigAdd(addFundsAmt1, addFundsAmt2), addFundsMsg.Message.Value) } -// TestPaychGetMergeAddFundsCtxCancelOne tests that when a queued add funds -// request is cancelled, its amount is removed from the total merged add funds -func TestPaychGetMergeAddFundsCtxCancelOne(t *testing.T) { - //stm: @TOKEN_PAYCH_WAIT_READY_001 +func TestPaychGetMergePrefundAndReserve(t *testing.T) { + ctx := context.Background() + store := NewStore(ds_sync.MutexWrap(ds.NewMapDatastore())) + + ch := tutils.NewIDAddr(t, 100) + from := tutils.NewIDAddr(t, 101) + to := tutils.NewIDAddr(t, 102) + + mock := newMockManagerAPI() + defer mock.close() + + act := &types.Actor{ + Code: builtin.AccountActorCodeID, + Head: cid.Cid{}, + Nonce: 0, + Balance: types.NewInt(20), + } + mock.setPaychState(ch, act, paychmock.NewMockPayChState(from, to, abi.ChainEpoch(0), make(map[uint64]paych.LaneState))) + + mgr, err := newManager(store, mock) + require.NoError(t, err) + + // Send create message for a channel with value 10 + createAmt := big.NewInt(10) + _, createMsgCid, err := mgr.GetPaych(ctx, from, to, createAmt, onChainReserve) + require.NoError(t, err) + + // Queue up two add funds requests behind create channel + var addFundsSent sync.WaitGroup + addFundsSent.Add(2) + + addFundsAmt1 := big.NewInt(5) // 1 prefunds + addFundsAmt2 := big.NewInt(3) // 2 reserves + var addFundsCh1 address.Address + var addFundsCh2 address.Address + var addFundsMcid1 cid.Cid + var addFundsMcid2 cid.Cid + go func() { + defer addFundsSent.Done() + + // Request add funds - should block until create channel has completed + var err error + addFundsCh1, addFundsMcid1, err = mgr.GetPaych(ctx, from, to, addFundsAmt1, onChainNoReserve) + require.NoError(t, err) + }() + + go func() { + defer addFundsSent.Done() + + // Request add funds again - should merge with waiting add funds request + var err error + addFundsCh2, addFundsMcid2, err = mgr.GetPaych(ctx, from, to, addFundsAmt2, onChainReserve) + require.NoError(t, err) + }() + // Wait for add funds requests to be queued up + waitForQueueSize(t, mgr, from, to, 2) + + // Send create channel response + response := testChannelResponse(t, ch) + mock.receiveMsgResponse(createMsgCid, response) + + // Wait for create channel response + chres, err := mgr.GetPaychWaitReady(ctx, createMsgCid) + require.NoError(t, err) + require.Equal(t, ch, chres) + + // Wait for add funds requests to be sent + addFundsSent.Wait() + + // Expect add funds requests to have same channel as create channel and + // same message cid as each other (because they should have been merged) + require.Equal(t, ch, addFundsCh1) + require.Equal(t, ch, addFundsCh2) + require.Equal(t, addFundsMcid1, addFundsMcid2) + + // Send success add funds response + mock.receiveMsgResponse(addFundsMcid1, types.MessageReceipt{ + ExitCode: 0, + Return: []byte{}, + }) + + // Wait for add funds response + addFundsCh, err := mgr.GetPaychWaitReady(ctx, addFundsMcid1) + require.NoError(t, err) + require.Equal(t, ch, addFundsCh) + + // Make sure that one create channel message and one add funds message was + // sent + require.Equal(t, 2, mock.pushedMessageCount()) + + // Check create message amount is correct + createMsg := mock.pushedMessages(createMsgCid) + require.Equal(t, from, createMsg.Message.From) + require.Equal(t, lotusinit.Address, createMsg.Message.To) + require.Equal(t, createAmt, createMsg.Message.Value) + + // Check merged add funds amount is the sum of the individual + // amounts + addFundsMsg := mock.pushedMessages(addFundsMcid1) + require.Equal(t, from, addFundsMsg.Message.From) + require.Equal(t, ch, addFundsMsg.Message.To) + require.Equal(t, types.BigAdd(addFundsAmt1, addFundsAmt2), addFundsMsg.Message.Value) +} + +func TestPaychGetMergePrefundAndReservePrefunded(t *testing.T) { ctx := context.Background() store := NewStore(ds_sync.MutexWrap(ds.NewMapDatastore())) @@ -750,12 +1030,393 @@ func TestPaychGetMergeAddFundsCtxCancelOne(t *testing.T) { mock := newMockManagerAPI() defer mock.close() + act := &types.Actor{ + Code: builtin.AccountActorCodeID, + Head: cid.Cid{}, + Nonce: 0, + Balance: types.NewInt(20), + } + mock.setPaychState(ch, act, paychmock.NewMockPayChState(from, to, abi.ChainEpoch(0), make(map[uint64]paych.LaneState))) + + mgr, err := newManager(store, mock) + require.NoError(t, err) + + // Send create message for a channel with value 10 + createAmt := big.NewInt(10) + _, createMsgCid, err := mgr.GetPaych(ctx, from, to, createAmt, onChainNoReserve) + require.NoError(t, err) + + // Queue up two add funds requests behind create channel + var addFundsSent sync.WaitGroup + addFundsSent.Add(2) + + addFundsAmt1 := big.NewInt(5) // 1 prefunds + addFundsAmt2 := big.NewInt(3) // 2 reserves + var addFundsCh1 address.Address + var addFundsCh2 address.Address + var addFundsMcid1 cid.Cid + var addFundsMcid2 cid.Cid + go func() { + defer addFundsSent.Done() + + // Request add funds - should block until create channel has completed + var err error + addFundsCh1, addFundsMcid1, err = mgr.GetPaych(ctx, from, to, addFundsAmt1, onChainNoReserve) + require.NoError(t, err) + }() + + go func() { + defer addFundsSent.Done() + + // Request add funds again - should merge with waiting add funds request + var err error + addFundsCh2, addFundsMcid2, err = mgr.GetPaych(ctx, from, to, addFundsAmt2, onChainReserve) + require.NoError(t, err) + }() + // Wait for add funds requests to be queued up + waitForQueueSize(t, mgr, from, to, 2) + + // Send create channel response + response := testChannelResponse(t, ch) + mock.receiveMsgResponse(createMsgCid, response) + + // Wait for create channel response + chres, err := mgr.GetPaychWaitReady(ctx, createMsgCid) + require.NoError(t, err) + require.Equal(t, ch, chres) + + // Wait for add funds requests to be sent + addFundsSent.Wait() + + // Expect add funds requests to have same channel as create channel and + // same message cid as each other (because they should have been merged) + require.Equal(t, ch, addFundsCh1) + require.Equal(t, ch, addFundsCh2) + require.NotEqual(t, cid.Undef, addFundsMcid1) + require.Equal(t, cid.Undef, addFundsMcid2) + + // Send success add funds response + mock.receiveMsgResponse(addFundsMcid1, types.MessageReceipt{ + ExitCode: 0, + Return: []byte{}, + }) + + // Wait for add funds response + addFundsCh, err := mgr.GetPaychWaitReady(ctx, addFundsMcid1) + require.NoError(t, err) + require.Equal(t, ch, addFundsCh) + + // Make sure that one create channel message and one add funds message was + // sent + require.Equal(t, 2, mock.pushedMessageCount()) + + // Check create message amount is correct + createMsg := mock.pushedMessages(createMsgCid) + require.Equal(t, from, createMsg.Message.From) + require.Equal(t, lotusinit.Address, createMsg.Message.To) + require.Equal(t, createAmt, createMsg.Message.Value) + + // Check merged add funds amount is the sum of the individual + // amounts + addFundsMsg := mock.pushedMessages(addFundsMcid1) + require.Equal(t, from, addFundsMsg.Message.From) + require.Equal(t, ch, addFundsMsg.Message.To) + require.Equal(t, addFundsAmt1, addFundsMsg.Message.Value) +} + +func TestPaychGetMergePrefundAndReservePrefundedOneOffchain(t *testing.T) { + ctx := context.Background() + store := NewStore(ds_sync.MutexWrap(ds.NewMapDatastore())) + + ch := tutils.NewIDAddr(t, 100) + from := tutils.NewIDAddr(t, 101) + to := tutils.NewIDAddr(t, 102) + + mock := newMockManagerAPI() + defer mock.close() + + act := &types.Actor{ + Code: builtin.AccountActorCodeID, + Head: cid.Cid{}, + Nonce: 0, + Balance: types.NewInt(20), + } + mock.setPaychState(ch, act, paychmock.NewMockPayChState(from, to, abi.ChainEpoch(0), make(map[uint64]paych.LaneState))) + + mgr, err := newManager(store, mock) + require.NoError(t, err) + + // Send create message for a channel with value 10 + createAmt := big.NewInt(10) + _, createMsgCid, err := mgr.GetPaych(ctx, from, to, createAmt, onChainNoReserve) + require.NoError(t, err) + + // Queue up two add funds requests behind create channel + var addFundsSent sync.WaitGroup + addFundsSent.Add(2) + + addFundsAmt1 := big.NewInt(5) // 1 reserves + addFundsAmt2 := big.NewInt(3) // 2 reserves + var addFundsCh1 address.Address + var addFundsCh2 address.Address + var addFundsMcid1 cid.Cid + var addFundsMcid2 cid.Cid + go func() { + defer addFundsSent.Done() + + // Request add funds - should block until create channel has completed + var err error + addFundsCh1, addFundsMcid1, err = mgr.GetPaych(ctx, from, to, addFundsAmt1, offChainReserve) + require.NoError(t, err) + }() + + go func() { + defer addFundsSent.Done() + + // Request add funds again - should merge with waiting add funds request + var err error + addFundsCh2, addFundsMcid2, err = mgr.GetPaych(ctx, from, to, addFundsAmt2, onChainReserve) + require.NoError(t, err) + }() + // Wait for add funds requests to be queued up + waitForQueueSize(t, mgr, from, to, 2) + + // Send create channel response + response := testChannelResponse(t, ch) + mock.receiveMsgResponse(createMsgCid, response) + + // Wait for create channel response + chres, err := mgr.GetPaychWaitReady(ctx, createMsgCid) + require.NoError(t, err) + require.Equal(t, ch, chres) + + // Wait for add funds requests to be sent + addFundsSent.Wait() + + // Expect add funds requests to have same channel as create channel and + // same message cid as each other (because they should have been merged) + require.Equal(t, ch, addFundsCh1) + require.Equal(t, ch, addFundsCh2) + require.Equal(t, cid.Undef, addFundsMcid1) + require.Equal(t, cid.Undef, addFundsMcid2) + + // Make sure that one create channel message was sent + require.Equal(t, 1, mock.pushedMessageCount()) + + // Check create message amount is correct + createMsg := mock.pushedMessages(createMsgCid) + require.Equal(t, from, createMsg.Message.From) + require.Equal(t, lotusinit.Address, createMsg.Message.To) + require.Equal(t, createAmt, createMsg.Message.Value) +} + +func TestPaychGetMergePrefundAndReservePrefundedBothOffchainOneFail(t *testing.T) { + ctx := context.Background() + store := NewStore(ds_sync.MutexWrap(ds.NewMapDatastore())) + + ch := tutils.NewIDAddr(t, 100) + from := tutils.NewIDAddr(t, 101) + to := tutils.NewIDAddr(t, 102) + + mock := newMockManagerAPI() + defer mock.close() + + act := &types.Actor{ + Code: builtin.AccountActorCodeID, + Head: cid.Cid{}, + Nonce: 0, + Balance: types.NewInt(20), + } + mock.setPaychState(ch, act, paychmock.NewMockPayChState(from, to, abi.ChainEpoch(0), make(map[uint64]paych.LaneState))) + + mgr, err := newManager(store, mock) + require.NoError(t, err) + + // Send create message for a channel with value 10 + createAmt := big.NewInt(10) + _, createMsgCid, err := mgr.GetPaych(ctx, from, to, createAmt, onChainNoReserve) + require.NoError(t, err) + + // Queue up two add funds requests behind create channel + var addFundsSent sync.WaitGroup + addFundsSent.Add(2) + + addFundsAmt1 := big.NewInt(5) // 1 reserves + addFundsAmt2 := big.NewInt(6) // 2 reserves too much + var addFundsCh1 address.Address + var addFundsCh2 address.Address + var addFundsMcid1 cid.Cid + var addFundsMcid2 cid.Cid + go func() { + defer addFundsSent.Done() + + // Request add funds - should block until create channel has completed + var err error + addFundsCh1, addFundsMcid1, err = mgr.GetPaych(ctx, from, to, addFundsAmt1, offChainReserve) + require.NoError(t, err) + }() + + go func() { + defer addFundsSent.Done() + + // Request add funds again - should merge with waiting add funds request + var err error + addFundsCh2, addFundsMcid2, err = mgr.GetPaych(ctx, from, to, addFundsAmt2, offChainReserve) + require.Error(t, err) + }() + // Wait for add funds requests to be queued up + waitForQueueSize(t, mgr, from, to, 2) + + // Send create channel response + response := testChannelResponse(t, ch) + mock.receiveMsgResponse(createMsgCid, response) + + // Wait for create channel response + chres, err := mgr.GetPaychWaitReady(ctx, createMsgCid) + require.NoError(t, err) + require.Equal(t, ch, chres) + + // Wait for add funds requests to be sent + addFundsSent.Wait() + + // Expect add funds requests to have same channel as create channel and + // same message cid as each other (because they should have been merged) + require.Equal(t, ch, addFundsCh1) + require.Equal(t, ch, addFundsCh2) + require.Equal(t, cid.Undef, addFundsMcid1) + require.Equal(t, cid.Undef, addFundsMcid2) + + // Make sure that one create channel message was sent + require.Equal(t, 1, mock.pushedMessageCount()) + + // Check create message amount is correct + createMsg := mock.pushedMessages(createMsgCid) + require.Equal(t, from, createMsg.Message.From) + require.Equal(t, lotusinit.Address, createMsg.Message.To) + require.Equal(t, createAmt, createMsg.Message.Value) +} + +func TestPaychGetMergePrefundAndReserveOneOffchainOneFail(t *testing.T) { + ctx := context.Background() + store := NewStore(ds_sync.MutexWrap(ds.NewMapDatastore())) + + ch := tutils.NewIDAddr(t, 100) + from := tutils.NewIDAddr(t, 101) + to := tutils.NewIDAddr(t, 102) + + mock := newMockManagerAPI() + defer mock.close() + + act := &types.Actor{ + Code: builtin.AccountActorCodeID, + Head: cid.Cid{}, + Nonce: 0, + Balance: types.NewInt(20), + } + mock.setPaychState(ch, act, paychmock.NewMockPayChState(from, to, abi.ChainEpoch(0), make(map[uint64]paych.LaneState))) + + mgr, err := newManager(store, mock) + require.NoError(t, err) + + // Send create message for a channel with value 10 + createAmt := big.NewInt(10) + _, createMsgCid, err := mgr.GetPaych(ctx, from, to, createAmt, onChainReserve) + require.NoError(t, err) + + // Queue up two add funds requests behind create channel + var addFundsSent sync.WaitGroup + addFundsSent.Add(2) + + addFundsAmt1 := big.NewInt(5) // 1 reserves + addFundsAmt2 := big.NewInt(6) // 2 reserves + var addFundsCh1 address.Address + var addFundsCh2 address.Address + var addFundsMcid1 cid.Cid + var addFundsMcid2 cid.Cid + go func() { + defer addFundsSent.Done() + + // Request add funds - should block until create channel has completed + var err error + addFundsCh1, addFundsMcid1, err = mgr.GetPaych(ctx, from, to, addFundsAmt1, onChainReserve) + require.NoError(t, err) + }() + + go func() { + defer addFundsSent.Done() + + // Request add funds again - should merge with waiting add funds request + var err error + addFundsCh2, addFundsMcid2, err = mgr.GetPaych(ctx, from, to, addFundsAmt2, offChainReserve) + require.Error(t, err) + }() + // Wait for add funds requests to be queued up + waitForQueueSize(t, mgr, from, to, 2) + + // Send create channel response + response := testChannelResponse(t, ch) + mock.receiveMsgResponse(createMsgCid, response) + + // Wait for create channel response + chres, err := mgr.GetPaychWaitReady(ctx, createMsgCid) + require.NoError(t, err) + require.Equal(t, ch, chres) + + // Wait for add funds requests to be sent + addFundsSent.Wait() + + // Expect add funds requests to have same channel as create channel and + // same message cid as each other (because they should have been merged) + require.Equal(t, ch, addFundsCh1) + require.Equal(t, ch, addFundsCh2) + require.NotEqual(t, cid.Undef, addFundsMcid1) + require.Equal(t, cid.Undef, addFundsMcid2) + + // Make sure that one create channel message was sent + require.Equal(t, 2, mock.pushedMessageCount()) + + // Check create message amount is correct + createMsg := mock.pushedMessages(createMsgCid) + require.Equal(t, from, createMsg.Message.From) + require.Equal(t, lotusinit.Address, createMsg.Message.To) + require.Equal(t, createAmt, createMsg.Message.Value) + + // Check merged add funds amount is the sum of the individual + // amounts + addFundsMsg := mock.pushedMessages(addFundsMcid1) + require.Equal(t, from, addFundsMsg.Message.From) + require.Equal(t, ch, addFundsMsg.Message.To) + require.Equal(t, addFundsAmt1, addFundsMsg.Message.Value) +} + +// TestPaychGetMergeAddFundsCtxCancelOne tests that when a queued add funds +// request is cancelled, its amount is removed from the total merged add funds +func TestPaychGetMergeAddFundsCtxCancelOne(t *testing.T) { + //stm: @TOKEN_PAYCH_WAIT_READY_001 + ctx := context.Background() + store := NewStore(ds_sync.MutexWrap(ds.NewMapDatastore())) + + ch := tutils.NewIDAddr(t, 100) + from := tutils.NewIDAddr(t, 101) + to := tutils.NewIDAddr(t, 102) + + mock := newMockManagerAPI() + defer mock.close() + + act := &types.Actor{ + Code: builtin.AccountActorCodeID, + Head: cid.Cid{}, + Nonce: 0, + Balance: types.NewInt(20), + } + mock.setPaychState(ch, act, paychmock.NewMockPayChState(from, to, abi.ChainEpoch(0), make(map[uint64]paych.LaneState))) + mgr, err := newManager(store, mock) require.NoError(t, err) // Send create message for a channel with value 10 createAmt := big.NewInt(10) - _, createMsgCid, err := mgr.GetPaych(ctx, from, to, createAmt) + _, createMsgCid, err := mgr.GetPaych(ctx, from, to, createAmt, onChainReserve) require.NoError(t, err) // Queue up two add funds requests behind create channel @@ -772,7 +1433,7 @@ func TestPaychGetMergeAddFundsCtxCancelOne(t *testing.T) { defer addFundsSent.Done() // Request add funds - should block until create channel has completed - _, _, addFundsErr1 = mgr.GetPaych(addFundsCtx1, from, to, addFundsAmt1) + _, _, addFundsErr1 = mgr.GetPaych(addFundsCtx1, from, to, addFundsAmt1, onChainReserve) }() go func() { @@ -780,7 +1441,7 @@ func TestPaychGetMergeAddFundsCtxCancelOne(t *testing.T) { // Request add funds again - should merge with waiting add funds request var err error - addFundsCh2, addFundsMcid2, err = mgr.GetPaych(ctx, from, to, addFundsAmt2) + addFundsCh2, addFundsMcid2, err = mgr.GetPaych(ctx, from, to, addFundsAmt2, onChainReserve) require.NoError(t, err) }() // Wait for add funds requests to be queued up @@ -853,7 +1514,7 @@ func TestPaychGetMergeAddFundsCtxCancelAll(t *testing.T) { // Send create message for a channel with value 10 createAmt := big.NewInt(10) - _, createMsgCid, err := mgr.GetPaych(ctx, from, to, createAmt) + _, createMsgCid, err := mgr.GetPaych(ctx, from, to, createAmt, onChainReserve) require.NoError(t, err) // Queue up two add funds requests behind create channel @@ -868,14 +1529,14 @@ func TestPaychGetMergeAddFundsCtxCancelAll(t *testing.T) { defer addFundsSent.Done() // Request add funds - should block until create channel has completed - _, _, addFundsErr1 = mgr.GetPaych(addFundsCtx1, from, to, big.NewInt(5)) + _, _, addFundsErr1 = mgr.GetPaych(addFundsCtx1, from, to, big.NewInt(5), onChainReserve) }() go func() { defer addFundsSent.Done() // Request add funds again - should merge with waiting add funds request - _, _, addFundsErr2 = mgr.GetPaych(addFundsCtx2, from, to, big.NewInt(3)) + _, _, addFundsErr2 = mgr.GetPaych(addFundsCtx2, from, to, big.NewInt(3), onChainReserve) }() // Wait for add funds requests to be queued up waitForQueueSize(t, mgr, from, to, 2) @@ -941,7 +1602,7 @@ func TestPaychAvailableFunds(t *testing.T) { // Send create message for a channel with value 10 createAmt := big.NewInt(10) - _, createMsgCid, err := mgr.GetPaych(ctx, from, to, createAmt) + _, createMsgCid, err := mgr.GetPaych(ctx, from, to, createAmt, onChainReserve) require.NoError(t, err) // Available funds should reflect create channel message sent @@ -966,7 +1627,7 @@ func TestPaychAvailableFunds(t *testing.T) { // Request add funds - should block until create channel has completed var err error - _, addFundsMcid, err = mgr.GetPaych(ctx, from, to, addFundsAmt) + _, addFundsMcid, err = mgr.GetPaych(ctx, from, to, addFundsAmt, onChainReserve) require.NoError(t, err) }() diff --git a/paychmgr/paychvoucherfunds_test.go b/paychmgr/paychvoucherfunds_test.go index f081ee606ff..dc894a04fd4 100644 --- a/paychmgr/paychvoucherfunds_test.go +++ b/paychmgr/paychvoucherfunds_test.go @@ -47,7 +47,7 @@ func TestPaychAddVoucherAfterAddFunds(t *testing.T) { // Send create message for a channel with value 10 createAmt := big.NewInt(10) - _, createMsgCid, err := mgr.GetPaych(ctx, from, to, createAmt) + _, createMsgCid, err := mgr.GetPaych(ctx, from, to, createAmt, onChainReserve) require.NoError(t, err) // Send create channel response @@ -83,7 +83,7 @@ func TestPaychAddVoucherAfterAddFunds(t *testing.T) { require.Equal(t, res.Shortfall, excessAmt) // Add funds so as to cover the voucher shortfall - _, addFundsMsgCid, err := mgr.GetPaych(ctx, from, to, excessAmt) + _, addFundsMsgCid, err := mgr.GetPaych(ctx, from, to, excessAmt, onChainReserve) require.NoError(t, err) // Trigger add funds confirmation diff --git a/paychmgr/settle_test.go b/paychmgr/settle_test.go index 4d2393e96bf..ffbffc66029 100644 --- a/paychmgr/settle_test.go +++ b/paychmgr/settle_test.go @@ -30,7 +30,7 @@ func TestPaychSettle(t *testing.T) { require.NoError(t, err) amt := big.NewInt(10) - _, mcid, err := mgr.GetPaych(ctx, from, to, amt) + _, mcid, err := mgr.GetPaych(ctx, from, to, amt, onChainReserve) require.NoError(t, err) // Send channel create response @@ -50,7 +50,7 @@ func TestPaychSettle(t *testing.T) { // (should create a new channel because the previous channel // is settling) amt2 := big.NewInt(5) - _, mcid2, err := mgr.GetPaych(ctx, from, to, amt2) + _, mcid2, err := mgr.GetPaych(ctx, from, to, amt2, onChainReserve) require.NoError(t, err) require.NotEqual(t, cid.Undef, mcid2) diff --git a/paychmgr/simple.go b/paychmgr/simple.go index 502338e2953..3d0992efec7 100644 --- a/paychmgr/simple.go +++ b/paychmgr/simple.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "sort" "sync" "github.com/ipfs/go-cid" @@ -32,18 +33,20 @@ type fundsReq struct { ctx context.Context promise chan *paychFundsRes amt types.BigInt + opts GetOpts lk sync.Mutex // merge parent, if this req is part of a merge merge *mergedFundsReq } -func newFundsReq(ctx context.Context, amt types.BigInt) *fundsReq { - promise := make(chan *paychFundsRes) +func newFundsReq(ctx context.Context, amt types.BigInt, opts GetOpts) *fundsReq { + promise := make(chan *paychFundsRes, 1) return &fundsReq{ ctx: ctx, promise: promise, amt: amt, + opts: opts, } } @@ -104,6 +107,19 @@ func newMergedFundsReq(reqs []*fundsReq) *mergedFundsReq { r.setMergeParent(m) } + sort.Slice(m.reqs, func(i, j int) bool { + if m.reqs[i].opts.OffChain != m.reqs[j].opts.OffChain { // off-chain first + return m.reqs[i].opts.OffChain + } + + if m.reqs[i].opts.Reserve != m.reqs[j].opts.Reserve { // non-reserve after off-chain + return m.reqs[i].opts.Reserve + } + + // sort by amount asc (reducing latency for smaller requests) + return m.reqs[i].amt.LessThan(m.reqs[j].amt) + }) + // If the requests were all cancelled while being added, cancel the context // immediately m.checkActive() @@ -135,18 +151,97 @@ func (m *mergedFundsReq) onComplete(res *paychFundsRes) { } // sum is the sum of the amounts in all requests in the merge -func (m *mergedFundsReq) sum() types.BigInt { +func (m *mergedFundsReq) sum() (types.BigInt, types.BigInt) { sum := types.NewInt(0) + avail := types.NewInt(0) + for _, r := range m.reqs { if r.isActive() { sum = types.BigAdd(sum, r.amt) + if !r.opts.Reserve { + avail = types.BigAdd(avail, r.amt) + } + } + } + + return sum, avail +} + +// completeAmount completes first non-reserving requests up to the available amount +func (m *mergedFundsReq) completeAmount(avail types.BigInt, channelInfo *ChannelInfo) (*paychFundsRes, types.BigInt, types.BigInt) { + used, failed := types.NewInt(0), types.NewInt(0) + next := 0 + + // order: [offchain+reserve, !offchain+reserve, !offchain+!reserve] + for i, r := range m.reqs { + if !r.opts.Reserve { + // non-reserving request are put after reserving requests, so we are done here + break + } + + // don't try to fill inactive requests + if !r.isActive() { + continue + } + + if r.amt.GreaterThan(types.BigSub(avail, used)) { + // requests are sorted by amount ascending, so if we hit this, there aren't any more requests we can fill + + if r.opts.OffChain { + // can't fill, so OffChain want an error + if r.isActive() { + failed = types.BigAdd(failed, r.amt) + r.onComplete(&paychFundsRes{ + channel: *channelInfo.Channel, + err: xerrors.Errorf("not enough funds available in the payment channel %s; add funds with 'lotus paych add-funds %s %s %s'", channelInfo.Channel, channelInfo.from(), channelInfo.to(), types.FIL(r.amt).Unitless()), + }) + } + next = i + 1 + continue + } + + break + } + + used = types.BigAdd(used, r.amt) + r.onComplete(&paychFundsRes{channel: *channelInfo.Channel}) + next = i + 1 + } + + m.reqs = m.reqs[next:] + if len(m.reqs) == 0 { + return &paychFundsRes{channel: *channelInfo.Channel}, used, failed + } + return nil, used, failed +} + +func (m *mergedFundsReq) failOffChainNoChannel(from, to address.Address) (*paychFundsRes, types.BigInt) { + next := 0 + freed := types.NewInt(0) + + for i, r := range m.reqs { + if !r.opts.OffChain { + break } + + freed = types.BigAdd(freed, r.amt) + if !r.isActive() { + continue + } + r.onComplete(&paychFundsRes{err: xerrors.Errorf("payment channel doesn't exist, create with 'lotus paych add-funds %s %s %s'", from, to, types.FIL(r.amt).Unitless())}) + next = i + 1 + } + + m.reqs = m.reqs[next:] + if len(m.reqs) == 0 { + return &paychFundsRes{err: xerrors.Errorf("payment channel doesn't exist, create with 'lotus paych add-funds %s %s 0'", from, to)}, freed } - return sum + + return nil, freed } // getPaych ensures that a channel exists between the from and to addresses, -// and adds the given amount of funds. +// and reserves (or adds as available) the given amount of funds. // If the channel does not exist a create channel message is sent and the // message CID is returned. // If the channel does exist an add funds message is sent and both the channel @@ -156,9 +251,9 @@ func (m *mergedFundsReq) sum() types.BigInt { // address and the CID of the new add funds message. // If an operation returns an error, subsequent waiting operations will still // be attempted. -func (ca *channelAccessor) getPaych(ctx context.Context, amt types.BigInt) (address.Address, cid.Cid, error) { +func (ca *channelAccessor) getPaych(ctx context.Context, amt types.BigInt, opts GetOpts) (address.Address, cid.Cid, error) { // Add the request to add funds to a queue and wait for the result - freq := newFundsReq(ctx, amt) + freq := newFundsReq(ctx, amt, opts) ca.enqueue(ctx, freq) select { case res := <-freq.promise: @@ -195,14 +290,14 @@ func (ca *channelAccessor) processQueue(ctx context.Context, channelID string) ( // For example if there are pending requests for 3, 2, 4 then // amt = 3 + 2 + 4 = 9 merged := newMergedFundsReq(ca.fundsReqQueue) - amt := merged.sum() + amt, avail := merged.sum() if amt.IsZero() { // Note: The amount can be zero if requests are cancelled as we're // building the mergedFundsReq return ca.currentAvailableFunds(ctx, channelID, amt) } - res := ca.processTask(merged.ctx, amt) + res := ca.processTask(merged, amt, avail) // If the task is waiting on an external event (eg something to appear on // chain) it will return nil @@ -322,6 +417,8 @@ func (ca *channelAccessor) currentAvailableFunds(ctx context.Context, channelID To: channelInfo.to(), ConfirmedAmt: channelInfo.Amount, PendingAmt: channelInfo.PendingAmount, + NonReservedAmt: channelInfo.AvailableAmount, + PendingAvailableAmt: channelInfo.PendingAvailableAmount, PendingWaitSentinel: waitSentinel, QueuedAmt: queuedAmt, VoucherReedeemedAmt: totalRedeemed, @@ -333,18 +430,26 @@ func (ca *channelAccessor) currentAvailableFunds(ctx context.Context, channelID // Note that processTask may be called repeatedly in the same state, and should // return nil if there is no state change to be made (eg when waiting for a // message to be confirmed on chain) -func (ca *channelAccessor) processTask(ctx context.Context, amt types.BigInt) *paychFundsRes { +func (ca *channelAccessor) processTask(merged *mergedFundsReq, amt, avail types.BigInt) *paychFundsRes { + ctx := merged.ctx + // Get the payment channel for the from/to addresses. // Note: It's ok if we get ErrChannelNotTracked. It just means we need to // create a channel. - channelInfo, err := ca.store.OutboundActiveByFromTo(ctx, ca.from, ca.to) + channelInfo, err := ca.store.OutboundActiveByFromTo(ctx, ca.api, ca.from, ca.to) if err != nil && err != ErrChannelNotTracked { return &paychFundsRes{err: err} } // If a channel has not yet been created, create one. if channelInfo == nil { - mcid, err := ca.createPaych(ctx, amt) + res, freed := merged.failOffChainNoChannel(ca.from, ca.to) + if res != nil { + return res + } + amt = types.BigSub(amt, freed) + + mcid, err := ca.createPaych(ctx, amt, avail) if err != nil { return &paychFundsRes{err: err} } @@ -366,9 +471,16 @@ func (ca *channelAccessor) processTask(ctx context.Context, amt types.BigInt) *p return nil } + // Try to fill requests using available funds, without going to the chain + res, amt := ca.completeAvailable(ctx, merged, channelInfo, amt, avail) + + if res != nil || amt.LessThanEqual(types.NewInt(0)) { + return res + } + // We need to add more funds, so send an add funds message to // cover the amount for this request - mcid, err := ca.addFunds(ctx, channelInfo, amt) + mcid, err := ca.addFunds(ctx, channelInfo, amt, avail) if err != nil { return &paychFundsRes{err: err} } @@ -376,7 +488,7 @@ func (ca *channelAccessor) processTask(ctx context.Context, amt types.BigInt) *p } // createPaych sends a message to create the channel and returns the message cid -func (ca *channelAccessor) createPaych(ctx context.Context, amt types.BigInt) (cid.Cid, error) { +func (ca *channelAccessor) createPaych(ctx context.Context, amt, avail types.BigInt) (cid.Cid, error) { mb, err := ca.messageBuilder(ctx, ca.from) if err != nil { return cid.Undef, err @@ -393,7 +505,7 @@ func (ca *channelAccessor) createPaych(ctx context.Context, amt types.BigInt) (c mcid := smsg.Cid() // Create a new channel in the store - ci, err := ca.store.CreateChannel(ctx, ca.from, ca.to, mcid, amt) + ci, err := ca.store.CreateChannel(ctx, ca.from, ca.to, mcid, amt, avail) if err != nil { log.Errorf("creating channel: %s", err) return cid.Undef, err @@ -452,15 +564,41 @@ func (ca *channelAccessor) waitPaychCreateMsg(ctx context.Context, channelID str ca.mutateChannelInfo(ctx, channelID, func(channelInfo *ChannelInfo) { channelInfo.Channel = &decodedReturn.RobustAddress channelInfo.Amount = channelInfo.PendingAmount + channelInfo.AvailableAmount = channelInfo.PendingAvailableAmount channelInfo.PendingAmount = big.NewInt(0) + channelInfo.PendingAvailableAmount = big.NewInt(0) channelInfo.CreateMsg = nil }) return nil } +// completeAvailable fills reserving fund requests using already available funds, without interacting with the chain +func (ca *channelAccessor) completeAvailable(ctx context.Context, merged *mergedFundsReq, channelInfo *ChannelInfo, amt, av types.BigInt) (*paychFundsRes, types.BigInt) { + toReserve := types.BigSub(amt, av) + avail := types.NewInt(0) + + // reserve at most what we need + ca.mutateChannelInfo(ctx, channelInfo.ChannelID, func(ci *ChannelInfo) { + avail = ci.AvailableAmount + if avail.GreaterThan(toReserve) { + avail = toReserve + } + ci.AvailableAmount = big.Sub(ci.AvailableAmount, avail) + }) + + res, used, failed := merged.completeAmount(avail, channelInfo) + + // return any unused reserved funds (e.g. from cancelled requests) + ca.mutateChannelInfo(ctx, channelInfo.ChannelID, func(ci *ChannelInfo) { + ci.AvailableAmount = types.BigAdd(ci.AvailableAmount, types.BigSub(avail, used)) + }) + + return res, types.BigSub(amt, types.BigAdd(used, failed)) +} + // addFunds sends a message to add funds to the channel and returns the message cid -func (ca *channelAccessor) addFunds(ctx context.Context, channelInfo *ChannelInfo, amt types.BigInt) (*cid.Cid, error) { +func (ca *channelAccessor) addFunds(ctx context.Context, channelInfo *ChannelInfo, amt, avail types.BigInt) (*cid.Cid, error) { msg := &types.Message{ To: *channelInfo.Channel, From: channelInfo.Control, @@ -477,6 +615,7 @@ func (ca *channelAccessor) addFunds(ctx context.Context, channelInfo *ChannelInf // Store the add funds message CID on the channel ca.mutateChannelInfo(ctx, channelInfo.ChannelID, func(ci *ChannelInfo) { ci.PendingAmount = amt + ci.PendingAvailableAmount = avail ci.AddFundsMsg = &mcid }) @@ -492,6 +631,8 @@ func (ca *channelAccessor) addFunds(ctx context.Context, channelInfo *ChannelInf return &mcid, nil } +// TODO func (ca *channelAccessor) freeFunds(ctx context.Context, channelInfo *ChannelInfo, amt, avail types.BigInt) (*cid.Cid, error) { + // waitForAddFundsMsg waits for mcid to appear on chain and returns error, if any func (ca *channelAccessor) waitForAddFundsMsg(ctx context.Context, channelID string, mcid cid.Cid) { err := ca.waitAddFundsMsg(ctx, channelID, mcid) @@ -514,6 +655,7 @@ func (ca *channelAccessor) waitAddFundsMsg(ctx context.Context, channelID string ca.mutateChannelInfo(ctx, channelID, func(channelInfo *ChannelInfo) { channelInfo.PendingAmount = big.NewInt(0) + channelInfo.PendingAvailableAmount = big.NewInt(0) channelInfo.AddFundsMsg = nil }) @@ -526,7 +668,9 @@ func (ca *channelAccessor) waitAddFundsMsg(ctx context.Context, channelID string // Store updated amount ca.mutateChannelInfo(ctx, channelID, func(channelInfo *ChannelInfo) { channelInfo.Amount = types.BigAdd(channelInfo.Amount, channelInfo.PendingAmount) + channelInfo.AvailableAmount = types.BigAdd(channelInfo.AvailableAmount, channelInfo.PendingAvailableAmount) channelInfo.PendingAmount = big.NewInt(0) + channelInfo.PendingAvailableAmount = big.NewInt(0) channelInfo.AddFundsMsg = nil }) diff --git a/paychmgr/store.go b/paychmgr/store.go index 62849e6bedd..f3c67e5650b 100644 --- a/paychmgr/store.go +++ b/paychmgr/store.go @@ -6,9 +6,8 @@ import ( "errors" "fmt" - "golang.org/x/xerrors" - "github.com/google/uuid" + "golang.org/x/xerrors" "github.com/filecoin-project/lotus/chain/types" @@ -74,6 +73,10 @@ type ChannelInfo struct { // has locally been added to the channel. It should reflect the channel's // Balance on chain as long as all operations occur on the same datastore. Amount types.BigInt + // AvailableAmount indicates how much afil is non-reserved + AvailableAmount types.BigInt + // PendingAvailableAmount is available amount that we're awaiting confirmation of + PendingAvailableAmount types.BigInt // PendingAmount is the amount that we're awaiting confirmation of PendingAmount types.BigInt // CreateMsg is the CID of a pending create message (while waiting for confirmation) @@ -376,7 +379,7 @@ func (ps *Store) GetMessage(ctx context.Context, mcid cid.Cid) (*MsgInfo, error) // OutboundActiveByFromTo looks for outbound channels that have not been // settled, with the given from / to addresses -func (ps *Store) OutboundActiveByFromTo(ctx context.Context, from address.Address, to address.Address) (*ChannelInfo, error) { +func (ps *Store) OutboundActiveByFromTo(ctx context.Context, sma stateManagerAPI, from address.Address, to address.Address) (*ChannelInfo, error) { return ps.findChan(ctx, func(ci *ChannelInfo) bool { if ci.Direction != DirOutbound { return false @@ -384,6 +387,21 @@ func (ps *Store) OutboundActiveByFromTo(ctx context.Context, from address.Addres if ci.Settling { return false } + + if ci.Channel != nil { + _, st, err := sma.GetPaychState(ctx, *ci.Channel, nil) + if err != nil { + return false + } + sat, err := st.SettlingAt() + if err != nil { + return false + } + if sat != 0 { + return false + } + } + return ci.Control == from && ci.Target == to }) } @@ -416,14 +434,15 @@ func (ps *Store) ByChannelID(ctx context.Context, channelID string) (*ChannelInf } // CreateChannel creates an outbound channel for the given from / to -func (ps *Store) CreateChannel(ctx context.Context, from address.Address, to address.Address, createMsgCid cid.Cid, amt types.BigInt) (*ChannelInfo, error) { +func (ps *Store) CreateChannel(ctx context.Context, from address.Address, to address.Address, createMsgCid cid.Cid, amt, avail types.BigInt) (*ChannelInfo, error) { ci := &ChannelInfo{ - Direction: DirOutbound, - NextLane: 0, - Control: from, - Target: to, - CreateMsg: &createMsgCid, - PendingAmount: amt, + Direction: DirOutbound, + NextLane: 0, + Control: from, + Target: to, + CreateMsg: &createMsgCid, + PendingAmount: amt, + PendingAvailableAmount: avail, } // Save the new channel @@ -497,5 +516,11 @@ func unmarshallChannelInfo(stored *ChannelInfo, value []byte) (*ChannelInfo, err stored.Channel = nil } + // backwards compat + if stored.AvailableAmount.Int == nil { + stored.AvailableAmount = types.NewInt(0) + stored.PendingAvailableAmount = types.NewInt(0) + } + return stored, nil } diff --git a/testplans/lotus-soup/deals_e2e.go b/testplans/lotus-soup/deals_e2e.go index 6737bdae226..44eec2d7ab1 100644 --- a/testplans/lotus-soup/deals_e2e.go +++ b/testplans/lotus-soup/deals_e2e.go @@ -207,7 +207,9 @@ func initPaymentChannel(t *testkit.TestEnvironment, ctx context.Context, cl *tes t.RecordMessage("my balance: %d", balance) t.RecordMessage("creating payment channel; from=%s, to=%s, funds=%d", cl.Wallet.Address, recv.WalletAddr, balance) - channel, err := cl.FullApi.PaychGet(ctx, cl.Wallet.Address, recv.WalletAddr, balance) + channel, err := cl.FullApi.PaychGet(ctx, cl.Wallet.Address, recv.WalletAddr, balance, api.PaychGetOpts{ + OffChain: false, + }) if err != nil { return fmt.Errorf("failed to create payment channel: %w", err) } @@ -230,7 +232,9 @@ func initPaymentChannel(t *testkit.TestEnvironment, ctx context.Context, cl *tes // we wait for 2 confirmations, so we have the assurance the channel is tracked. t.RecordMessage("reloading paych; now it should have an address") - channel, err = cl.FullApi.PaychGet(ctx, cl.Wallet.Address, recv.WalletAddr, big.Zero()) + channel, err = cl.FullApi.PaychGet(ctx, cl.Wallet.Address, recv.WalletAddr, big.Zero(), api.PaychGetOpts{ + OffChain: false, + }) if err != nil { return fmt.Errorf("failed to reload payment channel: %w", err) } diff --git a/testplans/lotus-soup/go.mod b/testplans/lotus-soup/go.mod index f227452e7ae..94673c08119 100644 --- a/testplans/lotus-soup/go.mod +++ b/testplans/lotus-soup/go.mod @@ -9,7 +9,7 @@ require ( github.com/drand/drand v1.3.0 github.com/filecoin-project/go-address v0.0.6 github.com/filecoin-project/go-data-transfer v1.14.0 - github.com/filecoin-project/go-fil-markets v1.19.0 + github.com/filecoin-project/go-fil-markets v1.19.1 github.com/filecoin-project/go-jsonrpc v0.1.5 github.com/filecoin-project/go-state-types v0.1.3 github.com/filecoin-project/go-storedcounter v0.1.0 diff --git a/testplans/lotus-soup/go.sum b/testplans/lotus-soup/go.sum index 852d462d279..62a41703a05 100644 --- a/testplans/lotus-soup/go.sum +++ b/testplans/lotus-soup/go.sum @@ -418,8 +418,8 @@ github.com/filecoin-project/go-fil-commcid v0.1.0 h1:3R4ds1A9r6cr8mvZBfMYxTS88Oq github.com/filecoin-project/go-fil-commcid v0.1.0/go.mod h1:Eaox7Hvus1JgPrL5+M3+h7aSPHc0cVqpSxA+TxIEpZQ= github.com/filecoin-project/go-fil-commp-hashhash v0.1.0 h1:imrrpZWEHRnNqqv0tN7LXep5bFEVOVmQWHJvl2mgsGo= github.com/filecoin-project/go-fil-commp-hashhash v0.1.0/go.mod h1:73S8WSEWh9vr0fDJVnKADhfIv/d6dCbAGaAGWbdJEI8= -github.com/filecoin-project/go-fil-markets v1.19.0 h1:kap2q2wTM6tfkVO5gMA5DD9GUeTvkDhMfhjCtEwMDM8= -github.com/filecoin-project/go-fil-markets v1.19.0/go.mod h1:qsb3apmo4RSJYCEq40QxVdU7UZospN6nFJLOBHuaIbc= +github.com/filecoin-project/go-fil-markets v1.19.1 h1:o5sziAp8zCsvIg3KYMgIpwm8gyOl4MDzEKEf0Qq5L3U= +github.com/filecoin-project/go-fil-markets v1.19.1/go.mod h1:qsb3apmo4RSJYCEq40QxVdU7UZospN6nFJLOBHuaIbc= github.com/filecoin-project/go-hamt-ipld v0.1.5 h1:uoXrKbCQZ49OHpsTCkrThPNelC4W3LPEk0OrS/ytIBM= github.com/filecoin-project/go-hamt-ipld v0.1.5/go.mod h1:6Is+ONR5Cd5R6XZoCse1CWaXZc0Hdb/JeX+EQCQzX24= github.com/filecoin-project/go-hamt-ipld/v2 v2.0.0 h1:b3UDemBYN2HNfk3KOXNuxgTTxlWi3xVvbQP0IT38fvM= @@ -1411,8 +1411,9 @@ github.com/libp2p/go-libp2p-record v0.1.2/go.mod h1:pal0eNcT5nqZaTV7UGhqeGqxFgGd github.com/libp2p/go-libp2p-record v0.1.3 h1:R27hoScIhQf/A8XJZ8lYpnqh9LatJ5YbHs28kCIfql0= github.com/libp2p/go-libp2p-record v0.1.3/go.mod h1:yNUff/adKIfPnYQXgp6FQmNu3gLJ6EMg7+/vv2+9pY4= github.com/libp2p/go-libp2p-resource-manager v0.1.0/go.mod h1:wJPNjeE4XQlxeidwqVY5G6DLOKqFK33u2n8blpl0I6Y= -github.com/libp2p/go-libp2p-resource-manager v0.1.3 h1:Umf0tW6WNXSb6Uoma0YT56azB5iikL/aeGAP7s7+f5o= github.com/libp2p/go-libp2p-resource-manager v0.1.3/go.mod h1:wJPNjeE4XQlxeidwqVY5G6DLOKqFK33u2n8blpl0I6Y= +github.com/libp2p/go-libp2p-resource-manager v0.1.4 h1:RcxMD0pytOUimx3BqTVs6IqItb3H5Qg44SD7XyT68lw= +github.com/libp2p/go-libp2p-resource-manager v0.1.4/go.mod h1:wJPNjeE4XQlxeidwqVY5G6DLOKqFK33u2n8blpl0I6Y= github.com/libp2p/go-libp2p-routing v0.0.1/go.mod h1:N51q3yTr4Zdr7V8Jt2JIktVU+3xBBylx1MZeVA6t1Ys= github.com/libp2p/go-libp2p-routing v0.1.0/go.mod h1:zfLhI1RI8RLEzmEaaPwzonRvXeeSHddONWkcTcB54nE= github.com/libp2p/go-libp2p-routing-helpers v0.2.3 h1:xY61alxJ6PurSi+MXbywZpelvuU4U4p/gPTxjqCqTzY= @@ -2941,4 +2942,4 @@ sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= -sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= \ No newline at end of file +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/testplans/lotus-soup/paych/stress.go b/testplans/lotus-soup/paych/stress.go index 85246603f0f..4f107bf5b67 100644 --- a/testplans/lotus-soup/paych/stress.go +++ b/testplans/lotus-soup/paych/stress.go @@ -124,7 +124,9 @@ func runSender(ctx context.Context, t *testkit.TestEnvironment, clients []*testk time.Sleep(20 * time.Second) - channel, err := cl.FullApi.PaychGet(ctx, cl.Wallet.Address, recv.WalletAddr, channelAmt) + channel, err := cl.FullApi.PaychGet(ctx, cl.Wallet.Address, recv.WalletAddr, channelAmt, api.PaychGetOpts{ + OffChain: false, + }) if err != nil { return fmt.Errorf("failed to create payment channel: %w", err) }