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 @@
-
\ No newline at end of file
+ }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)