Skip to content

Commit db66aad

Browse files
committed
Return Closed state during replays
We want to avoid returning the current state if a closed event was processed from the log. The application may just perform the event that caused the error instead of treating the session as terminally closed.
1 parent ef1c15a commit db66aad

File tree

6 files changed

+60
-26
lines changed

6 files changed

+60
-26
lines changed

payjoin-cli/src/app/v2/mod.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ use payjoin::persist::OptionalTransitionOutcome;
88
use payjoin::receive::v2::{
99
replay_event_log as replay_receiver_event_log, HasReplyableError, Initialized,
1010
MaybeInputsOwned, MaybeInputsSeen, OutputsUnknown, PayjoinProposal, ProvisionalProposal,
11-
ReceiveSession, Receiver, ReceiverBuilder, UncheckedOriginalPayload, WantsFeeRange,
12-
WantsInputs, WantsOutputs,
11+
ReceiveSession, Receiver, ReceiverBuilder, SessionOutcome as ReceiverSessionOutcome,
12+
UncheckedOriginalPayload, WantsFeeRange, WantsInputs, WantsOutputs,
1313
};
1414
use payjoin::send::v2::{
1515
replay_event_log as replay_sender_event_log, PollingForProposal, SendSession, Sender,
16-
SenderBuilder, WithReplyKey,
16+
SenderBuilder, SessionOutcome as SenderSessionOutcome, WithReplyKey,
1717
};
1818
use payjoin::{ImplementationError, PjParam, Uri};
1919
use tokio::sync::watch;
@@ -52,7 +52,11 @@ impl StatusText for SendSession {
5252
SendSession::WithReplyKey(_) | SendSession::PollingForProposal(_) =>
5353
"Waiting for proposal",
5454
SendSession::ProposalReceived(_) => "Proposal received",
55-
SendSession::TerminalFailure => "Session failure",
55+
SendSession::Closed(session_outcome) => match session_outcome {
56+
SenderSessionOutcome::Failure => "Session failure",
57+
SenderSessionOutcome::Success => "Session success",
58+
SenderSessionOutcome::Cancel => "Session cancelled",
59+
},
5660
}
5761
}
5862
}
@@ -70,7 +74,13 @@ impl StatusText for ReceiveSession {
7074
| ReceiveSession::WantsFeeRange(_)
7175
| ReceiveSession::ProvisionalProposal(_) => "Processing original proposal",
7276
ReceiveSession::PayjoinProposal(_) => "Payjoin proposal sent",
73-
ReceiveSession::HasReplyableError(_) => "Session failure",
77+
ReceiveSession::HasReplyableError(_) =>
78+
"Session failure, waiting to post error response",
79+
ReceiveSession::Closed(session_outcome) => match session_outcome {
80+
ReceiverSessionOutcome::Failure => "Session failure",
81+
ReceiverSessionOutcome::Success => "Session success",
82+
ReceiverSessionOutcome::Cancel => "Session cancelled",
83+
},
7484
}
7585
}
7686
}
@@ -523,6 +533,7 @@ impl App {
523533
self.send_payjoin_proposal(proposal, persister).await,
524534
ReceiveSession::HasReplyableError(error) =>
525535
self.handle_error(error, persister).await,
536+
ReceiveSession::Closed(_) => return Err(anyhow!("Session closed")),
526537
}
527538
};
528539
res

payjoin-ffi/src/receive/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,19 @@ impl ReceiverSessionEvent {
6666
}
6767
}
6868

69+
#[derive(Clone, uniffi::Object)]
70+
pub struct ReceiverSessionOutcome {
71+
inner: payjoin::receive::v2::SessionOutcome,
72+
}
73+
74+
impl From<payjoin::receive::v2::SessionOutcome> for ReceiverSessionOutcome {
75+
fn from(value: payjoin::receive::v2::SessionOutcome) -> Self { Self { inner: value } }
76+
}
77+
78+
impl From<ReceiverSessionOutcome> for payjoin::receive::v2::SessionOutcome {
79+
fn from(value: ReceiverSessionOutcome) -> Self { value.inner }
80+
}
81+
6982
#[derive(Clone, uniffi::Enum)]
7083
pub enum ReceiveSession {
7184
Initialized { inner: Arc<Initialized> },
@@ -79,6 +92,7 @@ pub enum ReceiveSession {
7992
ProvisionalProposal { inner: Arc<ProvisionalProposal> },
8093
PayjoinProposal { inner: Arc<PayjoinProposal> },
8194
HasReplyableError { inner: Arc<HasReplyableError> },
95+
Closed { inner: Arc<ReceiverSessionOutcome> },
8296
}
8397

8498
impl From<payjoin::receive::v2::ReceiveSession> for ReceiveSession {
@@ -107,6 +121,8 @@ impl From<payjoin::receive::v2::ReceiveSession> for ReceiveSession {
107121
Self::PayjoinProposal { inner: Arc::new(inner.into()) },
108122
ReceiveSession::HasReplyableError(inner) =>
109123
Self::HasReplyableError { inner: Arc::new(inner.into()) },
124+
ReceiveSession::Closed(session_outcome) =>
125+
Self::Closed { inner: Arc::new(session_outcome.into()) },
110126
}
111127
}
112128
}

payjoin-ffi/src/send/mod.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,25 @@ impl SenderSessionEvent {
5757
}
5858
}
5959

60+
#[derive(Clone, uniffi::Object)]
61+
pub struct SenderSessionOutcome {
62+
inner: payjoin::send::v2::SessionOutcome,
63+
}
64+
65+
impl From<payjoin::send::v2::SessionOutcome> for SenderSessionOutcome {
66+
fn from(value: payjoin::send::v2::SessionOutcome) -> Self { Self { inner: value } }
67+
}
68+
69+
impl From<SenderSessionOutcome> for payjoin::send::v2::SessionOutcome {
70+
fn from(value: SenderSessionOutcome) -> Self { value.inner }
71+
}
72+
6073
#[derive(Clone, uniffi::Enum)]
6174
pub enum SendSession {
6275
WithReplyKey { inner: Arc<WithReplyKey> },
6376
PollingForProposal { inner: Arc<PollingForProposal> },
6477
ProposalReceived { inner: Arc<Psbt> },
65-
TerminalFailure,
78+
Closed { inner: Arc<SenderSessionOutcome> },
6679
}
6780

6881
impl From<payjoin::send::v2::SendSession> for SendSession {
@@ -75,7 +88,8 @@ impl From<payjoin::send::v2::SendSession> for SendSession {
7588
Self::PollingForProposal { inner: Arc::new(inner.into()) },
7689
SendSession::ProposalReceived(inner) =>
7790
Self::ProposalReceived { inner: Arc::new(inner.into()) },
78-
SendSession::TerminalFailure => Self::TerminalFailure,
91+
SendSession::Closed(session_outcome) =>
92+
Self::Closed { inner: Arc::new(session_outcome.into()) },
7993
}
8094
}
8195
}

payjoin/src/core/receive/v2/mod.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub(crate) use error::InternalSessionError;
3434
pub use error::SessionError;
3535
use serde::de::Deserializer;
3636
use serde::{Deserialize, Serialize};
37-
pub use session::{replay_event_log, SessionEvent, SessionHistory, SessionStatus};
37+
pub use session::{replay_event_log, SessionEvent, SessionHistory, SessionOutcome, SessionStatus};
3838
use url::Url;
3939

4040
use super::error::{Error, InputContributionError};
@@ -51,7 +51,6 @@ use crate::persist::{
5151
MaybeFatalTransition, MaybeFatalTransitionWithNoResults, MaybeSuccessTransition,
5252
MaybeTransientTransition, NextStateTransition,
5353
};
54-
use crate::receive::v2::session::SessionOutcome;
5554
use crate::receive::{parse_payload, InputPair, OriginalPayload, PsbtContext};
5655
use crate::time::Time;
5756
use crate::uri::ShortId;
@@ -139,6 +138,7 @@ pub enum ReceiveSession {
139138
ProvisionalProposal(Receiver<ProvisionalProposal>),
140139
PayjoinProposal(Receiver<PayjoinProposal>),
141140
HasReplyableError(Receiver<HasReplyableError>),
141+
Closed(SessionOutcome),
142142
}
143143

144144
impl ReceiveSession {
@@ -188,6 +188,9 @@ impl ReceiveSession {
188188
SessionEvent::FinalizedProposal(payjoin_proposal),
189189
) => Ok(state.apply_finalized_proposal(payjoin_proposal)),
190190

191+
(_, SessionEvent::Closed(session_outcome)) =>
192+
Ok(ReceiveSession::Closed(session_outcome)),
193+
191194
(session, SessionEvent::GotReplyableError(error)) =>
192195
Ok(ReceiveSession::HasReplyableError(Receiver {
193196
state: HasReplyableError { error_reply: error.clone() },
@@ -203,11 +206,10 @@ impl ReceiveSession {
203206
ReceiveSession::ProvisionalProposal(r) => r.session_context,
204207
ReceiveSession::PayjoinProposal(r) => r.session_context,
205208
ReceiveSession::HasReplyableError(r) => r.session_context,
209+
ReceiveSession::Closed(_) => unreachable!(),
206210
},
207211
})),
208212

209-
(current_state, SessionEvent::Closed(_)) => Ok(current_state),
210-
211213
(current_state, event) => Err(InternalReplayError::InvalidEvent(
212214
Box::new(event),
213215
Some(Box::new(current_state)),

payjoin/src/core/receive/v2/session.rs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,7 @@ mod tests {
189189
use crate::receive::tests::original_from_test_vector;
190190
use crate::receive::v2::test::{mock_err, SHARED_CONTEXT};
191191
use crate::receive::v2::{
192-
HasReplyableError, Initialized, MaybeInputsOwned, PayjoinProposal, ProvisionalProposal,
193-
Receiver, UncheckedOriginalPayload,
192+
Initialized, MaybeInputsOwned, ProvisionalProposal, Receiver, UncheckedOriginalPayload,
194193
};
195194
use crate::receive::{InternalPayloadError, PayloadError};
196195

@@ -554,10 +553,7 @@ mod tests {
554553
fallback_tx: Some(expected_fallback),
555554
expected_status: SessionStatus::Completed,
556555
},
557-
expected_receiver_state: ReceiveSession::PayjoinProposal(Receiver {
558-
state: PayjoinProposal { psbt: payjoin_proposal.psbt().clone() },
559-
session_context: SessionContext { reply_key, ..session_context },
560-
}),
556+
expected_receiver_state: ReceiveSession::Closed(SessionOutcome::Success),
561557
};
562558
run_session_history_test(test)
563559
}
@@ -592,10 +588,7 @@ mod tests {
592588
fallback_tx: None,
593589
expected_status: SessionStatus::Failed,
594590
},
595-
expected_receiver_state: ReceiveSession::HasReplyableError(Receiver {
596-
state: HasReplyableError { error_reply: (&expected_error).into() },
597-
session_context: SessionContext { reply_key, ..session_context },
598-
}),
591+
expected_receiver_state: ReceiveSession::Closed(SessionOutcome::Failure),
599592
};
600593
run_session_history_test(test)
601594
}

payjoin/src/core/send/v2/mod.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub use error::{CreateRequestError, EncapsulationError};
3434
use error::{InternalCreateRequestError, InternalEncapsulationError};
3535
use ohttp::ClientResponse;
3636
use serde::{Deserialize, Serialize};
37-
pub use session::{replay_event_log, SessionEvent, SessionHistory};
37+
pub use session::{replay_event_log, SessionEvent, SessionHistory, SessionOutcome};
3838
use url::Url;
3939

4040
use super::error::BuildSenderError;
@@ -45,7 +45,6 @@ use crate::ohttp::{ohttp_encapsulate, process_get_res, process_post_res};
4545
use crate::persist::{
4646
MaybeFatalTransition, MaybeSuccessTransitionWithNoResults, NextStateTransition,
4747
};
48-
use crate::send::v2::session::SessionOutcome;
4948
use crate::uri::v2::PjParam;
5049
use crate::uri::ShortId;
5150
use crate::{HpkeKeyPair, HpkePublicKey, IntoUrl, OhttpKeys, PjUri, Request};
@@ -215,7 +214,7 @@ pub enum SendSession {
215214
WithReplyKey(Sender<WithReplyKey>),
216215
PollingForProposal(Sender<PollingForProposal>),
217216
ProposalReceived(Psbt),
218-
TerminalFailure,
217+
Closed(SessionOutcome),
219218
}
220219

221220
impl SendSession {
@@ -232,8 +231,7 @@ impl SendSession {
232231
SendSession::PollingForProposal(_state),
233232
SessionEvent::ReceivedProposalPsbt(proposal),
234233
) => Ok(SendSession::ProposalReceived(proposal)),
235-
(_, SessionEvent::Closed(SessionOutcome::Failure)) => Ok(SendSession::TerminalFailure),
236-
(current_state, SessionEvent::Closed(_)) => Ok(current_state),
234+
(_, SessionEvent::Closed(session_outcome)) => Ok(SendSession::Closed(session_outcome)),
237235
(current_state, event) => Err(InternalReplayError::InvalidEvent(
238236
Box::new(event),
239237
Some(Box::new(current_state)),

0 commit comments

Comments
 (0)