|
| 1 | +use crate::{ |
| 2 | + dag::DagUnit, |
| 3 | + dissemination::{Request, Response}, |
| 4 | + runway::{NewestUnitResponse, Salt}, |
| 5 | + units::{UnitCoord, UnitStore, UnitWithParents, WrappedUnit}, |
| 6 | + Data, Hasher, MultiKeychain, NodeIndex, Signed, |
| 7 | +}; |
| 8 | +use std::marker::PhantomData; |
| 9 | +use thiserror::Error; |
| 10 | + |
| 11 | +/// A responder that is able to answer requests for data about units. |
| 12 | +pub struct Responder<H: Hasher, D: Data, MK: MultiKeychain> { |
| 13 | + keychain: MK, |
| 14 | + _phantom: PhantomData<(H, D)>, |
| 15 | +} |
| 16 | + |
| 17 | +/// Ways in which it can be impossible for us to respond to a request. |
| 18 | +#[derive(Eq, Error, Debug, PartialEq)] |
| 19 | +pub enum Error<H: Hasher> { |
| 20 | + #[error("no canonical unit at {0}")] |
| 21 | + NoCanonicalAt(UnitCoord), |
| 22 | + #[error("unit with hash {0:?} not known")] |
| 23 | + UnknownUnit(H::Hash), |
| 24 | +} |
| 25 | + |
| 26 | +impl<H: Hasher, D: Data, MK: MultiKeychain> Responder<H, D, MK> { |
| 27 | + /// Create a new responder. |
| 28 | + pub fn new(keychain: MK) -> Self { |
| 29 | + Responder { |
| 30 | + keychain, |
| 31 | + _phantom: PhantomData, |
| 32 | + } |
| 33 | + } |
| 34 | + |
| 35 | + fn index(&self) -> NodeIndex { |
| 36 | + self.keychain.index() |
| 37 | + } |
| 38 | + |
| 39 | + fn on_request_coord( |
| 40 | + &self, |
| 41 | + coord: UnitCoord, |
| 42 | + units: &UnitStore<DagUnit<H, D, MK>>, |
| 43 | + ) -> Result<Response<H, D, MK::Signature>, Error<H>> { |
| 44 | + units |
| 45 | + .canonical_unit(coord) |
| 46 | + .map(|unit| Response::Coord(unit.clone().unpack().into())) |
| 47 | + .ok_or(Error::NoCanonicalAt(coord)) |
| 48 | + } |
| 49 | + |
| 50 | + fn on_request_parents( |
| 51 | + &self, |
| 52 | + hash: H::Hash, |
| 53 | + units: &UnitStore<DagUnit<H, D, MK>>, |
| 54 | + ) -> Result<Response<H, D, MK::Signature>, Error<H>> { |
| 55 | + units |
| 56 | + .unit(&hash) |
| 57 | + .map(|unit| { |
| 58 | + let parents = unit |
| 59 | + .parents() |
| 60 | + .values() |
| 61 | + .map(|parent_hash| { |
| 62 | + units |
| 63 | + .unit(parent_hash) |
| 64 | + .expect("Units are added to the store in order.") |
| 65 | + .clone() |
| 66 | + .unpack() |
| 67 | + .into_unchecked() |
| 68 | + }) |
| 69 | + .collect(); |
| 70 | + Response::Parents(hash, parents) |
| 71 | + }) |
| 72 | + .ok_or(Error::UnknownUnit(hash)) |
| 73 | + } |
| 74 | + |
| 75 | + fn on_request_newest( |
| 76 | + &self, |
| 77 | + requester: NodeIndex, |
| 78 | + salt: Salt, |
| 79 | + units: &UnitStore<DagUnit<H, D, MK>>, |
| 80 | + ) -> Response<H, D, MK::Signature> { |
| 81 | + let unit = units |
| 82 | + .canonical_units(requester) |
| 83 | + .last() |
| 84 | + .map(|unit| unit.clone().unpack().into_unchecked()); |
| 85 | + let response = NewestUnitResponse::new(requester, self.index(), unit, salt); |
| 86 | + |
| 87 | + let signed_response = Signed::sign(response, &self.keychain).into_unchecked(); |
| 88 | + Response::NewestUnit(signed_response) |
| 89 | + } |
| 90 | + |
| 91 | + /// Handle an incoming request returning either the appropriate response or an error if we |
| 92 | + /// aren't able to help. |
| 93 | + pub fn handle_request( |
| 94 | + &self, |
| 95 | + request: Request<H>, |
| 96 | + units: &UnitStore<DagUnit<H, D, MK>>, |
| 97 | + ) -> Result<Response<H, D, MK::Signature>, Error<H>> { |
| 98 | + use Request::*; |
| 99 | + match request { |
| 100 | + Coord(coord) => self.on_request_coord(coord, units), |
| 101 | + Parents(hash) => self.on_request_parents(hash, units), |
| 102 | + NewestUnit(node_id, salt) => Ok(self.on_request_newest(node_id, salt, units)), |
| 103 | + } |
| 104 | + } |
| 105 | +} |
| 106 | + |
| 107 | +#[cfg(test)] |
| 108 | +mod test { |
| 109 | + use crate::{ |
| 110 | + dissemination::{ |
| 111 | + responder::{Error, Responder}, |
| 112 | + Request, Response, |
| 113 | + }, |
| 114 | + units::{ |
| 115 | + random_full_parent_reconstrusted_units_up_to, TestingDagUnit, Unit, UnitCoord, |
| 116 | + UnitStore, UnitWithParents, WrappedUnit, |
| 117 | + }, |
| 118 | + NodeCount, NodeIndex, |
| 119 | + }; |
| 120 | + use aleph_bft_mock::{Data, Hasher64, Keychain}; |
| 121 | + use std::iter::zip; |
| 122 | + |
| 123 | + const NODE_ID: NodeIndex = NodeIndex(0); |
| 124 | + const NODE_COUNT: NodeCount = NodeCount(7); |
| 125 | + |
| 126 | + fn setup() -> ( |
| 127 | + Responder<Hasher64, Data, Keychain>, |
| 128 | + UnitStore<TestingDagUnit>, |
| 129 | + Vec<Keychain>, |
| 130 | + ) { |
| 131 | + let keychains = Keychain::new_vec(NODE_COUNT); |
| 132 | + ( |
| 133 | + Responder::new(keychains[NODE_ID.0]), |
| 134 | + UnitStore::new(NODE_COUNT), |
| 135 | + keychains, |
| 136 | + ) |
| 137 | + } |
| 138 | + |
| 139 | + #[test] |
| 140 | + fn empty_fails_to_respond_to_coords() { |
| 141 | + let (responder, store, _) = setup(); |
| 142 | + let coord = UnitCoord::new(0, NodeIndex(1)); |
| 143 | + let request = Request::Coord(coord); |
| 144 | + match responder.handle_request(request, &store) { |
| 145 | + Ok(response) => panic!("Unexpected response: {:?}.", response), |
| 146 | + Err(err) => assert_eq!(err, Error::NoCanonicalAt(coord)), |
| 147 | + } |
| 148 | + } |
| 149 | + |
| 150 | + #[test] |
| 151 | + fn empty_fails_to_respond_to_parents() { |
| 152 | + let (responder, store, keychains) = setup(); |
| 153 | + let session_id = 2137; |
| 154 | + let hash = |
| 155 | + random_full_parent_reconstrusted_units_up_to(1, NODE_COUNT, session_id, &keychains) |
| 156 | + .last() |
| 157 | + .expect("just created this round") |
| 158 | + .last() |
| 159 | + .expect("the round has at least one unit") |
| 160 | + .hash(); |
| 161 | + let request = Request::Parents(hash); |
| 162 | + match responder.handle_request(request, &store) { |
| 163 | + Ok(response) => panic!("Unexpected response: {:?}.", response), |
| 164 | + Err(err) => assert_eq!(err, Error::UnknownUnit(hash)), |
| 165 | + } |
| 166 | + } |
| 167 | + |
| 168 | + #[test] |
| 169 | + fn empty_newest_responds_with_no_units() { |
| 170 | + let (responder, store, keychains) = setup(); |
| 171 | + let requester = NodeIndex(1); |
| 172 | + let request = Request::NewestUnit(requester, rand::random()); |
| 173 | + let response = responder |
| 174 | + .handle_request(request, &store) |
| 175 | + .expect("newest unit requests always get a response"); |
| 176 | + match response { |
| 177 | + Response::NewestUnit(newest_unit_response) => { |
| 178 | + let checked_newest_unit_response = newest_unit_response |
| 179 | + .check(&keychains[NODE_ID.0]) |
| 180 | + .expect("should sign correctly"); |
| 181 | + assert_eq!( |
| 182 | + checked_newest_unit_response.as_signable().requester(), |
| 183 | + requester |
| 184 | + ); |
| 185 | + assert!(checked_newest_unit_response |
| 186 | + .as_signable() |
| 187 | + .included_data() |
| 188 | + .is_empty()); |
| 189 | + } |
| 190 | + other => panic!("Unexpected response: {:?}.", other), |
| 191 | + } |
| 192 | + } |
| 193 | + |
| 194 | + #[test] |
| 195 | + fn responds_to_coords_when_possible() { |
| 196 | + let (responder, mut store, keychains) = setup(); |
| 197 | + let session_id = 2137; |
| 198 | + let coord = UnitCoord::new(3, NodeIndex(1)); |
| 199 | + let units = random_full_parent_reconstrusted_units_up_to( |
| 200 | + coord.round() + 1, |
| 201 | + NODE_COUNT, |
| 202 | + session_id, |
| 203 | + &keychains, |
| 204 | + ); |
| 205 | + for round_units in &units { |
| 206 | + for unit in round_units { |
| 207 | + store.insert(unit.clone()); |
| 208 | + } |
| 209 | + } |
| 210 | + let request = Request::Coord(coord); |
| 211 | + let response = responder |
| 212 | + .handle_request(request, &store) |
| 213 | + .expect("should successfully respond"); |
| 214 | + match response { |
| 215 | + Response::Coord(unit) => assert_eq!( |
| 216 | + unit, |
| 217 | + units[coord.round() as usize][coord.creator().0] |
| 218 | + .clone() |
| 219 | + .unpack() |
| 220 | + .into_unchecked() |
| 221 | + ), |
| 222 | + other => panic!("Unexpected response: {:?}.", other), |
| 223 | + } |
| 224 | + } |
| 225 | + |
| 226 | + #[test] |
| 227 | + fn fails_to_responds_to_too_new_coords() { |
| 228 | + let (responder, mut store, keychains) = setup(); |
| 229 | + let session_id = 2137; |
| 230 | + let coord = UnitCoord::new(3, NodeIndex(1)); |
| 231 | + let units = random_full_parent_reconstrusted_units_up_to( |
| 232 | + coord.round() - 1, |
| 233 | + NODE_COUNT, |
| 234 | + session_id, |
| 235 | + &keychains, |
| 236 | + ); |
| 237 | + for round_units in &units { |
| 238 | + for unit in round_units { |
| 239 | + store.insert(unit.clone()); |
| 240 | + } |
| 241 | + } |
| 242 | + let request = Request::Coord(coord); |
| 243 | + match responder.handle_request(request, &store) { |
| 244 | + Ok(response) => panic!("Unexpected response: {:?}.", response), |
| 245 | + Err(err) => assert_eq!(err, Error::NoCanonicalAt(coord)), |
| 246 | + } |
| 247 | + } |
| 248 | + |
| 249 | + #[test] |
| 250 | + fn responds_to_parents_when_possible() { |
| 251 | + let (responder, mut store, keychains) = setup(); |
| 252 | + let session_id = 2137; |
| 253 | + let units = |
| 254 | + random_full_parent_reconstrusted_units_up_to(5, NODE_COUNT, session_id, &keychains); |
| 255 | + for round_units in &units { |
| 256 | + for unit in round_units { |
| 257 | + store.insert(unit.clone()); |
| 258 | + } |
| 259 | + } |
| 260 | + let requested_unit = units |
| 261 | + .last() |
| 262 | + .expect("just created this round") |
| 263 | + .last() |
| 264 | + .expect("the round has at least one unit") |
| 265 | + .clone(); |
| 266 | + let request = Request::Parents(requested_unit.hash()); |
| 267 | + let response = responder |
| 268 | + .handle_request(request, &store) |
| 269 | + .expect("should successfully respond"); |
| 270 | + match response { |
| 271 | + Response::Parents(response_hash, parents) => { |
| 272 | + assert_eq!(response_hash, requested_unit.hash()); |
| 273 | + assert_eq!(parents.len(), requested_unit.parents().size().0); |
| 274 | + for (parent, parent_hash) in zip(parents, requested_unit.parents().values()) { |
| 275 | + assert_eq!(&parent.as_signable().hash(), parent_hash); |
| 276 | + } |
| 277 | + } |
| 278 | + other => panic!("Unexpected response: {:?}.", other), |
| 279 | + } |
| 280 | + } |
| 281 | + |
| 282 | + #[test] |
| 283 | + fn fails_to_respond_to_unknown_parents() { |
| 284 | + let (responder, mut store, keychains) = setup(); |
| 285 | + let session_id = 2137; |
| 286 | + let units = |
| 287 | + random_full_parent_reconstrusted_units_up_to(5, NODE_COUNT, session_id, &keychains); |
| 288 | + for round_units in &units { |
| 289 | + for unit in round_units { |
| 290 | + store.insert(unit.clone()); |
| 291 | + } |
| 292 | + } |
| 293 | + let hash = |
| 294 | + random_full_parent_reconstrusted_units_up_to(1, NODE_COUNT, session_id, &keychains) |
| 295 | + .last() |
| 296 | + .expect("just created this round") |
| 297 | + .last() |
| 298 | + .expect("the round has at least one unit") |
| 299 | + .hash(); |
| 300 | + let request = Request::Parents(hash); |
| 301 | + match responder.handle_request(request, &store) { |
| 302 | + Ok(response) => panic!("Unexpected response: {:?}.", response), |
| 303 | + Err(err) => assert_eq!(err, Error::UnknownUnit(hash)), |
| 304 | + } |
| 305 | + } |
| 306 | + |
| 307 | + #[test] |
| 308 | + fn responds_to_existing_newest() { |
| 309 | + let (responder, mut store, keychains) = setup(); |
| 310 | + let session_id = 2137; |
| 311 | + let units = |
| 312 | + random_full_parent_reconstrusted_units_up_to(5, NODE_COUNT, session_id, &keychains); |
| 313 | + for round_units in &units { |
| 314 | + for unit in round_units { |
| 315 | + store.insert(unit.clone()); |
| 316 | + } |
| 317 | + } |
| 318 | + let requester = NodeIndex(1); |
| 319 | + let request = Request::NewestUnit(requester, rand::random()); |
| 320 | + let response = responder |
| 321 | + .handle_request(request, &store) |
| 322 | + .expect("newest unit requests always get a response"); |
| 323 | + match response { |
| 324 | + Response::NewestUnit(newest_unit_response) => { |
| 325 | + let checked_newest_unit_response = newest_unit_response |
| 326 | + .check(&keychains[NODE_ID.0]) |
| 327 | + .expect("should sign correctly"); |
| 328 | + assert_eq!( |
| 329 | + checked_newest_unit_response.as_signable().requester(), |
| 330 | + requester |
| 331 | + ); |
| 332 | + // unfortunately there is no easy way to check whether the response contains a unit |
| 333 | + // with its API :/ |
| 334 | + } |
| 335 | + other => panic!("Unexpected response: {:?}.", other), |
| 336 | + } |
| 337 | + } |
| 338 | +} |
0 commit comments