Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

python: block types and wrapper #1346

Merged
merged 31 commits into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2e3a55e
block types and wrapper
Alex6323 Sep 28, 2023
ac62da5
format
Alex6323 Sep 28, 2023
b1869e7
fix examples
Alex6323 Sep 28, 2023
4d6f7e8
nit
Alex6323 Sep 28, 2023
5b160f8
review 1
Alex6323 Sep 28, 2023
d0cc109
review 2
Alex6323 Sep 29, 2023
c2aeaad
review 3
Alex6323 Sep 29, 2023
ebc15e8
Merge branch '2.0' into python/block-changes
Alex6323 Sep 29, 2023
0d04825
fix wrapper deserialization, temp. disable some tests
Alex6323 Sep 29, 2023
501d60f
Merge branch '2.0' into python/block-changes
Alex6323 Sep 29, 2023
3fc8393
update BlockMetadata type
Alex6323 Sep 29, 2023
8a4fc2d
fix some tests
Alex6323 Sep 29, 2023
0220d4e
test nits
Alex6323 Sep 29, 2023
b0ccc9f
remove union for sig
Alex6323 Sep 29, 2023
20aa2f0
doc fix
Alex6323 Sep 29, 2023
a044634
lint sdk
Alex6323 Sep 29, 2023
29d9f6d
full circle
Alex6323 Sep 29, 2023
0f2b64a
review 4
Alex6323 Sep 29, 2023
15f7168
review 5
Alex6323 Sep 29, 2023
7afa497
review 6
Alex6323 Sep 29, 2023
9c76bb4
Merge branch '2.0' into python/block-changes
Alex6323 Oct 2, 2023
e3c6cf0
review 7
Alex6323 Oct 2, 2023
573eae9
did I mess up?
Alex6323 Oct 2, 2023
0cb5955
not ignore some tests
Alex6323 Oct 3, 2023
3dffaa6
Merge branch '2.0' into python/block-changes
Alex6323 Oct 3, 2023
177f07c
remove import
Alex6323 Oct 3, 2023
fc249b7
fix block tests
Alex6323 Oct 3, 2023
9a9f7fe
refer to issue in ignored tests
Alex6323 Oct 3, 2023
5e543cd
fix ci
Alex6323 Oct 4, 2023
55ce115
re-enable python tests in CI
Alex6323 Oct 4, 2023
09cfa59
long live the union (type alias)
Alex6323 Oct 4, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions bindings/python/examples/client/08_data_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import os
from dataclasses import asdict
from dotenv import load_dotenv
from iota_sdk import Client, utf8_to_hex, hex_to_utf8, TaggedDataPayload
from iota_sdk import BasicBlock, Client, utf8_to_hex, hex_to_utf8, TaggedDataPayload

load_dotenv()

Expand All @@ -13,17 +13,21 @@
client = Client(nodes=[node_url])

# Create and post a block with a tagged data payload
block = client.submit_payload(
block_id = client.submit_payload(
TaggedDataPayload(
utf8_to_hex("tag"),
utf8_to_hex("data")))
utf8_to_hex("data")))[0]

print(f'Data block sent: {os.environ["EXPLORER_URL"]}/block/{block[0]}')
print(f'Data block sent: {os.environ["EXPLORER_URL"]}/block/{block_id}')

block = client.get_block_data(block[0])
print(f'Block data: {json.dumps(asdict(block), indent=4)}')
block = client.get_block_data(block_id).block

payload = block.payload
if isinstance(block, BasicBlock):
print(f'Block data: {json.dumps(asdict(block), indent=4)}')

if payload and 'data' in payload and payload['data']:
print(f'Decoded data: { hex_to_utf8(payload["data"]) }')
payload = block.payload

if payload and 'data' in payload and payload['data']:
print(f'Decoded data: { hex_to_utf8(payload["data"]) }')
else:
raise ValueError("block must be an instance of BasicBlock")
23 changes: 13 additions & 10 deletions bindings/python/examples/client/submit_and_read_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# Make sure you have first installed it with `pip install iota_sdk`
import os
from dotenv import load_dotenv
from iota_sdk import Client, hex_to_utf8, utf8_to_hex, TaggedDataPayload
from iota_sdk import BasicBlock, Client, hex_to_utf8, utf8_to_hex, TaggedDataPayload

load_dotenv()

Expand Down Expand Up @@ -82,15 +82,18 @@
metadata = client.get_block_metadata(block_id)

# Get the whole block
block = client.get_block_data(block_id)
payload_out = block.payload
tag_hex_out = block.payload.tag
message_hex_out = block.payload.data

# Unpackage the payload (from hex to text)
message_out = hex_to_utf8(message_hex_out)
print('\nYour message, read from the Shimmer network:')
print(f' {message_out}')
block = client.get_block_data(block_id).block
if isinstance(block, BasicBlock):
payload_out = block.payload
tag_hex_out = block.payload.tag
message_hex_out = block.payload.data

# Unpackage the payload (from hex to text)
message_out = hex_to_utf8(message_hex_out)
print('\nYour message, read from the Shimmer network:')
print(f' {message_out}')
else:
raise ValueError("block must be an instance of BasicBlock")

# Or see the message online, with the testnet explorer.
print(
Expand Down
5 changes: 4 additions & 1 deletion bindings/python/iota_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
from .prefix_hex import *
from .types.address import *
from .types.balance import *
from .types.block import *
from .types.block.basic import *
from .types.block.block import *
from .types.block.wrapper import *
from .types.block.validation import *
from .types.block_builder_options import *
from .types.burn import *
from .types.client_options import *
Expand Down
6 changes: 3 additions & 3 deletions bindings/python/iota_sdk/client/_high_level_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from typing import List, Optional
from dataclasses import dataclass
from abc import ABCMeta, abstractmethod
from iota_sdk.types.block import Block
from iota_sdk.types.block.wrapper import BlockWrapper
from iota_sdk.types.common import CoinType, HexStr, json
from iota_sdk.types.output import OutputWithMetadata
from iota_sdk.types.output_id import OutputId
Expand Down Expand Up @@ -94,7 +94,7 @@ def get_outputs_ignore_errors(
})
return [OutputWithMetadata.from_dict(o) for o in outputs]

def find_blocks(self, block_ids: List[HexStr]) -> List[Block]:
def find_blocks(self, block_ids: List[HexStr]) -> List[BlockWrapper]:
"""Find all blocks by provided block IDs.

Args:
Expand All @@ -106,7 +106,7 @@ def find_blocks(self, block_ids: List[HexStr]) -> List[Block]:
blocks = self._call_method('findBlocks', {
'blockIds': block_ids
})
return [Block.from_dict(block) for block in blocks]
return [BlockWrapper.from_dict(block) for block in blocks]

def find_inputs(self, addresses: List[str], amount: int):
"""Function to find inputs from addresses for a provided amount(useful for offline signing).
Expand Down
13 changes: 7 additions & 6 deletions bindings/python/iota_sdk/client/_node_core_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
from abc import ABCMeta, abstractmethod
from dacite import from_dict

from iota_sdk.types.block import Block, BlockMetadata
from iota_sdk.types.block.wrapper import BlockWrapper
from iota_sdk.types.block.block import BlockMetadata
from iota_sdk.types.common import HexStr
from iota_sdk.types.node_info import NodeInfo, NodeInfoWrapper
from iota_sdk.types.output import OutputWithMetadata, OutputMetadata
Expand Down Expand Up @@ -57,7 +58,7 @@ def get_tips(self) -> List[HexStr]:
"""
return self._call_method('getTips')

def post_block(self, block: Block) -> HexStr:
def post_block(self, block: BlockWrapper) -> HexStr:
"""Post a block.

Args:
Expand All @@ -70,10 +71,10 @@ def post_block(self, block: Block) -> HexStr:
'block': block.__dict__
})

def get_block_data(self, block_id: HexStr) -> Block:
def get_block_data(self, block_id: HexStr) -> BlockWrapper:
thibault-martinez marked this conversation as resolved.
Show resolved Hide resolved
"""Get the block corresponding to the given block id.
"""
return Block.from_dict(self._call_method('getBlock', {
return BlockWrapper.from_dict(self._call_method('getBlock', {
'blockId': block_id
}))

Expand Down Expand Up @@ -127,13 +128,13 @@ def get_output_metadata(
'outputId': output_id_str
}))

def get_included_block(self, transaction_id: HexStr) -> Block:
def get_included_block(self, transaction_id: HexStr) -> BlockWrapper:
"""Returns the included block of the given transaction.

Returns:
The included block.
"""
return Block.from_dict(self._call_method('getIncludedBlock', {
return BlockWrapper.from_dict(self._call_method('getIncludedBlock', {
'transactionId': transaction_id
}))

Expand Down
7 changes: 4 additions & 3 deletions bindings/python/iota_sdk/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from iota_sdk.client._high_level_api import HighLevelAPI
from iota_sdk.client._utils import ClientUtils
from iota_sdk.secret_manager.secret_manager import LedgerNanoSecretManager, MnemonicSecretManager, StrongholdSecretManager, SeedSecretManager
from iota_sdk.types.block import Block
from iota_sdk.types.block.wrapper import BlockWrapper
from iota_sdk.types.common import HexStr, Node
from iota_sdk.types.feature import Feature
from iota_sdk.types.native_token import NativeToken
Expand Down Expand Up @@ -394,7 +394,8 @@ def sign_transaction(
'preparedTransactionData': prepared_transaction_data
}))

def submit_payload(self, payload: Payload) -> List[Union[HexStr, Block]]:
def submit_payload(
self, payload: Payload) -> List[Union[HexStr, BlockWrapper]]:
"""Submit a payload in a block.

Args:
Expand All @@ -406,7 +407,7 @@ def submit_payload(self, payload: Payload) -> List[Union[HexStr, Block]]:
result = self._call_method('postBlockPayload', {
'payload': payload.to_dict()
})
result[1] = Block.from_dict(result[1])
result[1] = BlockWrapper.from_dict(result[1])
return result

def listen_mqtt(self, topics: List[str], handler):
Expand Down
46 changes: 46 additions & 0 deletions bindings/python/iota_sdk/types/block/basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Copyright 2023 IOTA Stiftung
# SPDX-License-Identifier: Apache-2.0

from __future__ import annotations
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Union
from dacite import from_dict
from iota_sdk.types.block.block import Block, BlockType
from iota_sdk.types.common import HexStr, json
from iota_sdk.types.payload import TaggedDataPayload, TransactionPayload


@json
@dataclass
class BasicBlock(Block):
"""A `BasicBlock` is the most common type of block used to issue various kinds of payloads such as transactions
at the cost of burning Mana.
thibault-martinez marked this conversation as resolved.
Show resolved Hide resolved

Attributes:
strong_parents: Blocks that are strongly directly approved.
weak_parents: Blocks that are weakly directly approved.
shallow_like_parents: Blocks that are directly referenced to adjust opinion.
payload: The optional payload of this block.
burned_mana: The amount of Mana the Account identified by the IssuerId is at most willing to burn for this block.
"""
strong_parents: List[HexStr]
weak_parents: List[HexStr]
shallow_like_parents: List[HexStr]
payload: Optional[Union[TaggedDataPayload,
TransactionPayload]] = None
thibault-martinez marked this conversation as resolved.
Show resolved Hide resolved
burned_mana: str
Thoralf-M marked this conversation as resolved.
Show resolved Hide resolved
type: int = field(
default_factory=lambda: BlockType.Basic,
init=False)

@classmethod
def from_dict(cls, basic_block_dict: Dict) -> BasicBlock:
"""
The function `from_dict` takes a dictionary that contains the data needed to
create an instance of the `BasicBlock` class .

Returns:

An instance of the `BasicBlock` class .
"""
return from_dict(BasicBlock, basic_block_dict)
Thoralf-M marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,60 @@
# SPDX-License-Identifier: Apache-2.0

from __future__ import annotations
from enum import Enum
from enum import Enum, IntEnum
from dataclasses import dataclass
from typing import List, Optional, Union, Dict
from dacite import from_dict
from typing import List, Optional
from iota_sdk.types.common import HexStr, json
from iota_sdk.types.payload import TaggedDataPayload, TransactionPayload
from iota_sdk.utils import Utils


class BlockType(IntEnum):
"""Block types.

Attributes:
Basic (0): A Basic Block.
Validation (1): A Validation Block.
"""
Basic = 0
Validation = 1


@json
@dataclass
class Block:
"""Represent the object that nodes gossip around the network.
"""Base class for blocks.
"""
type: int


@json
@dataclass
class BlockMetadata:
thibault-martinez marked this conversation as resolved.
Show resolved Hide resolved
"""Block Metadata.

Attributes:
protocol_version: The protocol version with which this block was issued.
block_id: The id of the block.
strong_parents: Blocks that are strongly directly approved.
weak_parents: Blocks that are weakly directly approved.
shallow_like_parents: Blocks that are directly referenced to adjust opinion.
burned_mana: The amount of Mana the Account identified by the IssuerId is at most willing to burn for this block.
payload: The optional payload of this block.
is_solid: Whether the block is solid.
DaughterOfMars marked this conversation as resolved.
Show resolved Hide resolved
referenced_by_milestone_index: The milestone index referencing the block.
milestone_index: The milestone index if the block contains a milestone payload.
ledger_inclusion_state: The ledger inclusion state of the block.
conflict_reason: The optional conflict reason of the block.
should_promote: Whether the block should be promoted.
should_reattach: Whether the block should be reattached.
"""

protocol_version: int
block_id: HexStr
strong_parents: List[HexStr]
weak_parents: List[HexStr]
shallow_like_parents: List[HexStr]
burned_mana: str
payload: Optional[Union[TaggedDataPayload,
TransactionPayload]] = None

@classmethod
def from_dict(cls, block_dict: Dict) -> Block:
"""
The function `from_dict` takes a dictionary that contains the data needed to
create an instance of the `Block` class.

Returns:

An instance of the `Block` class.
"""
return from_dict(Block, block_dict)

def id(self) -> HexStr:
"""Rreturns the block ID as a hexadecimal string.
"""
return Utils.block_id(self)
is_solid: bool
referenced_by_milestone_index: Optional[int] = None
milestone_index: Optional[int] = None
ledger_inclusion_state: Optional[LedgerInclusionState] = None
conflict_reason: Optional[TransactionFailureReason] = None
should_promote: Optional[bool] = None
should_reattach: Optional[bool] = None


class LedgerInclusionState(str, Enum):
Expand Down Expand Up @@ -139,34 +146,3 @@ def __str__(self):
21: "Failed to claim delegation reward.",
255: "The semantic validation failed for a reason not covered by the previous variants."
}[self.value]


@json
@dataclass
class BlockMetadata:
"""Block Metadata.

Attributes:
block_id: The id of the block.
strong_parents: Blocks that are strongly directly approved.
weak_parents: Blocks that are weakly directly approved.
shallow_like_parents: Blocks that are directly referenced to adjust opinion.
is_solid: Whether the block is solid.
referenced_by_milestone_index: The milestone index referencing the block.
milestone_index: The milestone index if the block contains a milestone payload.
ledger_inclusion_state: The ledger inclusion state of the block.
conflict_reason: The optional conflict reason of the block.
should_promote: Whether the block should be promoted.
should_reattach: Whether the block should be reattached.
"""
block_id: HexStr
strong_parents: List[HexStr]
weak_parents: List[HexStr]
shallow_like_parents: List[HexStr]
is_solid: bool
referenced_by_milestone_index: Optional[int] = None
milestone_index: Optional[int] = None
ledger_inclusion_state: Optional[LedgerInclusionState] = None
conflict_reason: Optional[TransactionFailureReason] = None
should_promote: Optional[bool] = None
should_reattach: Optional[bool] = None
Loading
Loading