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