Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions hathor/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

""" Module that contains a Python API for interacting with a portion of the HTTP/WS APIs
"""

import asyncio
import random
import string
Expand All @@ -32,6 +31,7 @@
from hathor.mining import BlockTemplate, BlockTemplates
from hathor.pubsub import EventArguments, HathorEvents
from hathor.transaction import BaseTransaction, Block, TransactionMetadata
from hathor.transaction.base_transaction import get_cls_from_tx_version
from hathor.transaction.storage import TransactionStorage

logger = get_logger()
Expand Down Expand Up @@ -390,7 +390,7 @@ def create_tx_from_dict(data: dict[str, Any], update_hash: bool = False,
if storage:
data['storage'] = storage

cls = TxVersion(data['version']).get_cls()
cls = get_cls_from_tx_version(TxVersion(data['version']))
metadata = data.pop('metadata', None)
tx = cls(**data)
if update_hash:
Expand Down
4 changes: 2 additions & 2 deletions hathor/mining/block_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
"""
Module for abstractions around generating mining templates.
"""

from typing import Iterable, NamedTuple, Optional, TypeVar, cast

from hathor.transaction import BaseTransaction, Block, MergeMinedBlock
from hathor.transaction.base_transaction import get_cls_from_tx_version
from hathor.transaction.poa import PoaBlock
from hathor.transaction.storage import TransactionStorage
from hathor.util import Random
Expand All @@ -42,7 +42,7 @@ class BlockTemplate(NamedTuple):
def generate_minimally_valid_block(self) -> BaseTransaction:
""" Generates a block, without any extra information that is valid for this template. No random choices."""
from hathor.transaction import TxOutput, TxVersion
return TxVersion(min(self.versions)).get_cls()(
return get_cls_from_tx_version(TxVersion(min(self.versions)))(
timestamp=self.timestamp_min,
parents=self.parents[:] + sorted(self.parents_any)[:(3 - len(self.parents))],
outputs=[TxOutput(self.reward, b'')],
Expand Down
140 changes: 3 additions & 137 deletions hathor/nanocontracts/blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,140 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import annotations

from typing import TYPE_CHECKING, Any, final

from hathor.nanocontracts.blueprint_env import BlueprintEnvironment
from hathor.nanocontracts.exception import BlueprintSyntaxError
from hathor.nanocontracts.nc_types.utils import pretty_type
from hathor.nanocontracts.types import NC_FALLBACK_METHOD, NC_INITIALIZE_METHOD, NC_METHOD_TYPE_ATTR, NCMethodType

if TYPE_CHECKING:
from hathor.nanocontracts.nc_exec_logs import NCLogger

FORBIDDEN_NAMES = {
'syscall',
'log',
}

NC_FIELDS_ATTR: str = '__fields'


class _BlueprintBase(type):
"""Metaclass for blueprints.

This metaclass will modify the attributes and set Fields to them according to their types.
"""

def __new__(
cls: type[_BlueprintBase],
name: str,
bases: tuple[type, ...],
attrs: dict[str, Any],
/,
**kwargs: Any
) -> _BlueprintBase:
from hathor.nanocontracts.fields import make_field_for_type

# Initialize only subclasses of Blueprint.
parents = [b for b in bases if isinstance(b, _BlueprintBase)]
if not parents:
return super().__new__(cls, name, bases, attrs, **kwargs)

cls._validate_initialize_method(attrs)
cls._validate_fallback_method(attrs)
nc_fields = attrs.get('__annotations__', {})

# Check for forbidden names.
for field_name in nc_fields:
if field_name in FORBIDDEN_NAMES:
raise BlueprintSyntaxError(f'field name is forbidden: `{field_name}`')

if field_name.startswith('_'):
raise BlueprintSyntaxError(f'field name cannot start with underscore: `{field_name}`')

# Create the fields attribute with the type for each field.
attrs[NC_FIELDS_ATTR] = nc_fields

# Use an empty __slots__ to prevent storing any attributes directly on instances.
# The declared attributes are stored as fields on the class, so they still work despite the empty slots.
attrs['__slots__'] = tuple()

# Finally, create class!
new_class = super().__new__(cls, name, bases, attrs, **kwargs)

# Create the Field instance according to each type.
for field_name, field_type in attrs[NC_FIELDS_ATTR].items():
value = getattr(new_class, field_name, None)
if value is None:
# This is the case when a type is specified but not a value.
# Example:
# name: str
# age: int
try:
field = make_field_for_type(field_name, field_type)
except TypeError:
raise BlueprintSyntaxError(
f'unsupported field type: `{field_name}: {pretty_type(field_type)}`'
)
setattr(new_class, field_name, field)
else:
# This is the case when a value is specified.
# Example:
# name: str = StrField()
#
# This was not implemented yet and will be extended later.
raise BlueprintSyntaxError(f'fields with default values are currently not supported: `{field_name}`')

return new_class

@staticmethod
def _validate_initialize_method(attrs: Any) -> None:
if NC_INITIALIZE_METHOD not in attrs:
raise BlueprintSyntaxError(f'blueprints require a method called `{NC_INITIALIZE_METHOD}`')

method = attrs[NC_INITIALIZE_METHOD]
method_type = getattr(method, NC_METHOD_TYPE_ATTR, None)

if method_type is not NCMethodType.PUBLIC:
raise BlueprintSyntaxError(f'`{NC_INITIALIZE_METHOD}` method must be annotated with @public')

@staticmethod
def _validate_fallback_method(attrs: Any) -> None:
if NC_FALLBACK_METHOD not in attrs:
return

method = attrs[NC_FALLBACK_METHOD]
method_type = getattr(method, NC_METHOD_TYPE_ATTR, None)

if method_type is not NCMethodType.FALLBACK:
raise BlueprintSyntaxError(f'`{NC_FALLBACK_METHOD}` method must be annotated with @fallback')


class Blueprint(metaclass=_BlueprintBase):
"""Base class for all blueprints.

Example:

class MyBlueprint(Blueprint):
name: str
age: int
"""

__slots__ = ('__env',)

def __init__(self, env: BlueprintEnvironment) -> None:
self.__env = env

@final
@property
def syscall(self) -> BlueprintEnvironment:
"""Return the syscall provider for the current contract."""
return self.__env

@final
@property
def log(self) -> NCLogger:
"""Return the logger for the current contract."""
return self.syscall.__log__
# Re-export from hathorlib for backward compatibility
from hathorlib.nanocontracts.blueprint import * # noqa: F401,F403
from hathorlib.nanocontracts.blueprint import NC_FIELDS_ATTR, Blueprint, _BlueprintBase # noqa: F401
Loading
Loading