Description
P2P Networking proposal and tracking
RLPx Peer Connection
The RLPx peer connection should implement framed encryption as specified by RLPx.
Client
The ECIES transport should implement AsyncRead
and AsyncWrite
, so the p2p
connection can use it.
Long term, it would be nice to interact with framed encryption like this (similar to TLS libraries):
let tcp_conn = TcpStream::connect("127.0.0.1:30303")?;
let rlpx = RLPxConnector::new(secret_key, peer_id)?;
let mut client: ECIESStream = rlpx.connect(tcp_conn).await?;
client.write_all(b"hello").await?;
let mut buffer = [0; 16];
let n = client.read(&mut buffer[..]).await?;
High priority tasks:
- Implement
AsyncRead
onECIESStream<Io>
- Implement
AsyncWrite
onECIESStream<Io>
Lower priority:
- Refactor
ECIESStream<Io>
to support the above UX.
Server
The RLPx Server should also work with any transport that implements AsyncRead
and AsyncWrite
.
Longer term, it should be possible to serve RLPx like this:
let acceptor = RLPxAcceptor::new(secret_key);
let listener = TcpListener::bind(&addr).await?;
// roughly based off of the design of tokio::net::TcpListener
loop {
let (client: ECIESStream, remote_addr) = acceptor.accept(listener).await?;
process_socket(client).await;
}
Low priority tasks:
- Implement
accept
pattern onECIESStream
for an arbitraryIo
that implementsAsyncRead
andAsyncWrite
.
p2p
Peer Connection
The RLPx peer connection will contain a server and client portion, and will take care of capability negotiation (the p2p
capability), pings, and disconnects.
Both the client and server should be capable of working with a type that implements AsyncRead
and AsyncWrite
, meaning the transport does not need to implement framed encryption.
This makes it slightly easier to test, since it would not require creating an ECIESStream
.
Client
We should be able to do something roughly similar to the above designs, but we need an extra task to handle driving the p2p
state.
This is roughly inspired by the design of hyper
's client::conn
.
// it would be nice to have utilities to properly generate a `Hello` message
let hello = Hello { ... };
let tcp_conn = TcpStream::connect("127.0.0.1:30303")?;
let p2p_config = P2PConfig::new(hello);
let (mut client: P2PStream, p2p_state) = p2p_config.handshake(tcp_conn).await?;
tokio::spawn(async move {
if let Err(e) = p2p_state.await {
println!("Error! maybe ping error, etc: {}", e);
}
});
// just an example, real messages are likely to be much more concrete
let message: Message = MessageBuilder::new()
.with_hello(hello)
.request_id(0xf)
.message("hello");
// todo: may want to consider a message size limit on read
// ECIESStream can implement AsyncRead/AsyncWrite, but `p2p` changes the interface from working mainly with bufs to also including a message id
// luckily the stream after being decrypted should have a header that tells us the length
client.send(message).await?;
let peer_message = client.read().await?;
Tasks:
- Implement RLPx connection with the above UX.
Server
This is roughly inspired by the design of hyper
's server::conn
.
let mut listener = TcpListener::bind(&addr).await?;
let server = P2PServer::new(hello);
loop {
let (stream, remote_addr) = tcp_listener.accept().await?
let mut client: P2PStream = server.accept(stream).await
process_message(client).await;
}
Tasks:
- Implement
P2PServer
eth
Peer Connection
This contains a client and server, just like the RLPx and p2p
connection.
Instead of a p2p
handshake, both a p2p
and eth
handshake are performed.
Client
// would also be nice to have a sensible way to generate `Status` messages
let status = Status { ... };
let tcp_conn = TcpStream::connect("127.0.0.1:30303")?;
// this should create a `P2PConfig` under the hood
let eth_config = EthConfig::new(hello, status);
let (mut client: EthStream, p2p_state) = eth_config.handshake(tcp_conn).await?;
tokio::spawn(async move {
if let Err(e) = p2p_state.await {
println!("Error! maybe ping error, maybe disconnect, etc: {}", e);
}
});
// since we are in a subprotocol now we can create a more concrete message
let get_pooled_transactions: GetPooledTransactions = vec!["0x7cab7b2c72d1c6cc8539ee5e4b8af9b86a130d63b1428c2c52c4454ead266ff4"].into();
// TODO: should the client impl just take care of choosing request ids and abstract out multiplexing?
let pooled_transactions = client.send_request(get_pooled_transactions).await?;
let mut hashes = client.stream_hashes().await?;
// if there were another client its stream could be joined
while let Some(hash) = hashes.next().await {
// ...
}
Tasks:
- Implement
EthConfig
- Implement
EthStream
Server
The eth
server will wait for incoming connections and stream incoming
let mut listener = TcpListener::bind(&addr).await?;
// this call should make sure that the `Hello` message includes `eth` as a capability
let server = EthServer::new(hello, status);
loop {
let (stream, remote_addr) = tcp_listener.accept().await?
// this should create a `P2PServer` under the hood
let mut client: EthStream = server.accept(stream).await
process_message(client).await;
}
Tasks:
- Implement
EthServer
eth
Wire Protocol
NOTE See: https://github.com/rjected/ethp2p
We need to provide RLP encoding and decoding for each of the eth
protocol messages and each of the types they contain.
-
RequestPair
type -
Status
(feat: add eth-wire #20) - NewBlockHashes
- Transactions
- GetBlockHeaders
- BlockHeaders
- GetBlockBodies
- BlockBodies
- NewBlock
- NewPooledTransactionHashes
- GetPooledTransactions
- PooledTransactions
- GetReceipts
- What is this request/response pair used for?
- Receipts
Once this is done, we can also create a Message
type can be created that is the sum type of all the protocol messages.
eth
Network abstraction
Finally, the Network
abstraction will integrate all of the above components to provide a reliable connection to a set of peers on the network.
This will implement interfaces that other components (e.g. staged sync, txpool) will use.
The network abstraction will also integrate with discovery mechanisms and manage the discovery state.
This will act as a Server
(an eth
server) if the user is able to accept incoming connections and will use discovery methods to create outgoing connections, which will yield a set of EthStream
s.
Current questions
How will we implement the Engine API? It prompts the execution layer to request blocks, which happens over eth
p2p.
It could also just use the API provided by the Network
abstraction.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status