Skip to content

Commit c29d8dd

Browse files
committed
fix streaming upload
1 parent b5792b6 commit c29d8dd

18 files changed

Lines changed: 828 additions & 427 deletions

File tree

alertmanager/alerts.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,6 @@ var pdpTasks = []string{
173173
tasknames.PDPDelDataSet,
174174
tasknames.PDPInitPP,
175175
tasknames.PDPProvingPeriod,
176-
tasknames.PDPNotify,
177176
tasknames.PDPCommP,
178177
tasknames.PDPSaveCache,
179178
tasknames.AggregatePDPDeal,
@@ -183,7 +182,6 @@ var pdpTasks = []string{
183182
tasknames.PDPv0_SaveCache,
184183
tasknames.PDPv0_InitPP,
185184
tasknames.PDPv0_ProvPeriod,
186-
tasknames.PDPv0_Notify,
187185
}
188186

189187
// taskFailureCheckWith is the parameterized core shared by taskFailureCheck

cmd/pdptool/main.go

Lines changed: 3 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"encoding/pem"
1313
"fmt"
1414
"io"
15-
"net"
1615
"net/http"
1716
"os"
1817
"strconv"
@@ -375,50 +374,7 @@ var piecePrepareCmd = &cli.Command{
375374
},
376375
}
377376

378-
func startLocalNotifyServer() (string, chan struct{}, error) {
379-
var notifyReceived chan struct{}
380-
var server *http.Server
381-
var ln net.Listener
382-
383-
notifyReceived = make(chan struct{})
384-
var err error
385-
ln, err = net.Listen("tcp", "127.0.0.1:0")
386-
if err != nil {
387-
return "", nil, fmt.Errorf("failed to start local HTTP server: %v", err)
388-
}
389-
serverAddr := fmt.Sprintf("http://%s/notify", ln.Addr().String())
390-
391-
mux := http.NewServeMux()
392-
mux.HandleFunc("/notify", func(w http.ResponseWriter, r *http.Request) {
393-
fmt.Println("Received notification from server.")
394-
b, err := io.ReadAll(r.Body)
395-
if err != nil {
396-
fmt.Printf("Failed to read notification body: %v\n", err)
397-
w.WriteHeader(http.StatusInternalServerError)
398-
return
399-
}
400-
fmt.Printf("Notification body: %s\n", string(b))
401-
w.WriteHeader(http.StatusOK)
402-
// Signal that notification was received
403-
close(notifyReceived)
404-
})
405-
406-
server = &http.Server{Handler: mux}
407-
408-
go func() {
409-
if err := server.Serve(ln); err != nil && err != http.ErrServerClosed {
410-
fmt.Printf("HTTP server error: %v\n", err)
411-
}
412-
}()
413-
414-
defer func() {
415-
_ = server.Close()
416-
_ = ln.Close()
417-
}()
418-
return serverAddr, notifyReceived, nil
419-
}
420-
421-
func uploadOnePiece(client *http.Client, serviceURL string, reqBody []byte, jwtToken string, r io.ReadSeeker, pieceSize int64, localNotifWait bool, notifyReceived chan struct{}, verbose bool) error {
377+
func uploadOnePiece(client *http.Client, serviceURL string, reqBody []byte, jwtToken string, r io.ReadSeeker, pieceSize int64, verbose bool) error {
422378
req, err := http.NewRequest("POST", serviceURL+"/pdp/piece", bytes.NewReader(reqBody))
423379
if err != nil {
424380
return fmt.Errorf("failed to create request: %v", err)
@@ -491,13 +447,6 @@ func uploadOnePiece(client *http.Client, serviceURL string, reqBody []byte, jwtT
491447
body, _ := io.ReadAll(uploadResp.Body)
492448
return fmt.Errorf("upload failed with status code %d: %s", uploadResp.StatusCode, string(body))
493449
}
494-
if localNotifWait {
495-
if verbose {
496-
fmt.Println("Waiting for server notification...")
497-
}
498-
<-notifyReceived
499-
}
500-
501450
return nil
502451
default:
503452
body, _ := io.ReadAll(resp.Body)
@@ -523,20 +472,11 @@ var pieceUploadCmd = &cli.Command{
523472
Name: "service-name",
524473
Usage: "Service Name to include in the JWT token (used if --jwt-token is not provided)",
525474
},
526-
&cli.StringFlag{
527-
Name: "notify-url",
528-
Usage: "Notification URL",
529-
Required: false,
530-
},
531475
&cli.StringFlag{
532476
Name: "hash-type",
533477
Usage: "Hash type to use for verification (sha256 or commp)",
534478
Value: "sha256",
535479
},
536-
&cli.BoolFlag{
537-
Name: "local-notif-wait",
538-
Usage: "Wait for server notification by spawning a temporary local HTTP server",
539-
},
540480
},
541481
Action: func(cctx *cli.Context) error {
542482
inputFile := cctx.Args().Get(0)
@@ -546,10 +486,8 @@ var pieceUploadCmd = &cli.Command{
546486

547487
serviceURL := cctx.String("service-url")
548488
jwtToken := cctx.String("jwt-token")
549-
notifyURL := cctx.String("notify-url")
550489
serviceName := cctx.String("service-name")
551490
hashType := cctx.String("hash-type")
552-
localNotifWait := cctx.Bool("local-notif-wait")
553491

554492
if jwtToken == "" {
555493
if serviceName == "" {
@@ -566,20 +504,8 @@ var pieceUploadCmd = &cli.Command{
566504
return fmt.Errorf("invalid hash type: %s", hashType)
567505
}
568506

569-
if localNotifWait && notifyURL != "" {
570-
return fmt.Errorf("cannot specify both --notify-url and --local-notif-wait")
571-
}
572-
573-
var notifyReceived chan struct{}
574507
var err error
575508

576-
if localNotifWait {
577-
notifyURL, notifyReceived, err = startLocalNotifyServer()
578-
if err != nil {
579-
return fmt.Errorf("failed to start local HTTP server: %v", err)
580-
}
581-
}
582-
583509
// Open input file
584510
file, err := os.Open(inputFile)
585511
if err != nil {
@@ -626,15 +552,12 @@ var pieceUploadCmd = &cli.Command{
626552
return fmt.Errorf("unsupported hash type: %s", hashType)
627553
}
628554

629-
if notifyURL != "" {
630-
reqData["notify"] = notifyURL
631-
}
632555
reqBody, err = json.Marshal(reqData)
633556
if err != nil {
634557
return fmt.Errorf("failed to marshal request data: %v", err)
635558
}
636559
client := &http.Client{}
637-
if err := uploadOnePiece(client, serviceURL, reqBody, jwtToken, file, pieceSize, localNotifWait, notifyReceived, true); err != nil {
560+
if err := uploadOnePiece(client, serviceURL, reqBody, jwtToken, file, pieceSize, true); err != nil {
638561
return fmt.Errorf("failed to upload piece: %v", err)
639562
}
640563

@@ -662,20 +585,11 @@ var uploadFileCmd = &cli.Command{
662585
Name: "service-name",
663586
Usage: "Service Name to include in the JWT token (used if --jwt-token is not provided)",
664587
},
665-
&cli.StringFlag{
666-
Name: "notify-url",
667-
Usage: "Notification URL",
668-
Required: false,
669-
},
670588
&cli.StringFlag{
671589
Name: "hash-type",
672590
Usage: "Hash type to use for verification (sha256 or commp)",
673591
Value: "sha256",
674592
},
675-
&cli.BoolFlag{
676-
Name: "local-notif-wait",
677-
Usage: "Wait for server notification by spawning a temporary local HTTP server",
678-
},
679593
&cli.BoolFlag{
680594
Name: "verbose",
681595
Usage: "Verbose output",
@@ -702,8 +616,6 @@ var uploadFileCmd = &cli.Command{
702616
jwtToken := cctx.String("jwt-token")
703617
serviceName := cctx.String("service-name")
704618
hashType := cctx.String("hash-type")
705-
localNotifWait := cctx.Bool("local-notif-wait")
706-
notifyURL := cctx.String("notify-url")
707619
verbose := cctx.Bool("verbose")
708620
dryRun := cctx.Bool("dry-run")
709621
chunkFileName := cctx.String("chunk-file")
@@ -754,15 +666,6 @@ var uploadFileCmd = &cli.Command{
754666
bar = progressbar.NewOptions(int(fileSize/chunkSize), progressbar.OptionSetDescription("Uploading..."))
755667
}
756668

757-
// Setup local server if needed
758-
var notifyReceived chan struct{}
759-
if localNotifWait {
760-
notifyURL, notifyReceived, err = startLocalNotifyServer()
761-
if err != nil {
762-
return fmt.Errorf("failed to start local HTTP server: %v", err)
763-
}
764-
}
765-
766669
// group piece aggregations for tracking as onchain pieces into sector size chunks
767670
type pieceSetInfo struct {
768671
pieces []abi.PieceInfo
@@ -820,16 +723,13 @@ var uploadFileCmd = &cli.Command{
820723
return fmt.Errorf("unsupported hash type: %s", hashType)
821724
}
822725

823-
if notifyURL != "" {
824-
reqData["notify"] = notifyURL
825-
}
826726
reqBody, err = json.Marshal(reqData)
827727
if err != nil {
828728
return fmt.Errorf("failed to marshal request data: %v", err)
829729
}
830730

831731
// Upload the piece
832-
err = uploadOnePiece(client, serviceURL, reqBody, jwtToken, chunkReader, int64(n), localNotifWait, notifyReceived, verbose)
732+
err = uploadOnePiece(client, serviceURL, reqBody, jwtToken, chunkReader, int64(n), verbose)
833733
if err != nil {
834734
return fmt.Errorf("failed to upload piece: %v", err)
835735
}
@@ -1530,20 +1430,11 @@ var streamingPieceUploadCmd = &cli.Command{
15301430
Name: "service-name",
15311431
Usage: "Service Name to include in the JWT token (used if --jwt-token is not provided)",
15321432
},
1533-
&cli.StringFlag{
1534-
Name: "notify-url",
1535-
Usage: "Notification URL",
1536-
Required: false,
1537-
},
15381433
&cli.StringFlag{
15391434
Name: "hash-type",
15401435
Usage: "Hash type to use for verification (sha256 or commp)",
15411436
Value: "commp",
15421437
},
1543-
&cli.BoolFlag{
1544-
Name: "local-notif-wait",
1545-
Usage: "Wait for server notification by spawning a temporary local HTTP server",
1546-
},
15471438
},
15481439
Action: func(cctx *cli.Context) error {
15491440
inputFile := cctx.Args().Get(0)
@@ -1553,10 +1444,8 @@ var streamingPieceUploadCmd = &cli.Command{
15531444

15541445
serviceURL := cctx.String("service-url")
15551446
jwtToken := cctx.String("jwt-token")
1556-
notifyURL := cctx.String("notify-url")
15571447
serviceName := cctx.String("service-name")
15581448
hashType := cctx.String("hash-type")
1559-
localNotifWait := cctx.Bool("local-notif-wait")
15601449

15611450
if jwtToken == "" {
15621451
if serviceName == "" {
@@ -1573,20 +1462,8 @@ var streamingPieceUploadCmd = &cli.Command{
15731462
return fmt.Errorf("invalid hash type: %s", hashType)
15741463
}
15751464

1576-
if localNotifWait && notifyURL != "" {
1577-
return fmt.Errorf("cannot specify both --notify-url and --local-notif-wait")
1578-
}
1579-
1580-
var notifyReceived chan struct{}
15811465
var err error
15821466

1583-
if localNotifWait {
1584-
notifyURL, notifyReceived, err = startLocalNotifyServer()
1585-
if err != nil {
1586-
return fmt.Errorf("failed to start local HTTP server: %v", err)
1587-
}
1588-
}
1589-
15901467
// Open the input file
15911468
file, err := os.Open(inputFile)
15921469
if err != nil {
@@ -1715,17 +1592,12 @@ var streamingPieceUploadCmd = &cli.Command{
17151592

17161593
type finalize struct {
17171594
PieceCID string `json:"pieceCid"`
1718-
Notify string `json:"notify,omitempty"`
17191595
}
17201596

17211597
bd := finalize{
17221598
PieceCID: pcid2.String(),
17231599
}
17241600

1725-
if notifyURL != "" {
1726-
bd.Notify = notifyURL
1727-
}
1728-
17291601
bodyBytes, err := json.Marshal(bd)
17301602
if err != nil {
17311603
return fmt.Errorf("failed to marshal finalize request body: %v", err)
@@ -1758,10 +1630,6 @@ var streamingPieceUploadCmd = &cli.Command{
17581630

17591631
fmt.Printf("Piece CID: %s\n", pcid2.String())
17601632
fmt.Println("Piece uploaded successfully.")
1761-
if localNotifWait {
1762-
fmt.Println("Waiting for server notification...")
1763-
<-notifyReceived
1764-
}
17651633
return nil
17661634
},
17671635
}

cuhttp/server.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,12 @@ func attachRouters(ctx context.Context, r *chi.Mux, d *deps.Deps, sd *ServiceDep
9898

9999
if sd.EthSender != nil {
100100
if err := pdp.MountRoutes(ctx, r, pdp.MountDeps{
101-
DB: d.DB,
102-
LocalStore: d.LocalStore,
103-
PieceIO: piecestore.New(d.Stor, d.LocalStore, d.Si),
104-
EthClient: must.One(d.EthClient.Get()),
105-
Chain: d.Chain,
106-
EthSender: sd.EthSender,
107-
AlertTask: sd.AlertTask,
101+
DB: d.DB,
102+
PieceIO: piecestore.New(d.Stor, d.LocalStore, d.Si),
103+
EthClient: must.One(d.EthClient.Get()),
104+
Chain: d.Chain,
105+
EthSender: sd.EthSender,
106+
AlertTask: sd.AlertTask,
108107
}, ipp); err != nil {
109108
return nil, err
110109
}

documentation/en/experimental-features/PDPCURIOSPEC.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ TODO: the pdp datasets table etc
2020
## Managing Pieces
2121
TODO: add, upload, delete, pull, info
2222
TODO: the pdp pieceref table etc
23-
TODO: "finalization" notify task
2423

2524
# Storage
2625

@@ -179,13 +178,18 @@ The sections below describe how tasks and watchers connect to form the PDP lifec
179178

180179
## Piece Ingestion
181180

182-
There are two paths for getting pieces into the system:
181+
There are three paths for getting pieces into the system:
183182

184183
**Known-CID direct upload path:**
185184
1. The client posts the PieceCID. If a complete long-term copy already exists, the handler creates its `parked_piece_refs` and `pdp_piecerefs` rows immediately.
186185
2. Otherwise, the client PUTs the bytes to the returned upload URL. The handler claims a `parked_pieces` row, writes the request body once directly to final piece storage while computing CommP, and validates the declared size and PieceCID.
187186
3. After the write succeeds, one database transaction marks the parked piece complete, creates `pdp_piecerefs`, and deletes the upload row. The legacy `notify` request field is accepted for compatibility but this path does not call the URL or schedule a notify task.
188187

188+
**Streaming upload path:**
189+
1. The client creates an upload session and PUTs bytes without declaring a PieceCID. The handler claims a provisional `parked_pieces` identity for that session, then streams the request once directly into final piece storage while calculating CommP and the raw size.
190+
2. After the PieceCID is known, one transaction promotes the provisional row to the calculated identity. If a matching complete piece already exists, the session is pointed at that piece and the unreferenced provisional copy is left for normal parked-piece cleanup.
191+
3. The client finalizes with the calculated PieceCID. The handler validates it, creates `pdp_piecerefs`, and deletes the streaming session in one transaction. It does not use scratch space, create an intermediate `pdp_piece_uploads` row, or schedule a notify task.
192+
189193
**Pull path:**
190194
1. Client submits a pull request via HTTP, creating a row in `pdp_piece_pull_items`.
191195
2. **PDPv0_PullPiece** downloads the piece from the external URL, computes and verifies CommP, and stores the result in `parked_pieces` + `pdp_piecerefs`.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- The canonical pre-migration schema already used TIMESTAMPTZ for both
2+
-- columns, so retain their types and restore only the previous default.
3+
ALTER TABLE pdp_piece_streaming_uploads
4+
ALTER COLUMN created_at SET DEFAULT TIMEZONE('UTC', NOW());
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
Streaming-upload timestamps represent absolute instants. Convert legacy
3+
naive columns, if present, by interpreting their stored values as UTC.
4+
Existing TIMESTAMPTZ columns already store absolute instants and are left
5+
unchanged.
6+
*/
7+
8+
DO $$
9+
BEGIN
10+
IF EXISTS (
11+
SELECT 1
12+
FROM information_schema.columns
13+
WHERE table_schema = 'public'
14+
AND table_name = 'pdp_piece_streaming_uploads'
15+
AND column_name = 'created_at'
16+
AND data_type = 'timestamp without time zone'
17+
) THEN
18+
ALTER TABLE pdp_piece_streaming_uploads
19+
ALTER COLUMN created_at DROP DEFAULT;
20+
ALTER TABLE pdp_piece_streaming_uploads
21+
ALTER COLUMN created_at TYPE TIMESTAMPTZ
22+
USING created_at AT TIME ZONE 'UTC';
23+
END IF;
24+
25+
IF EXISTS (
26+
SELECT 1
27+
FROM information_schema.columns
28+
WHERE table_schema = 'public'
29+
AND table_name = 'pdp_piece_streaming_uploads'
30+
AND column_name = 'completed_at'
31+
AND data_type = 'timestamp without time zone'
32+
) THEN
33+
ALTER TABLE pdp_piece_streaming_uploads
34+
ALTER COLUMN completed_at TYPE TIMESTAMPTZ
35+
USING completed_at AT TIME ZONE 'UTC';
36+
END IF;
37+
END $$;
38+
39+
ALTER TABLE pdp_piece_streaming_uploads
40+
ALTER COLUMN created_at SET DEFAULT NOW();

0 commit comments

Comments
 (0)