bitcoin/p2p/
message_network.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! Bitcoin network-related network messages.
4//!
5//! This module defines network messages which describe peers and their
6//! capabilities.
7//!
8
9use hashes::sha256d;
10use io::{Read, Write};
11
12use crate::consensus::{encode, Decodable, Encodable, ReadExt};
13use crate::internal_macros::impl_consensus_encoding;
14use crate::p2p;
15use crate::p2p::address::Address;
16use crate::p2p::ServiceFlags;
17use crate::prelude::*;
18
19/// Some simple messages
20
21/// The `version` message
22#[derive(PartialEq, Eq, Clone, Debug)]
23pub struct VersionMessage {
24    /// The P2P network protocol version
25    pub version: u32,
26    /// A bitmask describing the services supported by this node
27    pub services: ServiceFlags,
28    /// The time at which the `version` message was sent
29    pub timestamp: i64,
30    /// The network address of the peer receiving the message
31    pub receiver: Address,
32    /// The network address of the peer sending the message
33    pub sender: Address,
34    /// A random nonce used to detect loops in the network
35    ///
36    /// The nonce can be used to detect situations when a node accidentally
37    /// connects to itself. Set it to a random value and, in case of incoming
38    /// connections, compare the value - same values mean self-connection.
39    ///
40    /// If your application uses P2P to only fetch the data and doesn't listen
41    /// you may just set it to 0.
42    pub nonce: u64,
43    /// A string describing the peer's software
44    pub user_agent: String,
45    /// The height of the maximum-work blockchain that the peer is aware of
46    pub start_height: i32,
47    /// Whether the receiving peer should relay messages to the sender; used
48    /// if the sender is bandwidth-limited and would like to support bloom
49    /// filtering. Defaults to false.
50    pub relay: bool,
51}
52
53impl VersionMessage {
54    /// Constructs a new `version` message with `relay` set to false
55    pub fn new(
56        services: ServiceFlags,
57        timestamp: i64,
58        receiver: Address,
59        sender: Address,
60        nonce: u64,
61        user_agent: String,
62        start_height: i32,
63    ) -> VersionMessage {
64        VersionMessage {
65            version: p2p::PROTOCOL_VERSION,
66            services,
67            timestamp,
68            receiver,
69            sender,
70            nonce,
71            user_agent,
72            start_height,
73            relay: false,
74        }
75    }
76}
77
78impl_consensus_encoding!(
79    VersionMessage,
80    version,
81    services,
82    timestamp,
83    receiver,
84    sender,
85    nonce,
86    user_agent,
87    start_height,
88    relay
89);
90
91/// message rejection reason as a code
92#[derive(PartialEq, Eq, Clone, Copy, Debug)]
93pub enum RejectReason {
94    /// malformed message
95    Malformed = 0x01,
96    /// invalid message
97    Invalid = 0x10,
98    /// obsolete message
99    Obsolete = 0x11,
100    /// duplicate message
101    Duplicate = 0x12,
102    /// nonstandard transaction
103    NonStandard = 0x40,
104    /// an output is below dust limit
105    Dust = 0x41,
106    /// insufficient fee
107    Fee = 0x42,
108    /// checkpoint
109    Checkpoint = 0x43,
110}
111
112impl Encodable for RejectReason {
113    fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
114        w.write_all(&[*self as u8])?;
115        Ok(1)
116    }
117}
118
119impl Decodable for RejectReason {
120    fn consensus_decode<R: Read + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
121        Ok(match r.read_u8()? {
122            0x01 => RejectReason::Malformed,
123            0x10 => RejectReason::Invalid,
124            0x11 => RejectReason::Obsolete,
125            0x12 => RejectReason::Duplicate,
126            0x40 => RejectReason::NonStandard,
127            0x41 => RejectReason::Dust,
128            0x42 => RejectReason::Fee,
129            0x43 => RejectReason::Checkpoint,
130            _ => return Err(encode::Error::ParseFailed("unknown reject code")),
131        })
132    }
133}
134
135/// Reject message might be sent by peers rejecting one of our messages
136#[derive(PartialEq, Eq, Clone, Debug)]
137pub struct Reject {
138    /// message type rejected
139    pub message: Cow<'static, str>,
140    /// reason of rejection as code
141    pub ccode: RejectReason,
142    /// reason of rejectection
143    pub reason: Cow<'static, str>,
144    /// reference to rejected item
145    pub hash: sha256d::Hash,
146}
147
148impl_consensus_encoding!(Reject, message, ccode, reason, hash);
149
150#[cfg(test)]
151mod tests {
152    use hashes::sha256d;
153    use hex::test_hex_unwrap as hex;
154
155    use super::{Reject, RejectReason, VersionMessage};
156    use crate::consensus::encode::{deserialize, serialize};
157    use crate::p2p::ServiceFlags;
158
159    #[test]
160    fn version_message_test() {
161        // This message is from my satoshi node, morning of May 27 2014
162        let from_sat = hex!("721101000100000000000000e6e0845300000000010000000000000000000000000000000000ffff0000000000000100000000000000fd87d87eeb4364f22cf54dca59412db7208d47d920cffce83ee8102f5361746f7368693a302e392e39392f2c9f040001");
163
164        let decode: Result<VersionMessage, _> = deserialize(&from_sat);
165        assert!(decode.is_ok());
166        let real_decode = decode.unwrap();
167        assert_eq!(real_decode.version, 70002);
168        assert_eq!(real_decode.services, ServiceFlags::NETWORK);
169        assert_eq!(real_decode.timestamp, 1401217254);
170        // address decodes should be covered by Address tests
171        assert_eq!(real_decode.nonce, 16735069437859780935);
172        assert_eq!(real_decode.user_agent, "/Satoshi:0.9.99/".to_string());
173        assert_eq!(real_decode.start_height, 302892);
174        assert!(real_decode.relay);
175
176        assert_eq!(serialize(&real_decode), from_sat);
177    }
178
179    #[test]
180    fn reject_message_test() {
181        let reject_tx_conflict = hex!("027478121474786e2d6d656d706f6f6c2d636f6e666c69637405df54d3860b3c41806a3546ab48279300affacf4b88591b229141dcf2f47004");
182        let reject_tx_nonfinal = hex!("02747840096e6f6e2d66696e616c259bbe6c83db8bbdfca7ca303b19413dc245d9f2371b344ede5f8b1339a5460b");
183
184        let decode_result_conflict: Result<Reject, _> = deserialize(&reject_tx_conflict);
185        let decode_result_nonfinal: Result<Reject, _> = deserialize(&reject_tx_nonfinal);
186
187        assert!(decode_result_conflict.is_ok());
188        assert!(decode_result_nonfinal.is_ok());
189
190        let conflict = decode_result_conflict.unwrap();
191        assert_eq!("tx", conflict.message);
192        assert_eq!(RejectReason::Duplicate, conflict.ccode);
193        assert_eq!("txn-mempool-conflict", conflict.reason);
194        assert_eq!(
195            "0470f4f2dc4191221b59884bcffaaf00932748ab46356a80413c0b86d354df05"
196                .parse::<sha256d::Hash>()
197                .unwrap(),
198            conflict.hash
199        );
200
201        let nonfinal = decode_result_nonfinal.unwrap();
202        assert_eq!("tx", nonfinal.message);
203        assert_eq!(RejectReason::NonStandard, nonfinal.ccode);
204        assert_eq!("non-final", nonfinal.reason);
205        assert_eq!(
206            "0b46a539138b5fde4e341b37f2d945c23d41193b30caa7fcbd8bdb836cbe9b25"
207                .parse::<sha256d::Hash>()
208                .unwrap(),
209            nonfinal.hash
210        );
211
212        assert_eq!(serialize(&conflict), reject_tx_conflict);
213        assert_eq!(serialize(&nonfinal), reject_tx_nonfinal);
214    }
215}