Skip to content

Commit 5860bd1

Browse files
authored
GH-674: Refinement of FailureReason and TxStatus according to the latest opinions (#675)
1 parent 3a11c6e commit 5860bd1

File tree

7 files changed

+380
-242
lines changed

7 files changed

+380
-242
lines changed

node/src/accountant/db_access_objects/failed_payable_dao.rs

Lines changed: 95 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ pub enum FailedPayableDaoError {
2626
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2727
pub 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)]
5252
pub 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+
5868
impl 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)]
7182
pub 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

Comments
 (0)