Skip to content

Commit

Permalink
27th Dec Update. All tests done for Bee class. All tests passed
Browse files Browse the repository at this point in the history
  • Loading branch information
Aviksaikat committed Dec 27, 2023
1 parent 18aa6d1 commit 7211a66
Show file tree
Hide file tree
Showing 10 changed files with 100 additions and 39 deletions.
15 changes: 9 additions & 6 deletions src/bee_py/bee.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ def upload_data(
assert_upload_options(options)
if request_options:
assert_request_options(request_options)
return bytes_api.upload(request_options, data, postage_batch_id, options)
return bytes_api.upload(self.__get_request_options_for_call(request_options), data, postage_batch_id, options)

def download_data(self, reference: ReferenceOrENS, options: Optional[BeeRequestOptions] = None) -> Data:
"""
Expand Down Expand Up @@ -1262,7 +1262,7 @@ def make_feed_reader(
def make_feed_writer(
self,
feed_type: Union[FeedType, str],
topic: Union[bytes, str],
topic: Union[Topic, bytes, str],
signer: Union[Signer, bytes, str],
options: Optional[BeeRequestOptions] = None,
) -> FeedWriter:
Expand Down Expand Up @@ -1301,6 +1301,7 @@ def set_json_feed(
postage_batch_id: Union[str, BatchId],
topic: str,
data,
signer: Union[AddressType, str],
options: Optional[Union[JsonFeedOptions, dict]] = None,
request_options: Optional[BeeRequestOptions] = None,
) -> Reference:
Expand Down Expand Up @@ -1328,20 +1329,20 @@ def set_json_feed(
if isinstance(options, dict):
options = JsonFeedOptions.model_validate(options)

if options.Type:
if options and options.Type:
feed_type = options.Type
else:
feed_type = DEFAULT_FEED_TYPE

writer = self.make_feed_writer(feed_type, hashed_topic, options.signer, request_options)
writer = self.make_feed_writer(feed_type, hashed_topic, signer, request_options)

return json_api.set_json_data(self, writer, postage_batch_id, data, options, request_options)

def get_json_feed(
self,
topic: Union[Topic, bytes, str],
options: Optional[Union[JsonFeedOptions, dict]] = None,
):
) -> dict:
"""
High-level function that allows you to easily get data from feed.
Returned data are parsed using json.loads().
Expand Down Expand Up @@ -1370,12 +1371,14 @@ def get_json_feed(
"""

assert_request_options(options, "JsonFeedOptions")
if not options:
options = {}
if isinstance(options, dict):
options = JsonFeedOptions.model_validate(options)

hashed_topic = self.make_feed_topic(topic)

if options.Type:
if options and options.Type:
feed_type = options.Type
else:
feed_type = DEFAULT_FEED_TYPE
Expand Down
18 changes: 12 additions & 6 deletions src/bee_py/chunk/signer.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from hashlib import sha3_256
from typing import Optional, Union

import eth_keys # type: ignore
from ape.managers.accounts import AccountAPI
from ape.types import AddressType
from ape.types.signatures import MessageSignature, recover_signer
from ape.types.signatures import MessageSignature
from ecdsa import SECP256k1, VerifyingKey # type: ignore
from eth_account.messages import SignableMessage, encode_defunct
from eth_keys import keys
from eth_pydantic_types import HexBytes
Expand All @@ -16,7 +18,6 @@
UNCOMPRESSED_RECOVERY_ID = 27


# * Don't need this as we are using ethereum foundation maintained modules
def hash_with_ethereum_prefix(data: Union[bytes, bytearray]) -> bytes:
"""
Calculates the Keccak-256 hash of the provided data, prefixed with the Ethereum signed message prefix.
Expand Down Expand Up @@ -99,18 +100,23 @@ def public_key_to_address(pub_key: Union[str, bytes, eth_keys.datatypes.PublicKe
return HexBytes(address)


def recover_address(message: SignableMessage, signature: MessageSignature) -> AddressType:
def recover_address(signature: bytes, digest: bytes) -> AddressType:
"""
Recovers the Ethereum address from a given signature and message digest.
This function can be used to verify the authenticity of a message by comparing
the recovered address with the actual address of the signer.
Args:
message (SignableMessage): The signature generated by the signer.
signature (MessageSignature): The message digest of the data to be verified.
signature (bytes): The signature generated by the signer.
digest (bytes): The message digest of the data to be verified.
Returns:
AddressType: The recovered Ethereum address.
"""
return recover_signer(message, signature)
curve = SECP256k1
hash_value = hash_with_ethereum_prefix(digest)

vk = VerifyingKey.from_public_key_recovery_with_digest(signature[:64], hash_value, curve, sha3_256)

return public_key_to_address(vk[0].to_string()).hex()
10 changes: 5 additions & 5 deletions src/bee_py/chunk/soc.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,27 +108,27 @@ def make_single_owner_chunk_from_data(data: Union[Data, bytes], address: Address
data = data.data
owner_address = recover_chunk_owner(data)
identifier = bytes_at_offset(data, SOC_IDENTIFIER_OFFSET, IDENTIFIER_SIZE)
soc_address = keccak256_hash(identifier, owner_address)
soc_address = keccak256_hash(identifier, hex_to_bytes(owner_address[2:]))

if bytes_equal(address, soc_address):
msg = "SOC Data does not match given address!"
raise BeeError(msg)

def signature():
def signature() -> bytes:
return bytes_at_offset(data, SOC_SIGNATURE_OFFSET, SIGNATURE_SIZE)

def span():
def span() -> bytes:
return bytes_at_offset(data, SOC_SPAN_OFFSET, SPAN_SIZE)

def payload():
def payload() -> bytes:
return flex_bytes_at_offset(data, SOC_PAYLOAD_OFFSET, MIN_PAYLOAD_SIZE, MAX_PAYLOAD_SIZE)

return SingleOwnerChunk(
data=data,
identifier=identifier,
signature=signature(),
span=span(),
payload=payload,
payload=payload(),
address=soc_address,
owner=owner_address,
)
Expand Down
2 changes: 1 addition & 1 deletion src/bee_py/feed/feed.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
from bee_py.modules.bytes import read_big_endian, write_big_endian
from bee_py.modules.chunk import download
from bee_py.modules.feed import fetch_latest_feed_update
from bee_py.types.type import FeedReader # Reference,; FeedType,
from bee_py.types.type import (
FEED_INDEX_HEX_LENGTH,
BatchId,
BeeRequestOptions,
FeedReader, # Reference,; FeedType,
FeedUpdate,
FeedUpdateOptions,
FeedWriter,
Expand Down
9 changes: 2 additions & 7 deletions src/bee_py/utils/eth.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,8 @@
from eth_account.messages import encode_defunct
from eth_pydantic_types import HexBytes
from eth_typing import ChecksumAddress as AddressType
from eth_utils import (
is_address,
is_checksum_address,
keccak,
to_checksum_address, # ValidationError,
to_normalized_address,
)
from eth_utils import to_checksum_address # ValidationError,
from eth_utils import is_address, is_checksum_address, keccak, to_normalized_address

from bee_py.Exceptions import AccountNotFoundError
from bee_py.utils.hex import hex_to_bytes, str_to_hex
Expand Down
12 changes: 7 additions & 5 deletions src/bee_py/utils/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,11 @@ def http(

# * Replace keys
options = {key_mapping.get(k, k): v for k, v in tmp_options.items()}
else:
tmp_options = options
key_mapping = {"base_url": "baseURL", "on_request": "onRequest"}
options = {key_mapping.get(k, k): v for k, v in tmp_options.items()}
else: # noqa: PLR5501
if options:
tmp_options = options
key_mapping = {"base_url": "baseURL", "on_request": "onRequest"}
options = {key_mapping.get(k, k): v for k, v in tmp_options.items()}

try:
intermediate_dict = always_merger.merge(config, options)
Expand All @@ -93,7 +94,8 @@ def maybe_run_on_request_hook(options: dict, request_config: dict) -> dict:
options: User defined settings.
request_config: The request configuration.
"""

if not options:
return {}
if options.get("onRequest"):
new_request_config = request_config.copy()
hook_result = {
Expand Down
2 changes: 2 additions & 0 deletions src/bee_py/utils/reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ def make_bytes_reference(reference: Union[Reference, bytes, str], offset: int =
Returns:
bytes: The byte array representation of the chunk reference.
"""
if isinstance(reference, Reference):
reference = str(reference)

if isinstance(reference, str):
if offset:
Expand Down
3 changes: 3 additions & 0 deletions src/bee_py/utils/type.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@ def assert_upload_options(value: Any, name: str = "UploadOptions") -> None:

if not isinstance(value, UploadOptions):
value = UploadOptions.model_validate(value)
elif isinstance(value, JsonFeedOptions):
value = JsonFeedOptions.model_validate(value)
return

options = value

Expand Down
58 changes: 58 additions & 0 deletions tests/integration/test_bee_integration.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# from unittest.mock import MagicMock, patch

# import pydantic
import json
import random
from datetime import datetime, timezone
from pathlib import Path
Expand Down Expand Up @@ -35,6 +36,9 @@
# * Global variables
ERR_TIMEOUT = 40_000
test_chunk_payload = bytes([1, 2, 3])
test_json_hash = "5a424d833847b8fe977c5c7ca205cd018f29c003c95db24f48e962d535aa3523"
test_json_payload = [{"some": "Json Object"}]
TOPIC = "some=very%nice#topic"


# * Helper Functions
Expand Down Expand Up @@ -411,3 +415,57 @@ def test_read_and_write(bee_url, signer, get_debug_postage, try_delete_chunk_fro
payload = soc.payload

assert payload == test_chunk_payload


def test_fail_not_signer_passed(bee_url, signer):
with pytest.raises(AttributeError):
bee_class = Bee(bee_url)
bee_class.make_soc_writer(signer)


@pytest.mark.timeout(ERR_TIMEOUT)
def test_set_JSON_to_feed(get_debug_postage, bee_url, signer): # noqa: N802
bee_class = Bee(bee_url, {"signer": signer})
bee_class.set_json_feed(get_debug_postage, TOPIC, test_json_payload, signer.address)

hashed_topic = bee_class.make_feed_topic(TOPIC)
reader = bee_class.make_feed_reader("sequence", hashed_topic, signer.address)
chunk_reference_response = reader.download()

assert chunk_reference_response.reference == test_json_hash

downloaded_data = bee_class.download_data(chunk_reference_response)

assert json.loads(downloaded_data.data.decode()) == test_json_payload


@pytest.mark.timeout(ERR_TIMEOUT)
def test_get_JSON_from_feed(get_debug_postage, bee_url, signer): # noqa: N802
bee_class = Bee(bee_url, {"signer": signer})
data = [{"some": {"other": "object"}}]

hashed_topic = bee_class.make_feed_topic(TOPIC)
writer = bee_class.make_feed_writer("sequence", hashed_topic, signer.address)
data_chunk_result = bee_class.upload_data(get_debug_postage, json.dumps(data))

writer.upload(get_debug_postage, data_chunk_result.reference)

fetched_data = bee_class.get_json_feed(TOPIC)

assert json.loads(fetched_data["data"]) == data


@pytest.mark.timeout(ERR_TIMEOUT)
def test_get_JSON_from_feed_with_address(get_debug_postage, bee_url, signer): # noqa: N802
bee_class = Bee(bee_url, {"signer": signer})
data = [{"some": {"other": "object"}}]

hashed_topic = bee_class.make_feed_topic(TOPIC)
writer = bee_class.make_feed_writer("sequence", hashed_topic, signer.address)
data_chunk_result = bee_class.upload_data(get_debug_postage, json.dumps(data))

writer.upload(get_debug_postage, data_chunk_result.reference)

fetched_data = bee_class.get_json_feed(TOPIC, {"address": signer.address})

assert json.loads(fetched_data["data"]) == data
10 changes: 1 addition & 9 deletions tests/unit/chunk/test_signer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from eth_pydantic_types import HexBytes
from eth_utils import is_same_address

from bee_py.chunk.signer import public_key_to_address, recover_address, sign
from bee_py.chunk.signer import public_key_to_address, sign

expected_signature_hex = "1bf05d437c1146b84b2cd410a25b70d300abdd54f4df17256472b2402849c07b5c240387a4ab5dfdc49c150997f435a7e66d0d001ba59b87600423a583f50ed0d0" # noqa: E501

Expand All @@ -24,14 +24,6 @@ def test_make_private_key_signer_bytes_data(signer):
assert signature.encode_vrs().hex() == expected_signature_hex # type: ignore


def test_recover_address_from_signature(signer):
msg = encode_defunct(text="Hi from bee_py")
signature = sign(data=msg, account=signer)
recovered_address = recover_address(msg, signature) # type: ignore

assert recovered_address == signer.address


def test_public_key_str_to_address(signer, public_key_str):
address = public_key_to_address(public_key_str)
assert address == HexBytes(signer.address)
Expand Down

0 comments on commit 7211a66

Please sign in to comment.