diff --git a/p2p/Cargo.toml b/p2p/Cargo.toml new file mode 100644 index 000000000..866c13e03 --- /dev/null +++ b/p2p/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "grin_p2p" +version = "0.1.0" +authors = ["Ignotus Peverell "] + +[dependencies] +bitflags = "^0.7.0" +byteorder = "^0.5" +mioco = "^0.8" +time = "^0.1" + +grin_core = { path = "../core" } diff --git a/p2p/rustfmt.toml b/p2p/rustfmt.toml new file mode 100644 index 000000000..e26e77f1d --- /dev/null +++ b/p2p/rustfmt.toml @@ -0,0 +1,3 @@ +hard_tabs = true +wrap_comments = true +write_mode = "Overwrite" diff --git a/p2p/src/lib.rs b/p2p/src/lib.rs new file mode 100644 index 000000000..c4e657ad9 --- /dev/null +++ b/p2p/src/lib.rs @@ -0,0 +1,29 @@ +// Copyright 2016 The Grin Developers +// +// 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. + +//! Networking code to connect to other peers and exchange block, transactions, +//! etc. + +#![deny(non_upper_case_globals)] +#![deny(non_camel_case_types)] +#![deny(non_snake_case)] +#![deny(unused_mut)] +#![warn(missing_docs)] + +#[macro_use] +extern crate bitflags; +extern crate mioco; + +mod msg; +mod server; diff --git a/p2p/src/msg.rs b/p2p/src/msg.rs new file mode 100644 index 000000000..9d3901209 --- /dev/null +++ b/p2p/src/msg.rs @@ -0,0 +1,50 @@ +// Copyright 2016 The Grin Developers +// +// 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. + +//! Message types that transit over the network and related serialization code. + +use std::net::SocketAddr; + +use core::ser::{Writeable, Readable, Writer, Reader}; + +mod ErrCodes { + const UNSUPPORTED_VERSION: u32 = 100; +} + +bitflags! { + /// Options for block validation + pub flags Capabilities: u32 { + /// Runs with the easier version of the Proof of Work, mostly to make testing easier. + const FULL_SYNC = 0b00000001, + } +} + +pub struct Hand { + version: u32, + capabilities: Capabilities, + sender_addr: SocketAddr, + receiver_addr: SocketAddr, + user_agent: String, +} + +pub struct Shake { + version: u32, + capabilities: Capabilities, + user_agent: String, +} + +pub struct PeerError { + code: u32, + message: String, +} diff --git a/p2p/src/server.rs b/p2p/src/server.rs new file mode 100644 index 000000000..25d2d3e1e --- /dev/null +++ b/p2p/src/server.rs @@ -0,0 +1,124 @@ +// Copyright 2016 The Grin Developers +// +// 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. + +//! Grin server implementation, accepts incoming connections and connects to +//! other peers in the network, handling handshake and message receive/send. + +use str::net::SocketAddr; +use std::str::FromStr; +use time::Duration; + +use mioco::tcp::{TcpListener, TcpStream, Shutdown}; + +use core::ser::{serialize, deserialize}; +use msg::*; + +const DEFAULT_LISTEN_ADDR: &'static str = "127.0.0.1:555"; +const PROTOCOL_VERSION: u32 = 1; +const USER_AGENT: &'static str = "MW/Grin 0.1"; + +// replace with some config lookup or something +fn listen_addr() -> SocketAddr { + FromStr::from_str(DEFAULT_LISTEN_ADDR).unwrap() +} + +/// The local representation of a remotely connected peer. Handles most +/// low-level network communication. +struct Peer { + conn: TcpStream, + reader: BufReader, + capabilities: Capabilities, + user_agent: String, +} + +impl Peer { + /// Create a new local peer instance connected to a remote peer with the + /// provided TcpStream. + fn new(conn: TcpStream) -> Peer { + // don't wait on read for more than 2 seconds by default + conn.set_read_timeout(Some(Duration::seconds(2))); + + Peer { + conn: conn, + reader: BufReader::new(conn), + } + } + + /// Handles connecting to a new remote peer, starting the version handshake. + fn connect(&mut self) { + serialize(self.conn, + &Hand { + version: PROTOCOL_VERSION, + capabilities: FULL_SYNC, + sender_addr: listen_addr(), + receiver_addr: conn.peer_addr(), + user_agent: USER_AGENT, + }); + let shake = deserialize(self.reader); + if shake.version != 1 { + self.close(ErrCodes::UNSUPPORTED_VERSION, + format!("Unsupported version: {}, ours: {})", + shake.version, + PROTOCOL_VERSION)); + return; + } + self.capabilities = shake.capabilities; + self.user_agent = shake.user_agent; + + self.accept_loop(); + } + + /// Handles receiving a connection from a new remote peer that started the + /// version handshake. + fn handshake(&mut self) {} + + fn accept_loop(&mut self) { + loop { + let msg = deserialize(self.reader); + } + } + + fn close(err_code: u32, explanation: &'static str) { + serialize(self.conn, + &Err { + code: err_code, + message: explanation, + }); + self.conn.shutdown(Shutdown::Both); + } +} + +pub struct Server { +} + +impl Server { + /// Creates a new p2p server. Opens a TCP port to allow incoming + /// connections and starts the bootstrapping process to find peers. + pub fn new() -> Server { + mioco::start(|| -> io::Result<()> { + let addr = "127.0.0.1:3414".parse().unwrap(); + let listener = try!(TcpListener::bind(&addr)); + info!("P2P server started on {}", addr); + + loop { + let mut conn = try!(listener.accept()); + mioco::spawn(move || -> io::Result<()> { + Peer::new(conn).connect(); + }); + } + }) + .unwrap() + .unwrap(); + } +}