Skip to content

Commit 23efe71

Browse files
authored
feat: make storage upload limits configurable (#1090)
Make MAX_FILE_SIZE and MAX_UNAUTHENTICATED_UPLOAD_FILE_SIZE configurable via the storage config section so testnets can raise them. - Add storage.max_file_size (default 100 MiB) and storage.max_unauthenticated_upload_file_size (default 25 MiB) config entries - Inject limits via constructor into StoreMessageHandler, BalanceCronJob, and CreditBalanceCronJob - Read limits from config in storage controller route handlers - Fix client_max_size bug (was 64 MiB, below the 100 MiB upload limit) - Rename constants to DEFAULT_MAX_FILE_SIZE / DEFAULT_MAX_UNAUTHENTICATED_UPLOAD_FILE_SIZE
1 parent 8a4e839 commit 23efe71

16 files changed

Lines changed: 183 additions & 69 deletions

File tree

src/aleph/api_entrypoint.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@ async def configure_aiohttp_app(
6262
)
6363
signature_verifier = SignatureVerifier()
6464

65-
app = create_aiohttp_app()
65+
app = create_aiohttp_app(
66+
max_file_size=config.storage.max_file_size.value,
67+
)
6668

6769
# Reuse the connection of the P2P client to avoid opening two connections
6870
mq_conn = p2p_client.mq_client.connection

src/aleph/commands.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,14 @@ async def main(args: List[str]) -> None:
153153
cron_job = CronJob(
154154
session_factory=session_factory,
155155
jobs={
156-
"balance": BalanceCronJob(session_factory=session_factory),
157-
"credit_balance": CreditBalanceCronJob(session_factory=session_factory),
156+
"balance": BalanceCronJob(
157+
session_factory=session_factory,
158+
max_unauthenticated_upload_file_size=config.storage.max_unauthenticated_upload_file_size.value,
159+
),
160+
"credit_balance": CreditBalanceCronJob(
161+
session_factory=session_factory,
162+
max_unauthenticated_upload_file_size=config.storage.max_unauthenticated_upload_file_size.value,
163+
),
158164
},
159165
)
160166
chain_data_service = ChainDataService(

src/aleph/config.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
from configmanager import Config
44

5+
from aleph.toolkit.constants import (
6+
DEFAULT_MAX_FILE_SIZE,
7+
DEFAULT_MAX_UNAUTHENTICATED_UPLOAD_FILE_SIZE,
8+
)
9+
510

611
def get_defaults():
712
return {
@@ -117,8 +122,12 @@ def get_defaults():
117122
"store_files": True,
118123
# Interval between garbage collector runs, expressed in hours.
119124
"garbage_collector_period": 24,
120-
# Grapce period for files, expressed in hours.
125+
# Grace period for files, expressed in hours.
121126
"grace_period": 24,
127+
# Maximum file size for authenticated uploads, in bytes.
128+
"max_file_size": DEFAULT_MAX_FILE_SIZE,
129+
# Maximum file size for unauthenticated uploads, in bytes.
130+
"max_unauthenticated_upload_file_size": DEFAULT_MAX_UNAUTHENTICATED_UPLOAD_FILE_SIZE,
122131
},
123132
"nuls2": {
124133
# NULS2 chain ID.

src/aleph/handlers/content/store.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
from aleph.services.cost_validation import validate_balance_for_payment
4141
from aleph.services.ipfs import IpfsService
4242
from aleph.storage import StorageService
43-
from aleph.toolkit.constants import MAX_UNAUTHENTICATED_UPLOAD_FILE_SIZE, MiB
43+
from aleph.toolkit.constants import MiB
4444
from aleph.toolkit.costs import are_store_and_program_free, is_credit_only_required
4545
from aleph.toolkit.timestamp import timestamp_to_datetime, utc_now
4646
from aleph.types.db_session import DbSession
@@ -135,9 +135,15 @@ def _should_pin_on_ipfs(
135135

136136

137137
class StoreMessageHandler(ContentHandler):
138-
def __init__(self, storage_service: StorageService, grace_period: int):
138+
def __init__(
139+
self,
140+
storage_service: StorageService,
141+
grace_period: int,
142+
max_unauthenticated_upload_file_size: int,
143+
):
139144
self.storage_service = storage_service
140145
self.grace_period = grace_period
146+
self.max_unauthenticated_upload_file_size = max_unauthenticated_upload_file_size
141147

142148
async def is_related_content_fetched(
143149
self, session: DbSession, message: MessageDb
@@ -245,7 +251,7 @@ async def pre_check_balance(self, session: DbSession, message: MessageDb):
245251

246252
# Allow users to pin small files (only for hold payment type, before cutoff)
247253
if payment_type == PaymentType.hold and storage_mib <= (
248-
MAX_UNAUTHENTICATED_UPLOAD_FILE_SIZE / MiB
254+
self.max_unauthenticated_upload_file_size / MiB
249255
):
250256
return None
251257

@@ -298,7 +304,7 @@ async def check_balance(
298304
if (
299305
payment_type == PaymentType.hold
300306
and storage_size_mib
301-
and storage_size_mib <= (MAX_UNAUTHENTICATED_UPLOAD_FILE_SIZE / MiB)
307+
and storage_size_mib <= (self.max_unauthenticated_upload_file_size / MiB)
302308
):
303309
return costs
304310

src/aleph/handlers/message_handler.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def __init__(
7878
MessageType.store: StoreMessageHandler(
7979
storage_service=storage_service,
8080
grace_period=config.storage.grace_period.value,
81+
max_unauthenticated_upload_file_size=config.storage.max_unauthenticated_upload_file_size.value,
8182
),
8283
}
8384

src/aleph/jobs/cron/balance_job.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,7 @@
1717
from aleph.db.models.messages import MessageDb, MessageStatusDb
1818
from aleph.jobs.cron.cron_job import BaseCronJob
1919
from aleph.services.cost import calculate_storage_size
20-
from aleph.toolkit.constants import (
21-
MAX_UNAUTHENTICATED_UPLOAD_FILE_SIZE,
22-
STORE_AND_PROGRAM_COST_CUTOFF_HEIGHT,
23-
MiB,
24-
)
20+
from aleph.toolkit.constants import STORE_AND_PROGRAM_COST_CUTOFF_HEIGHT, MiB
2521
from aleph.toolkit.timestamp import utc_now
2622
from aleph.types.db_session import DbSession, DbSessionFactory
2723
from aleph.types.message_status import MessageStatus
@@ -30,8 +26,13 @@
3026

3127

3228
class BalanceCronJob(BaseCronJob):
33-
def __init__(self, session_factory: DbSessionFactory):
29+
def __init__(
30+
self,
31+
session_factory: DbSessionFactory,
32+
max_unauthenticated_upload_file_size: int,
33+
):
3434
self.session_factory = session_factory
35+
self.max_unauthenticated_upload_file_size = max_unauthenticated_upload_file_size
3536

3637
async def run(self, now: dt.datetime, job: CronJobDb):
3738
with self.session_factory() as session:
@@ -101,7 +102,7 @@ async def delete_messages(self, session: DbSession, messages: List[ItemHash]):
101102
)
102103

103104
if storage_size_mib and storage_size_mib <= (
104-
MAX_UNAUTHENTICATED_UPLOAD_FILE_SIZE / MiB
105+
self.max_unauthenticated_upload_file_size / MiB
105106
):
106107
continue
107108

src/aleph/jobs/cron/credit_balance_job.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,7 @@
2020
from aleph.db.models.messages import MessageDb, MessageStatusDb
2121
from aleph.jobs.cron.cron_job import BaseCronJob
2222
from aleph.services.cost import calculate_storage_size
23-
from aleph.toolkit.constants import (
24-
CREDIT_ONLY_CUTOFF_TIMESTAMP,
25-
MAX_UNAUTHENTICATED_UPLOAD_FILE_SIZE,
26-
MiB,
27-
)
23+
from aleph.toolkit.constants import CREDIT_ONLY_CUTOFF_TIMESTAMP, MiB
2824
from aleph.toolkit.timestamp import utc_now
2925
from aleph.types.db_session import DbSession, DbSessionFactory
3026
from aleph.types.message_status import MessageStatus
@@ -33,8 +29,13 @@
3329

3430

3531
class CreditBalanceCronJob(BaseCronJob):
36-
def __init__(self, session_factory: DbSessionFactory):
32+
def __init__(
33+
self,
34+
session_factory: DbSessionFactory,
35+
max_unauthenticated_upload_file_size: int,
36+
):
3737
self.session_factory = session_factory
38+
self.max_unauthenticated_upload_file_size = max_unauthenticated_upload_file_size
3839

3940
async def run(self, now: dt.datetime, job: CronJobDb):
4041
with self.session_factory() as session:
@@ -114,7 +115,8 @@ async def delete_messages(self, session: DbSession, messages: List[ItemHash]):
114115
if (
115116
is_legacy_message
116117
and storage_size_mib
117-
and storage_size_mib <= (MAX_UNAUTHENTICATED_UPLOAD_FILE_SIZE / MiB)
118+
and storage_size_mib
119+
<= (self.max_unauthenticated_upload_file_size / MiB)
118120
):
119121
continue
120122

src/aleph/toolkit/constants.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -332,9 +332,8 @@ class ProductPriceType(str, Enum):
332332
CREDIT_PRECISION_CUTOFF_TIMESTAMP = 1769990400 # 2026-02-02 00:00:00 UTC
333333
CREDIT_PRECISION_MULTIPLIER = 10000
334334

335-
MAX_FILE_SIZE = 100 * MiB
336-
MAX_UNAUTHENTICATED_UPLOAD_FILE_SIZE = 25 * MiB
337-
# MAX_UPLOAD_FILE_SIZE = 1000 * MiB (not used?)
335+
DEFAULT_MAX_FILE_SIZE = 100 * MiB
336+
DEFAULT_MAX_UNAUTHENTICATED_UPLOAD_FILE_SIZE = 25 * MiB
338337
MIN_STORE_COST_MIB = 25 # Minimum MiB cost for pure STORE messages
339338
MIN_CREDIT_COST_PER_HOUR = (
340339
1 # Minimum cost per hour in credits for instances and volumes

src/aleph/web/__init__.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from aiohttp import web
1010
from aiohttp_swagger3 import SwaggerDocs, SwaggerInfo, SwaggerUiSettings
1111

12+
from aleph.toolkit.constants import DEFAULT_MAX_FILE_SIZE
1213
from aleph.version import get_version
1314
from aleph.web.controllers.routes import register_routes
1415

@@ -32,8 +33,11 @@ def init_cors(app: web.Application):
3233
cors.add(route)
3334

3435

35-
def create_aiohttp_app(with_swagger: bool = True) -> web.Application:
36-
app = web.Application(client_max_size=1024**2 * 64)
36+
def create_aiohttp_app(
37+
with_swagger: bool = True,
38+
max_file_size: int = DEFAULT_MAX_FILE_SIZE,
39+
) -> web.Application:
40+
app = web.Application(client_max_size=max_file_size)
3741

3842
swagger = None
3943
if with_swagger:

src/aleph/web/controllers/storage.py

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,7 @@
3333
)
3434
from aleph.services.cost import get_total_and_detailed_costs
3535
from aleph.storage import StorageService
36-
from aleph.toolkit.constants import (
37-
MAX_FILE_SIZE,
38-
MAX_UNAUTHENTICATED_UPLOAD_FILE_SIZE,
39-
MiB,
40-
)
36+
from aleph.toolkit.constants import MiB
4137
from aleph.types.db_session import DbSession, DbSessionFactory
4238
from aleph.types.files import FileTag, FileType
4339
from aleph.types.message_status import InvalidSignature
@@ -155,10 +151,12 @@ async def _verify_message_signature(
155151

156152

157153
def _verify_user_balance(
158-
session: DbSession, content: CostEstimationStoreContent
154+
session: DbSession,
155+
content: CostEstimationStoreContent,
156+
max_unauthenticated_upload_file_size: int,
159157
) -> None:
160158
if content.estimated_size_mib and content.estimated_size_mib > (
161-
MAX_UNAUTHENTICATED_UPLOAD_FILE_SIZE / MiB
159+
max_unauthenticated_upload_file_size / MiB
162160
):
163161
current_balance = get_total_balance(session=session, address=content.address)
164162
current_cost = get_total_cost_for_address(
@@ -271,6 +269,7 @@ async def _check_and_add_file(
271269
message: Optional[PendingStoreMessage],
272270
uploaded_file: UploadedFile,
273271
grace_period: int,
272+
max_unauthenticated_upload_file_size: int,
274273
) -> str:
275274
file_hash = uploaded_file.get_hash()
276275
# Perform authentication and balance checks
@@ -297,7 +296,11 @@ async def _check_and_add_file(
297296
)
298297

299298
with session_factory() as session:
300-
_verify_user_balance(session=session, content=message_content)
299+
_verify_user_balance(
300+
session=session,
301+
content=message_content,
302+
max_unauthenticated_upload_file_size=max_unauthenticated_upload_file_size,
303+
)
301304
else:
302305
message_content = None
303306

@@ -394,6 +397,10 @@ async def storage_add_file(request: web.Request):
394397
signature_verifier = get_signature_verifier_from_request(request)
395398
config = get_config_from_request(request)
396399
grace_period = config.storage.grace_period.value
400+
max_file_size = config.storage.max_file_size.value
401+
max_unauthenticated_upload_file_size = (
402+
config.storage.max_unauthenticated_upload_file_size.value
403+
)
397404
metadata = None
398405
uploaded_file: Optional[UploadedFile] = None
399406

@@ -409,13 +416,13 @@ async def storage_add_file(request: web.Request):
409416
raise web.HTTPBadRequest(text="Invalid multipart structure")
410417

411418
if part.name == "file":
412-
uploaded_file = MultipartUploadedFile(part, MAX_FILE_SIZE)
419+
uploaded_file = MultipartUploadedFile(part, max_file_size)
413420
await uploaded_file.read_and_validate()
414421
elif part.name == "metadata":
415422
metadata = await part.read(decode=True)
416423
else:
417424
uploaded_file = RawUploadedFile(
418-
request=request, max_size=MAX_UNAUTHENTICATED_UPLOAD_FILE_SIZE
425+
request=request, max_size=max_unauthenticated_upload_file_size
419426
)
420427
await uploaded_file.read_and_validate()
421428

@@ -425,7 +432,7 @@ async def storage_add_file(request: web.Request):
425432
)
426433

427434
max_upload_size = (
428-
MAX_UNAUTHENTICATED_UPLOAD_FILE_SIZE if not metadata else MAX_FILE_SIZE
435+
max_unauthenticated_upload_file_size if not metadata else max_file_size
429436
)
430437
if uploaded_file.size > max_upload_size:
431438
raise web.HTTPRequestEntityTooLarge(
@@ -460,6 +467,7 @@ async def storage_add_file(request: web.Request):
460467
message=message,
461468
uploaded_file=uploaded_file,
462469
grace_period=grace_period,
470+
max_unauthenticated_upload_file_size=max_unauthenticated_upload_file_size,
463471
)
464472
if message:
465473
broadcast_status = await broadcast_and_process_message(
@@ -475,7 +483,9 @@ async def storage_add_file(request: web.Request):
475483
await uploaded_file.cleanup()
476484

477485

478-
def assert_file_is_downloadable(session: DbSession, file_hash: str) -> None:
486+
def assert_file_is_downloadable(
487+
session: DbSession, file_hash: str, max_file_size: int
488+
) -> None:
479489
"""
480490
Check if the file is on the aleph.im network and can be downloaded from the API.
481491
This filters out requests for files outside the network / nonexistent files.
@@ -484,9 +494,9 @@ def assert_file_is_downloadable(session: DbSession, file_hash: str) -> None:
484494
if not file_metadata:
485495
raise web.HTTPNotFound(text="Not found")
486496

487-
if file_metadata.size > MAX_FILE_SIZE:
497+
if file_metadata.size > max_file_size:
488498
raise web.HTTPRequestEntityTooLarge(
489-
max_size=MAX_FILE_SIZE, actual_size=file_metadata.size
499+
max_size=max_file_size, actual_size=file_metadata.size
490500
)
491501

492502

@@ -529,9 +539,14 @@ async def get_hash(request):
529539
logger.warning(e.args[0])
530540
raise web.HTTPBadRequest(text="Invalid hash provided")
531541

542+
config = get_config_from_request(request)
543+
max_file_size = config.storage.max_file_size.value
544+
532545
session_factory = get_session_factory_from_request(request)
533546
with session_factory() as session:
534-
assert_file_is_downloadable(session=session, file_hash=file_hash)
547+
assert_file_is_downloadable(
548+
session=session, file_hash=file_hash, max_file_size=max_file_size
549+
)
535550

536551
storage_service = get_storage_service_from_request(request)
537552

0 commit comments

Comments
 (0)