Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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