Skip to content
This repository was archived by the owner on Jul 1, 2021. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions newsfragments/1543.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Implement the ``eth/64`` networking protocol according to
`EIP 2124 <https://eips.ethereum.org/EIPS/eip-2124>`_
6 changes: 3 additions & 3 deletions p2p/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,15 +483,15 @@ class HandshakeReceiptAPI(ABC):
THandshakeReceipt = TypeVar('THandshakeReceipt', bound=HandshakeReceiptAPI)


class HandshakerAPI(ABC):
class HandshakerAPI(ABC, Generic[TProtocol]):
logger: ExtendedDebugLogger

protocol_class: Type[ProtocolAPI]
protocol_class: Type[TProtocol]

@abstractmethod
async def do_handshake(self,
multiplexer: MultiplexerAPI,
protocol: ProtocolAPI) -> HandshakeReceiptAPI:
protocol: TProtocol) -> HandshakeReceiptAPI:
"""
Perform the actual handshake for the protocol.
"""
Expand Down
10 changes: 6 additions & 4 deletions p2p/handshake.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
MultiplexerAPI,
NodeAPI,
TransportAPI,
TProtocol,
ProtocolAPI,
)
from p2p.connection import Connection
from p2p.constants import DEVP2P_V5
Expand Down Expand Up @@ -58,7 +60,7 @@
)


class Handshaker(HandshakerAPI):
class Handshaker(HandshakerAPI[TProtocol]):
"""
Base class that handles the handshake for a given protocol. The primary
justification for this class's existence is to house parameters that are
Expand Down Expand Up @@ -179,7 +181,7 @@ async def _do_p2p_handshake(transport: TransportAPI,

async def negotiate_protocol_handshakes(transport: TransportAPI,
p2p_handshake_params: DevP2PHandshakeParams,
protocol_handshakers: Sequence[HandshakerAPI],
protocol_handshakers: Sequence[HandshakerAPI[ProtocolAPI]],
token: CancelToken,
) -> Tuple[MultiplexerAPI, DevP2PReceipt, Tuple[HandshakeReceiptAPI, ...]]: # noqa: E501
"""
Expand Down Expand Up @@ -294,7 +296,7 @@ async def negotiate_protocol_handshakes(transport: TransportAPI,
async def dial_out(remote: NodeAPI,
private_key: keys.PrivateKey,
p2p_handshake_params: DevP2PHandshakeParams,
protocol_handshakers: Sequence[HandshakerAPI],
protocol_handshakers: Sequence[HandshakerAPI[ProtocolAPI]],
token: CancelToken) -> ConnectionAPI:
"""
Perform the auth and P2P handshakes with the given remote.
Expand Down Expand Up @@ -345,7 +347,7 @@ async def receive_dial_in(reader: asyncio.StreamReader,
writer: asyncio.StreamWriter,
private_key: keys.PrivateKey,
p2p_handshake_params: DevP2PHandshakeParams,
protocol_handshakers: Sequence[HandshakerAPI],
protocol_handshakers: Sequence[HandshakerAPI[ProtocolAPI]],
token: CancelToken) -> Connection:
transport = await Transport.receive_connection(
reader=reader,
Expand Down
8 changes: 7 additions & 1 deletion p2p/multiplexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

from eth_utils import get_extended_debug_logger, ValidationError
from eth_utils.toolz import cons
import rlp

from p2p.abc import (
CommandAPI,
Expand All @@ -36,6 +37,7 @@
CorruptTransport,
UnknownProtocol,
UnknownProtocolCommand,
MalformedMessage,
)
from p2p.p2p_proto import BaseP2PProtocol
from p2p.transport_state import TransportState
Expand Down Expand Up @@ -84,7 +86,11 @@ async def stream_transport_messages(transport: TransportAPI,

msg_proto = command_id_cache[command_id]
command_type = msg_proto.get_command_type_for_command_id(command_id)
cmd = command_type.decode(msg, msg_proto.snappy_support)

try:
cmd = command_type.decode(msg, msg_proto.snappy_support)
except rlp.exceptions.DeserializationError as err:
raise MalformedMessage(f"Failed to decode {msg} for {command_type}") from err

yield msg_proto, cmd

Expand Down
6 changes: 3 additions & 3 deletions p2p/peer.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,10 @@ def setup_connection_tracker(self) -> BaseConnectionTracker:
return NoopConnectionTracker()

def __str__(self) -> str:
return f"{self.__class__.__name__} {self.session}"
return f"{self.__class__.__name__} {self.sub_proto} {self.session}"

def __repr__(self) -> str:
return f"{self.__class__.__name__} {self.session!r}"
return f"{self.__class__.__name__} {self.sub_proto!r} {self.session!r}"

#
# Proxy Transport attributes
Expand Down Expand Up @@ -474,7 +474,7 @@ def __init__(self,
self.event_bus = event_bus

@abstractmethod
async def get_handshakers(self) -> Tuple[HandshakerAPI, ...]:
async def get_handshakers(self) -> Tuple[HandshakerAPI[ProtocolAPI], ...]:
...

async def handshake(self, remote: NodeAPI) -> BasePeer:
Expand Down
6 changes: 3 additions & 3 deletions p2p/tools/factories/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from eth_keys import keys

from p2p.abc import ConnectionAPI, HandshakerAPI, NodeAPI
from p2p.abc import ConnectionAPI, HandshakerAPI, NodeAPI, ProtocolAPI
from p2p.connection import Connection
from p2p.constants import DEVP2P_V5
from p2p.handshake import (
Expand All @@ -24,8 +24,8 @@

@asynccontextmanager
async def ConnectionPairFactory(*,
alice_handshakers: Tuple[HandshakerAPI, ...] = (),
bob_handshakers: Tuple[HandshakerAPI, ...] = (),
alice_handshakers: Tuple[HandshakerAPI[ProtocolAPI], ...] = (),
bob_handshakers: Tuple[HandshakerAPI[ProtocolAPI], ...] = (),
alice_remote: NodeAPI = None,
alice_private_key: keys.PrivateKey = None,
alice_client_version: str = 'alice',
Expand Down
2 changes: 1 addition & 1 deletion p2p/tools/handshake.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from p2p.receipt import HandshakeReceipt


class NoopHandshaker(Handshaker):
class NoopHandshaker(Handshaker[ProtocolAPI]):
def __init__(self, protocol_class: Type[ProtocolAPI]) -> None:
self.protocol_class = protocol_class

Expand Down
9 changes: 5 additions & 4 deletions p2p/tools/paragon/peer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import (
Iterable,
Tuple,
Any,
)

from cached_property import cached_property
Expand All @@ -9,7 +10,7 @@
from p2p.handshake import Handshaker
from p2p.receipt import HandshakeReceipt

from p2p.abc import BehaviorAPI, ProtocolAPI
from p2p.abc import BehaviorAPI
from p2p.constants import DEVP2P_V5
from p2p.peer import (
BasePeer,
Expand Down Expand Up @@ -46,20 +47,20 @@ def __init__(self,
super().__init__(client_version_string, listen_port, p2p_version)


class ParagonHandshaker(Handshaker):
class ParagonHandshaker(Handshaker[ParagonProtocol]):
protocol_class = ParagonProtocol

async def do_handshake(self,
multiplexer: MultiplexerAPI,
protocol: ProtocolAPI) -> HandshakeReceipt:
protocol: ParagonProtocol) -> HandshakeReceipt:
return HandshakeReceipt(protocol)


class ParagonPeerFactory(BasePeerFactory):
peer_class = ParagonPeer
context: ParagonContext

async def get_handshakers(self) -> Tuple[HandshakerAPI, ...]:
async def get_handshakers(self) -> Tuple[HandshakerAPI[Any], ...]:
return (ParagonHandshaker(),)


Expand Down
104 changes: 89 additions & 15 deletions tests/core/p2p-proto/test_eth_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,34 @@
latest_mainnet_at,
mine_block,
)
from eth.vm.forks import MuirGlacierVM, PetersburgVM

from trinity._utils.assertions import assert_type_equality
from trinity.db.eth1.header import AsyncHeaderDB
from trinity.protocol.eth.api import ETHAPI
from trinity.exceptions import WrongForkIDFailure
from trinity.protocol.eth.api import ETHAPI, ETHV63API
from trinity.protocol.eth.commands import (
GetBlockHeaders,
GetNodeData,
NewBlock,
Status,
StatusV63,
)
from trinity.protocol.eth.handshaker import ETHHandshakeReceipt
from trinity.protocol.eth.handshaker import ETHHandshakeReceipt, ETHV63HandshakeReceipt
from trinity.protocol.eth.proto import ETHProtocolV63, ETHProtocol

from trinity.tools.factories.common import (
BlockHeadersQueryFactory,
)
from trinity.tools.factories.eth import (
StatusPayloadFactory,
StatusV63PayloadFactory,
)
from trinity.tools.factories import (
BlockHashFactory,
ChainContextFactory,
ETHPeerPairFactory,
ETHV63PeerPairFactory,
)


Expand Down Expand Up @@ -60,8 +66,23 @@ def alice_chain(bob_chain):


@pytest.fixture
async def alice_and_bob(alice_chain, bob_chain):
pair_factory = ETHPeerPairFactory(
def alice_chain_on_fork(bob_chain):
bob_genesis = bob_chain.headerdb.get_canonical_block_header_by_number(0)

chain = build(
MiningChain,
latest_mainnet_at(0),
disable_pow_check(),
genesis(params={"timestamp": bob_genesis.timestamp}),
mine_block(),
)

return chain


@pytest.fixture(params=(ETHV63PeerPairFactory, ETHPeerPairFactory))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, this causes all fixtures that use this one to be parameterized as well, I guess?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct! I tried to keep the tests structure similar to the LESV1 vs LESV2 tests that use the same pattern?

async def alice_and_bob(alice_chain, bob_chain, request):
pair_factory = request.param(
alice_client_version='alice',
alice_peer_context=ChainContextFactory(headerdb=AsyncHeaderDB(alice_chain.headerdb.db)),
bob_client_version='bob',
Expand All @@ -83,14 +104,48 @@ def bob(alice_and_bob):
return bob


@pytest.fixture
def protocol_specific_classes(alice):
if alice.connection.has_protocol(ETHProtocolV63):
return ETHV63API, ETHV63HandshakeReceipt, StatusV63, StatusV63PayloadFactory
elif alice.connection.has_protocol(ETHProtocol):
return ETHAPI, ETHHandshakeReceipt, Status, StatusPayloadFactory
else:
raise Exception("No ETH protocol found")


@pytest.fixture
def ETHAPI_class(protocol_specific_classes):
api_class, _, _, _ = protocol_specific_classes
return api_class


@pytest.fixture
def ETHHandshakeReceipt_class(protocol_specific_classes):
_, receipt_class, _, _ = protocol_specific_classes
return receipt_class


@pytest.fixture
def Status_class(protocol_specific_classes):
_, _, status_class, _ = protocol_specific_classes
return status_class


@pytest.fixture
def StatusPayloadFactory_class(protocol_specific_classes):
_, _, _, status_payload_factory_class = protocol_specific_classes
return status_payload_factory_class


@pytest.mark.asyncio
async def test_eth_api_properties(alice):
assert alice.connection.has_logic(ETHAPI.name)
eth_api = alice.connection.get_logic(ETHAPI.name, ETHAPI)
async def test_eth_api_properties(alice, ETHAPI_class, ETHHandshakeReceipt_class):
assert alice.connection.has_logic(ETHAPI_class.name)
eth_api = alice.connection.get_logic(ETHAPI_class.name, ETHAPI_class)

assert eth_api is alice.eth_api

eth_receipt = alice.connection.get_receipt_by_type(ETHHandshakeReceipt)
eth_receipt = alice.connection.get_receipt_by_type(ETHHandshakeReceipt_class)

assert eth_api.network_id == eth_receipt.network_id
assert eth_api.genesis_hash == eth_receipt.genesis_hash
Expand All @@ -101,7 +156,7 @@ async def test_eth_api_properties(alice):


@pytest.mark.asyncio
async def test_eth_api_head_info_updates_with_newblock(alice, bob, bob_chain):
async def test_eth_api_head_info_updates_with_newblock(alice, bob, bob_chain, ETHAPI_class):
# mine two blocks on bob's chain
bob_chain = build(
bob_chain,
Expand All @@ -118,8 +173,8 @@ async def _handle_new_block(connection, msg):

bob_genesis = bob_chain.headerdb.get_canonical_block_header_by_number(0)

bob_eth_api = bob.connection.get_logic(ETHAPI.name, ETHAPI)
alice_eth_api = alice.connection.get_logic(ETHAPI.name, ETHAPI)
bob_eth_api = bob.connection.get_logic(ETHAPI_class.name, ETHAPI_class)
alice_eth_api = alice.connection.get_logic(ETHAPI_class.name, ETHAPI_class)

assert alice_eth_api.head_info.head_hash == bob_genesis.hash
assert alice_eth_api.head_info.head_td == bob_genesis.difficulty
Expand All @@ -143,19 +198,19 @@ async def _handle_new_block(connection, msg):


@pytest.mark.asyncio
async def test_eth_api_send_status(alice, bob):
payload = StatusPayloadFactory()
async def test_eth_api_send_status(alice, bob, StatusPayloadFactory_class, Status_class):
payload = StatusPayloadFactory_class()

command_fut = asyncio.Future()

async def _handle_cmd(connection, cmd):
command_fut.set_result(cmd)

bob.connection.add_command_handler(Status, _handle_cmd)
bob.connection.add_command_handler(Status_class, _handle_cmd)
alice.eth_api.send_status(payload)

result = await asyncio.wait_for(command_fut, timeout=1)
assert isinstance(result, Status)
assert isinstance(result, Status_class)
assert_type_equality(payload, result.payload)


Expand Down Expand Up @@ -196,3 +251,22 @@ async def _handle_cmd(connection, cmd):
result = await asyncio.wait_for(command_fut, timeout=1)
assert isinstance(result, GetBlockHeaders)
assert_type_equality(payload, result.payload)


@pytest.mark.asyncio
async def test_handshake_with_incompatible_fork_id(alice_chain, bob_chain):

alice_chain = build(
alice_chain,
mine_block()
)

pair_factory = ETHPeerPairFactory(
alice_peer_context=ChainContextFactory(
headerdb=AsyncHeaderDB(alice_chain.headerdb.db),
vm_configuration=((1, PetersburgVM), (2, MuirGlacierVM))
),
)
with pytest.raises(WrongForkIDFailure):
async with pair_factory as (alice, bob):
pass
3 changes: 3 additions & 0 deletions tests/core/p2p-proto/test_eth_proto.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
Receipts,
Status,
Transactions,
StatusV63,
)

from trinity.tools.factories import (
Expand All @@ -32,12 +33,14 @@
NewBlockHashFactory,
NewBlockPayloadFactory,
StatusPayloadFactory,
StatusV63PayloadFactory,
)


@pytest.mark.parametrize(
'command_type,payload',
(
(StatusV63, StatusV63PayloadFactory()),
(Status, StatusPayloadFactory()),
(NewBlockHashes, tuple(NewBlockHashFactory.create_batch(2))),
(Transactions, tuple(BaseTransactionFieldsFactory.create_batch(2))),
Expand Down
Loading