Skip to content

Commit

Permalink
Fix: Could not generate signed messages outside HTTP client (#120)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
hoh authored Apr 29, 2024
1 parent 28ada83 commit 0c0cfea
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 54 deletions.
69 changes: 67 additions & 2 deletions src/aleph/sdk/client/abstract.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# An interface for all clients to implement.

import json
import logging
import time
from abc import ABC, abstractmethod
from pathlib import Path
from typing import (
Expand All @@ -18,19 +19,25 @@

from aleph_message.models import (
AlephMessage,
ItemType,
MessagesResponse,
MessageType,
Payment,
PostMessage,
parse_message,
)
from aleph_message.models.execution.environment import HypervisorType
from aleph_message.models.execution.program import Encoding
from aleph_message.status import MessageStatus

from aleph.sdk.conf import settings
from aleph.sdk.types import Account
from aleph.sdk.utils import extended_json_encoder

from ..query.filters import MessageFilter, PostFilter
from ..query.responses import PostsResponse
from ..types import GenericMessage, StorageEnum
from ..utils import Writable
from ..utils import Writable, compute_sha256

DEFAULT_PAGE_SIZE = 200

Expand Down Expand Up @@ -231,6 +238,8 @@ def watch_messages(


class AuthenticatedAlephClient(AlephClient):
account: Account

@abstractmethod
async def create_post(
self,
Expand Down Expand Up @@ -444,6 +453,62 @@ async def forget(
"Did you mean to import `AuthenticatedAlephHttpClient`?"
)

async def generate_signed_message(
self,
message_type: MessageType,
content: Dict[str, Any],
channel: Optional[str],
allow_inlining: bool = True,
storage_engine: StorageEnum = StorageEnum.storage,
) -> AlephMessage:
"""Generate a signed aleph.im message ready to be sent to the network.
If the content is not inlined, it will be pushed to the storage engine via the API of a Core Channel Node.
:param message_type: Type of the message (PostMessage, ...)
:param content: User-defined content of the message
:param channel: Channel to use (Default: "TEST")
:param allow_inlining: Whether to allow inlining the content of the message (Default: True)
:param storage_engine: Storage engine to use (Default: "storage")
"""

message_dict: Dict[str, Any] = {
"sender": self.account.get_address(),
"chain": self.account.CHAIN,
"type": message_type,
"content": content,
"time": time.time(),
"channel": channel,
}

# Use the Pydantic encoder to serialize types like UUID, datetimes, etc.
item_content: str = json.dumps(
content, separators=(",", ":"), default=extended_json_encoder
)

if allow_inlining and (len(item_content) < settings.MAX_INLINE_SIZE):
message_dict["item_content"] = item_content
message_dict["item_hash"] = compute_sha256(item_content)
message_dict["item_type"] = ItemType.inline
else:
if storage_engine == StorageEnum.ipfs:
message_dict["item_hash"] = await self.ipfs_push(
content=content,
)
message_dict["item_type"] = ItemType.ipfs
else: # storage
assert storage_engine == StorageEnum.storage
message_dict["item_hash"] = await self.storage_push(
content=content,
)
message_dict["item_type"] = ItemType.storage

message_dict = await self.account.sign_message(message_dict)
return parse_message(message_dict)

# Alias for backwards compatibility
_prepare_aleph_message = generate_signed_message

@abstractmethod
async def submit(
self,
Expand Down
54 changes: 2 additions & 52 deletions src/aleph/sdk/client/authenticated_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from typing import Any, Dict, List, Mapping, NoReturn, Optional, Tuple, Union

import aiohttp
from aleph_message import parse_message
from aleph_message.models import (
AggregateContent,
AggregateMessage,
Expand All @@ -17,7 +16,6 @@
ForgetMessage,
InstanceContent,
InstanceMessage,
ItemType,
MessageType,
PostContent,
PostMessage,
Expand Down Expand Up @@ -622,54 +620,6 @@ async def forget(
)
return message, status

@staticmethod
def compute_sha256(s: str) -> str:
h = hashlib.sha256()
h.update(s.encode("utf-8"))
return h.hexdigest()

async def _prepare_aleph_message(
self,
message_type: MessageType,
content: Dict[str, Any],
channel: Optional[str],
allow_inlining: bool = True,
storage_engine: StorageEnum = StorageEnum.storage,
) -> AlephMessage:
message_dict: Dict[str, Any] = {
"sender": self.account.get_address(),
"chain": self.account.CHAIN,
"type": message_type,
"content": content,
"time": time.time(),
"channel": channel,
}

# Use the Pydantic encoder to serialize types like UUID, datetimes, etc.
item_content: str = json.dumps(
content, separators=(",", ":"), default=extended_json_encoder
)

if allow_inlining and (len(item_content) < settings.MAX_INLINE_SIZE):
message_dict["item_content"] = item_content
message_dict["item_hash"] = self.compute_sha256(item_content)
message_dict["item_type"] = ItemType.inline
else:
if storage_engine == StorageEnum.ipfs:
message_dict["item_hash"] = await self.ipfs_push(
content=content,
)
message_dict["item_type"] = ItemType.ipfs
else: # storage
assert storage_engine == StorageEnum.storage
message_dict["item_hash"] = await self.storage_push(
content=content,
)
message_dict["item_type"] = ItemType.storage

message_dict = await self.account.sign_message(message_dict)
return parse_message(message_dict)

async def submit(
self,
content: Dict[str, Any],
Expand All @@ -680,7 +630,7 @@ async def submit(
sync: bool = False,
raise_on_rejected: bool = True,
) -> Tuple[AlephMessage, MessageStatus, Optional[Dict[str, Any]]]:
message = await self._prepare_aleph_message(
message = await self.generate_signed_message(
message_type=message_type,
content=content,
channel=channel,
Expand All @@ -703,7 +653,7 @@ async def _storage_push_file_with_message(
data = aiohttp.FormData()

# Prepare the STORE message
message = await self._prepare_aleph_message(
message = await self.generate_signed_message(
message_type=MessageType.store,
content=store_content.dict(exclude_none=True),
channel=channel,
Expand Down
6 changes: 6 additions & 0 deletions src/aleph/sdk/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import errno
import hashlib
import logging
import os
from datetime import date, datetime, time
Expand Down Expand Up @@ -178,3 +179,8 @@ def parse_volume(volume_dict: Union[Mapping, MachineVolume]) -> MachineVolume:
continue
else:
raise ValueError(f"Could not parse volume: {volume_dict}")


def compute_sha256(s: str) -> str:
"""Compute the SHA256 hash of a string."""
return hashlib.sha256(s.encode()).hexdigest()

0 comments on commit 0c0cfea

Please sign in to comment.