Skip to content

Commit c4838a4

Browse files
authored
chore: sync main with dev after manual merge (#63)
1 parent 1ba81d5 commit c4838a4

File tree

17 files changed

+824
-17
lines changed

17 files changed

+824
-17
lines changed

hathorlib/headers/nano_header.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@
2222
from hathorlib.headers.types import VertexHeaderId
2323
from hathorlib.nanocontracts import DeprecatedNanoContract
2424
from hathorlib.nanocontracts.types import NCActionType
25-
from hathorlib.utils import int_to_bytes, unpack, unpack_len
25+
from hathorlib.utils import decode_unsigned, encode_unsigned, int_to_bytes, unpack, unpack_len
2626

2727
if TYPE_CHECKING:
2828
from hathorlib.base_transaction import BaseTransaction
2929

30-
NC_VERSION = 1
3130
NC_INITIALIZE_METHOD = 'initialize'
3231
ADDRESS_LEN_BYTES = 25
32+
ADDRESS_SEQNUM_SIZE: int = 8 # bytes
33+
_NC_SCRIPT_LEN_MAX_BYTES: int = 2
3334

3435

3536
@dataclass(frozen=True)
@@ -43,6 +44,9 @@ class NanoHeaderAction:
4344
class NanoHeader(VertexBaseHeader):
4445
tx: BaseTransaction
4546

47+
# Sequence number for the caller.
48+
nc_seqnum: int
49+
4650
# nc_id equals to the blueprint_id when a Nano Contract is being created.
4751
# nc_id equals to the nanocontract_id when a method is being called.
4852
nc_id: bytes
@@ -59,8 +63,6 @@ class NanoHeader(VertexBaseHeader):
5963
nc_address: bytes
6064
nc_script: bytes
6165

62-
nc_version: int = NC_VERSION
63-
6466
@classmethod
6567
def _deserialize_action(cls, buf: bytes) -> tuple[NanoHeaderAction, bytes]:
6668
from hathorlib.base_transaction import bytes_to_output_value
@@ -78,11 +80,9 @@ def _deserialize_action(cls, buf: bytes) -> tuple[NanoHeaderAction, bytes]:
7880
def deserialize(cls, tx: BaseTransaction, buf: bytes) -> tuple[NanoHeader, bytes]:
7981
header_id, buf = buf[:1], buf[1:]
8082
assert header_id == VertexHeaderId.NANO_HEADER.value
81-
(nc_version,), buf = unpack('!B', buf)
82-
if nc_version != NC_VERSION:
83-
raise ValueError('unknown nanocontract version: {}'.format(nc_version))
8483

8584
nc_id, buf = unpack_len(32, buf)
85+
nc_seqnum, buf = decode_unsigned(buf, max_bytes=ADDRESS_SEQNUM_SIZE)
8686
(nc_method_len,), buf = unpack('!B', buf)
8787
nc_method, buf = unpack_len(nc_method_len, buf)
8888
(nc_args_bytes_len,), buf = unpack('!H', buf)
@@ -96,14 +96,14 @@ def deserialize(cls, tx: BaseTransaction, buf: bytes) -> tuple[NanoHeader, bytes
9696
nc_actions.append(action)
9797

9898
nc_address, buf = unpack_len(ADDRESS_LEN_BYTES, buf)
99-
(nc_script_len,), buf = unpack('!H', buf)
99+
nc_script_len, buf = decode_unsigned(buf, max_bytes=_NC_SCRIPT_LEN_MAX_BYTES)
100100
nc_script, buf = unpack_len(nc_script_len, buf)
101101

102102
decoded_nc_method = nc_method.decode('ascii')
103103

104104
return cls(
105105
tx=tx,
106-
nc_version=nc_version,
106+
nc_seqnum=nc_seqnum,
107107
nc_id=nc_id,
108108
nc_method=decoded_nc_method,
109109
nc_args_bytes=nc_args_bytes,
@@ -127,8 +127,8 @@ def _serialize_without_header_id(self, *, skip_signature: bool) -> deque[bytes]:
127127
encoded_method = self.nc_method.encode('ascii')
128128

129129
ret: deque[bytes] = deque()
130-
ret.append(int_to_bytes(NC_VERSION, 1))
131130
ret.append(self.nc_id)
131+
ret.append(encode_unsigned(self.nc_seqnum, max_bytes=ADDRESS_SEQNUM_SIZE))
132132
ret.append(int_to_bytes(len(encoded_method), 1))
133133
ret.append(encoded_method)
134134
ret.append(int_to_bytes(len(self.nc_args_bytes), 2))
@@ -141,10 +141,10 @@ def _serialize_without_header_id(self, *, skip_signature: bool) -> deque[bytes]:
141141

142142
ret.append(self.nc_address)
143143
if not skip_signature:
144-
ret.append(int_to_bytes(len(self.nc_script), 2))
144+
ret.append(encode_unsigned(len(self.nc_script), max_bytes=_NC_SCRIPT_LEN_MAX_BYTES))
145145
ret.append(self.nc_script)
146146
else:
147-
ret.append(int_to_bytes(0, 2))
147+
ret.append(encode_unsigned(0, max_bytes=_NC_SCRIPT_LEN_MAX_BYTES))
148148
return ret
149149

150150
def serialize(self) -> bytes:

hathorlib/nanocontracts/types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class NCActionType(Enum):
2525
DEPOSIT = 1
2626
WITHDRAWAL = 2
2727
GRANT_AUTHORITY = 3
28-
INVOKE_AUTHORITY = 4
28+
ACQUIRE_AUTHORITY = 4
2929

3030
def __str__(self) -> str:
3131
return self.name.lower()
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Copyright 2025 Hathor Labs
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from .deserializer import Deserializer
16+
from .exceptions import BadDataError, OutOfDataError, SerializationError, TooLongError, UnsupportedTypeError
17+
from .serializer import Serializer
18+
19+
__all__ = [
20+
'Serializer',
21+
'Deserializer',
22+
'SerializationError',
23+
'UnsupportedTypeError',
24+
'TooLongError',
25+
'OutOfDataError',
26+
'BadDataError',
27+
]
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from .generic_adapter import GenericDeserializerAdapter, GenericSerializerAdapter
2+
from .max_bytes import MaxBytesDeserializer, MaxBytesExceededError, MaxBytesSerializer
3+
4+
__all__ = [
5+
'GenericDeserializerAdapter',
6+
'GenericSerializerAdapter',
7+
'MaxBytesDeserializer',
8+
'MaxBytesExceededError',
9+
'MaxBytesSerializer',
10+
]
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Copyright 2025 Hathor Labs
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from types import TracebackType
16+
from typing import Generic, TypeVar, Union
17+
18+
from typing_extensions import Self, override
19+
20+
from hathorlib.serialization.deserializer import Deserializer
21+
from hathorlib.serialization.serializer import Serializer
22+
23+
from ..types import Buffer
24+
25+
S = TypeVar('S', bound=Serializer)
26+
D = TypeVar('D', bound=Deserializer)
27+
28+
29+
class GenericSerializerAdapter(Serializer, Generic[S]):
30+
inner: S
31+
32+
def __init__(self, serializer: S) -> None:
33+
self.inner = serializer
34+
35+
@override
36+
def finalize(self) -> Buffer:
37+
return self.inner.finalize()
38+
39+
@override
40+
def cur_pos(self) -> int:
41+
return self.inner.cur_pos()
42+
43+
@override
44+
def write_byte(self, data: int) -> None:
45+
self.inner.write_byte(data)
46+
47+
@override
48+
def write_bytes(self, data: Buffer) -> None:
49+
self.inner.write_bytes(data)
50+
51+
# allow using this adapter as a context manager:
52+
53+
def __enter__(self) -> Self:
54+
return self
55+
56+
def __exit__(
57+
self,
58+
exc_type: Union[type[BaseException], None],
59+
exc_value: Union[BaseException, None],
60+
traceback: Union[TracebackType, None],
61+
) -> None:
62+
pass
63+
64+
65+
class GenericDeserializerAdapter(Deserializer, Generic[D]):
66+
inner: D
67+
68+
def __init__(self, deserializer: D) -> None:
69+
self.inner = deserializer
70+
71+
@override
72+
def finalize(self) -> None:
73+
return self.inner.finalize()
74+
75+
@override
76+
def is_empty(self) -> bool:
77+
return self.inner.is_empty()
78+
79+
@override
80+
def peek_byte(self) -> int:
81+
return self.inner.peek_byte()
82+
83+
@override
84+
def peek_bytes(self, n: int, *, exact: bool = True) -> Buffer:
85+
return self.inner.peek_bytes(n, exact=exact)
86+
87+
@override
88+
def read_byte(self) -> int:
89+
return self.inner.read_byte()
90+
91+
@override
92+
def read_bytes(self, n: int, *, exact: bool = True) -> Buffer:
93+
return self.inner.read_bytes(n, exact=exact)
94+
95+
@override
96+
def read_all(self) -> Buffer:
97+
return self.inner.read_all()
98+
99+
# allow using this adapter as a context manager:
100+
101+
def __enter__(self) -> Self:
102+
return self
103+
104+
def __exit__(
105+
self,
106+
exc_type: Union[type[BaseException], None],
107+
exc_value: Union[BaseException, None],
108+
traceback: Union[TracebackType, None],
109+
) -> None:
110+
pass
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Copyright 2025 Hathor Labs
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from typing import TypeVar
16+
17+
from typing_extensions import override
18+
19+
from hathorlib.serialization.deserializer import Deserializer
20+
from hathorlib.serialization.exceptions import SerializationError
21+
from hathorlib.serialization.serializer import Serializer
22+
23+
from ..types import Buffer
24+
from .generic_adapter import GenericDeserializerAdapter, GenericSerializerAdapter
25+
26+
S = TypeVar('S', bound=Serializer)
27+
D = TypeVar('D', bound=Deserializer)
28+
29+
30+
class MaxBytesExceededError(SerializationError):
31+
""" This error is raised when the adapted serializer reached its maximum bytes write/read.
32+
33+
After this exception is raised the adapted serializer cannot be used anymore. Handlers of this exception are
34+
expected to either: bubble up the exception (or an equivalente exception), or return an error. Handlers should not
35+
try to write again on the same serializer.
36+
37+
It is possible that the inner serializer is still usable, but the point where the serialized stopped writing or
38+
reading might leave the rest of the data unusable, so for that reason it should be considered a failed
39+
(de)serialization overall, and not simply a failed "read/write" operation.
40+
"""
41+
pass
42+
43+
44+
class MaxBytesSerializer(GenericSerializerAdapter[S]):
45+
def __init__(self, serializer: S, max_bytes: int) -> None:
46+
super().__init__(serializer)
47+
self._bytes_left = max_bytes
48+
49+
def _check_update_exceeds(self, write_size: int) -> None:
50+
self._bytes_left -= write_size
51+
if self._bytes_left < 0:
52+
raise MaxBytesExceededError
53+
54+
@override
55+
def write_byte(self, data: int) -> None:
56+
self._check_update_exceeds(1)
57+
super().write_byte(data)
58+
59+
@override
60+
def write_bytes(self, data: Buffer) -> None:
61+
data_view = memoryview(data)
62+
self._check_update_exceeds(len(data_view))
63+
super().write_bytes(data_view)
64+
65+
66+
class MaxBytesDeserializer(GenericDeserializerAdapter[D]):
67+
def __init__(self, deserializer: D, max_bytes: int) -> None:
68+
super().__init__(deserializer)
69+
self._bytes_left = max_bytes
70+
71+
def _check_update_exceeds(self, read_size: int) -> None:
72+
self._bytes_left -= read_size
73+
if self._bytes_left < 0:
74+
raise MaxBytesExceededError
75+
76+
@override
77+
def read_byte(self) -> int:
78+
self._check_update_exceeds(1)
79+
return super().read_byte()
80+
81+
@override
82+
def read_bytes(self, n: int, *, exact: bool = True) -> Buffer:
83+
self._check_update_exceeds(n)
84+
return super().read_bytes(n, exact=exact)
85+
86+
@override
87+
def read_all(self) -> Buffer:
88+
result = super().read_bytes(self._bytes_left, exact=False)
89+
if not self.is_empty():
90+
raise MaxBytesExceededError
91+
return result

0 commit comments

Comments
 (0)