1use crate::types::evonode_status::EvoNodeStatus;
2use crate::types::CurrentQuorumsInfo;
3use crate::Error;
4use dapi_grpc::platform::v0::ResponseMetadata;
5use dapi_grpc::platform::v0::{self as platform};
6use dapi_grpc::tonic::async_trait;
7use dpp::bls_signatures::PublicKey as BlsPublicKey;
8use dpp::core_types::validator::v0::ValidatorV0;
9use dpp::core_types::validator_set::v0::ValidatorSetV0;
10use dpp::core_types::validator_set::ValidatorSet;
11use dpp::dashcore::hashes::Hash;
12use dpp::dashcore::{Network, ProTxHash, PubkeyHash, QuorumHash};
13use dpp::version::PlatformVersion;
14use std::collections::BTreeMap;
15
16pub trait FromUnproved<Req> {
41 type Request;
43 type Response;
45
46 fn maybe_from_unproved<I: Into<Self::Request>, O: Into<Self::Response>>(
61 request: I,
62 response: O,
63 network: Network,
64 platform_version: &PlatformVersion,
65 ) -> Result<Option<Self>, Error>
66 where
67 Self: Sized,
68 {
69 Self::maybe_from_unproved_with_metadata(request, response, network, platform_version)
70 .map(|maybe_result| maybe_result.0)
71 }
72
73 fn maybe_from_unproved_with_metadata<I: Into<Self::Request>, O: Into<Self::Response>>(
88 request: I,
89 response: O,
90 network: Network,
91 platform_version: &PlatformVersion,
92 ) -> Result<(Option<Self>, ResponseMetadata), Error>
93 where
94 Self: Sized;
95
96 fn from_unproved<I: Into<Self::Request>, O: Into<Self::Response>>(
111 request: I,
112 response: O,
113 network: Network,
114 platform_version: &PlatformVersion,
115 ) -> Result<Self, Error>
116 where
117 Self: Sized,
118 {
119 Self::maybe_from_unproved(request, response, network, platform_version)?
120 .ok_or(Error::NotFound)
121 }
122
123 fn from_unproved_with_metadata<I: Into<Self::Request>, O: Into<Self::Response>>(
138 request: I,
139 response: O,
140 network: Network,
141 platform_version: &PlatformVersion,
142 ) -> Result<(Self, ResponseMetadata), Error>
143 where
144 Self: Sized,
145 {
146 let (main_item, response_metadata) =
147 Self::maybe_from_unproved_with_metadata(request, response, network, platform_version)?;
148 Ok((main_item.ok_or(Error::NotFound)?, response_metadata))
149 }
150}
151
152impl FromUnproved<platform::GetCurrentQuorumsInfoRequest> for CurrentQuorumsInfo {
153 type Request = platform::GetCurrentQuorumsInfoRequest;
154 type Response = platform::GetCurrentQuorumsInfoResponse;
155
156 fn maybe_from_unproved_with_metadata<I: Into<Self::Request>, O: Into<Self::Response>>(
157 _request: I,
158 response: O,
159 _network: Network,
160 _platform_version: &PlatformVersion,
161 ) -> Result<(Option<Self>, ResponseMetadata), Error>
162 where
163 Self: Sized,
164 {
165 let response: platform::GetCurrentQuorumsInfoResponse = response.into();
167
168 let metadata = match &response.version {
170 Some(platform::get_current_quorums_info_response::Version::V0(ref v0)) => {
171 v0.metadata.clone()
172 }
173 None => None,
174 }
175 .ok_or(Error::EmptyResponseMetadata)?;
176
177 let info = match response.version.ok_or(Error::EmptyVersion)? {
179 platform::get_current_quorums_info_response::Version::V0(v0) => {
180 let quorum_hashes = v0
182 .quorum_hashes
183 .into_iter()
184 .map(|q_hash| {
185 let mut q_hash_array = [0u8; 32];
186 if q_hash.len() != 32 {
187 return Err(Error::ProtocolError {
188 error: "Invalid quorum_hash length".to_string(),
189 });
190 }
191 q_hash_array.copy_from_slice(&q_hash);
192 Ok(q_hash_array)
193 })
194 .collect::<Result<Vec<[u8; 32]>, Error>>()?;
195
196 let mut current_quorum_hash = [0u8; 32];
198 if v0.current_quorum_hash.len() != 32 {
199 return Err(Error::ProtocolError {
200 error: "Invalid current_quorum_hash length".to_string(),
201 });
202 }
203 current_quorum_hash.copy_from_slice(&v0.current_quorum_hash);
204
205 let mut last_block_proposer = [0u8; 32];
206 if v0.last_block_proposer.len() != 32 {
207 return Err(Error::ProtocolError {
208 error: "Invalid last_block_proposer length".to_string(),
209 });
210 }
211 last_block_proposer.copy_from_slice(&v0.last_block_proposer);
212
213 let validator_sets =
215 v0.validator_sets
216 .into_iter()
217 .map(|vs| {
218 let mut quorum_hash = [0u8; 32];
220 quorum_hash.copy_from_slice(&vs.quorum_hash);
221
222 let members = vs
224 .members
225 .into_iter()
226 .map(|member| {
227 let pro_tx_hash = ProTxHash::from_slice(&member.pro_tx_hash)
228 .map_err(|_| Error::ProtocolError {
229 error: "Invalid ProTxHash format".to_string(),
230 })?;
231 let validator = ValidatorV0 {
232 pro_tx_hash,
233 public_key: None, node_ip: member.node_ip,
235 node_id: PubkeyHash::from_slice(&[0; 20]).expect("expected to make pub key hash from 20 byte empty array"), core_port: 0, platform_http_port: 0, platform_p2p_port: 0, is_banned: member.is_banned,
240 };
241 Ok((pro_tx_hash, validator))
242 })
243 .collect::<Result<BTreeMap<ProTxHash, ValidatorV0>, Error>>()?;
244
245 Ok(ValidatorSet::V0(ValidatorSetV0 {
246 quorum_hash: QuorumHash::from_slice(quorum_hash.as_slice())
247 .map_err(|_| Error::ProtocolError {
248 error: "Invalid Quorum Hash format".to_string(),
249 })?,
250 quorum_index: None, core_height: vs.core_height,
252 members,
253 threshold_public_key: BlsPublicKey::try_from(
254 vs.threshold_public_key.as_slice(),
255 )
256 .map_err(|_| Error::ProtocolError {
257 error: "Invalid BlsPublicKey format".to_string(),
258 })?,
259 }))
260 })
261 .collect::<Result<Vec<ValidatorSet>, Error>>()?;
262
263 Ok::<CurrentQuorumsInfo, Error>(CurrentQuorumsInfo {
265 quorum_hashes,
266 current_quorum_hash,
267 validator_sets,
268 last_block_proposer,
269 last_platform_block_height: metadata.height,
270 last_core_block_height: metadata.core_chain_locked_height,
271 })
272 }
273 }?;
274
275 Ok((Some(info), metadata))
276 }
277}
278
279#[async_trait]
280impl FromUnproved<platform::GetStatusRequest> for EvoNodeStatus {
281 type Request = platform::GetStatusRequest;
282 type Response = platform::GetStatusResponse;
283
284 fn maybe_from_unproved_with_metadata<I: Into<Self::Request>, O: Into<Self::Response>>(
285 _request: I,
286 response: O,
287 _network: Network,
288 _platform_version: &PlatformVersion,
289 ) -> Result<(Option<Self>, ResponseMetadata), Error>
290 where
291 Self: Sized,
292 {
293 let status = Self::try_from(response.into())?;
294 Ok((Some(status), Default::default()))
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302 use dapi_grpc::platform::v0::{
303 get_current_quorums_info_response, get_status_response, ResponseMetadata,
304 };
305 use dpp::bls_signatures::{Bls12381G2Impl, SecretKey};
306 use dpp::version::PlatformVersion;
307
308 fn generate_valid_bls_public_key_bytes(seed: u8) -> Vec<u8> {
311 let mut secret_bytes = [0u8; 32];
312 secret_bytes[31] = seed.max(1); let sk: SecretKey<Bls12381G2Impl> =
314 SecretKey::<Bls12381G2Impl>::from_be_bytes(&secret_bytes)
315 .into_option()
316 .expect("valid secret key");
317 sk.public_key().0.to_compressed().to_vec()
318 }
319
320 fn build_valid_quorums_info_response() -> platform::GetCurrentQuorumsInfoResponse {
323 let quorum_hash = vec![1u8; 32];
324 let current_quorum_hash = vec![2u8; 32];
325 let last_block_proposer = vec![3u8; 32];
326 let pro_tx_hash = vec![4u8; 32];
327 let threshold_public_key = generate_valid_bls_public_key_bytes(42);
328
329 let member = get_current_quorums_info_response::ValidatorV0 {
330 pro_tx_hash: pro_tx_hash.clone(),
331 node_ip: "127.0.0.1".to_string(),
332 is_banned: false,
333 };
334
335 let validator_set = get_current_quorums_info_response::ValidatorSetV0 {
336 quorum_hash: quorum_hash.clone(),
337 core_height: 100,
338 members: vec![member],
339 threshold_public_key,
340 };
341
342 let v0 = get_current_quorums_info_response::GetCurrentQuorumsInfoResponseV0 {
343 quorum_hashes: vec![quorum_hash],
344 current_quorum_hash,
345 validator_sets: vec![validator_set],
346 last_block_proposer,
347 metadata: Some(ResponseMetadata {
348 height: 500,
349 core_chain_locked_height: 200,
350 epoch: 10,
351 time_ms: 1234567890,
352 protocol_version: 1,
353 chain_id: "dash-testnet-1".to_string(),
354 }),
355 };
356
357 platform::GetCurrentQuorumsInfoResponse {
358 version: Some(get_current_quorums_info_response::Version::V0(v0)),
359 }
360 }
361
362 fn build_valid_status_response() -> platform::GetStatusResponse {
364 use dapi_grpc::platform::v0::get_status_response::get_status_response_v0;
365
366 let software = get_status_response_v0::version::Software {
367 dapi: "1.0.0".to_string(),
368 drive: Some("2.0.0".to_string()),
369 tenderdash: Some("0.14.0".to_string()),
370 };
371
372 let tenderdash_protocol =
373 get_status_response_v0::version::protocol::Tenderdash { p2p: 8, block: 11 };
374
375 let drive_protocol = get_status_response_v0::version::protocol::Drive {
376 latest: 5,
377 current: 4,
378 next_epoch: 5,
379 };
380
381 let protocol = get_status_response_v0::version::Protocol {
382 tenderdash: Some(tenderdash_protocol),
383 drive: Some(drive_protocol),
384 };
385
386 let version = get_status_response_v0::Version {
387 software: Some(software),
388 protocol: Some(protocol),
389 };
390
391 let time = get_status_response_v0::Time {
392 local: 1700000000,
393 block: Some(1699999900),
394 genesis: Some(1690000000),
395 epoch: Some(42),
396 };
397
398 let node = get_status_response_v0::Node {
399 id: vec![10u8; 20],
400 pro_tx_hash: Some(vec![11u8; 32]),
401 };
402
403 let chain = get_status_response_v0::Chain {
404 catching_up: false,
405 latest_block_hash: vec![20u8; 32],
406 latest_app_hash: vec![21u8; 32],
407 latest_block_height: 1000,
408 earliest_block_hash: vec![22u8; 32],
409 earliest_app_hash: vec![23u8; 32],
410 earliest_block_height: 1,
411 max_peer_block_height: 1001,
412 core_chain_locked_height: Some(500),
413 };
414
415 let network = get_status_response_v0::Network {
416 chain_id: "dash-testnet-1".to_string(),
417 peers_count: 25,
418 listening: true,
419 };
420
421 let state_sync = get_status_response_v0::StateSync {
422 total_synced_time: 3600,
423 remaining_time: 120,
424 total_snapshots: 5,
425 chunk_process_avg_time: 50,
426 snapshot_height: 900,
427 snapshot_chunks_count: 100,
428 backfilled_blocks: 800,
429 backfill_blocks_total: 1000,
430 };
431
432 let v0 = get_status_response::GetStatusResponseV0 {
433 version: Some(version),
434 node: Some(node),
435 chain: Some(chain),
436 network: Some(network),
437 state_sync: Some(state_sync),
438 time: Some(time),
439 };
440
441 platform::GetStatusResponse {
442 version: Some(get_status_response::Version::V0(v0)),
443 }
444 }
445
446 #[test]
447 fn test_current_quorums_info_valid_response() {
448 let request = platform::GetCurrentQuorumsInfoRequest { version: None };
449 let response = build_valid_quorums_info_response();
450 let platform_version = PlatformVersion::latest();
451
452 let result = CurrentQuorumsInfo::maybe_from_unproved_with_metadata(
453 request,
454 response,
455 Network::Testnet,
456 platform_version,
457 );
458
459 let (maybe_info, metadata) = result.expect("should parse valid response");
460 let info = maybe_info.expect("should contain CurrentQuorumsInfo");
461
462 assert_eq!(info.quorum_hashes.len(), 1);
463 assert_eq!(info.quorum_hashes[0], [1u8; 32]);
464 assert_eq!(info.current_quorum_hash, [2u8; 32]);
465 assert_eq!(info.last_block_proposer, [3u8; 32]);
466 assert_eq!(info.validator_sets.len(), 1);
467 assert_eq!(info.last_platform_block_height, 500);
468 assert_eq!(info.last_core_block_height, 200);
469 assert_eq!(metadata.height, 500);
470 assert_eq!(metadata.core_chain_locked_height, 200);
471 }
472
473 #[test]
474 fn test_current_quorums_info_invalid_quorum_hash_length() {
475 let mut response = build_valid_quorums_info_response();
476
477 if let Some(get_current_quorums_info_response::Version::V0(ref mut v0)) = response.version {
479 v0.quorum_hashes = vec![vec![0u8; 16]]; }
481
482 let request = platform::GetCurrentQuorumsInfoRequest { version: None };
483 let platform_version = PlatformVersion::latest();
484
485 let result = CurrentQuorumsInfo::maybe_from_unproved_with_metadata(
486 request,
487 response,
488 Network::Testnet,
489 platform_version,
490 );
491
492 let err = result.expect_err("should fail for invalid quorum_hash length");
493 let err_string = err.to_string();
494 assert!(
495 err_string.contains("Invalid quorum_hash length"),
496 "unexpected error: {err_string}"
497 );
498 }
499
500 #[test]
501 fn test_current_quorums_info_invalid_current_quorum_hash_length() {
502 let mut response = build_valid_quorums_info_response();
503
504 if let Some(get_current_quorums_info_response::Version::V0(ref mut v0)) = response.version {
506 v0.current_quorum_hash = vec![0u8; 10]; }
508
509 let request = platform::GetCurrentQuorumsInfoRequest { version: None };
510 let platform_version = PlatformVersion::latest();
511
512 let result = CurrentQuorumsInfo::maybe_from_unproved_with_metadata(
513 request,
514 response,
515 Network::Testnet,
516 platform_version,
517 );
518
519 let err = result.expect_err("should fail for invalid current_quorum_hash length");
520 let err_string = err.to_string();
521 assert!(
522 err_string.contains("Invalid current_quorum_hash length"),
523 "unexpected error: {err_string}"
524 );
525 }
526
527 #[test]
528 fn test_current_quorums_info_invalid_last_block_proposer() {
529 let mut response = build_valid_quorums_info_response();
530
531 if let Some(get_current_quorums_info_response::Version::V0(ref mut v0)) = response.version {
533 v0.last_block_proposer = vec![0u8; 5]; }
535
536 let request = platform::GetCurrentQuorumsInfoRequest { version: None };
537 let platform_version = PlatformVersion::latest();
538
539 let result = CurrentQuorumsInfo::maybe_from_unproved_with_metadata(
540 request,
541 response,
542 Network::Testnet,
543 platform_version,
544 );
545
546 let err = result.expect_err("should fail for invalid last_block_proposer length");
547 let err_string = err.to_string();
548 assert!(
549 err_string.contains("Invalid last_block_proposer length"),
550 "unexpected error: {err_string}"
551 );
552 }
553
554 #[test]
555 fn test_current_quorums_info_none_metadata() {
556 let mut response = build_valid_quorums_info_response();
557
558 if let Some(get_current_quorums_info_response::Version::V0(ref mut v0)) = response.version {
560 v0.metadata = None;
561 }
562
563 let request = platform::GetCurrentQuorumsInfoRequest { version: None };
564 let platform_version = PlatformVersion::latest();
565
566 let result = CurrentQuorumsInfo::maybe_from_unproved_with_metadata(
567 request,
568 response,
569 Network::Testnet,
570 platform_version,
571 );
572
573 let err = result.expect_err("should fail when metadata is missing");
574 let err_string = err.to_string();
575 assert!(
576 err_string.contains("empty response metadata"),
577 "unexpected error: {err_string}"
578 );
579 }
580
581 #[test]
582 fn test_evo_node_status_valid_response() {
583 let request = platform::GetStatusRequest { version: None };
584 let response = build_valid_status_response();
585 let platform_version = PlatformVersion::latest();
586
587 let result = EvoNodeStatus::maybe_from_unproved_with_metadata(
588 request,
589 response,
590 Network::Testnet,
591 platform_version,
592 );
593
594 let (maybe_status, _metadata) = result.expect("should parse valid status response");
595 let status = maybe_status.expect("should contain EvoNodeStatus");
596
597 let software = status.version.software.as_ref().unwrap();
599 assert_eq!(software.dapi, "1.0.0");
600 assert_eq!(software.drive.as_deref(), Some("2.0.0"));
601 assert_eq!(software.tenderdash.as_deref(), Some("0.14.0"));
602
603 let protocol = status.version.protocol.as_ref().unwrap();
604 let td = protocol.tenderdash.as_ref().unwrap();
605 assert_eq!(td.p2p, 8);
606 assert_eq!(td.block, 11);
607 let drv = protocol.drive.as_ref().unwrap();
608 assert_eq!(drv.latest, 5);
609 assert_eq!(drv.current, 4);
610 assert_eq!(drv.next_epoch, 5);
611
612 assert_eq!(status.node.id, vec![10u8; 20]);
614 assert_eq!(status.node.pro_tx_hash, Some(vec![11u8; 32]));
615
616 assert!(!status.chain.catching_up);
618 assert_eq!(status.chain.latest_block_height, 1000);
619 assert_eq!(status.chain.core_chain_locked_height, Some(500));
620
621 assert_eq!(status.network.chain_id, "dash-testnet-1");
623 assert_eq!(status.network.peers_count, 25);
624 assert!(status.network.listening);
625
626 assert_eq!(status.time.local, 1700000000);
628 assert_eq!(status.time.block, Some(1699999900));
629 assert_eq!(status.time.epoch, Some(42));
630 }
631}