smoldot/network/codec/block_announces.rs
1// Smoldot
2// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
3// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
4
5// This program is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14
15// You should have received a copy of the GNU General Public License
16// along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18use crate::header;
19
20use alloc::vec;
21use nom::Finish as _;
22
23/// Decoded handshake sent or received when opening a block announces notifications substream.
24#[derive(Debug, Copy, Clone, PartialEq, Eq)]
25pub struct BlockAnnouncesHandshakeRef<'a> {
26 /// Role a node reports playing on the network.
27 pub role: Role,
28
29 /// Height of the best block according to this node.
30 pub best_number: u64,
31
32 /// Hash of the best block according to this node.
33 pub best_hash: &'a [u8; 32],
34
35 /// Hash of the genesis block according to this node.
36 ///
37 /// > **Note**: This should be compared to the locally known genesis block hash, to make sure
38 /// > that both nodes are on the same chain.
39 pub genesis_hash: &'a [u8; 32],
40}
41
42/// Role a node reports playing on the network.
43///
44/// This role can be seen more or less as a priority level. The role a node reports cannot be
45/// trusted but is used as a hint. For example, Grandpa votes can be broadcasted with a higher
46/// priority to the nodes that report themselves as authorities.
47#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
48pub enum Role {
49 /// Authorities author blocks and participate in the consensus.
50 ///
51 /// This role is non-binding, and is used only as a hint to prioritize some nodes over others.
52 Authority,
53
54 /// Full nodes store the state of the chain. They are part of the infrastructure of the chain
55 /// in the sense that light nodes benefit from having a lot of full nodes to connect to.
56 ///
57 /// This role is non-binding, and is used only as a hint to prioritize some nodes over others.
58 Full,
59
60 /// Light nodes are the lowest priority nodes.
61 Light,
62}
63
64impl Role {
65 /// Returns the SCALE encoding of this enum. Always guaranteed to be one byte.
66 pub fn scale_encoding(&self) -> [u8; 1] {
67 match *self {
68 Role::Full => [0b1],
69 Role::Light => [0b10],
70 Role::Authority => [0b100],
71 }
72 }
73}
74
75/// Decoded block announcement notification.
76#[derive(Debug)]
77pub struct BlockAnnounceRef<'a> {
78 /// SCALE-encoded header in the announce.
79 ///
80 /// > **Note**: Due to the way block announces are encoded, the header is always guaranteed to
81 /// > decode correctly. However this shouldn't be relied upon.
82 pub scale_encoded_header: &'a [u8],
83
84 /// True if the block is the new best block of the announcer.
85 pub is_best: bool,
86 // TODO: missing a `Vec<u8>` field that SCALE-decodes into this type: https://github.com/paritytech/polkadot/blob/fff4635925c12c80717a524367687fcc304bcb13/node%2Fprimitives%2Fsrc%2Flib.rs#L87
87}
88
89/// Turns a block announcement into its SCALE-encoding ready to be sent over the wire.
90///
91/// This function returns an iterator of buffers. The encoded message consists in the
92/// concatenation of the buffers.
93pub fn encode_block_announce(announce: BlockAnnounceRef) -> impl Iterator<Item = impl AsRef<[u8]>> {
94 let is_best = if announce.is_best { [1u8] } else { [0u8] };
95
96 [
97 either::Left(announce.scale_encoded_header),
98 either::Right(is_best),
99 either::Right([0u8]),
100 ]
101 .into_iter()
102}
103
104/// Decodes a block announcement.
105pub fn decode_block_announce(
106 bytes: &'_ [u8],
107 block_number_bytes: usize,
108) -> Result<BlockAnnounceRef<'_>, DecodeBlockAnnounceError> {
109 let result: Result<_, nom::error::Error<_>> = nom::Parser::parse(
110 &mut nom::combinator::all_consuming(nom::combinator::complete(nom::combinator::map(
111 (
112 nom::combinator::recognize(|enc_hdr| {
113 match header::decode_partial(enc_hdr, block_number_bytes) {
114 Ok((hdr, rest)) => Ok((rest, hdr)),
115 Err(_) => Err(nom::Err::Failure(nom::error::make_error(
116 enc_hdr,
117 nom::error::ErrorKind::Verify,
118 ))),
119 }
120 }),
121 nom::branch::alt((
122 nom::combinator::map(nom::bytes::streaming::tag(&[0][..]), |_| false),
123 nom::combinator::map(nom::bytes::streaming::tag(&[1][..]), |_| true),
124 )),
125 crate::util::nom_bytes_decode,
126 ),
127 |(scale_encoded_header, is_best, _)| BlockAnnounceRef {
128 scale_encoded_header,
129 is_best,
130 },
131 ))),
132 bytes,
133 )
134 .finish();
135
136 match result {
137 Ok((_, ann)) => Ok(ann),
138 Err(err) => Err(DecodeBlockAnnounceError(err.code)),
139 }
140}
141
142/// Error potentially returned by [`decode_block_announces_handshake`].
143#[derive(Debug, derive_more::Display, derive_more::Error)]
144#[display("Failed to decode a block announcement")]
145// TODO: nom doesn't implement the Error trait at the moment; remove error(not(source)) eventually
146pub struct DecodeBlockAnnounceError(#[error(not(source))] nom::error::ErrorKind);
147
148/// Turns a block announces handshake into its SCALE-encoding ready to be sent over the wire.
149///
150/// This function returns an iterator of buffers. The encoded message consists in the
151/// concatenation of the buffers.
152pub fn encode_block_announces_handshake(
153 handshake: BlockAnnouncesHandshakeRef,
154 block_number_bytes: usize,
155) -> impl Iterator<Item = impl AsRef<[u8]>> {
156 let mut header = vec![0; 1 + block_number_bytes];
157 header[0] = handshake.role.scale_encoding()[0];
158 // TODO: what to do if the best number doesn't fit in the given size? right now we just wrap around
159 header[1..].copy_from_slice(&handshake.best_number.to_le_bytes()[..block_number_bytes]);
160
161 [
162 either::Left(header),
163 either::Right(handshake.best_hash),
164 either::Right(handshake.genesis_hash),
165 ]
166 .into_iter()
167}
168
169/// Decodes a SCALE-encoded block announces handshake.
170pub fn decode_block_announces_handshake(
171 expected_block_number_bytes: usize,
172 handshake: &'_ [u8],
173) -> Result<BlockAnnouncesHandshakeRef<'_>, BlockAnnouncesHandshakeDecodeError> {
174 let result: Result<_, nom::error::Error<_>> = nom::Parser::parse(
175 &mut nom::combinator::all_consuming(nom::combinator::complete(nom::combinator::map(
176 (
177 nom::branch::alt((
178 nom::combinator::map(nom::bytes::streaming::tag(&[0b1][..]), |_| Role::Full),
179 nom::combinator::map(nom::bytes::streaming::tag(&[0b10][..]), |_| Role::Light),
180 nom::combinator::map(nom::bytes::streaming::tag(&[0b100][..]), |_| {
181 Role::Authority
182 }),
183 )),
184 crate::util::nom_varsize_number_decode_u64(expected_block_number_bytes),
185 nom::bytes::streaming::take(32u32),
186 nom::bytes::streaming::take(32u32),
187 ),
188 |(role, best_number, best_hash, genesis_hash)| BlockAnnouncesHandshakeRef {
189 role,
190 best_number,
191 best_hash: TryFrom::try_from(best_hash).unwrap(),
192 genesis_hash: TryFrom::try_from(genesis_hash).unwrap(),
193 },
194 ))),
195 handshake,
196 )
197 .finish();
198
199 match result {
200 Ok((_, hs)) => Ok(hs),
201 Err(err) => Err(BlockAnnouncesHandshakeDecodeError(err.code)),
202 }
203}
204
205/// Error potentially returned by [`decode_block_announces_handshake`].
206#[derive(Debug, Clone, derive_more::Display, derive_more::Error)]
207#[display("Failed to decode a block announces handshake")]
208// TODO: nom doesn't implement the Error trait at the moment; remove error(not(source)) eventually
209pub struct BlockAnnouncesHandshakeDecodeError(#[error(not(source))] nom::error::ErrorKind);