Skip to content

Commit 60a4189

Browse files
committed
add Deal.WalletID, decouple from Actor FK
Deal.ClientID remains a plain string for on-chain deal matching (Key()) but no longer has a FK constraint to the actors table. New WalletID *uint FK points to wallets.id, letting PDP deals reference the originating wallet even when no f0 actor exists yet. - model: add WalletID + Wallet FK on Deal, remove Actor FK - migrate: backfill wallet_id from client_id→actor→wallet, drop fk_deals_actor constraint - all deal creation sites set WalletID (dealpusher, pdp_schedule, send-manual, dealtracker, eventprocessor) - datacap query uses wallet_id instead of client_id - pdp_schedule no longer requires actor to exist for PDP deals
1 parent 90dd8d2 commit 60a4189

18 files changed

Lines changed: 159 additions & 32 deletions

File tree

client/swagger/models/model_deal.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/swagger/docs.go

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/swagger/swagger.json

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/swagger/swagger.yaml

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

handler/deal/send-manual.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ func (DefaultHandler) SendManualHandler(
140140
AnnounceToIPNI: request.IPNI,
141141
StartDelay: startDelay,
142142
Duration: duration,
143+
WalletID: &walletObj.ID,
143144
}
144145

145146
// resolve actor lazily — only makes RPC call if ActorID not yet linked

handler/deal/send-manual_test.go

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package deal
33
import (
44
"context"
55
"testing"
6-
"time"
76

87
"github.com/data-preservation-programs/singularity/handler/handlererror"
98
"github.com/data-preservation-programs/singularity/model"
@@ -160,19 +159,13 @@ func TestSendManualHandler(t *testing.T) {
160159
createTestWalletAndActor(t, db, true)
161160

162161
mockDealMaker := new(MockDealMaker)
163-
mockDealMaker.On("MakeDeal", mock.Anything, actor, mock.Anything, replication.DealConfig{
164-
Provider: proposal.ProviderID,
165-
StartDelay: 24 * time.Hour,
166-
Duration: 2400 * time.Hour,
167-
Verified: proposal.Verified,
168-
HTTPHeaders: map[string]string{"a": "b"},
169-
URLTemplate: proposal.URLTemplate,
170-
KeepUnsealed: proposal.KeepUnsealed,
171-
AnnounceToIPNI: proposal.IPNI,
172-
PricePerDeal: proposal.PricePerDeal,
173-
PricePerGB: proposal.PricePerGB,
174-
PricePerGBEpoch: proposal.PricePerGBEpoch,
175-
}).Return(&model.Deal{}, nil)
162+
mockDealMaker.On("MakeDeal", mock.Anything, actor, mock.Anything,
163+
mock.MatchedBy(func(dc replication.DealConfig) bool {
164+
return dc.Provider == proposal.ProviderID &&
165+
dc.Verified == proposal.Verified &&
166+
dc.WalletID != nil
167+
}),
168+
).Return(&model.Deal{}, nil)
176169
// lotusClient is nil — GetOrCreateActor won't call lotus because ActorID is already set
177170
resp, err := Default.SendManualHandler(ctx, db, nil, nil, mockDealMaker, proposal)
178171
mockDealMaker.AssertExpectations(t)

handler/file/deals_test.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,10 @@ func TestGetFileDealsHandler(t *testing.T) {
7777

7878
deals := []model.Deal{{
7979
PieceCID: model.CID(testCid1),
80-
Actor: &model.Actor{},
8180
}, {
8281
PieceCID: model.CID(testCid2),
83-
Actor: &model.Actor{},
8482
}, {
8583
PieceCID: model.CID(testCid2),
86-
Actor: &model.Actor{},
8784
}}
8885
err = db.Create(deals).Error
8986
require.NoError(t, err)

handler/file/retrieve_test.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,6 @@ func TestRetrieveFileHandler(t *testing.T) {
144144
State: model.DealActive,
145145
PieceCID: model.CID(testCid),
146146
Provider: "apples" + strconv.Itoa(i),
147-
Actor: &model.Actor{},
148147
}
149148
err = db.Create(&deal).Error
150149
require.NoError(t, err)
@@ -158,7 +157,6 @@ func TestRetrieveFileHandler(t *testing.T) {
158157
State: state,
159158
PieceCID: model.CID(testCid),
160159
Provider: "oranges" + strconv.Itoa(i),
161-
Actor: &model.Actor{},
162160
}
163161
err = db.Create(&deal).Error
164162
require.NoError(t, err)
@@ -489,7 +487,6 @@ func BenchmarkFilecoinRetrieve(b *testing.B) {
489487
State: model.DealActive,
490488
PieceCID: model.CID(testCid),
491489
Provider: "apples" + strconv.Itoa(i),
492-
Actor: &model.Actor{},
493490
}
494491
err = db.Create(&deal).Error
495492
require.NoError(b, err)
@@ -502,7 +499,6 @@ func BenchmarkFilecoinRetrieve(b *testing.B) {
502499
State: state,
503500
PieceCID: model.CID(testCid),
504501
Provider: "oranges" + strconv.Itoa(i),
505-
Actor: &model.Actor{},
506502
}
507503
err = db.Create(&deal).Error
508504
require.NoError(b, err)

model/migrate.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,16 @@ func AutoMigrate(db *gorm.DB) error {
124124
return errors.Wrap(err, "failed to infer deal types")
125125
}
126126

127+
// Drop legacy fk_deals_actor constraint (Deal.ClientID no longer FKs to Actor)
128+
if err := dropDealActorFK(db); err != nil {
129+
return errors.Wrap(err, "failed to drop deal-actor FK")
130+
}
131+
132+
// Backfill wallet_id for deals that predate the column
133+
if err := backfillDealWalletID(db); err != nil {
134+
return errors.Wrap(err, "failed to backfill deal wallet IDs")
135+
}
136+
127137
return nil
128138
}
129139

@@ -344,6 +354,92 @@ func inferDealTypes(db *gorm.DB) error {
344354
return nil
345355
}
346356

357+
// dropDealActorFK removes the legacy fk_deals_actor constraint if it exists.
358+
// Deal.ClientID is now a plain string (no FK to actors table).
359+
func dropDealActorFK(db *gorm.DB) error {
360+
dialect := db.Dialector.Name()
361+
if dialect == "sqlite" {
362+
return nil
363+
}
364+
365+
constraint := "fk_deals_actor"
366+
var exists bool
367+
368+
if dialect == "postgres" {
369+
err := db.Raw(`
370+
SELECT EXISTS (
371+
SELECT 1 FROM information_schema.table_constraints
372+
WHERE table_name = 'deals' AND constraint_name = ?
373+
)`, constraint).Scan(&exists).Error
374+
if err != nil {
375+
return errors.Wrapf(err, "failed to check constraint %s", constraint)
376+
}
377+
} else if dialect == "mysql" {
378+
err := db.Raw(`
379+
SELECT COUNT(*) > 0 FROM information_schema.TABLE_CONSTRAINTS
380+
WHERE TABLE_NAME = 'deals' AND CONSTRAINT_NAME = ?
381+
`, constraint).Scan(&exists).Error
382+
if err != nil {
383+
return errors.Wrapf(err, "failed to check constraint %s", constraint)
384+
}
385+
}
386+
387+
if !exists {
388+
return nil
389+
}
390+
391+
logger.Infow("dropping legacy deal-actor FK constraint", "constraint", constraint)
392+
if dialect == "postgres" {
393+
return db.Exec(`ALTER TABLE deals DROP CONSTRAINT ` + constraint).Error
394+
}
395+
// mysql
396+
return db.Exec(`ALTER TABLE deals DROP FOREIGN KEY ` + constraint).Error
397+
}
398+
399+
// backfillDealWalletID sets wallet_id for existing deals that have a client_id
400+
// matching an actor linked to a wallet. idempotent — only touches NULL wallet_id rows.
401+
func backfillDealWalletID(db *gorm.DB) error {
402+
var count int64
403+
err := db.Raw(`SELECT COUNT(*) FROM deals WHERE wallet_id IS NULL AND client_id != ''`).Scan(&count).Error
404+
if err != nil {
405+
logger.Debugw("skipping wallet_id backfill", "error", err)
406+
return nil
407+
}
408+
if count == 0 {
409+
return nil
410+
}
411+
412+
logger.Infow("backfilling deal wallet_id from client_id → actor → wallet", "count", count)
413+
414+
dialect := db.Dialector.Name()
415+
var query string
416+
if dialect == "sqlite" {
417+
query = `
418+
UPDATE deals SET wallet_id = (
419+
SELECT w.id FROM wallets w
420+
WHERE w.actor_id = deals.client_id
421+
LIMIT 1
422+
) WHERE wallet_id IS NULL AND client_id != ''
423+
AND EXISTS (SELECT 1 FROM wallets w WHERE w.actor_id = deals.client_id)`
424+
} else {
425+
query = `
426+
UPDATE deals d
427+
SET wallet_id = (
428+
SELECT w.id FROM wallets w
429+
WHERE w.actor_id = d.client_id
430+
LIMIT 1
431+
) WHERE d.wallet_id IS NULL AND d.client_id != ''
432+
AND EXISTS (SELECT 1 FROM wallets w WHERE w.actor_id = d.client_id)`
433+
}
434+
435+
result := db.Exec(query)
436+
if result.Error != nil {
437+
return errors.Wrap(result.Error, "failed to backfill wallet_id")
438+
}
439+
logger.Infow("backfilled deal wallet_id", "updated", result.RowsAffected)
440+
return nil
441+
}
442+
347443
// DropAll removes all tables specified in the Tables slice from the database.
348444
//
349445
// This function is typically used during development or testing where a clean database

model/replication.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,8 @@ type Deal struct {
123123
ScheduleID *ScheduleID `json:"scheduleId" table:"verbose"`
124124
Schedule *Schedule `gorm:"foreignKey:ScheduleID;constraint:OnDelete:SET NULL" json:"schedule,omitempty" swaggerignore:"true" table:"expand"`
125125
ClientID string `gorm:"index:idx_pending" json:"clientId"`
126-
Actor *Actor `gorm:"foreignKey:ClientID;constraint:OnDelete:SET NULL" json:"actor,omitempty" swaggerignore:"true" table:"expand"`
126+
WalletID *uint `gorm:"index:idx_deal_wallet" json:"walletId,omitempty" table:"verbose"`
127+
Wallet *Wallet `gorm:"foreignKey:WalletID;constraint:OnDelete:SET NULL" json:"wallet,omitempty" swaggerignore:"true" table:"expand"`
127128
}
128129

129130
// Key returns a mostly unique key to match deal from locally proposed deals and deals from the chain.

0 commit comments

Comments
 (0)