1use serde::{Deserialize, Serialize};
93use smallvec::SmallVec;
94use std::borrow::Cow;
95use std::collections::HashMap;
96use std::io;
97use std::net::SocketAddr;
98use std::time::Duration;
99use thiserror::Error;
100
101#[derive(Debug, Error)]
107pub enum P2PError {
108 #[error("Network error: {0}")]
110 Network(#[from] NetworkError),
111
112 #[error("DHT error: {0}")]
114 Dht(#[from] DhtError),
115
116 #[error("Identity error: {0}")]
118 Identity(#[from] IdentityError),
119
120 #[error("Cryptography error: {0}")]
122 Crypto(#[from] CryptoError),
123
124 #[error("Storage error: {0}")]
126 Storage(#[from] StorageError),
127
128 #[error("Transport error: {0}")]
130 Transport(#[from] TransportError),
131
132 #[error("MCP error: {0}")]
134 Mcp(#[from] McpError),
135
136 #[error("Configuration error: {0}")]
138 Config(#[from] ConfigError),
139
140 #[error("Security error: {0}")]
142 Security(#[from] SecurityError),
143
144 #[error("Bootstrap error: {0}")]
146 Bootstrap(#[from] BootstrapError),
147
148 #[error("IO error: {0}")]
150 Io(#[from] io::Error),
151
152 #[error("Serialization error: {0}")]
154 Serialization(Cow<'static, str>),
155
156 #[error("Validation error: {0}")]
158 Validation(Cow<'static, str>),
159
160 #[error("Operation timed out after {0:?}")]
162 Timeout(Duration),
163
164 #[error("Resource exhausted: {0}")]
166 ResourceExhausted(Cow<'static, str>),
167
168 #[error("Internal error: {0}")]
170 Internal(Cow<'static, str>),
171
172 #[error("Encoding error: {0}")]
174 Encoding(Cow<'static, str>),
175
176 #[error("Record too large: {0} bytes (max 512)")]
178 RecordTooLarge(usize),
179
180 #[error("Proof of work computation failed")]
182 ProofOfWorkFailed,
183
184 #[error("Time error")]
186 TimeError,
187
188 #[error("Invalid input: {0}")]
190 InvalidInput(String),
191
192 #[error("WebRTC error: {0}")]
194 WebRtcError(String),
195}
196
197#[derive(Debug, Error)]
199pub enum NetworkError {
200 #[error("Connection failed to {addr}: {reason}")]
201 ConnectionFailed {
202 addr: SocketAddr,
203 reason: Cow<'static, str>,
204 },
205
206 #[error("Connection closed unexpectedly")]
207 ConnectionClosed,
208
209 #[error("Invalid network address: {0}")]
210 InvalidAddress(Cow<'static, str>),
211
212 #[error("Peer not found: {0}")]
213 PeerNotFound(Cow<'static, str>),
214
215 #[error("Peer disconnected - peer: {peer}, reason: {reason}")]
216 PeerDisconnected { peer: String, reason: String },
217
218 #[error("Network timeout")]
219 Timeout,
220
221 #[error("Too many connections")]
222 TooManyConnections,
223
224 #[error("Protocol error: {0}")]
225 ProtocolError(Cow<'static, str>),
226
227 #[error("Bind error: {0}")]
228 BindError(Cow<'static, str>),
229}
230
231#[derive(Debug, Error)]
233pub enum DhtError {
234 #[error("Key not found: {0}")]
235 KeyNotFound(Cow<'static, str>),
236
237 #[error("Store operation failed: {0}")]
238 StoreFailed(Cow<'static, str>),
239
240 #[error("Invalid key format: {0}")]
241 InvalidKey(Cow<'static, str>),
242
243 #[error("Routing table full")]
244 RoutingTableFull,
245
246 #[error("No suitable peers found")]
247 NoPeersFound,
248
249 #[error("Replication failed: {0}")]
250 ReplicationFailed(Cow<'static, str>),
251
252 #[error("Query timeout")]
253 QueryTimeout,
254
255 #[error("Routing error: {0}")]
256 RoutingError(Cow<'static, str>),
257
258 #[error("Storage failed: {0}")]
259 StorageFailed(Cow<'static, str>),
260
261 #[error("Insufficient replicas: {0}")]
262 InsufficientReplicas(Cow<'static, str>),
263}
264
265#[derive(Debug, Error)]
267pub enum IdentityError {
268 #[error("Invalid three-word address: {0}")]
269 InvalidThreeWordAddress(Cow<'static, str>),
270
271 #[error("Invalid four-word address: {0}")]
272 InvalidFourWordAddress(Cow<'static, str>),
273
274 #[error("Identity not found: {0}")]
275 IdentityNotFound(Cow<'static, str>),
276
277 #[error("Identity already exists: {0}")]
278 IdentityExists(Cow<'static, str>),
279
280 #[error("Invalid signature")]
281 InvalidSignature,
282
283 #[error("Key derivation failed: {0}")]
284 KeyDerivationFailed(Cow<'static, str>),
285
286 #[error("Permission denied")]
287 PermissionDenied,
288
289 #[error("Invalid peer ID: {0}")]
290 InvalidPeerId(Cow<'static, str>),
291
292 #[error("Invalid format: {0}")]
293 InvalidFormat(Cow<'static, str>),
294
295 #[error("System time error: {0}")]
296 SystemTime(Cow<'static, str>),
297
298 #[error("Invalid proof of work")]
299 InvalidProofOfWork,
300
301 #[error("Not found: {0}")]
302 NotFound(Cow<'static, str>),
303
304 #[error("Verification failed: {0}")]
305 VerificationFailed(Cow<'static, str>),
306
307 #[error("Insufficient entropy")]
308 InsufficientEntropy,
309
310 #[error("Access denied: {0}")]
311 AccessDenied(Cow<'static, str>),
312}
313
314#[derive(Debug, Error)]
316pub enum CryptoError {
317 #[error("Encryption failed: {0}")]
318 EncryptionFailed(Cow<'static, str>),
319
320 #[error("Decryption failed: {0}")]
321 DecryptionFailed(Cow<'static, str>),
322
323 #[error("Invalid key length: expected {expected}, got {actual}")]
324 InvalidKeyLength { expected: usize, actual: usize },
325
326 #[error("Signature verification failed")]
327 SignatureVerificationFailed,
328
329 #[error("Key generation failed: {0}")]
330 KeyGenerationFailed(Cow<'static, str>),
331
332 #[error("Invalid public key")]
333 InvalidPublicKey,
334
335 #[error("Invalid private key")]
336 InvalidPrivateKey,
337
338 #[error("HKDF expansion failed: {0}")]
339 HkdfError(Cow<'static, str>),
340}
341
342#[derive(Debug, Error)]
344pub enum StorageError {
345 #[error("Database error: {0}")]
346 Database(Cow<'static, str>),
347
348 #[error("Disk full")]
349 DiskFull,
350
351 #[error("Corrupt data: {0}")]
352 CorruptData(Cow<'static, str>),
353
354 #[error("Storage path not found: {0}")]
355 PathNotFound(Cow<'static, str>),
356
357 #[error("Permission denied: {0}")]
358 PermissionDenied(Cow<'static, str>),
359
360 #[error("Lock acquisition failed")]
361 LockFailed,
362
363 #[error("Lock poisoned: {0}")]
364 LockPoisoned(Cow<'static, str>),
365
366 #[error("File not found: {0}")]
367 FileNotFound(Cow<'static, str>),
368
369 #[error("Corruption detected: {0}")]
370 CorruptionDetected(Cow<'static, str>),
371}
372
373#[derive(Debug, Error)]
375pub enum TransportError {
376 #[error("QUIC error: {0}")]
377 Quic(Cow<'static, str>),
378
379 #[error("TCP error: {0}")]
380 Tcp(Cow<'static, str>),
381
382 #[error("Invalid transport configuration: {0}")]
383 InvalidConfig(Cow<'static, str>),
384
385 #[error("Transport not supported: {0}")]
386 NotSupported(Cow<'static, str>),
387
388 #[error("Stream error: {0}")]
389 StreamError(Cow<'static, str>),
390
391 #[error("Certificate error: {0}")]
392 CertificateError(Cow<'static, str>),
393
394 #[error("Setup failed: {0}")]
395 SetupFailed(Cow<'static, str>),
396
397 #[error("Connection failed to {addr}: {reason}")]
398 ConnectionFailed {
399 addr: SocketAddr,
400 reason: Cow<'static, str>,
401 },
402
403 #[error("Bind error: {0}")]
404 BindError(Cow<'static, str>),
405
406 #[error("Accept failed: {0}")]
407 AcceptFailed(Cow<'static, str>),
408
409 #[error("Not listening")]
410 NotListening,
411
412 #[error("Not initialized")]
413 NotInitialized,
414}
415
416#[derive(Debug, Error)]
418pub enum McpError {
419 #[error("Tool not found: {0}")]
420 ToolNotFound(Cow<'static, str>),
421
422 #[error("Invalid tool configuration: {0}")]
423 InvalidToolConfig(Cow<'static, str>),
424
425 #[error("Execution failed: {0}")]
426 ExecutionFailed(Cow<'static, str>),
427
428 #[error("Permission denied for tool: {0}")]
429 PermissionDenied(Cow<'static, str>),
430
431 #[error("Invalid response format: {0}")]
432 InvalidResponse(Cow<'static, str>),
433
434 #[error("Server unavailable: {0}")]
435 ServerUnavailable(Cow<'static, str>),
436
437 #[error("Invalid request: {0}")]
438 InvalidRequest(Cow<'static, str>),
439
440 #[error("Tool execution failed: {0}")]
441 ToolExecutionFailed(Cow<'static, str>),
442}
443
444#[derive(Debug, Error)]
446pub enum ConfigError {
447 #[error("Missing required field: {0}")]
448 MissingField(Cow<'static, str>),
449
450 #[error("Invalid value for {field}: {reason}")]
451 InvalidValue {
452 field: Cow<'static, str>,
453 reason: Cow<'static, str>,
454 },
455
456 #[error("Configuration file not found: {0}")]
457 FileNotFound(Cow<'static, str>),
458
459 #[error("Parse error: {0}")]
460 ParseError(Cow<'static, str>),
461
462 #[error("Validation failed: {0}")]
463 ValidationFailed(Cow<'static, str>),
464
465 #[error("IO error for {path}: {source}")]
466 IoError {
467 path: Cow<'static, str>,
468 #[source]
469 source: std::io::Error,
470 },
471}
472
473#[derive(Debug, Error)]
475pub enum SecurityError {
476 #[error("Authentication failed")]
477 AuthenticationFailed,
478
479 #[error("Authorization denied")]
480 AuthorizationDenied,
481
482 #[error("Invalid credentials")]
483 InvalidCredentials,
484
485 #[error("Certificate error: {0}")]
486 CertificateError(Cow<'static, str>),
487
488 #[error("Encryption failed: {0}")]
489 EncryptionFailed(Cow<'static, str>),
490
491 #[error("Decryption failed: {0}")]
492 DecryptionFailed(Cow<'static, str>),
493
494 #[error("Invalid key: {0}")]
495 InvalidKey(Cow<'static, str>),
496
497 #[error("Signature verification failed: {0}")]
498 SignatureVerificationFailed(Cow<'static, str>),
499
500 #[error("Key generation failed: {0}")]
501 KeyGenerationFailed(Cow<'static, str>),
502
503 #[error("Authorization failed: {0}")]
504 AuthorizationFailed(Cow<'static, str>),
505}
506
507#[derive(Debug, Error)]
509pub enum BootstrapError {
510 #[error("No bootstrap nodes available")]
511 NoBootstrapNodes,
512
513 #[error("Bootstrap failed: {0}")]
514 BootstrapFailed(Cow<'static, str>),
515
516 #[error("Invalid bootstrap node: {0}")]
517 InvalidBootstrapNode(Cow<'static, str>),
518
519 #[error("Bootstrap timeout")]
520 BootstrapTimeout,
521
522 #[error("Cache error: {0}")]
523 CacheError(Cow<'static, str>),
524
525 #[error("Invalid data: {0}")]
526 InvalidData(Cow<'static, str>),
527}
528
529pub type P2pResult<T> = Result<T, P2PError>;
531
532pub trait Recoverable {
536 fn is_transient(&self) -> bool;
538
539 fn suggested_retry_after(&self) -> Option<Duration>;
541
542 fn max_retries(&self) -> usize;
544}
545
546impl Recoverable for P2PError {
547 fn is_transient(&self) -> bool {
548 match self {
549 P2PError::Network(NetworkError::ConnectionFailed { .. }) => true,
550 P2PError::Network(NetworkError::Timeout) => true,
551 P2PError::Transport(TransportError::ConnectionFailed { .. }) => true,
552 P2PError::Dht(DhtError::QueryTimeout) => true,
553 P2PError::Timeout(_) => true,
554 P2PError::ResourceExhausted(_) => true,
555 P2PError::Io(err) => matches!(
556 err.kind(),
557 io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut | io::ErrorKind::Interrupted
558 ),
559 _ => false,
560 }
561 }
562
563 fn suggested_retry_after(&self) -> Option<Duration> {
564 match self {
565 P2PError::Network(NetworkError::Timeout) => Some(Duration::from_secs(5)),
566 P2PError::Timeout(duration) => Some(*duration * 2),
567 P2PError::ResourceExhausted(_) => Some(Duration::from_secs(30)),
568 P2PError::Transport(TransportError::ConnectionFailed { .. }) => {
569 Some(Duration::from_secs(1))
570 }
571 _ => None,
572 }
573 }
574
575 fn max_retries(&self) -> usize {
576 match self {
577 P2PError::Network(NetworkError::ConnectionFailed { .. }) => 3,
578 P2PError::Transport(TransportError::ConnectionFailed { .. }) => 3,
579 P2PError::Timeout(_) => 2,
580 P2PError::ResourceExhausted(_) => 1,
581 _ => 0,
582 }
583 }
584}
585
586pub trait ErrorContext<T> {
588 fn context(self, msg: &str) -> Result<T, P2PError>;
590
591 fn with_context<F>(self, f: F) -> Result<T, P2PError>
593 where
594 F: FnOnce() -> String;
595}
596
597impl<T, E> ErrorContext<T> for Result<T, E>
598where
599 E: Into<P2PError>,
600{
601 fn context(self, msg: &str) -> Result<T, P2PError> {
602 self.map_err(|e| {
603 let base_error = e.into();
604 P2PError::Internal(format!("{}: {}", msg, base_error).into())
605 })
606 }
607
608 fn with_context<F>(self, f: F) -> Result<T, P2PError>
609 where
610 F: FnOnce() -> String,
611 {
612 self.map_err(|e| {
613 let base_error = e.into();
614 P2PError::Internal(format!("{}: {}", f(), base_error).into())
615 })
616 }
617}
618
619impl P2PError {
621 pub fn connection_failed(addr: SocketAddr, reason: impl Into<String>) -> Self {
623 P2PError::Network(NetworkError::ConnectionFailed {
624 addr,
625 reason: reason.into().into(),
626 })
627 }
628
629 pub fn timeout(duration: Duration) -> Self {
631 P2PError::Timeout(duration)
632 }
633
634 pub fn validation(msg: impl Into<Cow<'static, str>>) -> Self {
636 P2PError::Validation(msg.into())
637 }
638
639 pub fn internal(msg: impl Into<Cow<'static, str>>) -> Self {
641 P2PError::Internal(msg.into())
642 }
643}
644
645impl P2PError {
647 pub fn log(&self) {
649 use log::{error, warn};
650
651 match self {
652 P2PError::Network(NetworkError::Timeout) | P2PError::Timeout(_) => warn!("{}", self),
653
654 P2PError::Validation(_) | P2PError::Config(_) => warn!("{}", self),
655
656 _ => error!("{}", self),
657 }
658 }
659
660 pub fn log_with_context(&self, context: &str) {
662 use log::error;
663 error!("{}: {}", context, self);
664 }
665}
666
667impl From<serde_json::Error> for P2PError {
670 fn from(err: serde_json::Error) -> Self {
671 P2PError::Serialization(err.to_string().into())
672 }
673}
674
675impl From<bincode::Error> for P2PError {
676 fn from(err: bincode::Error) -> Self {
677 P2PError::Serialization(err.to_string().into())
678 }
679}
680
681impl From<std::net::AddrParseError> for P2PError {
682 fn from(err: std::net::AddrParseError) -> Self {
683 P2PError::Network(NetworkError::InvalidAddress(err.to_string().into()))
684 }
685}
686
687impl From<tokio::time::error::Elapsed> for P2PError {
688 fn from(_: tokio::time::error::Elapsed) -> Self {
689 P2PError::Network(NetworkError::Timeout)
690 }
691}
692
693impl From<crate::adaptive::AdaptiveNetworkError> for P2PError {
694 fn from(err: crate::adaptive::AdaptiveNetworkError) -> Self {
695 use crate::adaptive::AdaptiveNetworkError;
696 match err {
697 AdaptiveNetworkError::Network(io_err) => P2PError::Io(io_err),
698 AdaptiveNetworkError::Io(io_err) => P2PError::Io(io_err),
699 AdaptiveNetworkError::Serialization(ser_err) => {
700 P2PError::Serialization(ser_err.to_string().into())
701 }
702 AdaptiveNetworkError::Routing(msg) => {
703 P2PError::Internal(format!("Routing error: {}", msg).into())
704 }
705 AdaptiveNetworkError::Trust(msg) => {
706 P2PError::Internal(format!("Trust error: {}", msg).into())
707 }
708 AdaptiveNetworkError::Learning(msg) => {
709 P2PError::Internal(format!("Learning error: {}", msg).into())
710 }
711 AdaptiveNetworkError::Gossip(msg) => {
712 P2PError::Internal(format!("Gossip error: {}", msg).into())
713 }
714 AdaptiveNetworkError::Other(msg) => P2PError::Internal(msg.into()),
715 }
716 }
717}
718
719#[derive(Debug, Clone, Serialize, Deserialize)]
723pub enum ErrorValue {
724 String(Cow<'static, str>),
725 Number(i64),
726 Bool(bool),
727 Duration(Duration),
728 Address(SocketAddr),
729}
730
731#[derive(Debug, Serialize, Deserialize)]
733pub struct ErrorLog {
734 pub timestamp: i64, pub error_type: &'static str,
736 pub message: Cow<'static, str>,
737 pub context: SmallVec<[(&'static str, ErrorValue); 4]>, pub stack_trace: Option<Cow<'static, str>>,
739}
740
741impl ErrorLog {
742 pub fn from_error(error: &P2PError) -> Self {
744 let mut context = SmallVec::new();
745
746 match error {
748 P2PError::Network(NetworkError::ConnectionFailed { addr, reason }) => {
749 context.push(("address", ErrorValue::Address(*addr)));
750 context.push(("reason", ErrorValue::String(reason.clone())));
751 }
752 P2PError::Timeout(duration) => {
753 context.push(("timeout", ErrorValue::Duration(*duration)));
754 }
755 P2PError::Crypto(CryptoError::InvalidKeyLength { expected, actual }) => {
756 context.push(("expected_length", ErrorValue::Number(*expected as i64)));
757 context.push(("actual_length", ErrorValue::Number(*actual as i64)));
758 }
759 _ => {}
760 }
761
762 ErrorLog {
763 timestamp: chrono::Utc::now().timestamp(),
764 error_type: error_type_name(error),
765 message: error.to_string().into(),
766 context,
767 stack_trace: None,
768 }
769 }
770
771 pub fn with_context(mut self, key: &'static str, value: ErrorValue) -> Self {
772 self.context.push((key, value));
773 self
774 }
775
776 pub fn log(&self) {
777 use log::{error, warn};
778
779 let json = serde_json::to_string(self).unwrap_or_else(|_| self.message.to_string());
780
781 match self.error_type {
782 "Validation" | "Config" => warn!("{}", json),
783 _ => error!("{}", json),
784 }
785 }
786}
787
788fn error_type_name(error: &P2PError) -> &'static str {
789 match error {
790 P2PError::Network(_) => "Network",
791 P2PError::Dht(_) => "DHT",
792 P2PError::Identity(_) => "Identity",
793 P2PError::Crypto(_) => "Crypto",
794 P2PError::Storage(_) => "Storage",
795 P2PError::Transport(_) => "Transport",
796 P2PError::Mcp(_) => "MCP",
797 P2PError::Config(_) => "Config",
798 P2PError::Io(_) => "IO",
799 P2PError::Serialization(_) => "Serialization",
800 P2PError::Validation(_) => "Validation",
801 P2PError::Timeout(_) => "Timeout",
802 P2PError::ResourceExhausted(_) => "ResourceExhausted",
803 P2PError::Internal(_) => "Internal",
804 P2PError::Security(_) => "Security",
805 P2PError::Bootstrap(_) => "Bootstrap",
806 P2PError::Encoding(_) => "Encoding",
807 P2PError::RecordTooLarge(_) => "RecordTooLarge",
808 P2PError::ProofOfWorkFailed => "ProofOfWorkFailed",
809 P2PError::TimeError => "TimeError",
810 P2PError::InvalidInput(_) => "InvalidInput",
811 P2PError::WebRtcError(_) => "WebRTC",
812 }
813}
814
815pub trait ErrorReporting {
817 fn report(&self) -> ErrorLog;
818 fn report_with_context(&self, context: HashMap<String, serde_json::Value>) -> ErrorLog;
819}
820
821impl ErrorReporting for P2PError {
822 fn report(&self) -> ErrorLog {
823 ErrorLog::from_error(self)
824 }
825
826 fn report_with_context(&self, context: HashMap<String, serde_json::Value>) -> ErrorLog {
827 let log = ErrorLog::from_error(self);
828 for (_key, _value) in context {
830 }
834 log
835 }
836}
837
838pub trait IntoAnyhow<T> {
842 fn into_anyhow(self) -> anyhow::Result<T>;
843}
844
845impl<T> IntoAnyhow<T> for P2pResult<T> {
846 fn into_anyhow(self) -> anyhow::Result<T> {
847 self.map_err(|e| anyhow::anyhow!(e))
848 }
849}
850
851pub trait FromAnyhowExt<T> {
852 fn into_p2p_result(self) -> P2pResult<T>;
853}
854
855impl<T> FromAnyhowExt<T> for anyhow::Result<T> {
856 fn into_p2p_result(self) -> P2pResult<T> {
857 self.map_err(|e| P2PError::Internal(e.to_string().into()))
858 }
859}
860
861pub use anyhow::{Context as AnyhowContext, Result as AnyhowResult};
863
864#[cfg(test)]
865mod tests {
866 use super::*;
867
868 #[test]
869 fn test_error_display() {
870 let err =
871 P2PError::connection_failed("127.0.0.1:8080".parse().unwrap(), "Connection refused");
872 assert_eq!(
873 err.to_string(),
874 "Network error: Connection failed to 127.0.0.1:8080: Connection refused"
875 );
876 }
877
878 #[test]
879 fn test_error_context() {
880 let result: Result<(), io::Error> =
881 Err(io::Error::new(io::ErrorKind::NotFound, "file not found"));
882
883 let with_context = crate::error::ErrorContext::context(result, "Failed to load config");
884 assert!(with_context.is_err());
885 assert!(
886 with_context
887 .unwrap_err()
888 .to_string()
889 .contains("Failed to load config")
890 );
891 }
892
893 #[test]
894 fn test_timeout_error() {
895 let err = P2PError::timeout(Duration::from_secs(30));
896 assert_eq!(err.to_string(), "Operation timed out after 30s");
897 }
898
899 #[test]
900 fn test_crypto_error() {
901 let err = P2PError::Crypto(CryptoError::InvalidKeyLength {
902 expected: 32,
903 actual: 16,
904 });
905 assert_eq!(
906 err.to_string(),
907 "Cryptography error: Invalid key length: expected 32, got 16"
908 );
909 }
910
911 #[test]
912 fn test_error_log_serialization() {
913 let error = P2PError::Network(NetworkError::ConnectionFailed {
914 addr: "127.0.0.1:8080".parse().unwrap(),
915 reason: "Connection refused".into(),
916 });
917
918 let log = error
919 .report()
920 .with_context("peer_id", ErrorValue::String("peer123".into()))
921 .with_context("retry_count", ErrorValue::Number(3));
922
923 let json = serde_json::to_string_pretty(&log).unwrap();
924 assert!(json.contains("Network"));
925 assert!(json.contains("127.0.0.1:8080"));
926 assert!(json.contains("peer123"));
927 }
928
929 #[test]
930 fn test_anyhow_conversion() {
931 let p2p_result: P2pResult<()> = Err(P2PError::validation("Invalid input"));
932 let anyhow_result = p2p_result.into_anyhow();
933 assert!(anyhow_result.is_err());
934
935 let anyhow_err = anyhow::anyhow!("Test error");
936 let anyhow_result: anyhow::Result<()> = Err(anyhow_err);
937 let p2p_result = crate::error::FromAnyhowExt::into_p2p_result(anyhow_result);
938 assert!(p2p_result.is_err());
939 match p2p_result.unwrap_err() {
940 P2PError::Internal(msg) => assert!(msg.contains("Test error")),
941 _ => panic!("Expected Internal error"),
942 }
943 }
944}