diff --git a/hathor/builder/builder.py b/hathor/builder/builder.py index 4499b69a6..7d409b6c9 100644 --- a/hathor/builder/builder.py +++ b/hathor/builder/builder.py @@ -24,6 +24,8 @@ from hathor.event import EventManager from hathor.event.storage import EventMemoryStorage, EventRocksDBStorage, EventStorage from hathor.event.websocket import EventWebsocketFactory +from hathor.feature_activation.bit_signaling_service import BitSignalingService +from hathor.feature_activation.feature import Feature from hathor.feature_activation.feature_service import FeatureService from hathor.indexes import IndexesManager, MemoryIndexesManager, RocksDBIndexesManager from hathor.manager import HathorManager @@ -64,7 +66,6 @@ class BuildArtifacts(NamedTuple): wallet: Optional[BaseWallet] rocksdb_storage: Optional[RocksDBStorage] stratum_factory: Optional[StratumFactory] - feature_service: FeatureService class Builder: @@ -80,7 +81,7 @@ def __init__(self) -> None: self.log = logger.new() self.artifacts: Optional[BuildArtifacts] = None - self._settings: HathorSettingsType = HathorSettings() + self._settings: Optional[HathorSettingsType] = None self._rng: Random = Random() self._checkpoints: Optional[list[Checkpoint]] = None self._capabilities: Optional[list[str]] = None @@ -95,6 +96,11 @@ def __init__(self) -> None: self._event_manager: Optional[EventManager] = None self._enable_event_queue: Optional[bool] = None + self._support_features: set[Feature] = set() + self._not_support_features: set[Feature] = set() + self._feature_service: Optional[FeatureService] = None + self._bit_signaling_service: Optional[BitSignalingService] = None + self._rocksdb_path: Optional[str] = None self._rocksdb_storage: Optional[RocksDBStorage] = None self._rocksdb_cache_capacity: Optional[int] = None @@ -134,7 +140,7 @@ def build(self) -> BuildArtifacts: if self._network is None: raise TypeError('you must set a network') - settings = self._get_settings() + settings = self._get_or_create_settings() reactor = self._get_reactor() pubsub = self._get_or_create_pubsub() @@ -149,6 +155,7 @@ def build(self) -> BuildArtifacts: event_manager = self._get_or_create_event_manager() indexes = self._get_or_create_indexes_manager() tx_storage = self._get_or_create_tx_storage(indexes) + bit_signaling_service = self._get_or_create_bit_signaling_service(tx_storage) if self._enable_address_index: indexes.enable_address_index(pubsub) @@ -181,6 +188,7 @@ def build(self) -> BuildArtifacts: checkpoints=self._checkpoints, capabilities=self._capabilities, environment_info=get_environment_info(self._cmdline, peer_id.id), + bit_signaling_service=bit_signaling_service, **kwargs ) @@ -190,8 +198,6 @@ def build(self) -> BuildArtifacts: if self._enable_stratum_server: stratum_factory = self._create_stratum_server(manager) - feature_service = self._create_feature_service(tx_storage) - self.artifacts = BuildArtifacts( peer_id=peer_id, settings=settings, @@ -206,7 +212,6 @@ def build(self) -> BuildArtifacts: wallet=wallet, rocksdb_storage=self._rocksdb_storage, stratum_factory=stratum_factory, - feature_service=feature_service ) return self.artifacts @@ -220,6 +225,16 @@ def set_event_manager(self, event_manager: EventManager) -> 'Builder': self._event_manager = event_manager return self + def set_feature_service(self, feature_service: FeatureService) -> 'Builder': + self.check_if_can_modify() + self._feature_service = feature_service + return self + + def set_bit_signaling_service(self, bit_signaling_service: BitSignalingService) -> 'Builder': + self.check_if_can_modify() + self._bit_signaling_service = bit_signaling_service + return self + def set_rng(self, rng: Random) -> 'Builder': self.check_if_can_modify() self._rng = rng @@ -240,7 +255,9 @@ def set_peer_id(self, peer_id: PeerId) -> 'Builder': self._peer_id = peer_id return self - def _get_settings(self) -> HathorSettingsType: + def _get_or_create_settings(self) -> HathorSettingsType: + if self._settings is None: + self._settings = HathorSettings() return self._settings def _get_reactor(self) -> Reactor: @@ -252,7 +269,7 @@ def _get_soft_voided_tx_ids(self) -> set[bytes]: if self._soft_voided_tx_ids is not None: return self._soft_voided_tx_ids - settings = self._get_settings() + settings = self._get_or_create_settings() return set(settings.SOFT_VOIDED_TX_IDS) @@ -272,12 +289,6 @@ def _create_stratum_server(self, manager: HathorManager) -> StratumFactory: manager.metrics.stratum_factory = stratum_factory return stratum_factory - def _create_feature_service(self, tx_storage: TransactionStorage) -> FeatureService: - return FeatureService( - feature_settings=self._settings.FEATURE_ACTIVATION, - tx_storage=tx_storage - ) - def _get_or_create_rocksdb_storage(self) -> RocksDBStorage: assert self._rocksdb_path is not None @@ -388,6 +399,29 @@ def _get_or_create_event_manager(self) -> EventManager: return self._event_manager + def _get_or_create_feature_service(self, tx_storage: TransactionStorage) -> FeatureService: + if self._feature_service is None: + settings = self._get_or_create_settings() + self._feature_service = FeatureService( + feature_settings=settings.FEATURE_ACTIVATION, + tx_storage=tx_storage + ) + + return self._feature_service + + def _get_or_create_bit_signaling_service(self, tx_storage: TransactionStorage) -> BitSignalingService: + if self._bit_signaling_service is None: + settings = self._get_or_create_settings() + self._bit_signaling_service = BitSignalingService( + feature_settings=settings.FEATURE_ACTIVATION, + feature_service=self._get_or_create_feature_service(tx_storage), + tx_storage=tx_storage, + support_features=self._support_features, + not_support_features=self._not_support_features, + ) + + return self._bit_signaling_service + def use_memory(self) -> 'Builder': self.check_if_can_modify() self._storage_type = StorageType.MEMORY @@ -559,3 +593,19 @@ def set_soft_voided_tx_ids(self, soft_voided_tx_ids: set[bytes]) -> 'Builder': self.check_if_can_modify() self._soft_voided_tx_ids = soft_voided_tx_ids return self + + def set_features( + self, + *, + support_features: Optional[set[Feature]], + not_support_features: Optional[set[Feature]] + ) -> 'Builder': + self.check_if_can_modify() + self._support_features = support_features or set() + self._not_support_features = not_support_features or set() + return self + + def set_settings(self, settings: HathorSettingsType) -> 'Builder': + self.check_if_can_modify() + self._settings = settings + return self diff --git a/hathor/builder/cli_builder.py b/hathor/builder/cli_builder.py index b057145f8..16e22b79a 100644 --- a/hathor/builder/cli_builder.py +++ b/hathor/builder/cli_builder.py @@ -26,6 +26,8 @@ from hathor.consensus import ConsensusAlgorithm from hathor.event import EventManager from hathor.exception import BuilderError +from hathor.feature_activation.bit_signaling_service import BitSignalingService +from hathor.feature_activation.feature_service import FeatureService from hathor.indexes import IndexesManager, MemoryIndexesManager, RocksDBIndexesManager from hathor.manager import HathorManager from hathor.p2p.manager import ConnectionsManager @@ -189,6 +191,19 @@ def create_manager(self, reactor: PosixReactorBase) -> HathorManager: self.log.info('--x-enable-event-queue flag provided. ' 'The events detected by the full node will be stored and can be retrieved by clients') + self.feature_service = FeatureService( + feature_settings=settings.FEATURE_ACTIVATION, + tx_storage=tx_storage + ) + + bit_signaling_service = BitSignalingService( + feature_settings=settings.FEATURE_ACTIVATION, + feature_service=self.feature_service, + tx_storage=tx_storage, + support_features=self._args.signal_support, + not_support_features=self._args.signal_not_support + ) + p2p_manager = ConnectionsManager( reactor, network=network, @@ -216,7 +231,8 @@ def create_manager(self, reactor: PosixReactorBase) -> HathorManager: checkpoints=settings.CHECKPOINTS, environment_info=get_environment_info(args=str(self._args), peer_id=peer_id.id), full_verification=full_verification, - enable_event_queue=self._args.x_enable_event_queue + enable_event_queue=self._args.x_enable_event_queue, + bit_signaling_service=bit_signaling_service ) p2p_manager.set_manager(self.manager) diff --git a/hathor/cli/run_node.py b/hathor/cli/run_node.py index 00cbebe0e..e04e54a2d 100644 --- a/hathor/cli/run_node.py +++ b/hathor/cli/run_node.py @@ -159,16 +159,15 @@ def prepare(self, *, register_resources: bool = True) -> None: self.reactor.listenTCP(self._args.stratum, self.manager.stratum_factory) from hathor.conf import HathorSettings - from hathor.feature_activation.feature_service import FeatureService settings = HathorSettings() - feature_service = FeatureService( - feature_settings=settings.FEATURE_ACTIVATION, - tx_storage=self.manager.tx_storage - ) - if register_resources: - resources_builder = ResourcesBuilder(self.manager, self._args, builder.event_ws_factory, feature_service) + resources_builder = ResourcesBuilder( + self.manager, + self._args, + builder.event_ws_factory, + builder.feature_service + ) status_server = resources_builder.build() if self._args.status: self.reactor.listenTCP(self._args.status, status_server) @@ -188,7 +187,6 @@ def prepare(self, *, register_resources: bool = True) -> None: wallet=self.manager.wallet, rocksdb_storage=getattr(builder, 'rocksdb_storage', None), stratum_factory=self.manager.stratum_factory, - feature_service=feature_service ) def start_sentry_if_possible(self) -> None: diff --git a/hathor/manager.py b/hathor/manager.py index 2d0b14577..c87e18d04 100644 --- a/hathor/manager.py +++ b/hathor/manager.py @@ -39,6 +39,7 @@ RewardLockedError, SpendingVoidedError, ) +from hathor.feature_activation.bit_signaling_service import BitSignalingService from hathor.mining import BlockTemplate, BlockTemplates from hathor.p2p.manager import ConnectionsManager from hathor.p2p.peer_discovery import PeerDiscovery @@ -97,6 +98,7 @@ def __init__(self, tx_storage: TransactionStorage, p2p_manager: ConnectionsManager, event_manager: EventManager, + bit_signaling_service: BitSignalingService, network: str, hostname: Optional[str] = None, wallet: Optional[BaseWallet] = None, @@ -170,6 +172,8 @@ def __init__(self, self._event_manager.save_event_queue_state(enable_event_queue) self._enable_event_queue = enable_event_queue + self._bit_signaling_service = bit_signaling_service + self.consensus_algorithm = consensus_algorithm self.peer_discoveries: list[PeerDiscovery] = [] @@ -261,6 +265,8 @@ def start(self) -> None: if self._enable_event_queue: self._event_manager.start(not_none(self.my_peer.id)) + self._bit_signaling_service.start() + self.state = self.NodeState.INITIALIZING self.pubsub.publish(HathorEvents.MANAGER_ON_START) self._event_manager.load_started() @@ -838,12 +844,13 @@ def _make_block_template(self, parent_block: Block, parent_txs: 'ParentTxs', cur parents_any=parents_any, height=height, score=sum_weights(parent_block_metadata.score, weight), + signal_bits=self._bit_signaling_service.generate_signal_bits(block=parent_block) ) def generate_mining_block(self, timestamp: Optional[int] = None, parent_block_hash: Optional[VertexId] = None, data: bytes = b'', address: Optional[Address] = None, - merge_mined: bool = False, signal_bits: int = 0) -> Union[Block, MergeMinedBlock]: + merge_mined: bool = False) -> Union[Block, MergeMinedBlock]: """ Generates a block ready to be mined. The block includes new issued tokens, parents, and the weight. @@ -860,7 +867,6 @@ def generate_mining_block(self, timestamp: Optional[int] = None, merge_mined=merge_mined, address=address or None, # XXX: because we allow b'' for explicit empty output script data=data, - signal_bits=signal_bits ) return block diff --git a/hathor/mining/block_template.py b/hathor/mining/block_template.py index 007ce89ea..6a77c274e 100644 --- a/hathor/mining/block_template.py +++ b/hathor/mining/block_template.py @@ -34,6 +34,7 @@ class BlockTemplate(NamedTuple): parents_any: list[bytes] # list of extra parents to choose from when there are more options height: int # metadata score: float # metadata + signal_bits: int # signal bits for blocks generated from this template def generate_minimaly_valid_block(self) -> BaseTransaction: """ Generates a block, without any extra information that is valid for this template. No random choices.""" @@ -47,8 +48,8 @@ def generate_minimaly_valid_block(self) -> BaseTransaction: def generate_mining_block(self, rng: Random, merge_mined: bool = False, address: Optional[bytes] = None, timestamp: Optional[int] = None, data: Optional[bytes] = None, - storage: Optional[TransactionStorage] = None, include_metadata: bool = False, - signal_bits: int = 0) -> Union[Block, MergeMinedBlock]: + storage: Optional[TransactionStorage] = None, include_metadata: bool = False + ) -> Union[Block, MergeMinedBlock]: """ Generates a block by filling the template with the given options and random parents (if multiple choices). Note that if a timestamp is given it will be coerced into the [timestamp_min, timestamp_max] range. @@ -64,7 +65,7 @@ def generate_mining_block(self, rng: Random, merge_mined: bool = False, address: tx_outputs = [TxOutput(self.reward, output_script)] cls: Union[type['Block'], type['MergeMinedBlock']] = MergeMinedBlock if merge_mined else Block block = cls(outputs=tx_outputs, parents=parents, timestamp=block_timestamp, - data=data or b'', storage=storage, weight=self.weight, signal_bits=signal_bits) + data=data or b'', storage=storage, weight=self.weight, signal_bits=self.signal_bits) if include_metadata: block._metadata = TransactionMetadata(height=self.height, score=self.score) block.get_metadata(use_storage=False) @@ -93,6 +94,7 @@ def to_dict(self) -> dict: 'parents_any': [p.hex() for p in self.parents_any], 'height': self.height, 'score': self.score, + 'signal_bits': self.signal_bits, } @classmethod @@ -108,6 +110,7 @@ def from_dict(cls, data: dict) -> 'BlockTemplate': parents_any=[bytes.fromhex(p) for p in data['parents_any']], height=int(data['height']), score=int(data['score']), + signal_bits=int(data.get('signal_bits', 0)), ) @@ -129,5 +132,4 @@ def generate_mining_block(self, rng: Random, merge_mined: bool = False, address: return self.choose_random_template(rng).generate_mining_block(rng, merge_mined=merge_mined, address=address, timestamp=timestamp, data=data, storage=storage or self.storage, - include_metadata=include_metadata, - signal_bits=signal_bits) + include_metadata=include_metadata) diff --git a/hathor/simulator/miner/geometric_miner.py b/hathor/simulator/miner/geometric_miner.py index be8d71cc3..5887b2d94 100644 --- a/hathor/simulator/miner/geometric_miner.py +++ b/hathor/simulator/miner/geometric_miner.py @@ -75,7 +75,10 @@ def _generate_mining_block(self) -> 'Block': except IndexError: signal_bits = 0 - return self._manager.generate_mining_block(signal_bits=signal_bits) + block = self._manager.generate_mining_block() + block.signal_bits = signal_bits + + return block def _schedule_next_block(self): if self._block: diff --git a/hathor/transaction/base_transaction.py b/hathor/transaction/base_transaction.py index 10c4dfd0c..ef8350fde 100644 --- a/hathor/transaction/base_transaction.py +++ b/hathor/transaction/base_transaction.py @@ -1035,6 +1035,7 @@ def to_json(self, decode_script: bool = False, include_metadata: bool = False) - data['timestamp'] = self.timestamp data['version'] = int(self.version) data['weight'] = self.weight + data['signal_bits'] = self.signal_bits data['parents'] = [] for parent in self.parents: diff --git a/tests/feature_activation/test_feature_simulation.py b/tests/feature_activation/test_feature_simulation.py index bb4faeae5..5b5f0b475 100644 --- a/tests/feature_activation/test_feature_simulation.py +++ b/tests/feature_activation/test_feature_simulation.py @@ -75,8 +75,10 @@ def test_feature(self) -> None: } ) - feature_service = artifacts.feature_service - feature_service._feature_settings = feature_settings + feature_service = FeatureService( + feature_settings=feature_settings, + tx_storage=artifacts.tx_storage + ) feature_resource = FeatureResource( feature_settings=feature_settings, feature_service=feature_service, @@ -338,8 +340,10 @@ def test_reorg(self) -> None: ) } ) - feature_service = artifacts.feature_service - feature_service._feature_settings = feature_settings + feature_service = FeatureService( + feature_settings=feature_settings, + tx_storage=artifacts.tx_storage + ) feature_resource = FeatureResource( feature_settings=feature_settings, feature_service=feature_service, @@ -562,8 +566,10 @@ def test_feature_from_existing_storage(self) -> None: } ) - feature_service = artifacts1.feature_service - feature_service._feature_settings = feature_settings + feature_service = FeatureService( + feature_settings=feature_settings, + tx_storage=artifacts1.tx_storage + ) feature_resource = FeatureResource( feature_settings=feature_settings, feature_service=feature_service, @@ -620,8 +626,10 @@ def test_feature_from_existing_storage(self) -> None: artifacts2 = self.simulator.create_artifacts(builder) # new feature_service is created with the same storage generated above - feature_service = artifacts2.feature_service - feature_service._feature_settings = feature_settings + feature_service = FeatureService( + feature_settings=feature_settings, + tx_storage=artifacts2.tx_storage + ) feature_resource = FeatureResource( feature_settings=feature_settings, feature_service=feature_service, diff --git a/tests/feature_activation/test_mining_simulation.py b/tests/feature_activation/test_mining_simulation.py new file mode 100644 index 000000000..1f1ec1354 --- /dev/null +++ b/tests/feature_activation/test_mining_simulation.py @@ -0,0 +1,190 @@ +# Copyright 2023 Hathor Labs +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import base64 +from json import JSONDecodeError +from typing import Any, Iterable +from unittest.mock import Mock + +from twisted.internet.testing import StringTransport + +from hathor.conf import HathorSettings as get_settings +from hathor.conf.settings import HathorSettings +from hathor.feature_activation.feature import Feature +from hathor.feature_activation.model.criteria import Criteria +from hathor.feature_activation.settings import Settings as FeatureSettings +from hathor.mining.ws import MiningWebsocketFactory, MiningWebsocketProtocol +from hathor.p2p.resources import MiningResource +from hathor.simulator.trigger import StopAfterNMinedBlocks +from hathor.transaction.resources import GetBlockTemplateResource +from hathor.transaction.util import unpack, unpack_len +from hathor.util import json_loadb +from tests import unittest +from tests.resources.base_resource import StubSite +from tests.simulation.base import SimulatorTestCase + + +class BaseMiningSimulationTest(SimulatorTestCase): + def test_signal_bits_in_mining(self) -> None: + settings_dict = get_settings()._asdict() + settings_dict.update( + FEATURE_ACTIVATION=FeatureSettings( + evaluation_interval=4, + default_threshold=3, + features={ + Feature.NOP_FEATURE_1: Criteria( + bit=0, + start_height=8, + timeout_height=20, + version='0.0.0', + signal_support_by_default=True + ), + Feature.NOP_FEATURE_2: Criteria( + bit=2, + start_height=12, + timeout_height=24, + version='0.0.0' + ), + } + ) + ) + settings = HathorSettings(**settings_dict) + + builder = self.simulator.get_default_builder() \ + .set_settings(settings) \ + .set_features(support_features={Feature.NOP_FEATURE_2}, not_support_features=set()) + + manager = self.simulator.create_peer(builder) + manager.allow_mining_without_peers() + miner = self.simulator.create_miner(manager, hashpower=1e6) + miner.start() + + # There are 3 resources available for miners, and all of them should contain the correct signal_bits + get_block_template_resource = GetBlockTemplateResource(manager) + get_block_template_client = StubSite(get_block_template_resource) + + mining_resource = MiningResource(manager) + mining_client = StubSite(mining_resource) + + ws_factory = MiningWebsocketFactory(manager) + ws_factory.openHandshakeTimeout = 0 + ws_protocol = ws_factory.buildProtocol(addr=Mock()) + ws_transport = StringTransport() + ws_protocol.makeConnection(ws_transport) + ws_protocol.state = MiningWebsocketProtocol.STATE_OPEN + ws_protocol.onOpen() + + # At the beginning, all features are outside their signaling period, so none are signaled. + expected_signal_bits = 0b0000 + assert self._get_ws_signal_bits(ws_transport) == [expected_signal_bits] + self.simulator.run(3600, trigger=StopAfterNMinedBlocks(miner, quantity=1)) + assert self._get_signal_bits_from_get_block_template(get_block_template_client) == expected_signal_bits + assert self._get_signal_bits_from_mining(mining_client) == expected_signal_bits + assert self._get_ws_signal_bits(ws_transport) == [expected_signal_bits] + + self.simulator.run(3600, trigger=StopAfterNMinedBlocks(miner, quantity=6)) + assert self._get_ws_signal_bits(ws_transport) == [expected_signal_bits] * 6 + + # At height=8, NOP_FEATURE_1 is signaling, so it's enabled by the default support. + expected_signal_bits = 0b0001 + self.simulator.run(3600, trigger=StopAfterNMinedBlocks(miner, quantity=1)) + assert self._get_signal_bits_from_get_block_template(get_block_template_client) == expected_signal_bits + assert self._get_signal_bits_from_mining(mining_client) == expected_signal_bits + assert self._get_ws_signal_bits(ws_transport) == [expected_signal_bits] + + self.simulator.run(3600, trigger=StopAfterNMinedBlocks(miner, quantity=3)) + assert self._get_ws_signal_bits(ws_transport) == [expected_signal_bits] * 3 + + # At height=12, NOP_FEATURE_2 is signaling, enabled by the user. NOP_FEATURE_1 also continues signaling. + expected_signal_bits = 0b0101 + self.simulator.run(3600, trigger=StopAfterNMinedBlocks(miner, quantity=1)) + assert self._get_signal_bits_from_get_block_template(get_block_template_client) == expected_signal_bits + assert self._get_signal_bits_from_mining(mining_client) == expected_signal_bits + assert self._get_ws_signal_bits(ws_transport) == [expected_signal_bits] + + self.simulator.run(3600, trigger=StopAfterNMinedBlocks(miner, quantity=7)) + assert self._get_ws_signal_bits(ws_transport) == [expected_signal_bits] * 7 + + # At height=20, NOP_FEATURE_1 stops signaling, and NOP_FEATURE_2 continues. + expected_signal_bits = 0b0100 + self.simulator.run(3600, trigger=StopAfterNMinedBlocks(miner, quantity=1)) + assert self._get_signal_bits_from_get_block_template(get_block_template_client) == expected_signal_bits + assert self._get_signal_bits_from_mining(mining_client) == expected_signal_bits + assert self._get_ws_signal_bits(ws_transport) == [expected_signal_bits] + + self.simulator.run(3600, trigger=StopAfterNMinedBlocks(miner, quantity=3)) + assert self._get_ws_signal_bits(ws_transport) == [expected_signal_bits] * 3 + + # At height=24, all features have left their signaling period and therefore none are signaled. + expected_signal_bits = 0b0000 + self.simulator.run(3600, trigger=StopAfterNMinedBlocks(miner, quantity=1)) + assert self._get_signal_bits_from_get_block_template(get_block_template_client) == expected_signal_bits + assert self._get_signal_bits_from_mining(mining_client) == expected_signal_bits + assert self._get_ws_signal_bits(ws_transport) == [expected_signal_bits] + + def _get_signal_bits_from_get_block_template(self, web_client: StubSite) -> int: + result = self._get_result(web_client) + return result['signal_bits'] + + def _get_signal_bits_from_mining(self, web_client: StubSite) -> int: + result = self._get_result(web_client) + block_bytes = base64.b64decode(result['block_bytes']) + return block_bytes[0] + + @staticmethod + def _get_result(web_client: StubSite) -> dict[str, Any]: + response = web_client.get('') + return response.result.json_value() + + def _get_ws_signal_bits(self, transport: StringTransport) -> list[int]: + messages = self._get_transport_messages(transport) + signal_bits = [message['params'][0]['signal_bits'] for message in messages] + + return signal_bits + + def _get_transport_messages(self, transport: StringTransport) -> list[dict[str, Any]]: + values = transport.value() + result = self._decode_values(values) + + transport.clear() + + return list(result) + + @staticmethod + def _decode_values(values: bytes) -> Iterable[dict[str, Any]]: + buf = values + + while buf: + try: + (_, _, value_length), new_buf = unpack('!BBH', buf) + value, new_buf = unpack_len(value_length, new_buf) + yield json_loadb(value) + except JSONDecodeError: + (_, value_length), new_buf = unpack('!BB', buf) + value, new_buf = unpack_len(value_length, new_buf) + yield json_loadb(value) + + buf = new_buf + + +class SyncV1MiningSimulationTest(unittest.SyncV1Params, BaseMiningSimulationTest): + __test__ = True + + +class SyncV2MiningSimulationTest(unittest.SyncV2Params, BaseMiningSimulationTest): + __test__ = True + + +class SyncBridgeMiningSimulationTest(unittest.SyncBridgeParams, BaseMiningSimulationTest): + __test__ = True diff --git a/tests/resources/transaction/test_mining.py b/tests/resources/transaction/test_mining.py index 885abfebf..0981794bd 100644 --- a/tests/resources/transaction/test_mining.py +++ b/tests/resources/transaction/test_mining.py @@ -43,6 +43,7 @@ def test_get_block_template_with_address(self): }, 'tokens': [], 'data': '', + 'signal_bits': 0 }) @inlineCallbacks @@ -75,6 +76,7 @@ def test_get_block_template_without_address(self): }, 'tokens': [], 'data': '', + 'signal_bits': 0 }) @inlineCallbacks diff --git a/tests/tx/test_mining.py b/tests/tx/test_mining.py index 468923c31..064739ad3 100644 --- a/tests/tx/test_mining.py +++ b/tests/tx/test_mining.py @@ -30,7 +30,7 @@ def setUp(self): self.genesis_blocks = [tx for tx in self.genesis if tx.is_block] self.genesis_txs = [tx for tx in self.genesis if not tx.is_block] - def test_block_template_after_genesis(self): + def test_block_template_after_genesis(self) -> None: manager = self.create_peer('testnet', tx_storage=self.tx_storage) block_templates = manager.get_block_templates() @@ -47,9 +47,10 @@ def test_block_template_after_genesis(self): parents_any=[], height=1, # genesis is 0 score=sum_weights(self.genesis_blocks[0].weight, 1.0), + signal_bits=0 )) - def test_regular_block_template(self): + def test_regular_block_template(self) -> None: manager = self.create_peer('testnet', tx_storage=self.tx_storage) # add 100 blocks @@ -69,6 +70,7 @@ def test_regular_block_template(self): parents_any=[], height=101, # genesis is 0 score=sum_weights(blocks[-1].get_metadata().score, 1.0), + signal_bits=0 )) self.assertConsensusValid(manager)