Skip to content

Commit 8c1b925

Browse files
committed
refactor: use Hathor simulator on events simulator CLI
1 parent 376c55b commit 8c1b925

10 files changed

+274
-317
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Copyright 2023 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 Any
16+
17+
from twisted.internet.interfaces import IAddress
18+
19+
from hathor.cli.events_simulator.event_forwarding_websocket_protocol import EventForwardingWebsocketProtocol
20+
from hathor.event.websocket import EventWebsocketFactory
21+
from hathor.simulator import Simulator
22+
23+
24+
class EventForwardingWebsocketFactory(EventWebsocketFactory):
25+
def __init__(self, simulator: Simulator, *args: Any, **kwargs: Any) -> None:
26+
self._simulator = simulator
27+
super().__init__(*args, **kwargs)
28+
29+
def buildProtocol(self, _: IAddress) -> EventForwardingWebsocketProtocol:
30+
protocol = EventForwardingWebsocketProtocol(self._simulator)
31+
protocol.factory = self
32+
return protocol
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Copyright 2023 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 TYPE_CHECKING
16+
17+
from autobahn.websocket import ConnectionRequest
18+
19+
from hathor.event.websocket import EventWebsocketProtocol
20+
from hathor.simulator import Simulator
21+
22+
if TYPE_CHECKING:
23+
from hathor.cli.events_simulator.event_forwarding_websocket_factory import EventForwardingWebsocketFactory
24+
25+
26+
class EventForwardingWebsocketProtocol(EventWebsocketProtocol):
27+
factory: 'EventForwardingWebsocketFactory'
28+
29+
def __init__(self, simulator: Simulator) -> None:
30+
self._simulator = simulator
31+
super().__init__()
32+
33+
def onConnect(self, request: ConnectionRequest) -> None:
34+
super().onConnect(request)
35+
self._simulator.run(60)
36+
37+
def onOpen(self) -> None:
38+
super().onOpen()
39+
self._simulator.run(60)
40+
41+
def onClose(self, wasClean: bool, code: int, reason: str) -> None:
42+
super().onClose(wasClean, code, reason)
43+
self._simulator.run(60)
44+
45+
def onMessage(self, payload: bytes, isBinary: bool) -> None:
46+
super().onMessage(payload, isBinary)
47+
self._simulator.run(60)

hathor/cli/events_simulator/events_simulator.py

+40-8
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,18 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import os
1516
from argparse import ArgumentParser, Namespace
1617

18+
from autobahn.twisted.resource import WebSocketResource
19+
from structlog import get_logger
20+
from twisted.web.resource import Resource
21+
from twisted.web.server import Site
22+
1723
DEFAULT_PORT = 8080
1824

25+
logger = get_logger()
26+
1927

2028
def create_parser() -> ArgumentParser:
2129
from hathor.cli.events_simulator.scenario import Scenario
@@ -26,14 +34,17 @@ def create_parser() -> ArgumentParser:
2634

2735
parser.add_argument('--scenario', help=f'One of {possible_scenarios}', type=str, required=True)
2836
parser.add_argument('--port', help='Port to run the WebSocket server', type=int, default=DEFAULT_PORT)
37+
parser.add_argument('--seed', help='The seed used to create simulated events', type=int)
2938

3039
return parser
3140

3241

3342
def execute(args: Namespace) -> None:
43+
from hathor.conf import UNITTESTS_SETTINGS_FILEPATH
44+
os.environ['HATHOR_CONFIG_YAML'] = UNITTESTS_SETTINGS_FILEPATH
45+
from hathor.cli.events_simulator.event_forwarding_websocket_factory import EventForwardingWebsocketFactory
3446
from hathor.cli.events_simulator.scenario import Scenario
35-
from hathor.event.storage import EventMemoryStorage
36-
from hathor.event.websocket import EventWebsocketFactory
47+
from hathor.simulator import Simulator
3748
from hathor.util import reactor
3849

3950
try:
@@ -42,15 +53,36 @@ def execute(args: Namespace) -> None:
4253
possible_scenarios = [scenario.name for scenario in Scenario]
4354
raise ValueError(f'Invalid scenario "{args.scenario}". Choose one of {possible_scenarios}') from e
4455

45-
storage = EventMemoryStorage()
56+
log = logger.new()
57+
simulator = Simulator(args.seed)
58+
simulator.start()
59+
builder = simulator.get_default_builder() \
60+
.disable_full_verification() \
61+
.enable_event_queue()
62+
63+
manager = simulator.create_peer(builder)
64+
event_ws_factory = manager._event_manager._event_ws_factory
65+
assert event_ws_factory is not None
66+
67+
forwarding_ws_factory = EventForwardingWebsocketFactory(
68+
simulator=simulator,
69+
reactor=reactor,
70+
event_storage=event_ws_factory._event_storage
71+
)
72+
73+
manager._event_manager._event_ws_factory = forwarding_ws_factory
4674

47-
for event in scenario.value:
48-
storage.save_event(event)
75+
root = Resource()
76+
api = Resource()
77+
root.putChild(b'v1a', api)
78+
api.putChild(b'event_ws', WebSocketResource(forwarding_ws_factory))
79+
site = Site(root)
4980

50-
factory = EventWebsocketFactory(reactor, storage)
81+
log.info('Started simulating events', scenario=args.scenario, seed=simulator.seed)
5182

52-
factory.start()
53-
reactor.listenTCP(args.port, factory)
83+
forwarding_ws_factory.start()
84+
scenario.simulate(simulator, manager)
85+
reactor.listenTCP(args.port, site)
5486
reactor.run()
5587

5688

hathor/cli/events_simulator/scenario.py

+76-14
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,83 @@
1313
# limitations under the License.
1414

1515
from enum import Enum
16+
from typing import TYPE_CHECKING
1617

17-
from hathor.cli.events_simulator.scenarios.only_load_events import ONLY_LOAD_EVENTS
18-
from hathor.cli.events_simulator.scenarios.reorg_events import REORG_EVENTS
19-
from hathor.cli.events_simulator.scenarios.single_chain_blocks_and_transactions_events import (
20-
SINGLE_CHAIN_BLOCKS_AND_TRANSACTIONS_EVENTS,
21-
)
22-
from hathor.cli.events_simulator.scenarios.single_chain_one_block_events import SINGLE_CHAIN_ONE_BLOCK_EVENTS
18+
if TYPE_CHECKING:
19+
from hathor.manager import HathorManager
20+
from hathor.simulator import Simulator
2321

2422

2523
class Scenario(Enum):
26-
"""
27-
NOTE: The lists of events used in each scenario's enum value below were obtained from the tests in
28-
tests.event.test_simulation.TestEventSimulation
29-
"""
30-
ONLY_LOAD = ONLY_LOAD_EVENTS
31-
SINGLE_CHAIN_ONE_BLOCK = SINGLE_CHAIN_ONE_BLOCK_EVENTS
32-
SINGLE_CHAIN_BLOCKS_AND_TRANSACTIONS = SINGLE_CHAIN_BLOCKS_AND_TRANSACTIONS_EVENTS
33-
REORG = REORG_EVENTS
24+
ONLY_LOAD = 'ONLY_LOAD'
25+
SINGLE_CHAIN_ONE_BLOCK = 'SINGLE_CHAIN_ONE_BLOCK'
26+
SINGLE_CHAIN_BLOCKS_AND_TRANSACTIONS = 'SINGLE_CHAIN_BLOCKS_AND_TRANSACTIONS'
27+
REORG = 'REORG'
28+
29+
def simulate(self, simulator: 'Simulator', manager: 'HathorManager') -> None:
30+
simulate_fns = {
31+
Scenario.ONLY_LOAD: simulate_only_load,
32+
Scenario.SINGLE_CHAIN_ONE_BLOCK: simulate_single_chain_one_block,
33+
Scenario.SINGLE_CHAIN_BLOCKS_AND_TRANSACTIONS: simulate_single_chain_blocks_and_transactions,
34+
Scenario.REORG: simulate_reorg,
35+
}
36+
37+
simulate_fn = simulate_fns[self]
38+
39+
simulate_fn(simulator, manager)
40+
41+
42+
def simulate_only_load(simulator: 'Simulator', _manager: 'HathorManager') -> None:
43+
simulator.run(60)
44+
45+
46+
def simulate_single_chain_one_block(simulator: 'Simulator', manager: 'HathorManager') -> None:
47+
from tests.utils import add_new_blocks
48+
add_new_blocks(manager, 1)
49+
simulator.run(60)
50+
51+
52+
def simulate_single_chain_blocks_and_transactions(simulator: 'Simulator', manager: 'HathorManager') -> None:
53+
from hathor import daa
54+
from hathor.conf import HathorSettings
55+
from tests.utils import add_new_blocks, gen_new_tx
56+
57+
settings = HathorSettings()
58+
assert manager.wallet is not None
59+
address = manager.wallet.get_unused_address(mark_as_used=False)
60+
61+
add_new_blocks(manager, settings.REWARD_SPEND_MIN_BLOCKS + 1)
62+
simulator.run(60)
63+
64+
tx = gen_new_tx(manager, address, 1000)
65+
tx.weight = daa.minimum_tx_weight(tx)
66+
tx.update_hash()
67+
assert manager.propagate_tx(tx, fails_silently=False)
68+
simulator.run(60)
69+
70+
tx = gen_new_tx(manager, address, 2000)
71+
tx.weight = daa.minimum_tx_weight(tx)
72+
tx.update_hash()
73+
assert manager.propagate_tx(tx, fails_silently=False)
74+
simulator.run(60)
75+
76+
add_new_blocks(manager, 1)
77+
simulator.run(60)
78+
79+
80+
def simulate_reorg(simulator: 'Simulator', manager: 'HathorManager') -> None:
81+
from hathor.simulator import FakeConnection
82+
from tests.utils import add_new_blocks
83+
84+
builder = simulator.get_default_builder()
85+
manager2 = simulator.create_peer(builder)
86+
87+
add_new_blocks(manager, 1)
88+
simulator.run(60)
89+
90+
add_new_blocks(manager2, 2)
91+
simulator.run(60)
92+
93+
connection = FakeConnection(manager, manager2)
94+
simulator.add_connection(connection)
95+
simulator.run(60)

hathor/cli/events_simulator/scenarios/__init__.py

Whitespace-only changes.

hathor/cli/events_simulator/scenarios/only_load_events.py

-24
This file was deleted.

0 commit comments

Comments
 (0)