@@ -26,7 +26,6 @@ pub enum FailedPayableDaoError {
2626#[ derive( Debug , Clone , PartialEq , Eq , Serialize , Deserialize ) ]
2727pub enum FailureReason {
2828 Submission ( AppRpcError ) ,
29- Validation ( AppRpcError ) ,
3029 Reverted ,
3130 PendingTooLong ,
3231}
@@ -35,6 +34,7 @@ impl Display for FailureReason {
3534 fn fmt ( & self , f : & mut Formatter < ' _ > ) -> std:: fmt:: Result {
3635 match serde_json:: to_string ( self ) {
3736 Ok ( json) => write ! ( f, "{}" , json) ,
37+ // Untestable
3838 Err ( _) => write ! ( f, "<invalid FailureReason>" ) ,
3939 }
4040 }
@@ -44,29 +44,40 @@ impl FromStr for FailureReason {
4444 type Err = String ;
4545
4646 fn from_str ( s : & str ) -> Result < Self , Self :: Err > {
47- serde_json:: from_str ( s) . map_err ( |e| e . to_string ( ) )
47+ serde_json:: from_str ( s) . map_err ( |e| format ! ( "{} in '{}'" , e , s ) )
4848 }
4949}
5050
51- #[ derive( Clone , Debug , PartialEq , Eq ) ]
51+ #[ derive( Debug , Clone , PartialEq , Eq , Serialize , Deserialize ) ]
5252pub enum FailureStatus {
5353 RetryRequired ,
54- RecheckRequired ,
54+ RecheckRequired ( ValidationStatus ) ,
5555 Concluded ,
5656}
5757
58+ impl Display for FailureStatus {
59+ fn fmt ( & self , f : & mut Formatter < ' _ > ) -> std:: fmt:: Result {
60+ match serde_json:: to_string ( self ) {
61+ Ok ( json) => write ! ( f, "{}" , json) ,
62+ // Untestable
63+ Err ( _) => write ! ( f, "<invalid FailureStatus>" ) ,
64+ }
65+ }
66+ }
67+
5868impl FromStr for FailureStatus {
5969 type Err = String ;
6070 fn from_str ( s : & str ) -> Result < Self , Self :: Err > {
61- match s {
62- "RetryRequired" => Ok ( FailureStatus :: RetryRequired ) ,
63- "RecheckRequired" => Ok ( FailureStatus :: RecheckRequired ) ,
64- "Concluded" => Ok ( FailureStatus :: Concluded ) ,
65- _ => Err ( format ! ( "Invalid FailureStatus: {}" , s) ) ,
66- }
71+ serde_json:: from_str ( s) . map_err ( |e| format ! ( "{} in '{}'" , e, s) )
6772 }
6873}
6974
75+ #[ derive( Debug , Clone , PartialEq , Eq , Serialize , Deserialize ) ]
76+ pub enum ValidationStatus {
77+ Waiting ,
78+ Reattempting { attempt : usize , error : AppRpcError } ,
79+ }
80+
7081#[ derive( Clone , Debug , PartialEq , Eq ) ]
7182pub struct FailedTx {
7283 pub hash : TxHash ,
@@ -87,7 +98,7 @@ impl Display for FailureRetrieveCondition {
8798 fn fmt ( & self , f : & mut Formatter < ' _ > ) -> std:: fmt:: Result {
8899 match self {
89100 FailureRetrieveCondition :: ByStatus ( status) => {
90- write ! ( f, "WHERE status = '{:? }'" , status)
101+ write ! ( f, "WHERE status = '{}'" , status)
91102 }
92103 }
93104 }
@@ -181,7 +192,7 @@ impl FailedPayableDao for FailedPayableDaoReal<'_> {
181192 let ( gas_price_wei_high_b, gas_price_wei_low_b) =
182193 BigIntDivider :: deconstruct( gas_price_wei_checked) ;
183194 format!(
184- "('{:?}', '{:?}', {}, {}, {}, {}, {}, {}, '{}', '{:? }')" ,
195+ "('{:?}', '{:?}', {}, {}, {}, {}, {}, {}, '{}', '{}')" ,
185196 tx. hash,
186197 tx. receiver_address,
187198 amount_high_b,
@@ -283,7 +294,7 @@ impl FailedPayableDao for FailedPayableDaoReal<'_> {
283294
284295 let case_statements = status_updates
285296 . iter ( )
286- . map ( |( hash, status) | format ! ( "WHEN tx_hash = '{:?}' THEN '{:? }'" , hash, status) )
297+ . map ( |( hash, status) | format ! ( "WHEN tx_hash = '{:?}' THEN '{}'" , hash, status) )
287298 . join ( " " ) ;
288299 let tx_hashes = comma_joined_stringifiable ( & status_updates. keys ( ) . collect_vec ( ) , |hash| {
289300 format ! ( "'{:?}'" , hash)
@@ -364,7 +375,7 @@ mod tests {
364375 } ;
365376 use crate :: accountant:: db_access_objects:: failed_payable_dao:: {
366377 FailedPayableDao , FailedPayableDaoError , FailedPayableDaoReal , FailureReason ,
367- FailureRetrieveCondition , FailureStatus ,
378+ FailureRetrieveCondition , FailureStatus , ValidationStatus ,
368379 } ;
369380 use crate :: accountant:: db_access_objects:: test_utils:: {
370381 make_read_only_db_connection, FailedTxBuilder ,
@@ -439,7 +450,7 @@ mod tests {
439450 . build ( ) ;
440451 let tx2 = FailedTxBuilder :: default ( )
441452 . hash ( hash)
442- . status ( RecheckRequired )
453+ . status ( RecheckRequired ( ValidationStatus :: Waiting ) )
443454 . build ( ) ;
444455 let subject = FailedPayableDaoReal :: new ( wrapped_conn) ;
445456
@@ -458,7 +469,7 @@ mod tests {
458469 hash: 0x000000000000000000000000000000000000000000000000000000000000007b, \
459470 receiver_address: 0x0000000000000000000000000000000000000000, \
460471 amount: 0, timestamp: 0, gas_price_wei: 0, \
461- nonce: 0, reason: PendingTooLong, status: RecheckRequired }]"
472+ nonce: 0, reason: PendingTooLong, status: RecheckRequired(Waiting) }]"
462473 . to_string( )
463474 ) )
464475 ) ;
@@ -480,7 +491,7 @@ mod tests {
480491 . build ( ) ;
481492 let tx2 = FailedTxBuilder :: default ( )
482493 . hash ( hash)
483- . status ( RecheckRequired )
494+ . status ( RecheckRequired ( ValidationStatus :: Waiting ) )
484495 . build ( ) ;
485496 let subject = FailedPayableDaoReal :: new ( wrapped_conn) ;
486497 let initial_insertion_result = subject. insert_new_records ( & vec ! [ tx1] ) ;
@@ -580,62 +591,75 @@ mod tests {
580591 ) ) )
581592 ) ;
582593
583- // Validation error
584- assert_eq ! (
585- FailureReason :: from_str( r#"{"Validation":{"Remote":{"Web3RpcError":{"code":42,"message":"Test RPC error"}}}}"# ) . unwrap( ) ,
586- FailureReason :: Validation ( AppRpcError :: Remote ( RemoteError :: Web3RpcError {
587- code: 42 ,
588- message: "Test RPC error" . to_string( )
589- } ) )
590- ) ;
591-
592594 // Reverted
593595 assert_eq ! (
594- FailureReason :: from_str( r#"{ "Reverted":null}"# ) . unwrap( ) ,
596+ FailureReason :: from_str( " \ " Reverted\" " ) . unwrap( ) ,
595597 FailureReason :: Reverted
596598 ) ;
597599
598600 // PendingTooLong
599601 assert_eq ! (
600- FailureReason :: from_str( r#"{ "PendingTooLong":null}"# ) . unwrap( ) ,
602+ FailureReason :: from_str( " \ " PendingTooLong\" " ) . unwrap( ) ,
601603 FailureReason :: PendingTooLong
602604 ) ;
603605
604606 // Invalid Variant
605607 assert_eq ! (
606- FailureReason :: from_str( r#"{ "UnknownReason":null}"# ) . unwrap_err( ) ,
608+ FailureReason :: from_str( " \ " UnknownReason\" " ) . unwrap_err( ) ,
607609 "unknown variant `UnknownReason`, \
608- expected one of `Submission`, `Validation`, `Reverted`, `PendingTooLong` \
609- at line 1 column 16"
610- . to_string( )
610+ expected one of `Submission`, `Reverted`, `PendingTooLong` \
611+ at line 1 column 15 in '\" UnknownReason\" '"
611612 ) ;
612613
613614 // Invalid Input
614615 assert_eq ! (
615- FailureReason :: from_str( "random string " ) . unwrap_err( ) ,
616- "expected value at line 1 column 1" . to_string ( )
616+ FailureReason :: from_str( "not a failure reason " ) . unwrap_err( ) ,
617+ "expected value at line 1 column 1 in 'not a failure reason'"
617618 ) ;
618619 }
619620
620621 #[ test]
621622 fn failure_status_from_str_works ( ) {
622- assert_eq ! ( FailureStatus :: from_str( "RetryRequired" ) , Ok ( RetryRequired ) ) ;
623623 assert_eq ! (
624- FailureStatus :: from_str( "RecheckRequired" ) ,
625- Ok ( RecheckRequired )
624+ FailureStatus :: from_str( "\" RetryRequired \" " ) . unwrap ( ) ,
625+ FailureStatus :: RetryRequired
626626 ) ;
627- assert_eq ! ( FailureStatus :: from_str( "Concluded" ) , Ok ( Concluded ) ) ;
627+
628+ assert_eq ! (
629+ FailureStatus :: from_str( r#"{"RecheckRequired":"Waiting"}"# ) . unwrap( ) ,
630+ FailureStatus :: RecheckRequired ( ValidationStatus :: Waiting )
631+ ) ;
632+
628633 assert_eq ! (
629- FailureStatus :: from_str( "InvalidStatus" ) ,
630- Err ( "Invalid FailureStatus: InvalidStatus" . to_string( ) )
634+ FailureStatus :: from_str( r#"{"RecheckRequired":{"Reattempting":{"attempt":2,"error":{"Remote":"Unreachable"}}}}"# ) . unwrap( ) ,
635+ FailureStatus :: RecheckRequired ( ValidationStatus :: Reattempting { attempt: 2 , error: AppRpcError :: Remote ( RemoteError :: Unreachable ) } )
636+ ) ;
637+
638+ assert_eq ! (
639+ FailureStatus :: from_str( "\" Concluded\" " ) . unwrap( ) ,
640+ FailureStatus :: Concluded
641+ ) ;
642+
643+ // Invalid Variant
644+ assert_eq ! (
645+ FailureStatus :: from_str( "\" UnknownStatus\" " ) . unwrap_err( ) ,
646+ "unknown variant `UnknownStatus`, \
647+ expected one of `RetryRequired`, `RecheckRequired`, `Concluded` \
648+ at line 1 column 15 in '\" UnknownStatus\" '"
649+ ) ;
650+
651+ // Invalid Input
652+ assert_eq ! (
653+ FailureStatus :: from_str( "not a failure status" ) . unwrap_err( ) ,
654+ "expected value at line 1 column 1 in 'not a failure status'"
631655 ) ;
632656 }
633657
634658 #[ test]
635659 fn retrieve_condition_display_works ( ) {
636660 assert_eq ! (
637661 FailureRetrieveCondition :: ByStatus ( RetryRequired ) . to_string( ) ,
638- "WHERE status = 'RetryRequired'"
662+ "WHERE status = '\" RetryRequired\" '"
639663 ) ;
640664 }
641665
@@ -689,7 +713,10 @@ mod tests {
689713 let tx3 = FailedTxBuilder :: default ( )
690714 . hash ( make_tx_hash ( 3 ) )
691715 . reason ( PendingTooLong )
692- . status ( RecheckRequired )
716+ . status ( RecheckRequired ( ValidationStatus :: Reattempting {
717+ attempt : 1 ,
718+ error : AppRpcError :: Remote ( RemoteError :: Unreachable ) ,
719+ } ) )
693720 . build ( ) ;
694721 let tx4 = FailedTxBuilder :: default ( )
695722 . hash ( make_tx_hash ( 4 ) )
@@ -722,24 +749,30 @@ mod tests {
722749 let tx2 = FailedTxBuilder :: default ( )
723750 . hash ( make_tx_hash ( 2 ) )
724751 . reason ( PendingTooLong )
725- . status ( RetryRequired )
752+ . status ( RecheckRequired ( ValidationStatus :: Waiting ) )
726753 . build ( ) ;
727754 let tx3 = FailedTxBuilder :: default ( )
728755 . hash ( make_tx_hash ( 3 ) )
729756 . reason ( PendingTooLong )
730- . status ( RecheckRequired )
757+ . status ( RetryRequired )
731758 . build ( ) ;
732759 let tx4 = FailedTxBuilder :: default ( )
733760 . hash ( make_tx_hash ( 4 ) )
734761 . reason ( PendingTooLong )
735- . status ( RecheckRequired )
762+ . status ( RecheckRequired ( ValidationStatus :: Waiting ) )
736763 . build ( ) ;
737764 subject
738- . insert_new_records ( & vec ! [ tx1. clone( ) , tx2. clone( ) , tx3. clone( ) , tx4] )
765+ . insert_new_records ( & vec ! [ tx1. clone( ) , tx2. clone( ) , tx3. clone( ) , tx4. clone ( ) ] )
739766 . unwrap ( ) ;
740767 let hashmap = HashMap :: from ( [
741768 ( tx1. hash , Concluded ) ,
742- ( tx2. hash , RecheckRequired ) ,
769+ (
770+ tx2. hash ,
771+ RecheckRequired ( ValidationStatus :: Reattempting {
772+ attempt : 1 ,
773+ error : AppRpcError :: Remote ( RemoteError :: Unreachable ) ,
774+ } ) ,
775+ ) ,
743776 ( tx3. hash , Concluded ) ,
744777 ] ) ;
745778
@@ -749,12 +782,21 @@ mod tests {
749782 assert_eq ! ( result, Ok ( ( ) ) ) ;
750783 assert_eq ! ( tx1. status, RetryRequired ) ;
751784 assert_eq ! ( updated_txs[ 0 ] . status, Concluded ) ;
752- assert_eq ! ( tx2. status, RetryRequired ) ;
753- assert_eq ! ( updated_txs[ 1 ] . status, RecheckRequired ) ;
754- assert_eq ! ( tx3. status, RecheckRequired ) ;
785+ assert_eq ! ( tx2. status, RecheckRequired ( ValidationStatus :: Waiting ) ) ;
786+ assert_eq ! (
787+ updated_txs[ 1 ] . status,
788+ RecheckRequired ( ValidationStatus :: Reattempting {
789+ attempt: 1 ,
790+ error: AppRpcError :: Remote ( RemoteError :: Unreachable )
791+ } )
792+ ) ;
793+ assert_eq ! ( tx3. status, RetryRequired ) ;
755794 assert_eq ! ( updated_txs[ 2 ] . status, Concluded ) ;
756- assert_eq ! ( tx3. status, RecheckRequired ) ;
757- assert_eq ! ( updated_txs[ 3 ] . status, RecheckRequired ) ;
795+ assert_eq ! ( tx4. status, RecheckRequired ( ValidationStatus :: Waiting ) ) ;
796+ assert_eq ! (
797+ updated_txs[ 3 ] . status,
798+ RecheckRequired ( ValidationStatus :: Waiting )
799+ ) ;
758800 }
759801
760802 #[ test]
@@ -782,7 +824,7 @@ mod tests {
782824 let wrapped_conn = make_read_only_db_connection ( home_dir) ;
783825 let subject = FailedPayableDaoReal :: new ( Box :: new ( wrapped_conn) ) ;
784826
785- let result = subject. update_statuses ( HashMap :: from ( [ ( make_tx_hash ( 1 ) , RecheckRequired ) ] ) ) ;
827+ let result = subject. update_statuses ( HashMap :: from ( [ ( make_tx_hash ( 1 ) , Concluded ) ] ) ) ;
786828
787829 assert_eq ! (
788830 result,
0 commit comments