Skip to content

Commit 2b7ee29

Browse files
committed
add docs
1 parent 33ad884 commit 2b7ee29

3 files changed

Lines changed: 325 additions & 1 deletion

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,8 @@ Your node is now running. To start additional nodes, repeat these steps in a new
147147
- [API Endpoints](docs/API.md)
148148
- [Environmental Variables](docs/env.md)
149149
- [Database Guide](docs/database.md)
150-
- [Storage Types](docs/Storage.md)
150+
- [Asset Storage Types](docs/Storage.md)
151+
- [Persistent storage for c2d jobs](docs/persistentStorage.md)
151152
- [Testing Guide](docs/testing.md)
152153
- [Network Configuration](docs/networking.md)
153154
- [Logging & accessing logs](docs/networking.md)

docs/API.md

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1586,3 +1586,154 @@ returns job result
15861586
#### Response
15871587

15881588
File content
1589+
1590+
---
1591+
1592+
## Persistent Storage
1593+
1594+
### `HTTP` POST /api/services/persistentStorage/buckets
1595+
1596+
#### Description
1597+
1598+
Create a new persistent storage bucket. Bucket ownership is set to the request `consumerAddress`.
1599+
1600+
#### Request Headers
1601+
1602+
| name | type | required | description |
1603+
| --------------- | ------ | -------- | ----------- |
1604+
| Authorization | string | | auth token (optional; depends on node auth configuration) |
1605+
1606+
#### Request Body
1607+
1608+
```json
1609+
{
1610+
"consumerAddress": "0x...",
1611+
"signature": "0x...",
1612+
"nonce": "123",
1613+
"accessLists": []
1614+
}
1615+
```
1616+
1617+
#### Response (200)
1618+
1619+
```json
1620+
{
1621+
"bucketId": "uuid",
1622+
"owner": "0x...",
1623+
"accessList": []
1624+
}
1625+
```
1626+
1627+
---
1628+
1629+
### `HTTP` GET /api/services/persistentStorage/buckets
1630+
1631+
#### Description
1632+
1633+
List buckets for a given `owner`. Results are filtered by bucket access lists for the calling consumer.
1634+
1635+
#### Query Parameters
1636+
1637+
| name | type | required | description |
1638+
| --------------- | ------ | -------- | ----------- |
1639+
| consumerAddress | string | v | consumer address |
1640+
| signature | string | v | signed message (consumerAddress + nonce + command) |
1641+
| nonce | string | v | request nonce |
1642+
| chainId | number | v | chain id (used by auth/signature checks) |
1643+
| owner | string | v | bucket owner to filter by |
1644+
1645+
#### Response (200)
1646+
1647+
```json
1648+
[
1649+
{
1650+
"bucketId": "uuid",
1651+
"owner": "0x...",
1652+
"createdAt": 1710000000,
1653+
"accessLists": []
1654+
}
1655+
]
1656+
```
1657+
1658+
---
1659+
1660+
### `HTTP` GET /api/services/persistentStorage/buckets/:bucketId/files
1661+
1662+
#### Description
1663+
1664+
List files in a bucket.
1665+
1666+
#### Query Parameters
1667+
1668+
| name | type | required | description |
1669+
| --------------- | ------ | -------- | ----------- |
1670+
| consumerAddress | string | v | consumer address |
1671+
| signature | string | v | signed message (consumerAddress + nonce + command) |
1672+
| nonce | string | v | request nonce |
1673+
1674+
#### Response (200)
1675+
1676+
```json
1677+
[
1678+
{
1679+
"bucketId": "uuid",
1680+
"name": "hello.txt",
1681+
"size": 123,
1682+
"lastModified": 1710000000
1683+
}
1684+
]
1685+
```
1686+
1687+
---
1688+
1689+
### `HTTP` POST /api/services/persistentStorage/buckets/:bucketId/files/:fileName
1690+
1691+
#### Description
1692+
1693+
Upload a file to a bucket. The request body is treated as raw bytes.
1694+
1695+
#### Query Parameters
1696+
1697+
| name | type | required | description |
1698+
| --------------- | ------ | -------- | ----------- |
1699+
| consumerAddress | string | v | consumer address |
1700+
| signature | string | v | signed message (consumerAddress + nonce + command) |
1701+
| nonce | string | v | request nonce |
1702+
1703+
#### Request Body
1704+
1705+
Raw bytes (any content-type).
1706+
1707+
#### Response (200)
1708+
1709+
```json
1710+
{
1711+
"bucketId": "uuid",
1712+
"name": "hello.txt",
1713+
"size": 123,
1714+
"lastModified": 1710000000
1715+
}
1716+
```
1717+
1718+
---
1719+
1720+
### `HTTP` DELETE /api/services/persistentStorage/buckets/:bucketId/files/:fileName
1721+
1722+
#### Description
1723+
1724+
Delete a file from a bucket.
1725+
1726+
#### Query Parameters
1727+
1728+
| name | type | required | description |
1729+
| --------------- | ------ | -------- | ----------- |
1730+
| consumerAddress | string | v | consumer address |
1731+
| signature | string | v | signed message (consumerAddress + nonce + command) |
1732+
| nonce | string | v | request nonce |
1733+
| chainId | number | v | chain id (used by auth/signature checks) |
1734+
1735+
#### Response (200)
1736+
1737+
```json
1738+
{ "success": true }
1739+
```

docs/persistentStorage.md

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
# Persistent Storage
2+
3+
This document describes Ocean Node **Persistent Storage** at a high level: what it is, how it is structured, how access control works, and how to use it via **P2P commands** and **HTTP endpoints**.
4+
5+
---
6+
7+
## What it is
8+
9+
Persistent Storage is a simple bucket + file store intended for **long-lived artifacts** that Ocean Node needs to keep across requests (and potentially across restarts), and to reference later (e.g. as file objects for compute).
10+
11+
Key primitives:
12+
- **Bucket**: a logical container for files.
13+
- **File**: binary content stored inside a bucket.
14+
- **Bucket registry**: a local SQLite table that stores bucket metadata (owner, access lists, createdAt).
15+
16+
---
17+
18+
## Architecture (high level)
19+
20+
### Components
21+
22+
- **Handlers (protocol layer)**: `src/components/core/handler/persistentStorage.ts`
23+
- Implements protocol commands such as create bucket, list files, upload, delete, and get buckets.
24+
- Validates auth (token or signature) and applies high-level authorization checks.
25+
26+
- **Persistent storage backends (storage layer)**: `src/components/persistentStorage/*`
27+
- `PersistentStorageFactory`: shared functionality (SQLite bucket registry, access list checks).
28+
- `PersistentStorageLocalFS`: local filesystem backend.
29+
- `PersistentStorageS3`: stub for future S3-compatible backend.
30+
31+
- **HTTP routes (HTTP interface)**: `src/components/httpRoutes/persistentStorage.ts`
32+
- Exposes REST-ish endpoints under `/api/services/persistentStorage/...` that call the same handlers.
33+
34+
### Data storage
35+
36+
Persistent Storage uses two stores:
37+
38+
1) **Bucket registry (SQLite)**
39+
- File: `databases/persistentStorage.sqlite`
40+
- Table: `persistent_storage_buckets`
41+
- Columns:
42+
- `bucketId` (primary key)
43+
- `owner` (address, stored as a string)
44+
- `accessListJson` (JSON-encoded access list array)
45+
- `createdAt` (unix timestamp)
46+
47+
2) **Backend data**
48+
- `localfs`: writes file bytes to the configured folder under `buckets/<bucketId>/<fileName>`.
49+
- `s3`: not implemented yet.
50+
51+
---
52+
53+
## Ownership and access control
54+
55+
### Ownership
56+
57+
Every bucket has a single **owner** address, stored in the bucket registry.
58+
59+
- When a bucket is created, the node sets:
60+
- `owner = consumerAddress` (normalized via `ethers.getAddress`)
61+
62+
### Bucket access list
63+
64+
Each bucket stores an **AccessList[]** (per-chain list(s) of access list contract addresses):
65+
66+
```ts
67+
export interface AccessList {
68+
[chainId: string]: string[]
69+
}
70+
```
71+
72+
This access list is used to decide whether a given `consumerAddress` is allowed to interact with a bucket.
73+
74+
### Where checks happen
75+
76+
Access checks happen at two levels:
77+
78+
1) **Backend enforcement** (required)
79+
- Backend operations `listFiles`, `uploadFile`, `deleteFile`, and `getFileObject` all require `consumerAddress`.
80+
- The base class helper `assertConsumerAllowedForBucket(consumerAddress, bucketId)` loads the bucket ACL and throws `PersistentStorageAccessDeniedError` if the consumer is not allowed.
81+
82+
2) **Handler enforcement** (command-specific)
83+
- `createBucket`: additionally checks the node-level allow list `config.persistentStorage.accessLists` (who can create buckets at all).
84+
- `getBuckets`: queries registry rows filtered by `owner` and then:
85+
- if `consumerAddress === owner`: returns all buckets for that owner
86+
- else: filters buckets by the bucket ACL
87+
88+
### Error behavior
89+
90+
- Backends throw `PersistentStorageAccessDeniedError` when forbidden.
91+
- Handlers translate that into **HTTP 403** / `status.httpStatus = 403`.
92+
93+
---
94+
95+
## Features
96+
97+
### Supported today
98+
99+
- **Create bucket**
100+
- Creates a bucket id (UUID), persists it in SQLite with `owner` and `accessListJson`, and creates a local directory (localfs).
101+
102+
- **List buckets (by owner)**
103+
- Returns buckets from the registry filtered by `owner` (mandatory arg).
104+
- Applies ACL filtering for non-owners.
105+
106+
- **Upload file**
107+
- Writes a stream to the backend.
108+
- Enforces bucket ACL.
109+
110+
- **List files**
111+
- Returns file metadata (`name`, `size`, `lastModified`) for a bucket.
112+
- Enforces bucket ACL.
113+
114+
- **Delete file**
115+
- Deletes the named file from the bucket.
116+
- Enforces bucket ACL.
117+
118+
### Not implemented yet
119+
120+
- **S3 backend**
121+
- `PersistentStorageS3` exists as a placeholder and currently throws “not implemented”.
122+
123+
---
124+
125+
## Configuration
126+
127+
Persistent storage is controlled by `persistentStorage` in node config.
128+
129+
Key fields:
130+
- `enabled`: boolean
131+
- `type`: `"localfs"` or `"s3"`
132+
- `accessLists`: AccessList[] — node-level allow list to create buckets
133+
- `options`:
134+
- localfs: `{ "folder": "/path/to/storage" }`
135+
- s3: `{ endpoint, objectKey, accessKeyId, secretAccessKey, ... }` (future)
136+
137+
---
138+
139+
## Usage
140+
141+
### P2P commands
142+
143+
All persistent storage operations are implemented as protocol commands in the handler:
144+
- `persistentStorageCreateBucket`
145+
- `persistentStorageGetBuckets`
146+
- `persistentStorageListFiles`
147+
- `persistentStorageUploadFile`
148+
- `persistentStorageDeleteFile`
149+
150+
Each command requires authentication (token or signature) based on Ocean Node’s auth configuration.
151+
152+
### HTTP endpoints
153+
154+
HTTP routes are available under `/api/services/persistentStorage/...` and call the same handlers. See `docs/API.md` for the full parameter lists and examples.
155+
156+
At a glance:
157+
- `POST /api/services/persistentStorage/buckets`
158+
- `GET /api/services/persistentStorage/buckets`
159+
- `GET /api/services/persistentStorage/buckets/:bucketId/files`
160+
- `POST /api/services/persistentStorage/buckets/:bucketId/files/:fileName`
161+
- `DELETE /api/services/persistentStorage/buckets/:bucketId/files/:fileName`
162+
163+
Upload uses the raw request body as bytes and forwards it to the handler as a stream.
164+
165+
---
166+
167+
## Limitations and notes
168+
169+
- The bucket registry is local to the node (SQLite file). If you run multiple nodes, each node’s registry is independent unless you externalize/replicate it.
170+
- `listBuckets(owner)` requires `owner` and only returns buckets that were created with that owner recorded.
171+
- Filenames in `localfs` are constrained (no path separators) to avoid path traversal.
172+

0 commit comments

Comments
 (0)