Skip to content

Commit

Permalink
docs: update documentation for indexer gateway (#66)
Browse files Browse the repository at this point in the history
- Also deprecated `multipart/form-data` support for file segment upload in REST API.
  • Loading branch information
wanliqun authored Oct 21, 2024
1 parent 8780c50 commit 9d367b1
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 70 deletions.
75 changes: 70 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,77 @@ Indexer service provides RPC to index storages nodes in two ways:

Please refer to the [RPC API](https://docs.0g.ai/0g-doc/docs/0g-storage/rpc/indexer-api) documentation for more details.

Besides, indexer supports to download files via HTTP GET request:
Besides, the Indexer provides a RESTful API gateway for file downloads and uploads.

### File Download

Files can be downloaded via HTTP GET requests in two ways:

- By transaction sequence number:

```
GET /file?txSeq=7
```

- By file Merkle root:

```
GET /file?root=0x0376e0d95e483b62d5100968ed17fe1b1d84f0bc5d9bda8000cdfd3f39a59927
```

You can also specify the `name` parameter to rename the downloaded file:

```
GET /file?txSeq=7&name=foo.log
```

### File Download Within a Folder

Files within a folder can also be downloaded via HTTP GET requests:

- By transaction sequence number:

```
GET /file/{txSeq}/path/to/file
```

- By file Merkle root:

```
GET /file/{merkleRoot}/path/to/file
```

This allows users to retrieve specific files from within a structured folder stored in the network.

### File Upload

File segments can be uploaded via HTTP POST requests in JSON format:

```
/file?txSeq=7
or
/file?root=0x0376e0d95e483b62d5100968ed17fe1b1d84f0bc5d9bda8000cdfd3f39a59927
POST /file/segment
```

Note, user could specify `name=foo.log` parameter in GET URL to rename the downloaded file.
There are two options for uploading:

- By transaction sequence:

```json
{
"txSeq": /* Transaction sequence number */,
"index": /* Segment index (decimal) */,
"data": "/* Base64-encoded segment data */",
"proof": {/* Merkle proof object for validation */}
}
```
- By file Merkle root:

```json
{
"root": "/* Merkle root of the file (in 0x-prefixed hexadecimal) */",
"index": /* Segment index (decimal) */,
"data": "/* Base64-encoded segment data */",
"proof": {/* Merkle proof object for validation */}
}
```

> **Note:** The `proof` field should contain a [`merkle.Proof`](https://github.com/0glabs/0g-storage-client/blob/8780c5020928a79fb60ed7dea26a42d9876ecfae/core/merkle/proof.go#L20) object, which is used to verify the integrity of each segment.
76 changes: 11 additions & 65 deletions indexer/gateway/upload.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package gateway

import (
"encoding/json"
"io"
"mime/multipart"
"net/http"

"github.com/0glabs/0g-storage-client/core"
Expand All @@ -18,76 +15,30 @@ import (
var expectedReplica uint

type UploadSegmentRequest struct {
Root string `form:"root" json:"root"` // Merkle root
TxSeq *uint64 `form:"txSeq" json:"txSeq"` // Transaction sequence
Index uint64 `form:"index" json:"index"` // Segment index
Proof string `form:"proof" json:"proof"` // Merkle proof (encoded as JSON string)

// Data can be either byte array or nil depending on request type
Data []byte `json:"data,omitempty"` // for `application/json` request
// OR (mutually exclusive with Data)
File *multipart.FileHeader `form:"data,omitempty"` // for `multipart/form-data` request
}

// GetData reads segment data from the request
func (req *UploadSegmentRequest) GetData() ([]byte, error) {
if req.Data != nil {
return req.Data, nil
}
if req.File == nil {
return nil, errors.New("either `data` or `file` is required")
}
data, err := req.readFileData()
if err != nil {
return nil, errors.WithMessage(err, "failed to load file data")
}
req.Data = data
return data, nil
}

// readFileData reads data from the uploaded file
func (req *UploadSegmentRequest) readFileData() ([]byte, error) {
file, err := req.File.Open()
if err != nil {
return nil, err
}
defer file.Close()

fileBytes, err := io.ReadAll(file)
if err != nil {
return nil, err
}
return fileBytes, nil
Root common.Hash `json:"root"` // file merkle root
TxSeq *uint64 `json:"txSeq"` // Transaction sequence
Data []byte `json:"data"` // segment data
Index uint64 `json:"index"` // segment index
Proof merkle.Proof `json:"proof"` // segment merkle proof
}

func uploadSegment(c *gin.Context) {
var input UploadSegmentRequest

// bind the request (supports both `application/json` and `multipart/form-data`)
// bind the `application/json` request
if err := c.ShouldBind(&input); err != nil {
c.JSON(http.StatusBadRequest, errors.WithMessagef(err, "Failed to bind input parameters").Error())
return
}

// validate root and transaction sequence
if input.TxSeq == nil && len(input.Root) == 0 {
c.JSON(http.StatusBadRequest, "Either 'root' or 'txSeq' must be provided")
return
}

// validate segment data
if _, err := input.GetData(); err != nil {
c.JSON(http.StatusBadRequest, errors.WithMessagef(err, "Failed to extract data").Error())
return
}

if len(input.Data) == 0 {
c.JSON(http.StatusBadRequest, "Segment data is empty")
return
}

// retrieve and validate file info
fileInfo, err := getFileInfo(c, common.HexToHash(input.Root), input.TxSeq)
fileInfo, err := getFileInfo(c, input.Root, input.TxSeq)
if err != nil {
c.JSON(http.StatusInternalServerError, errors.WithMessage(err, "Failed to retrieve file info").Error())
return
Expand All @@ -98,12 +49,7 @@ func uploadSegment(c *gin.Context) {
}

// validate merkle proof
var proof merkle.Proof
if err := json.Unmarshal([]byte(input.Proof), &proof); err != nil {
c.JSON(http.StatusBadRequest, "Failed to json unmarshal merkle proof")
return
}
if err := validateMerkleProof(input, proof, fileInfo); err != nil {
if err := validateMerkleProof(input, fileInfo); err != nil {
c.JSON(http.StatusBadRequest, errors.WithMessagef(err, "Failed to validate merkle proof").Error())
return
}
Expand All @@ -113,7 +59,7 @@ func uploadSegment(c *gin.Context) {
Root: fileInfo.Tx.DataMerkleRoot,
Data: input.Data,
Index: input.Index,
Proof: proof,
Proof: input.Proof,
FileSize: fileInfo.Tx.Size,
}
if err := uploadSegmentWithProof(c, segment, fileInfo); err != nil {
Expand All @@ -125,11 +71,11 @@ func uploadSegment(c *gin.Context) {
}

// validateMerkleProof is a helper function to validate merkle proof for the upload request
func validateMerkleProof(req UploadSegmentRequest, proof merkle.Proof, fileInfo *node.FileInfo) error {
func validateMerkleProof(req UploadSegmentRequest, fileInfo *node.FileInfo) error {
fileSize := int64(fileInfo.Tx.Size)
merkleRoot := fileInfo.Tx.DataMerkleRoot
segmentRootHash, numSegmentsFlowPadded := core.PaddedSegmentRoot(req.Index, req.Data, fileSize)
return proof.ValidateHash(merkleRoot, segmentRootHash, req.Index, numSegmentsFlowPadded)
return req.Proof.ValidateHash(merkleRoot, segmentRootHash, req.Index, numSegmentsFlowPadded)
}

// uploadSegmentWithProof is a helper function to upload the segment with proof
Expand Down

0 comments on commit 9d367b1

Please sign in to comment.