Skip to content

Commit fd2b1dc

Browse files
committed
Add explicit deal type support for schedules
1 parent 1ac668e commit fd2b1dc

9 files changed

Lines changed: 81 additions & 5 deletions

File tree

cmd/deal/schedule/create.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/data-preservation-programs/singularity/cmd/cliutil"
1010
"github.com/data-preservation-programs/singularity/database"
1111
"github.com/data-preservation-programs/singularity/handler/deal/schedule"
12+
"github.com/data-preservation-programs/singularity/model"
1213
"github.com/data-preservation-programs/singularity/util"
1314
"github.com/urfave/cli/v2"
1415
)
@@ -58,6 +59,12 @@ var CreateCmd = &cli.Command{
5859
Usage: "Storage Provider ID to send deals to",
5960
Required: true,
6061
},
62+
&cli.StringFlag{
63+
Name: "deal-type",
64+
Category: "Deal Proposal",
65+
Usage: "Deal type: market (legacy f05) or pdp (f41)",
66+
Value: string(model.DealTypeMarket),
67+
},
6168
&cli.StringSliceFlag{
6269
Name: "http-header",
6370
Category: "Boost Only",
@@ -219,6 +226,7 @@ var CreateCmd = &cli.Command{
219226
request := schedule.CreateRequest{
220227
Preparation: c.String("preparation"),
221228
Provider: c.String("provider"),
229+
DealType: c.String("deal-type"),
222230
HTTPHeaders: c.StringSlice("http-header"),
223231
URLTemplate: c.String("url-template"),
224232
PricePerGBEpoch: c.Float64("price-per-gb-epoch"),

cmd/deal/schedule/update.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ var UpdateCmd = &cli.Command{
8383
Usage: "Whether to propose deals as verified",
8484
Value: true,
8585
},
86+
&cli.StringFlag{
87+
Name: "deal-type",
88+
Category: "Deal Proposal",
89+
Usage: "Deal type: market (legacy f05) or pdp (f41)",
90+
},
8691
&cli.BoolFlag{
8792
Name: "ipni",
8893
Category: "Boost Only",
@@ -208,6 +213,9 @@ var UpdateCmd = &cli.Command{
208213
if c.IsSet("verified") {
209214
request.Verified = ptr.Of(c.Bool("verified"))
210215
}
216+
if c.IsSet("deal-type") {
217+
request.DealType = ptr.Of(c.String("deal-type"))
218+
}
211219
if c.IsSet("ipni") {
212220
request.IPNI = ptr.Of(c.Bool("ipni"))
213221
}

handler/deal/schedule/create.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package schedule
33
import (
44
"context"
55
"net/url"
6+
"slices"
67
"strconv"
78
"strings"
89
"time"
@@ -23,6 +24,7 @@ import (
2324
type CreateRequest struct {
2425
Preparation string `json:"preparation" validation:"required"` // Preparation ID or name
2526
Provider string `json:"provider" validation:"required"` // Provider
27+
DealType string `json:"dealType"` // Deal type: market (f05) or pdp (f41)
2628
HTTPHeaders []string `json:"httpHeaders"` // http headers to be passed with the request (i.e. key=value)
2729
URLTemplate string `json:"urlTemplate"` // URL template with PIECE_CID placeholder for boost to fetch the CAR file, i.e. http://127.0.0.1/piece/{PIECE_CID}.car
2830
PricePerGBEpoch float64 `default:"0" json:"pricePerGbEpoch"` // Price in FIL per GiB per epoch
@@ -167,6 +169,13 @@ func (DefaultHandler) CreateHandler(
167169
if err != nil {
168170
return nil, errors.Join(handlererror.ErrInvalidParameter, errors.Wrapf(err, "provider %s cannot be resolved", request.Provider))
169171
}
172+
dealType := model.DealType(request.DealType)
173+
if dealType == "" {
174+
dealType = model.DealTypeMarket
175+
}
176+
if !slices.Contains(model.DealTypes, dealType) {
177+
return nil, errors.Wrapf(handlererror.ErrInvalidParameter, "invalid deal type %q", request.DealType)
178+
}
170179

171180
headers := make(map[string]string)
172181
for _, header := range request.HTTPHeaders {
@@ -205,6 +214,7 @@ func (DefaultHandler) CreateHandler(
205214
PricePerDeal: request.PricePerDeal,
206215
ScheduleCronPerpetual: request.ScheduleCronPerpetual,
207216
Force: request.Force,
217+
DealType: dealType,
208218
}
209219

210220
if err := database.DoRetry(ctx, func() error {

handler/deal/schedule/create_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ func getMockLotusClient() jsonrpc.RPCClient {
5353
var createRequest = CreateRequest{
5454
Preparation: "1",
5555
Provider: "f01000",
56+
DealType: string(model.DealTypeMarket),
5657
HTTPHeaders: []string{"a=b"},
5758
URLTemplate: "http://127.0.0.1",
5859
PricePerGBEpoch: 0,
@@ -186,6 +187,20 @@ func TestCreateHandler_NoAssociatedWallet(t *testing.T) {
186187
})
187188
}
188189

190+
func TestCreateHandler_InvalidDealType(t *testing.T) {
191+
testutil.All(t, func(ctx context.Context, t *testing.T, db *gorm.DB) {
192+
err := db.Create(&model.Preparation{
193+
Wallets: []model.Wallet{{ID: "f01"}},
194+
}).Error
195+
require.NoError(t, err)
196+
badRequest := createRequest
197+
badRequest.DealType = "unknown"
198+
_, err = Default.CreateHandler(ctx, db, getMockLotusClient(), badRequest)
199+
require.ErrorIs(t, err, handlererror.ErrInvalidParameter)
200+
require.ErrorContains(t, err, "invalid deal type")
201+
})
202+
}
203+
189204
func TestCreateHandler_InvalidProvider(t *testing.T) {
190205
testutil.All(t, func(ctx context.Context, t *testing.T, db *gorm.DB) {
191206
err := db.Create(&model.Preparation{
@@ -260,6 +275,7 @@ func TestCreateHandler_Success(t *testing.T) {
260275
schedule, err := Default.CreateHandler(ctx, db, getMockLotusClient(), createRequest)
261276
require.NoError(t, err)
262277
require.NotNil(t, schedule)
278+
require.Equal(t, model.DealTypeMarket, schedule.DealType)
263279
require.True(t, createRequest.Force)
264280
})
265281
})

handler/deal/schedule/update.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package schedule
33
import (
44
"context"
55
"net/url"
6+
"slices"
67
"strings"
78

89
"github.com/cockroachdb/errors"
@@ -39,6 +40,7 @@ type UpdateRequest struct {
3940
//nolint:tagliatelle
4041
AllowedPieceCIDs []string `json:"allowedPieceCids"` // Allowed piece CIDs in this schedule
4142
Force *bool `json:"force"` // Force to send out deals regardless of replication restriction
43+
DealType *string `json:"dealType"` // Deal type: market (f05) or pdp (f41)
4244
}
4345

4446
// UpdateHandler modifies an existing schedule record based on the provided update request.
@@ -235,6 +237,13 @@ func (DefaultHandler) UpdateHandler(
235237
if request.Force != nil {
236238
updates["force"] = *request.Force
237239
}
240+
if request.DealType != nil {
241+
dealType := model.DealType(*request.DealType)
242+
if !slices.Contains(model.DealTypes, dealType) {
243+
return nil, errors.Wrapf(handlererror.ErrInvalidParameter, "invalid deal type %q", *request.DealType)
244+
}
245+
updates["deal_type"] = dealType
246+
}
238247

239248
err = db.Model(&schedule).Updates(updates).Error
240249
if err != nil {

handler/deal/schedule/update_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ var updateRequest = UpdateRequest{
3434
AllowedPieceCIDs: []string{"baga6ea4seaqao7s73y24kcutaosvacpdjgfe5pw76ooefnyqw4ynr3d2y6x2mpq"},
3535
ScheduleCronPerpetual: ptr.Of(true),
3636
Force: ptr.Of(true),
37+
DealType: ptr.Of(string(model.DealTypeMarket)),
3738
}
3839

3940
func TestUpdateHandler_DatasetNotFound(t *testing.T) {
@@ -194,6 +195,20 @@ func TestUpdateHandler_InvalidAllowedPieceCID_NotCommp(t *testing.T) {
194195
})
195196
}
196197

198+
func TestUpdateHandler_InvalidDealType(t *testing.T) {
199+
testutil.All(t, func(ctx context.Context, t *testing.T, db *gorm.DB) {
200+
err := db.Create(&model.Schedule{
201+
Preparation: &model.Preparation{},
202+
}).Error
203+
require.NoError(t, err)
204+
badRequest := updateRequest
205+
badRequest.DealType = ptr.Of("unknown")
206+
_, err = Default.UpdateHandler(ctx, db, 1, badRequest)
207+
require.ErrorIs(t, err, handlererror.ErrInvalidParameter)
208+
require.ErrorContains(t, err, "invalid deal type")
209+
})
210+
}
211+
197212
func TestUpdateHandler_Success(t *testing.T) {
198213
testutil.All(t, func(ctx context.Context, t *testing.T, db *gorm.DB) {
199214
err := db.Create(&model.Schedule{
@@ -204,6 +219,7 @@ func TestUpdateHandler_Success(t *testing.T) {
204219
require.NoError(t, err)
205220
require.NotNil(t, schedule)
206221
require.True(t, schedule.Force)
222+
require.Equal(t, model.DealTypeMarket, schedule.DealType)
207223
})
208224
}
209225

model/replication.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ type Schedule struct {
161161
ErrorMessage string `json:"errorMessage" table:"verbose"`
162162
AllowedPieceCIDs StringSlice `gorm:"type:JSON;column:allowed_piece_cids" json:"allowedPieceCids" table:"verbose"`
163163
Force bool `json:"force"`
164+
DealType DealType `gorm:"index;default:'market'" json:"dealType"`
164165

165166
// Associations
166167
PreparationID PreparationID `json:"preparationId"`

service/dealpusher/dealpusher.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@ var waitPendingInterval = time.Minute
3535

3636
// DealPusher represents a struct that encapsulates the data and functionality related to pushing deals in a replication process.
3737
type DealPusher struct {
38-
dbNoContext *gorm.DB // Pointer to a gorm.DB object representing a database connection.
39-
walletChooser replication.WalletChooser // Object responsible for choosing a wallet for replication.
40-
dealMaker replication.DealMaker // Object responsible for making a deal in replication.
41-
pdpProofSetManager PDPProofSetManager // Optional PDP proof set lifecycle manager.
42-
pdpTxConfirmer PDPTransactionConfirmer // Optional PDP transaction confirmer.
38+
dbNoContext *gorm.DB // Pointer to a gorm.DB object representing a database connection.
39+
walletChooser replication.WalletChooser // Object responsible for choosing a wallet for replication.
40+
dealMaker replication.DealMaker // Object responsible for making a deal in replication.
41+
pdpProofSetManager PDPProofSetManager // Optional PDP proof set lifecycle manager.
42+
pdpTxConfirmer PDPTransactionConfirmer // Optional PDP transaction confirmer.
4343
// Resolver is injected so tests and future wiring can switch deal type behavior without coupling DealPusher to config storage.
4444
scheduleDealTypeResolver func(schedule *model.Schedule) model.DealType
4545
workerID uuid.UUID // UUID identifying the associated worker.
@@ -433,6 +433,9 @@ func (d *DealPusher) runSchedule(ctx context.Context, schedule *model.Schedule)
433433

434434
func (d *DealPusher) resolveScheduleDealType(schedule *model.Schedule) model.DealType {
435435
if d.scheduleDealTypeResolver == nil {
436+
if schedule != nil && schedule.DealType == model.DealTypePDP {
437+
return model.DealTypePDP
438+
}
436439
return model.DealTypeMarket
437440
}
438441
return d.scheduleDealTypeResolver(schedule)

service/dealpusher/pdp_wiring_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ func TestDealPusher_ResolveScheduleDealType_DefaultsToMarket(t *testing.T) {
3131
require.Equal(t, model.DealTypeMarket, d.resolveScheduleDealType(&model.Schedule{}))
3232
}
3333

34+
func TestDealPusher_ResolveScheduleDealType_UsesScheduleDealType(t *testing.T) {
35+
d := &DealPusher{}
36+
require.Equal(t, model.DealTypePDP, d.resolveScheduleDealType(&model.Schedule{DealType: model.DealTypePDP}))
37+
}
38+
3439
func TestDealPusher_RunSchedule_PDPWithoutDependenciesReturnsConfiguredError(t *testing.T) {
3540
d := &DealPusher{
3641
scheduleDealTypeResolver: func(_ *model.Schedule) model.DealType {

0 commit comments

Comments
 (0)