Skip to content

Commit 0c0cfea

Browse files
authored
Fix: Could not generate signed messages outside HTTP client (#120)
Problem: Developers could not generate signed messages without using the AlephHttpClient. Solution: Move and rename the method on the abstract class; Move the utility that generates sha256 hashes to utils.py.
1 parent 28ada83 commit 0c0cfea

File tree

3 files changed

+75
-54
lines changed

3 files changed

+75
-54
lines changed

src/aleph/sdk/client/abstract.py

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# An interface for all clients to implement.
2-
2+
import json
33
import logging
4+
import time
45
from abc import ABC, abstractmethod
56
from pathlib import Path
67
from typing import (
@@ -18,19 +19,25 @@
1819

1920
from aleph_message.models import (
2021
AlephMessage,
22+
ItemType,
2123
MessagesResponse,
2224
MessageType,
2325
Payment,
2426
PostMessage,
27+
parse_message,
2528
)
2629
from aleph_message.models.execution.environment import HypervisorType
2730
from aleph_message.models.execution.program import Encoding
2831
from aleph_message.status import MessageStatus
2932

33+
from aleph.sdk.conf import settings
34+
from aleph.sdk.types import Account
35+
from aleph.sdk.utils import extended_json_encoder
36+
3037
from ..query.filters import MessageFilter, PostFilter
3138
from ..query.responses import PostsResponse
3239
from ..types import GenericMessage, StorageEnum
33-
from ..utils import Writable
40+
from ..utils import Writable, compute_sha256
3441

3542
DEFAULT_PAGE_SIZE = 200
3643

@@ -231,6 +238,8 @@ def watch_messages(
231238

232239

233240
class AuthenticatedAlephClient(AlephClient):
241+
account: Account
242+
234243
@abstractmethod
235244
async def create_post(
236245
self,
@@ -444,6 +453,62 @@ async def forget(
444453
"Did you mean to import `AuthenticatedAlephHttpClient`?"
445454
)
446455

456+
async def generate_signed_message(
457+
self,
458+
message_type: MessageType,
459+
content: Dict[str, Any],
460+
channel: Optional[str],
461+
allow_inlining: bool = True,
462+
storage_engine: StorageEnum = StorageEnum.storage,
463+
) -> AlephMessage:
464+
"""Generate a signed aleph.im message ready to be sent to the network.
465+
466+
If the content is not inlined, it will be pushed to the storage engine via the API of a Core Channel Node.
467+
468+
:param message_type: Type of the message (PostMessage, ...)
469+
:param content: User-defined content of the message
470+
:param channel: Channel to use (Default: "TEST")
471+
:param allow_inlining: Whether to allow inlining the content of the message (Default: True)
472+
:param storage_engine: Storage engine to use (Default: "storage")
473+
"""
474+
475+
message_dict: Dict[str, Any] = {
476+
"sender": self.account.get_address(),
477+
"chain": self.account.CHAIN,
478+
"type": message_type,
479+
"content": content,
480+
"time": time.time(),
481+
"channel": channel,
482+
}
483+
484+
# Use the Pydantic encoder to serialize types like UUID, datetimes, etc.
485+
item_content: str = json.dumps(
486+
content, separators=(",", ":"), default=extended_json_encoder
487+
)
488+
489+
if allow_inlining and (len(item_content) < settings.MAX_INLINE_SIZE):
490+
message_dict["item_content"] = item_content
491+
message_dict["item_hash"] = compute_sha256(item_content)
492+
message_dict["item_type"] = ItemType.inline
493+
else:
494+
if storage_engine == StorageEnum.ipfs:
495+
message_dict["item_hash"] = await self.ipfs_push(
496+
content=content,
497+
)
498+
message_dict["item_type"] = ItemType.ipfs
499+
else: # storage
500+
assert storage_engine == StorageEnum.storage
501+
message_dict["item_hash"] = await self.storage_push(
502+
content=content,
503+
)
504+
message_dict["item_type"] = ItemType.storage
505+
506+
message_dict = await self.account.sign_message(message_dict)
507+
return parse_message(message_dict)
508+
509+
# Alias for backwards compatibility
510+
_prepare_aleph_message = generate_signed_message
511+
447512
@abstractmethod
448513
async def submit(
449514
self,

src/aleph/sdk/client/authenticated_http.py

Lines changed: 2 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from typing import Any, Dict, List, Mapping, NoReturn, Optional, Tuple, Union
88

99
import aiohttp
10-
from aleph_message import parse_message
1110
from aleph_message.models import (
1211
AggregateContent,
1312
AggregateMessage,
@@ -17,7 +16,6 @@
1716
ForgetMessage,
1817
InstanceContent,
1918
InstanceMessage,
20-
ItemType,
2119
MessageType,
2220
PostContent,
2321
PostMessage,
@@ -622,54 +620,6 @@ async def forget(
622620
)
623621
return message, status
624622

625-
@staticmethod
626-
def compute_sha256(s: str) -> str:
627-
h = hashlib.sha256()
628-
h.update(s.encode("utf-8"))
629-
return h.hexdigest()
630-
631-
async def _prepare_aleph_message(
632-
self,
633-
message_type: MessageType,
634-
content: Dict[str, Any],
635-
channel: Optional[str],
636-
allow_inlining: bool = True,
637-
storage_engine: StorageEnum = StorageEnum.storage,
638-
) -> AlephMessage:
639-
message_dict: Dict[str, Any] = {
640-
"sender": self.account.get_address(),
641-
"chain": self.account.CHAIN,
642-
"type": message_type,
643-
"content": content,
644-
"time": time.time(),
645-
"channel": channel,
646-
}
647-
648-
# Use the Pydantic encoder to serialize types like UUID, datetimes, etc.
649-
item_content: str = json.dumps(
650-
content, separators=(",", ":"), default=extended_json_encoder
651-
)
652-
653-
if allow_inlining and (len(item_content) < settings.MAX_INLINE_SIZE):
654-
message_dict["item_content"] = item_content
655-
message_dict["item_hash"] = self.compute_sha256(item_content)
656-
message_dict["item_type"] = ItemType.inline
657-
else:
658-
if storage_engine == StorageEnum.ipfs:
659-
message_dict["item_hash"] = await self.ipfs_push(
660-
content=content,
661-
)
662-
message_dict["item_type"] = ItemType.ipfs
663-
else: # storage
664-
assert storage_engine == StorageEnum.storage
665-
message_dict["item_hash"] = await self.storage_push(
666-
content=content,
667-
)
668-
message_dict["item_type"] = ItemType.storage
669-
670-
message_dict = await self.account.sign_message(message_dict)
671-
return parse_message(message_dict)
672-
673623
async def submit(
674624
self,
675625
content: Dict[str, Any],
@@ -680,7 +630,7 @@ async def submit(
680630
sync: bool = False,
681631
raise_on_rejected: bool = True,
682632
) -> Tuple[AlephMessage, MessageStatus, Optional[Dict[str, Any]]]:
683-
message = await self._prepare_aleph_message(
633+
message = await self.generate_signed_message(
684634
message_type=message_type,
685635
content=content,
686636
channel=channel,
@@ -703,7 +653,7 @@ async def _storage_push_file_with_message(
703653
data = aiohttp.FormData()
704654

705655
# Prepare the STORE message
706-
message = await self._prepare_aleph_message(
656+
message = await self.generate_signed_message(
707657
message_type=MessageType.store,
708658
content=store_content.dict(exclude_none=True),
709659
channel=channel,

src/aleph/sdk/utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import errno
2+
import hashlib
23
import logging
34
import os
45
from datetime import date, datetime, time
@@ -178,3 +179,8 @@ def parse_volume(volume_dict: Union[Mapping, MachineVolume]) -> MachineVolume:
178179
continue
179180
else:
180181
raise ValueError(f"Could not parse volume: {volume_dict}")
182+
183+
184+
def compute_sha256(s: str) -> str:
185+
"""Compute the SHA256 hash of a string."""
186+
return hashlib.sha256(s.encode()).hexdigest()

0 commit comments

Comments
 (0)