Skip to content

Commit 0b295e8

Browse files
committed
Simply return replay error on expiration
Adhering to the principle that only primary information becomes an event, knowing the session's expiration (in SessionContext) is sufficient and no new information implies no outcome event. The state machines already adhere by this and only returns an error without resulting in a state transition. This commit applies the same logic to `replay_event_log` for sender and receiver. Even assuming a non-monotonic system clock, the implication of this change is that a session that was previously considered expired may turn out to not actually be expired, and is allowed to resume. This is desirable behavior if e.g. the clock being incorrectly set to a future time was what led to the session being expired in the first place.
1 parent e8bb1a7 commit 0b295e8

3 files changed

Lines changed: 30 additions & 53 deletions

File tree

payjoin/src/core/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ impl<SessionState: Debug, SessionEvent: Debug> std::fmt::Display
5151
Some(session) => write!(f, "Invalid event ({event:?}) for session ({session:?})",),
5252
None => write!(f, "Invalid first event ({event:?}) for session",),
5353
},
54+
Expired(time) => write!(f, "Session expired at {time:?}"),
5455
PersistenceFailure(e) => write!(f, "Persistence failure: {e}"),
5556
}
5657
}
@@ -75,6 +76,8 @@ pub(crate) enum InternalReplayError<SessionState, SessionEvent> {
7576
NoEvents,
7677
/// Invalid initial event
7778
InvalidEvent(Box<SessionEvent>, Option<Box<SessionState>>),
79+
/// Session is expired
80+
Expired(crate::time::Time),
7881
/// Application storage error
7982
PersistenceFailure(ImplementationError),
8083
}

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

Lines changed: 13 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use super::{ReceiveSession, SessionContext};
44
use crate::error::{InternalReplayError, ReplayError};
55
use crate::output_substitution::OutputSubstitution;
66
use crate::persist::SessionPersister;
7-
use crate::receive::v2::{extract_err_req, InternalSessionError, SessionError};
7+
use crate::receive::v2::{extract_err_req, SessionError};
88
use crate::receive::{common, InputPair, JsonReply, OriginalPayload, PsbtContext};
99
use crate::{ImplementationError, IntoUrl, PjUri, Request};
1010

@@ -44,27 +44,15 @@ where
4444
let history = SessionHistory::new(session_events.clone());
4545
let ctx = history.session_context();
4646
if ctx.expiration.elapsed() {
47-
// Session has expired: close the session and persist a fatal error
48-
let err = SessionError(InternalSessionError::Expired(ctx.expiration));
49-
persister
50-
.save_event(SessionEvent::SessionInvalid(err.to_string(), None).into())
51-
.map_err(|e| InternalReplayError::PersistenceFailure(ImplementationError::new(e)))?;
52-
persister
53-
.close()
54-
.map_err(|e| InternalReplayError::PersistenceFailure(ImplementationError::new(e)))?;
55-
56-
session_events.push(SessionEvent::SessionInvalid(err.to_string(), None));
57-
let history = SessionHistory::new(session_events);
58-
59-
return Ok((ReceiveSession::TerminalFailure, history));
47+
return Err(InternalReplayError::Expired(ctx.expiration).into());
6048
}
6149

6250
Ok((receiver, history))
6351
}
6452

6553
/// A collection of events that have occurred during a receiver's session.
6654
/// It is obtained by calling [replay_event_log].
67-
#[derive(Clone)]
55+
#[derive(Debug, Clone)]
6856
pub struct SessionHistory {
6957
events: Vec<SessionEvent>,
7058
}
@@ -372,21 +360,16 @@ mod tests {
372360

373361
#[test]
374362
fn test_replaying_session_creation_with_expired_session() -> Result<(), BoxError> {
375-
let session_context = SessionContext {
376-
expiration: (SystemTime::now() - Duration::from_secs(1)).try_into().unwrap(),
377-
..SHARED_CONTEXT.clone()
378-
};
379-
let test = SessionHistoryTest {
380-
events: vec![SessionEvent::Created(session_context.clone())],
381-
expected_session_history: SessionHistoryExpectedOutcome {
382-
psbt_with_fee_contributions: None,
383-
fallback_tx: None,
384-
expected_status: SessionStatus::Expired,
385-
},
386-
expected_receiver_state: ReceiveSession::TerminalFailure,
387-
};
388-
// TODO: should check for the expired error message off the session history
389-
run_session_history_test(test)
363+
let expiration = (SystemTime::now() - Duration::from_secs(1)).try_into().unwrap();
364+
let session_context = SessionContext { expiration, ..SHARED_CONTEXT.clone() };
365+
let persister = InMemoryTestPersister::<SessionEvent>::default();
366+
persister.save_event(SessionEvent::Created(session_context))?;
367+
368+
let err = replay_event_log(&persister).expect_err("session should be expired");
369+
let expected_err: ReplayError<ReceiveSession, SessionEvent> =
370+
InternalReplayError::Expired(expiration).into();
371+
assert_eq!(err.to_string(), expected_err.to_string());
372+
Ok(())
390373
}
391374

392375
#[test]

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

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -40,24 +40,12 @@ where
4040
let history = SessionHistory::new(session_events.clone());
4141
let pj_param = history.pj_param();
4242
if pj_param.expiration().elapsed() {
43-
// Session has expired: close the session and persist a fatal error
44-
let session_event = SessionEvent::SessionInvalid("Session expired".to_string());
45-
persister
46-
.save_event(session_event.clone().into())
47-
.map_err(|e| InternalReplayError::PersistenceFailure(ImplementationError::new(e)))?;
48-
persister
49-
.close()
50-
.map_err(|e| InternalReplayError::PersistenceFailure(ImplementationError::new(e)))?;
51-
52-
session_events.push(session_event);
53-
let history = SessionHistory::new(session_events);
54-
55-
return Ok((SendSession::TerminalFailure, history));
43+
return Err(InternalReplayError::Expired(pj_param.expiration()).into());
5644
}
5745
Ok((sender, history))
5846
}
5947

60-
#[derive(Clone)]
48+
#[derive(Debug, Clone)]
6149
pub struct SessionHistory {
6250
events: Vec<SessionEvent>,
6351
}
@@ -225,12 +213,13 @@ mod tests {
225213
.unwrap();
226214
let reply_key = HpkeKeyPair::gen_keypair();
227215
let endpoint = sender.endpoint().clone();
228-
let fallback_tx = sender.psbt_ctx.original_psbt.clone().extract_tx_unchecked_fee_rate();
229216
let id = crate::uri::ShortId::try_from(&b"12345670"[..]).expect("valid short id");
217+
let expiration =
218+
(std::time::SystemTime::now() - std::time::Duration::from_secs(1)).try_into().unwrap();
230219
let pj_param = crate::uri::v2::PjParam::new(
231220
endpoint,
232221
id,
233-
(std::time::SystemTime::now() - std::time::Duration::from_secs(1)).try_into().unwrap(),
222+
expiration,
234223
crate::OhttpKeys(
235224
ohttp::KeyConfig::new(KEY_ID, KEM, Vec::from(SYMMETRIC)).expect("valid key config"),
236225
),
@@ -241,13 +230,15 @@ mod tests {
241230
psbt_ctx: sender.psbt_ctx.clone(),
242231
reply_key: reply_key.0,
243232
};
244-
let test = SessionHistoryTest {
245-
events: vec![SessionEvent::CreatedReplyKey(with_reply_key)],
246-
expected_session_history: SessionHistoryExpectedOutcome { fallback_tx, pj_param },
247-
expected_sender_state: SendSession::TerminalFailure,
248-
expected_error: Some("Session expired".to_string()),
249-
};
250-
run_session_history_test(test);
233+
let persister = InMemoryTestPersister::<SessionEvent>::default();
234+
persister
235+
.save_event(SessionEvent::CreatedReplyKey(with_reply_key))
236+
.expect("save_event should succeed");
237+
238+
let err = replay_event_log(&persister).expect_err("session should be expired");
239+
let expected_err: ReplayError<SendSession, SessionEvent> =
240+
InternalReplayError::Expired(expiration).into();
241+
assert_eq!(err.to_string(), expected_err.to_string());
251242
}
252243

253244
#[test]

0 commit comments

Comments
 (0)