diff --git a/docs/retrievalclient.mmd b/docs/retrievalclient.mmd index 3ba1ac7e..d7ff4442 100644 --- a/docs/retrievalclient.mmd +++ b/docs/retrievalclient.mmd @@ -63,6 +63,7 @@ stateDiagram-v2 4 --> 8 : ClientEventPaymentChannelErrored 5 --> 8 : ClientEventPaymentChannelErrored 6 --> 8 : ClientEventPaymentChannelErrored + 6 --> 13 : ClientEventPaymentChannelSkip 6 --> 4 : ClientEventPaymentChannelCreateInitiated 6 --> 24 : ClientEventPaymentChannelAddingFunds 22 --> 5 : ClientEventPaymentChannelAddingFunds @@ -101,6 +102,7 @@ stateDiagram-v2 11 --> 13 : ClientEventPaymentSent 12 --> 19 : ClientEventPaymentSent 13 --> 21 : ClientEventComplete + 18 --> 21 : ClientEventComplete 19 --> 15 : ClientEventComplete 21 --> 15 : ClientEventCompleteVerified 21 --> 17 : ClientEventEarlyTermination diff --git a/docs/retrievalclient.mmd.png b/docs/retrievalclient.mmd.png index e93d52ae..a16ffed6 100644 Binary files a/docs/retrievalclient.mmd.png and b/docs/retrievalclient.mmd.png differ diff --git a/docs/retrievalclient.mmd.svg b/docs/retrievalclient.mmd.svg index d1958c01..f0d29fc2 100644 --- a/docs/retrievalclient.mmd.svg +++ b/docs/retrievalclient.mmd.svg @@ -1,6 +1,6 @@ -ClientEventOpenClientEventDealProposedClientEventDealProposedClientEventDealRejectedClientEventDealRejectedClientEventDealNotFoundClientEventDealNotFoundClientEventDealAcceptedClientEventDealAcceptedClientEventPaymentChannelErroredClientEventPaymentChannelErroredClientEventPaymentChannelErrored<invalid Value>ClientEventPaymentChannelCreateInitiatedClientEventPaymentChannelAddingFundsClientEventPaymentChannelAddingFundsClientEventPaymentChannelReadyClientEventPaymentChannelReadyClientEventPaymentChannelReadyClientEventAllocateLaneErroredClientEventLaneAllocatedClientEventLastPaymentRequestedClientEventLastPaymentRequestedClientEventLastPaymentRequestedClientEventLastPaymentRequestedClientEventPaymentRequestedClientEventPaymentRequestedClientEventPaymentRequestedClientEventUnsealPaymentRequestedClientEventUnsealPaymentRequestedClientEventAllBlocksReceivedClientEventAllBlocksReceivedClientEventAllBlocksReceivedClientEventAllBlocksReceivedClientEventBlocksReceivedClientEventBlocksReceivedClientEventBlocksReceivedClientEventBlocksReceivedClientEventBlocksReceivedClientEventSendFundsClientEventSendFundsClientEventFundsExpendedClientEventBadPaymentRequestedClientEventBadPaymentRequestedClientEventCreateVoucherFailedClientEventCreateVoucherFailedClientEventVoucherShortfallClientEventVoucherShortfallClientEventPaymentSentClientEventPaymentSentClientEventCompleteClientEventCompleteClientEventCompleteClientEventCompleteVerifiedClientEventEarlyTerminationClientEventWaitForLastBlocksClientEventCancelCompleteClientEventCancelCompleteClientEventRecheckFundsDealStatusNewOn entry runs ProposeDealDealStatusWaitForAcceptanceDealStatusPaymentChannelCreatingOn entry runs WaitPaymentChannelReadyDealStatusPaymentChannelAddingFundsOn entry runs WaitPaymentChannelReadyDealStatusAcceptedOn entry runs SetupPaymentChannelStartDealStatusFailingOn entry runs CancelDealDealStatusRejectedDealStatusFundsNeededOn entry runs ProcessPaymentRequestedDealStatusSendFundsOn entry runs SendFundsDealStatusSendFundsLastPaymentOn entry runs SendFundsDealStatusOngoingOn entry runs OngoingDealStatusFundsNeededLastPaymentOn entry runs ProcessPaymentRequestedDealStatusCompletedDealStatusDealNotFoundDealStatusErroredDealStatusBlocksCompleteDealStatusFinalizingDealStatusCheckCompleteOn entry runs CheckCompleteDealStatusCheckFundsOn entry runs CheckFundsDealStatusInsufficientFundsDealStatusPaymentChannelAllocatingLaneOn entry runs AllocateLaneDealStatusCancellingOn entry runs CancelDealDealStatusCancelledDealStatusRetryLegacyOn entry runs ProposeDealDealStatusWaitForAcceptanceLegacyDealStatusWaitingForLastBlocksThe following events are not shown cause they can trigger from any state.ClientEventWriteDealProposalErrored - transitions state to DealStatusErroredClientEventUnknownResponseReceived - transitions state to DealStatusFailingClientEventDataTransferError - transitions state to DealStatusErroredClientEventWriteDealPaymentErrored - transitions state to DealStatusErroredClientEventProviderCancelled - transitions state to DealStatusCancellingClientEventCancel - transitions state to DealStatusCancellingThe following events only record in this state.ClientEventLastPaymentRequestedClientEventPaymentRequestedClientEventAllBlocksReceivedClientEventBlocksReceivedThe following events only record in this state.ClientEventLastPaymentRequestedClientEventPaymentRequestedClientEventAllBlocksReceivedClientEventBlocksReceivedThe following events only record in this state.ClientEventLastPaymentRequestedClientEventPaymentRequestedClientEventAllBlocksReceivedClientEventBlocksReceivedThe following events only record in this state.ClientEventProviderCancelledThe following events only record in this state.ClientEventAllBlocksReceivedThe following events only record in this state.ClientEventAllBlocksReceivedThe following events only record in this state.ClientEventLastPaymentRequestedClientEventPaymentRequestedClientEventAllBlocksReceivedClientEventBlocksReceivedThe following events only record in this state.ClientEventProviderCancelledThe following events only record in this state.ClientEventLastPaymentRequestedClientEventPaymentRequestedClientEventAllBlocksReceivedClientEventBlocksReceived \ No newline at end of file diff --git a/retrievalmarket/events.go b/retrievalmarket/events.go index 218d39fd..a77e47b5 100644 --- a/retrievalmarket/events.go +++ b/retrievalmarket/events.go @@ -119,6 +119,10 @@ const ( // the client that all blocks were sent for the deal, and the client is // waiting for the last blocks to arrive ClientEventWaitForLastBlocks + + // ClientEventPaymentChannelSkip is fired when the total deal price is zero + // so there's no need to set up a payment channel + ClientEventPaymentChannelSkip ) // ClientEvents is a human readable map of client event name -> event description @@ -158,6 +162,7 @@ var ClientEvents = map[ClientEvent]string{ ClientEventRecheckFunds: "ClientEventRecheckFunds", ClientEventCancel: "ClientEventCancel", ClientEventWaitForLastBlocks: "ClientEventWaitForLastBlocks", + ClientEventPaymentChannelSkip: "ClientEventPaymentChannelSkip", } // ProviderEvent is an event that occurs in a deal lifecycle on the provider diff --git a/retrievalmarket/impl/clientstates/client_fsm.go b/retrievalmarket/impl/clientstates/client_fsm.go index 8de1fd99..5d70b271 100644 --- a/retrievalmarket/impl/clientstates/client_fsm.go +++ b/retrievalmarket/impl/clientstates/client_fsm.go @@ -80,12 +80,18 @@ var ClientEvents = fsm.Events{ deal.Message = xerrors.Errorf("error from payment channel: %w", err).Error() return nil }), + + // Price of deal is zero so skip creating a payment channel + fsm.Event(rm.ClientEventPaymentChannelSkip). + From(rm.DealStatusAccepted).To(rm.DealStatusOngoing), + fsm.Event(rm.ClientEventPaymentChannelCreateInitiated). From(rm.DealStatusAccepted).To(rm.DealStatusPaymentChannelCreating). Action(func(deal *rm.ClientDealState, msgCID cid.Cid) error { deal.WaitMsgCID = &msgCID return nil }), + fsm.Event(rm.ClientEventPaymentChannelAddingFunds). FromMany(rm.DealStatusAccepted).To(rm.DealStatusPaymentChannelAllocatingLane). FromMany(rm.DealStatusCheckFunds).To(rm.DealStatusPaymentChannelAddingFunds). @@ -98,6 +104,7 @@ var ClientEvents = fsm.Events{ } return nil }), + fsm.Event(rm.ClientEventPaymentChannelReady). From(rm.DealStatusPaymentChannelCreating).To(rm.DealStatusPaymentChannelAllocatingLane). From(rm.DealStatusPaymentChannelAddingFunds).To(rm.DealStatusOngoing). @@ -113,6 +120,7 @@ var ClientEvents = fsm.Events{ deal.Message = "" return nil }), + fsm.Event(rm.ClientEventAllocateLaneErrored). FromMany(rm.DealStatusPaymentChannelAllocatingLane). To(rm.DealStatusFailing). @@ -253,6 +261,7 @@ var ClientEvents = fsm.Events{ // completing deals fsm.Event(rm.ClientEventComplete). From(rm.DealStatusOngoing).To(rm.DealStatusCheckComplete). + From(rm.DealStatusBlocksComplete).To(rm.DealStatusCheckComplete). From(rm.DealStatusFinalizing).To(rm.DealStatusCompleted), fsm.Event(rm.ClientEventCompleteVerified). From(rm.DealStatusCheckComplete).To(rm.DealStatusCompleted), diff --git a/retrievalmarket/impl/clientstates/client_states.go b/retrievalmarket/impl/clientstates/client_states.go index 5190567a..39915d3f 100644 --- a/retrievalmarket/impl/clientstates/client_states.go +++ b/retrievalmarket/impl/clientstates/client_states.go @@ -36,6 +36,10 @@ func ProposeDeal(ctx fsm.Context, environment ClientDealEnvironment, deal rm.Cli // SetupPaymentChannelStart initiates setting up a payment channel for a deal func SetupPaymentChannelStart(ctx fsm.Context, environment ClientDealEnvironment, deal rm.ClientDealState) error { + // If the total funds required for the deal are zero, skip creating the payment channel + if deal.TotalFunds.IsZero() { + return ctx.Trigger(rm.ClientEventPaymentChannelSkip) + } tok, _, err := environment.Node().GetChainHead(ctx.Context()) if err != nil { diff --git a/retrievalmarket/impl/clientstates/client_states_test.go b/retrievalmarket/impl/clientstates/client_states_test.go index 8a394f52..ccf86128 100644 --- a/retrievalmarket/impl/clientstates/client_states_test.go +++ b/retrievalmarket/impl/clientstates/client_states_test.go @@ -146,6 +146,14 @@ func TestSetupPaymentChannel(t *testing.T) { require.Equal(t, dealState.Status, retrievalmarket.DealStatusFailing) }) + t.Run("payment channel skip if total funds is zero", func(t *testing.T) { + envParams := testnodes.TestRetrievalClientNodeParams{} + dealState := makeDealState(retrievalmarket.DealStatusAccepted) + dealState.TotalFunds = abi.NewTokenAmount(0) + runSetupPaymentChannel(t, envParams, dealState) + assert.Empty(t, dealState.Message) + assert.Equal(t, dealState.Status, retrievalmarket.DealStatusOngoing) + }) } func TestWaitForPaymentReady(t *testing.T) { diff --git a/retrievalmarket/impl/integration_test.go b/retrievalmarket/impl/integration_test.go index 42fd9823..b169ae3d 100644 --- a/retrievalmarket/impl/integration_test.go +++ b/retrievalmarket/impl/integration_test.go @@ -514,11 +514,12 @@ CurrentInterval: %d } else if testCase.cancelled { assert.Equal(t, retrievalmarket.DealStatusCancelled, clientDealState.Status) } else { - assert.Equal(t, clientDealState.PaymentInfo.Lane, expectedVoucher.Lane) - require.NotNil(t, createdChan) - require.Equal(t, expectedTotal, createdChan.amt) - require.Equal(t, clientPaymentChannel, *newLaneAddr) if !testCase.zeroPricePerByte { + assert.Equal(t, clientDealState.PaymentInfo.Lane, expectedVoucher.Lane) + require.NotNil(t, createdChan) + require.Equal(t, expectedTotal, createdChan.amt) + require.Equal(t, clientPaymentChannel, *newLaneAddr) + // verify that the voucher was saved/seen by the client with correct values require.NotNil(t, createdVoucher) tut.TestVoucherEquality(t, createdVoucher, expectedVoucher)