// Copyright 2018 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. use crate::util::RwLock; use std::convert::From; use std::fs::File; use std::io; use std::net::{IpAddr, SocketAddr}; use std::sync::mpsc; use std::sync::Arc; use chrono::prelude::*; use crate::core::core::hash::Hash; use crate::core::pow::Difficulty; use crate::core::{core, ser}; use grin_store; /// Maximum number of block headers a peer should ever send pub const MAX_BLOCK_HEADERS: u32 = 512; /// Maximum number of block bodies a peer should ever ask for and send #[allow(dead_code)] pub const MAX_BLOCK_BODIES: u32 = 16; /// Maximum number of peer addresses a peer should ever send pub const MAX_PEER_ADDRS: u32 = 256; /// Maximum number of block header hashes to send as part of a locator pub const MAX_LOCATORS: u32 = 20; /// How long a banned peer should be banned for const BAN_WINDOW: i64 = 10800; /// The max peer count const PEER_MAX_COUNT: u32 = 25; /// min preferred peer count const PEER_MIN_PREFERRED_COUNT: u32 = 8; #[derive(Debug)] pub enum Error { Serialization(ser::Error), Connection(io::Error), /// Header type does not match the expected message type BadMessage, MsgLen, Banned, ConnectionClose, Timeout, Store(grin_store::Error), PeerWithSelf, NoDandelionRelay, ProtocolMismatch { us: u32, peer: u32, }, GenesisMismatch { us: Hash, peer: Hash, }, Send(String), PeerException, } impl From for Error { fn from(e: ser::Error) -> Error { Error::Serialization(e) } } impl From for Error { fn from(e: grin_store::Error) -> Error { Error::Store(e) } } impl From for Error { fn from(e: io::Error) -> Error { Error::Connection(e) } } impl From> for Error { fn from(e: mpsc::TrySendError) -> Error { Error::Send(e.to_string()) } } /// Configuration for the peer-to-peer server. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct P2PConfig { pub host: IpAddr, pub port: u16, /// Method used to get the list of seed nodes for initial bootstrap. #[serde(default)] pub seeding_type: Seeding, /// The list of seed nodes, if using Seeding as a seed type pub seeds: Option>, /// Capabilities expose by this node, also conditions which other peers this /// node will have an affinity toward when connection. pub capabilities: Capabilities, pub peers_allow: Option>, pub peers_deny: Option>, /// The list of preferred peers that we will try to connect to pub peers_preferred: Option>, pub ban_window: Option, pub peer_max_count: Option, pub peer_min_preferred_count: Option, pub dandelion_peer: Option, } /// Default address for peer-to-peer connections. impl Default for P2PConfig { fn default() -> P2PConfig { let ipaddr = "0.0.0.0".parse().unwrap(); P2PConfig { host: ipaddr, port: 3414, capabilities: Capabilities::FULL_NODE, seeding_type: Seeding::default(), seeds: None, peers_allow: None, peers_deny: None, peers_preferred: None, ban_window: None, peer_max_count: None, peer_min_preferred_count: None, dandelion_peer: None, } } } /// Note certain fields are options just so they don't have to be /// included in grin-server.toml, but we don't want them to ever return none impl P2PConfig { /// return ban window pub fn ban_window(&self) -> i64 { match self.ban_window { Some(n) => n, None => BAN_WINDOW, } } /// return peer_max_count pub fn peer_max_count(&self) -> u32 { match self.peer_max_count { Some(n) => n, None => PEER_MAX_COUNT, } } /// return peer_preferred_count pub fn peer_min_preferred_count(&self) -> u32 { match self.peer_min_preferred_count { Some(n) => n, None => PEER_MIN_PREFERRED_COUNT, } } } /// Type of seeding the server will use to find other peers on the network. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum Seeding { /// No seeding, mostly for tests that programmatically connect None, /// A list of seed addresses provided to the server List, /// Automatically get a list of seeds from multiple DNS DNSSeed, /// Mostly for tests, where connections are initiated programmatically Programmatic, } impl Default for Seeding { fn default() -> Seeding { Seeding::DNSSeed } } bitflags! { /// Options for what type of interaction a peer supports #[derive(Serialize, Deserialize)] pub struct Capabilities: u32 { /// We don't know (yet) what the peer can do. const UNKNOWN = 0b00000000; /// Can provide full history of headers back to genesis /// (for at least one arbitrary fork). const HEADER_HIST = 0b00000001; /// Can provide block headers and the TxHashSet for some recent-enough /// height. const TXHASHSET_HIST = 0b00000010; /// Can provide a list of healthy peers const PEER_LIST = 0b00000100; /// Can broadcast and request txs by kernel hash. const TX_KERNEL_HASH = 0b00001000; /// All nodes right now are "full nodes". /// Some nodes internally may maintain longer block histories (archival_mode) /// but we do not advertise this to other nodes. /// All nodes by default will accept lightweight "kernel first" tx broadcast. const FULL_NODE = Capabilities::HEADER_HIST.bits | Capabilities::TXHASHSET_HIST.bits | Capabilities::PEER_LIST.bits | Capabilities::TX_KERNEL_HASH.bits; } } /// Types of connection enum_from_primitive! { #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub enum Direction { Inbound = 0, Outbound = 1, } } /// Ban reason enum_from_primitive! { #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub enum ReasonForBan { None = 0, BadBlock = 1, BadCompactBlock = 2, BadBlockHeader = 3, BadTxHashSet = 4, ManualBan = 5, FraudHeight = 6, BadHandshake = 7, } } #[derive(Clone, Debug)] pub struct PeerLiveInfo { pub total_difficulty: Difficulty, pub height: u64, pub last_seen: DateTime, pub stuck_detector: DateTime, } /// General information about a connected peer that's useful to other modules. #[derive(Clone, Debug)] pub struct PeerInfo { pub capabilities: Capabilities, pub user_agent: String, pub version: u32, pub addr: SocketAddr, pub direction: Direction, pub live_info: Arc>, } impl PeerInfo { /// The current total_difficulty of the peer. pub fn total_difficulty(&self) -> Difficulty { self.live_info.read().total_difficulty } /// The current height of the peer. pub fn height(&self) -> u64 { self.live_info.read().height } /// Time of last_seen for this peer (via ping/pong). pub fn last_seen(&self) -> DateTime { self.live_info.read().last_seen } /// Update the total_difficulty, height and last_seen of the peer. /// Takes a write lock on the live_info. pub fn update(&self, height: u64, total_difficulty: Difficulty) { let mut live_info = self.live_info.write(); if total_difficulty != live_info.total_difficulty { live_info.stuck_detector = Utc::now(); } live_info.height = height; live_info.total_difficulty = total_difficulty; live_info.last_seen = Utc::now() } } /// Flatten out a PeerInfo and nested PeerLiveInfo (taking a read lock on it) /// so we can serialize/deserialize the data for the API and the TUI. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PeerInfoDisplay { pub capabilities: Capabilities, pub user_agent: String, pub version: u32, pub addr: SocketAddr, pub direction: Direction, pub total_difficulty: Difficulty, pub height: u64, } impl From for PeerInfoDisplay { fn from(info: PeerInfo) -> PeerInfoDisplay { PeerInfoDisplay { capabilities: info.capabilities.clone(), user_agent: info.user_agent.clone(), version: info.version.clone(), addr: info.addr.clone(), direction: info.direction.clone(), total_difficulty: info.total_difficulty(), height: info.height(), } } } /// The full txhashset data along with indexes required for a consumer to /// rewind to a consistent requested state. pub struct TxHashSetRead { /// Output tree index the receiver should rewind to pub output_index: u64, /// Kernel tree index the receiver should rewind to pub kernel_index: u64, /// Binary stream for the txhashset zipped data pub reader: File, } /// Bridge between the networking layer and the rest of the system. Handles the /// forwarding or querying of blocks and transactions from the network among /// other things. pub trait ChainAdapter: Sync + Send { /// Current total difficulty on our chain fn total_difficulty(&self) -> Difficulty; /// Current total height fn total_height(&self) -> u64; /// A valid transaction has been received from one of our peers fn transaction_received(&self, tx: core::Transaction, stem: bool); fn get_transaction(&self, kernel_hash: Hash) -> Option; fn tx_kernel_received(&self, kernel_hash: Hash, addr: SocketAddr); /// A block has been received from one of our peers. Returns true if the /// block could be handled properly and is not deemed defective by the /// chain. Returning false means the block will never be valid and /// may result in the peer being banned. fn block_received(&self, b: core::Block, addr: SocketAddr, was_requested: bool) -> bool; fn compact_block_received(&self, cb: core::CompactBlock, addr: SocketAddr) -> bool; fn header_received(&self, bh: core::BlockHeader, addr: SocketAddr) -> bool; /// A set of block header has been received, typically in response to a /// block /// header request. fn headers_received(&self, bh: &[core::BlockHeader], addr: SocketAddr) -> bool; /// Finds a list of block headers based on the provided locator. Tries to /// identify the common chain and gets the headers that follow it /// immediately. fn locate_headers(&self, locator: &[Hash]) -> Vec; /// Gets a full block by its hash. fn get_block(&self, h: Hash) -> Option; /// Provides a reading view into the current txhashset state as well as /// the required indexes for a consumer to rewind to a consistant state /// at the provided block hash. fn txhashset_read(&self, h: Hash) -> Option; /// Whether the node is ready to accept a new txhashset. If this isn't the /// case, the archive is provided without being requested and likely an /// attack attempt. This should be checked *before* downloading the whole /// state data. fn txhashset_receive_ready(&self) -> bool; /// Update txhashset downloading progress fn txhashset_download_update( &self, start_time: DateTime, downloaded_size: u64, total_size: u64, ) -> bool; /// Writes a reading view on a txhashset state that's been provided to us. /// If we're willing to accept that new state, the data stream will be /// read as a zip file, unzipped and the resulting state files should be /// rewound to the provided indexes. fn txhashset_write(&self, h: Hash, txhashset_data: File, peer_addr: SocketAddr) -> bool; } /// Additional methods required by the protocol that don't need to be /// externally implemented. pub trait NetAdapter: ChainAdapter { /// Find good peers we know with the provided capability and return their /// addresses. fn find_peer_addrs(&self, capab: Capabilities) -> Vec; /// A list of peers has been received from one of our peers. fn peer_addrs_received(&self, _: Vec); /// Heard total_difficulty from a connected peer (via ping/pong). fn peer_difficulty(&self, _: SocketAddr, _: Difficulty, _: u64); /// Is this peer currently banned? fn is_banned(&self, addr: SocketAddr) -> bool; }