Add IPFS node to L2 for federated content storage

- Add IPFS container to docker-compose
- Add ipfshttpclient dependency
- Add ipfs_client.py module for IPFS operations
- Add ipfs_cid field to Asset model and database schema
- Pin content on L2 IPFS when assets are published/registered

L2 now stores content on its own IPFS node, enabling
federation - content remains available even if L1 goes down.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
gilesb
2026-01-08 18:21:33 +00:00
parent 82cb1cf711
commit 96631e1e4c
5 changed files with 159 additions and 2 deletions

View File

@@ -86,6 +86,7 @@ class Asset(BaseModel):
"""An owned asset."""
name: str
content_hash: str
ipfs_cid: Optional[str] = None # IPFS content identifier
asset_type: str # image, video, effect, recipe, infrastructure
tags: list[str] = []
metadata: dict = {}
@@ -108,6 +109,7 @@ class RegisterRequest(BaseModel):
"""Request to register an asset."""
name: str
content_hash: str
ipfs_cid: Optional[str] = None # IPFS content identifier
asset_type: str
tags: list[str] = []
metadata: dict = {}
@@ -125,6 +127,7 @@ class RecordRunRequest(BaseModel):
class PublishCacheRequest(BaseModel):
"""Request to publish a cache item from L1."""
content_hash: str
ipfs_cid: Optional[str] = None # IPFS content identifier
asset_name: str
asset_type: str = "image"
origin: dict # {type: "self"|"external", url?: str, note?: str}
@@ -1613,11 +1616,21 @@ async def _register_asset_impl(req: RegisterRequest, owner: str):
if await db.asset_exists(req.name):
raise HTTPException(400, f"Asset already exists: {req.name}")
# Pin content on IPFS if CID provided
if req.ipfs_cid:
try:
import ipfs_client
if ipfs_client.is_available():
ipfs_client.pin(req.ipfs_cid)
except Exception as e:
logger.warning(f"Failed to pin IPFS content {req.ipfs_cid}: {e}")
# Create asset
now = datetime.now(timezone.utc).isoformat()
asset = {
"name": req.name,
"content_hash": req.content_hash,
"ipfs_cid": req.ipfs_cid,
"asset_type": req.asset_type,
"tags": req.tags,
"metadata": req.metadata,
@@ -1735,11 +1748,21 @@ async def publish_cache(req: PublishCacheRequest, user: User = Depends(get_requi
if await db.asset_exists(req.asset_name):
raise HTTPException(400, f"Asset name already exists: {req.asset_name}")
# Pin content on IPFS if CID provided
if req.ipfs_cid:
try:
import ipfs_client
if ipfs_client.is_available():
ipfs_client.pin(req.ipfs_cid)
except Exception as e:
logger.warning(f"Failed to pin IPFS content {req.ipfs_cid}: {e}")
# Create asset
now = datetime.now(timezone.utc).isoformat()
asset = {
"name": req.asset_name,
"content_hash": req.content_hash,
"ipfs_cid": req.ipfs_cid,
"asset_type": req.asset_type,
"tags": req.tags,
"description": req.description,