Skip to content

Commit

Permalink
Make JSON-RPC Server an isolated plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
cburgdorf committed Sep 13, 2018
1 parent c5bc7d8 commit 90e39d7
Show file tree
Hide file tree
Showing 29 changed files with 397 additions and 193 deletions.
12 changes: 12 additions & 0 deletions eth/chains/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -892,3 +892,15 @@ async def coro_validate_receipt(self,
receipt: Receipt,
at_header: BlockHeader) -> None:
raise NotImplementedError()

async def coro_get_block_by_hash(self,
block_hash: Hash32) -> BaseBlock:
raise NotImplementedError()

async def coro_get_block_by_header(self,
header: BlockHeader) -> BaseBlock:
raise NotImplementedError()

async def coro_get_canonical_block_by_number(self,
block_number: BlockNumber) -> BaseBlock:
raise NotImplementedError()
4 changes: 2 additions & 2 deletions eth/tools/fixtures/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,12 @@ def genesis_params_from_fixture(fixture):
}


def new_chain_from_fixture(fixture):
def new_chain_from_fixture(fixture, chain_cls=MainnetChain):
base_db = MemoryDB()

vm_config = chain_vm_configuration(fixture)

ChainFromFixture = MainnetChain.configure(
ChainFromFixture = chain_cls.configure(
'ChainFromFixture',
vm_configuration=vm_config,
)
Expand Down
21 changes: 21 additions & 0 deletions p2p/events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from typing import (
Type,
)

from lahja import (
BaseEvent,
BaseRequestResponseEvent,
)


class PeerCountResponse(BaseEvent):

def __init__(self, peer_count: int) -> None:
self.peer_count = peer_count


class PeerCountRequest(BaseRequestResponseEvent[PeerCountResponse]):

@staticmethod
def expected_response_type() -> Type[PeerCountResponse]:
return PeerCountResponse
20 changes: 20 additions & 0 deletions p2p/peer.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@

from cancel_token import CancelToken, OperationCancelled

from lahja import (
Endpoint,
)

from eth.chains.mainnet import MAINNET_NETWORK_ID
from eth.chains.ropsten import ROPSTEN_NETWORK_ID
from eth.constants import GENESIS_BLOCK_NUMBER
Expand Down Expand Up @@ -101,6 +105,11 @@
MAC_LEN,
)

from .events import (
PeerCountRequest,
PeerCountResponse,
)

if TYPE_CHECKING:
from trinity.db.header import BaseAsyncHeaderDB # noqa: F401
from trinity.protocol.common.proto import ChainInfo # noqa: F401
Expand Down Expand Up @@ -784,6 +793,7 @@ def __init__(self,
vm_configuration: Tuple[Tuple[int, Type[BaseVM]], ...],
max_peers: int = DEFAULT_MAX_PEERS,
token: CancelToken = None,
event_bus: Endpoint = None
) -> None:
super().__init__(token)
self.peer_class = peer_class
Expand All @@ -794,6 +804,16 @@ def __init__(self,
self.max_peers = max_peers
self.connected_nodes: Dict[Node, BasePeer] = {}
self._subscribers: List[PeerSubscriber] = []
self.event_bus = event_bus
self.run_task(self.handle_peer_count_requests())

async def handle_peer_count_requests(self) -> None:
async for req in self.event_bus.stream(PeerCountRequest):
# We are listening for all `PeerCountRequest` events but we ensure to
# only send a `PeerCountResponse` to the callsite that made the request.
# We do that by retrieving a `BroadcastConfig` from the request via the
# `event.broadcast_config()` API.
self.event_bus.broadcast(PeerCountResponse(len(self)), req.broadcast_config())

def __len__(self) -> int:
return len(self.connected_nodes)
Expand Down
11 changes: 8 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,7 @@ def funded_address_initial_balance():
return to_wei(1000, 'ether')


@pytest.fixture
def chain_with_block_validation(base_db, genesis_state):
def _chain_with_block_validation(base_db, genesis_state, chain_cls=Chain):
"""
Return a Chain object containing just the genesis block.
Expand Down Expand Up @@ -107,7 +106,8 @@ def chain_with_block_validation(base_db, genesis_state):
"transaction_root": decode_hex("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"), # noqa: E501
"uncles_hash": decode_hex("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347") # noqa: E501
}
klass = Chain.configure(

klass = chain_cls.configure(
__name__='TestChain',
vm_configuration=(
(constants.GENESIS_BLOCK_NUMBER, SpuriousDragonVM),
Expand All @@ -118,6 +118,11 @@ def chain_with_block_validation(base_db, genesis_state):
return chain


@pytest.fixture
def chain_with_block_validation(base_db, genesis_state):
return _chain_with_block_validation(base_db, genesis_state)


def import_block_without_validation(chain, block):
return super(type(chain), chain).import_block(block, perform_validation=False)

Expand Down
40 changes: 38 additions & 2 deletions tests/trinity/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,19 @@
import tempfile
import uuid

from lahja import (
EventBus,
)

from eth.chains import (
Chain,
)

from p2p.peer import PeerPool

from trinity.chains.coro import (
AsyncChainMixin,
)
from trinity.rpc.main import (
RPCServer,
)
Expand All @@ -22,6 +33,13 @@
from trinity.utils.filesystem import (
is_under_path,
)
from tests.conftest import (
_chain_with_block_validation,
)


class TestAsyncChain(Chain, AsyncChainMixin):
pass


def pytest_addoption(parser):
Expand Down Expand Up @@ -51,6 +69,19 @@ def event_loop():
loop.close()


@pytest.fixture(scope='module')
def event_bus(event_loop):
bus = EventBus()
endpoint = bus.create_endpoint('test')
bus.start(event_loop)
endpoint.connect(event_loop)
try:
yield endpoint
finally:
endpoint.stop()
bus.stop()


@pytest.fixture(scope='session')
def jsonrpc_ipc_pipe_path():
with tempfile.TemporaryDirectory() as temp_dir:
Expand All @@ -64,11 +95,16 @@ def p2p_server(monkeypatch, jsonrpc_ipc_pipe_path):
return Server(None, None, None, None, None, None, None)


@pytest.fixture
def chain_with_block_validation(base_db, genesis_state):
return _chain_with_block_validation(base_db, genesis_state, TestAsyncChain)


@pytest.mark.asyncio
@pytest.fixture
async def ipc_server(
monkeypatch,
p2p_server,
event_bus,
jsonrpc_ipc_pipe_path,
event_loop,
chain_with_block_validation):
Expand All @@ -77,7 +113,7 @@ async def ipc_server(
the course of all tests. It yields the IPC server only for monkeypatching purposes
'''

rpc = RPCServer(chain_with_block_validation, p2p_server.peer_pool)
rpc = RPCServer(chain_with_block_validation, event_bus)
ipc_server = IPCServer(rpc, jsonrpc_ipc_pipe_path, loop=event_loop)

asyncio.ensure_future(ipc_server.run(), loop=event_loop)
Expand Down
43 changes: 28 additions & 15 deletions tests/trinity/core/json-rpc/test_ipc.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
to_hex,
)

from p2p.events import (
PeerCountRequest,
PeerCountResponse,
)

from trinity.utils.version import construct_trinity_client_identifier


Expand All @@ -35,15 +40,6 @@ def build_request(method, params=[]):
return json.dumps(request).encode()


class MockPeerPool:

def __init__(self, peer_count=0):
self.peer_count = peer_count

def __len__(self):
return self.peer_count


def id_from_rpc_request(param):
if isinstance(param, bytes):
request = json.loads(param.decode())
Expand All @@ -68,6 +64,7 @@ async def get_ipc_response(
jsonrpc_ipc_pipe_path,
request_msg,
event_loop):

assert wait_for(jsonrpc_ipc_pipe_path), "IPC server did not successfully start with IPC file"

reader, writer = await asyncio.open_unix_connection(str(jsonrpc_ipc_pipe_path), loop=event_loop)
Expand Down Expand Up @@ -399,18 +396,27 @@ async def test_eth_call_with_contract_on_ipc(
assert result == expected


def mock_peer_count(count):
async def mock_event_bus_interaction(bus):
async for req in bus.stream(PeerCountRequest):
bus.broadcast(PeerCountResponse(count), req.broadcast_config())
break

return mock_event_bus_interaction


@pytest.mark.asyncio
@pytest.mark.parametrize(
'request_msg, mock_peer_pool, expected',
'request_msg, event_bus_setup_fn, expected',
(
(
build_request('net_peerCount'),
MockPeerPool(peer_count=1),
mock_peer_count(1),
{'result': '0x1', 'id': 3, 'jsonrpc': '2.0'},
),
(
build_request('net_peerCount'),
MockPeerPool(peer_count=0),
mock_peer_count(0),
{'result': '0x0', 'id': 3, 'jsonrpc': '2.0'},
),
),
Expand All @@ -422,10 +428,17 @@ async def test_peer_pool_over_ipc(
monkeypatch,
jsonrpc_ipc_pipe_path,
request_msg,
mock_peer_pool,
event_bus_setup_fn,
event_bus,
expected,
event_loop,
ipc_server):
monkeypatch.setattr(ipc_server.rpc.modules['net'], '_peer_pool', mock_peer_pool)
result = await get_ipc_response(jsonrpc_ipc_pipe_path, request_msg, event_loop)

asyncio.ensure_future(event_bus_setup_fn(event_bus))

result = await get_ipc_response(
jsonrpc_ipc_pipe_path,
request_msg,
event_loop
)
assert result == expected
5 changes: 4 additions & 1 deletion tests/trinity/json-fixtures-over-rpc/test_rpc_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
should_run_slow_tests,
)

from trinity.chains.mainnet import (
MainnetFullChain
)
from trinity.rpc import RPCServer
from trinity.rpc.format import (
empty_to_0x,
Expand Down Expand Up @@ -383,7 +386,7 @@ def chain(chain_without_block_validation):

@pytest.mark.asyncio
async def test_rpc_against_fixtures(chain, ipc_server, chain_fixture, fixture_data):
rpc = RPCServer(None)
rpc = RPCServer(MainnetFullChain(None))

setup_result, setup_error = await call_rpc(rpc, 'evm_resetToGenesisFixture', [chain_fixture])
assert setup_error is None and setup_result is True, "cannot load chain for %r" % fixture_data
Expand Down
10 changes: 10 additions & 0 deletions trinity/chains/coro.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from trinity.utils.async_dispatch import (
async_method,
)


class AsyncChainMixin:

coro_get_canonical_block_by_number = async_method('get_canonical_block_by_number')
coro_get_block_by_hash = async_method('get_block_by_hash')
coro_get_block_by_header = async_method('get_block_by_header')
Loading

0 comments on commit 90e39d7

Please sign in to comment.