1use std::borrow::Cow;
12use std::fmt;
13
14use bitcoin::{Amount, Transaction};
15use bitcoin::secp256k1::{Keypair, PublicKey};
16use bitcoin_ext::BlockHeight;
17use lightning_invoice::Bolt11Invoice;
18
19use ark::{Vtxo, VtxoId, VtxoPolicy, VtxoRequest};
20use ark::vtxo::Full;
21use ark::mailbox::MailboxIdentifier;
22use ark::tree::signed::{UnlockHash, VtxoTreeSpec};
23use ark::lightning::{PaymentHash, Preimage};
24use ark::rounds::RoundSeq;
25
26use crate::WalletVtxo;
27use crate::exit::{ExitState, ExitTxOrigin, ExitVtxo};
28use crate::movement::MovementId;
29use crate::lock_manager::LockGuard;
30use crate::round::{AttemptState, RoundFlowState, RoundParticipation, RoundState};
31use crate::vtxo::VtxoState;
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct SerdeVtxo {
43 #[serde(with = "ark::encode::serde")]
44 pub vtxo: Vtxo<Full>,
45 pub states: Vec<VtxoState>,
47 #[serde(default)]
50 pub registered: bool,
51}
52
53#[derive(Debug, thiserror::Error)]
54#[error("vtxo has no state")]
55pub struct MissingStateError;
56
57impl SerdeVtxo {
58 pub fn current_state(&self) -> Option<&VtxoState> {
59 self.states.last()
60 }
61
62 pub fn to_wallet_vtxo(&self) -> Result<WalletVtxo, MissingStateError> {
63 let state = self.current_state().cloned().ok_or(MissingStateError)?;
64 Ok(wallet_vtxo_from_full(&self.vtxo, state, self.registered))
65 }
66}
67
68pub(crate) fn wallet_vtxo_from_full(
78 vtxo: &Vtxo<Full>,
79 state: VtxoState,
80 registered: bool,
81) -> WalletVtxo {
82 WalletVtxo {
83 vtxo: vtxo.to_bare(),
84 state,
85 exit_depth: vtxo.exit_depth(),
86 exit_tx_weight: vtxo.transactions().map(|t| t.tx.weight()).sum(),
87 registered,
88 }
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct SerdeVtxoKey {
94 pub index: u32,
95 pub public_key: PublicKey,
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
100pub struct RoundStateId(pub u32);
101
102impl RoundStateId {
103 pub fn to_bytes(&self) -> [u8; 4] {
104 self.0.to_be_bytes()
105 }
106}
107
108impl fmt::Display for RoundStateId {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 fmt::Display::fmt(&self.0, f)
111 }
112}
113
114#[allow(unused)]
115pub struct Locked(Box<dyn LockGuard>);
116
117pub struct Unlocked;
118
119pub struct StoredRoundState<G = Locked> {
120 id: RoundStateId,
121 state: RoundState,
122 _guard: G
123}
124
125impl<G> StoredRoundState<G> {
126 pub fn id(&self) -> RoundStateId {
127 self.id
128 }
129
130 pub fn state(&self) -> &RoundState {
131 &self.state
132 }
133}
134
135impl StoredRoundState<Unlocked> {
136 pub fn new(id: RoundStateId, state: RoundState) -> Self {
137 Self { id, state, _guard: Unlocked }
138 }
139
140 pub fn lock(self, guard: Box<dyn LockGuard>) -> StoredRoundState {
141 StoredRoundState { id: self.id, state: self.state, _guard: Locked(guard) }
142 }
143}
144
145impl StoredRoundState<Locked> {
146 pub fn state_mut(&mut self) -> &mut RoundState {
147 &mut self.state
148 }
149
150 pub fn unlock(self) -> StoredRoundState<Unlocked> {
151 StoredRoundState { id: self.id, state: self.state, _guard: Unlocked }
152 }
153}
154
155#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
157pub struct PendingBoard {
158 #[serde(with = "bitcoin_ext::serde::encodable")]
161 pub funding_tx: Transaction,
162 pub vtxos: Vec<VtxoId>,
166 #[serde(with = "bitcoin::amount::serde::as_sat")]
168 pub amount: Amount,
169 pub movement_id: MovementId,
171}
172
173#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
179pub struct PaidInvoice {
180 pub payment_hash: PaymentHash,
181 pub preimage: Preimage,
182 pub paid_at: chrono::DateTime<chrono::Local>,
183}
184
185#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
190pub struct SettledLightningReceive {
191 pub payment_hash: PaymentHash,
192 pub preimage: Preimage,
193 pub invoice: Bolt11Invoice,
194 pub amount: Amount,
195 pub settled_at: chrono::DateTime<chrono::Local>,
196}
197
198#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
204pub struct StoredExit {
205 pub vtxo_id: VtxoId,
207 pub state: ExitState,
209 pub history: Vec<ExitState>,
211 pub movement_id: Option<MovementId>,
214}
215
216impl StoredExit {
217 pub fn new(exit: &ExitVtxo) -> Self {
219 Self {
220 vtxo_id: exit.id(),
221 state: exit.state().clone(),
222 history: exit.history().clone(),
223 movement_id: exit.movement_id(),
224 }
225 }
226}
227
228#[derive(Debug, Clone, Serialize, Deserialize)]
230pub struct SerdeExitChildTx {
231 #[serde(with = "bitcoin_ext::serde::encodable")]
232 pub child_tx: Transaction,
233 pub origin: ExitTxOrigin,
234}
235
236#[derive(Debug, Clone, Deserialize, Serialize)]
237struct SerdeVtxoRequest<'a> {
238 #[serde(with = "bitcoin::amount::serde::as_sat")]
239 amount: Amount,
240 #[serde(with = "ark::encode::serde")]
241 policy: Cow<'a, VtxoPolicy>,
242}
243
244impl<'a> From<&'a VtxoRequest> for SerdeVtxoRequest<'a> {
245 fn from(v: &'a VtxoRequest) -> Self {
246 Self {
247 amount: v.amount,
248 policy: Cow::Borrowed(&v.policy),
249 }
250 }
251}
252
253impl<'a> From<SerdeVtxoRequest<'a>> for VtxoRequest {
254 fn from(v: SerdeVtxoRequest<'a>) -> Self {
255 VtxoRequest {
256 amount: v.amount,
257 policy: v.policy.into_owned(),
258 }
259 }
260}
261
262#[derive(Debug, Clone, Serialize, Deserialize)]
264struct SerdeRoundParticipation<'a> {
265 #[serde(with = "ark::encode::serde::cow::vec")]
266 inputs: Cow<'a, [Vtxo<Full>]>,
267 outputs: Vec<SerdeVtxoRequest<'a>>,
268 #[serde(default, skip_serializing_if = "Option::is_none", with = "ark::encode::serde::opt")]
269 unblinded_mailbox_id: Option<MailboxIdentifier>,
270}
271
272impl<'a> From<&'a RoundParticipation> for SerdeRoundParticipation<'a> {
273 fn from(v: &'a RoundParticipation) -> Self {
274 Self {
275 inputs: Cow::Borrowed(&v.inputs),
276 outputs: v.outputs.iter().map(|v| v.into()).collect(),
277 unblinded_mailbox_id: v.unblinded_mailbox_id,
278 }
279 }
280}
281
282impl<'a> From<SerdeRoundParticipation<'a>> for RoundParticipation {
283 fn from(v: SerdeRoundParticipation<'a>) -> Self {
284 Self {
285 inputs: v.inputs.into_owned(),
286 outputs: v.outputs.into_iter().map(|v| v.into()).collect(),
287 unblinded_mailbox_id: v.unblinded_mailbox_id,
288 }
289 }
290}
291
292#[derive(Debug, Default)]
295struct PersistedNoncesPlaceholder;
296
297impl ::serde::Serialize for PersistedNoncesPlaceholder {
298 fn serialize<S: ::serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
299 s.collect_seq(std::iter::empty::<()>())
300 }
301}
302
303impl<'de> ::serde::Deserialize<'de> for PersistedNoncesPlaceholder {
304 fn deserialize<D: ::serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
305 ::serde::de::IgnoredAny::deserialize(d)?;
306 Ok(PersistedNoncesPlaceholder)
307 }
308}
309
310#[derive(Debug, Serialize, Deserialize)]
312enum SerdeAttemptState<'a> {
313 AwaitingAttempt,
314 AwaitingUnsignedVtxoTree {
315 cosign_keys: Cow<'a, [Keypair]>,
316 #[serde(rename = "secret_nonces", default)]
319 _legacy_secret_nonces: PersistedNoncesPlaceholder,
320 unlock_hash: UnlockHash,
321 },
322 AwaitingFinishedRound {
323 #[serde(with = "bitcoin_ext::serde::encodable::cow")]
324 unsigned_round_tx: Cow<'a, Transaction>,
325 #[serde(with = "ark::encode::serde")]
326 vtxos_spec: Cow<'a, VtxoTreeSpec>,
327 unlock_hash: UnlockHash,
328 },
329}
330
331impl<'a> From<&'a AttemptState> for SerdeAttemptState<'a> {
332 fn from(state: &'a AttemptState) -> Self {
333 match state {
334 AttemptState::AwaitingAttempt => SerdeAttemptState::AwaitingAttempt,
335 AttemptState::AwaitingUnsignedVtxoTree { cosign_keys, unlock_hash } => {
336 SerdeAttemptState::AwaitingUnsignedVtxoTree {
337 cosign_keys: Cow::Borrowed(cosign_keys),
338 _legacy_secret_nonces: PersistedNoncesPlaceholder,
339 unlock_hash: *unlock_hash,
340 }
341 },
342 AttemptState::AwaitingFinishedRound { unsigned_round_tx, vtxos_spec, unlock_hash } => {
343 SerdeAttemptState::AwaitingFinishedRound {
344 unsigned_round_tx: Cow::Borrowed(unsigned_round_tx),
345 vtxos_spec: Cow::Borrowed(vtxos_spec),
346 unlock_hash: *unlock_hash,
347 }
348 },
349 }
350 }
351}
352
353impl<'a> From<SerdeAttemptState<'a>> for AttemptState {
354 fn from(state: SerdeAttemptState<'a>) -> Self {
355 match state {
356 SerdeAttemptState::AwaitingAttempt => AttemptState::AwaitingAttempt,
357 SerdeAttemptState::AwaitingUnsignedVtxoTree { cosign_keys, _legacy_secret_nonces: _, unlock_hash } => {
358 AttemptState::AwaitingUnsignedVtxoTree {
359 cosign_keys: cosign_keys.into_owned(),
360 unlock_hash: unlock_hash,
361 }
362 },
363 SerdeAttemptState::AwaitingFinishedRound { unsigned_round_tx, vtxos_spec, unlock_hash } => {
364 AttemptState::AwaitingFinishedRound {
365 unsigned_round_tx: unsigned_round_tx.into_owned(),
366 vtxos_spec: vtxos_spec.into_owned(),
367 unlock_hash: unlock_hash,
368 }
369 },
370 }
371 }
372}
373
374#[derive(Debug, Serialize, Deserialize)]
376enum SerdeRoundFlowState<'a> {
377 NonInteractivePending {
379 unlock_hash: UnlockHash,
380 #[serde(default)]
382 scheduled_height: Option<BlockHeight>,
383 },
384
385 InteractivePending,
387 InteractiveOngoing {
389 round_seq: RoundSeq,
390 attempt_seq: usize,
391 state: SerdeAttemptState<'a>,
392 },
393
394 Finished {
396 funding_tx: Cow<'a, Transaction>,
397 unlock_hash: UnlockHash,
398 },
399
400 Failed {
402 error: Cow<'a, str>,
403 },
404
405 Canceled,
407}
408
409impl<'a> From<&'a RoundFlowState> for SerdeRoundFlowState<'a> {
410 fn from(state: &'a RoundFlowState) -> Self {
411 match state {
412 RoundFlowState::NonInteractivePending { unlock_hash, scheduled_height } => {
413 SerdeRoundFlowState::NonInteractivePending {
414 unlock_hash: *unlock_hash,
415 scheduled_height: *scheduled_height,
416 }
417 },
418 RoundFlowState::InteractivePending => SerdeRoundFlowState::InteractivePending,
419 RoundFlowState::InteractiveOngoing { round_seq, attempt_seq, state } => {
420 SerdeRoundFlowState::InteractiveOngoing {
421 round_seq: *round_seq,
422 attempt_seq: *attempt_seq,
423 state: state.into(),
424 }
425 },
426 RoundFlowState::Finished { funding_tx, unlock_hash } => {
427 SerdeRoundFlowState::Finished {
428 funding_tx: Cow::Borrowed(funding_tx),
429 unlock_hash: *unlock_hash,
430 }
431 },
432 RoundFlowState::Failed { error } => {
433 SerdeRoundFlowState::Failed {
434 error: Cow::Borrowed(error),
435 }
436 },
437 RoundFlowState::Canceled => SerdeRoundFlowState::Canceled,
438 }
439 }
440}
441
442impl<'a> From<SerdeRoundFlowState<'a>> for RoundFlowState {
443 fn from(state: SerdeRoundFlowState<'a>) -> Self {
444 match state {
445 SerdeRoundFlowState::NonInteractivePending { unlock_hash, scheduled_height } => {
446 RoundFlowState::NonInteractivePending { unlock_hash, scheduled_height }
447 },
448 SerdeRoundFlowState::InteractivePending => RoundFlowState::InteractivePending,
449 SerdeRoundFlowState::InteractiveOngoing { round_seq, attempt_seq, state } => {
450 RoundFlowState::InteractiveOngoing {
451 round_seq: round_seq,
452 attempt_seq: attempt_seq,
453 state: state.into(),
454 }
455 },
456 SerdeRoundFlowState::Finished { funding_tx, unlock_hash } => {
457 RoundFlowState::Finished {
458 funding_tx: funding_tx.into_owned(),
459 unlock_hash,
460 }
461 },
462 SerdeRoundFlowState::Failed { error } => {
463 RoundFlowState::Failed {
464 error: error.into_owned(),
465 }
466 },
467 SerdeRoundFlowState::Canceled => RoundFlowState::Canceled,
468 }
469 }
470}
471
472#[derive(Debug, Serialize, Deserialize)]
474pub struct SerdeRoundState<'a> {
475 done: bool,
476 participation: SerdeRoundParticipation<'a>,
477 movement_id: Option<MovementId>,
478 flow: SerdeRoundFlowState<'a>,
479 #[serde(with = "ark::encode::serde::cow::vec")]
480 new_vtxos: Cow<'a, [Vtxo<Full>]>,
481 sent_forfeit_sigs: bool,
482}
483
484impl<'a> From<&'a RoundState> for SerdeRoundState<'a> {
485 fn from(state: &'a RoundState) -> Self {
486 Self {
487 done: state.done,
488 participation: (&state.participation).into(),
489 movement_id: state.movement_id,
490 flow: (&state.flow).into(),
491 new_vtxos: Cow::Borrowed(&state.new_vtxos),
492 sent_forfeit_sigs: state.sent_forfeit_sigs,
493 }
494 }
495}
496
497impl<'a> From<SerdeRoundState<'a>> for RoundState {
498 fn from(state: SerdeRoundState<'a>) -> Self {
499 Self {
500 done: state.done,
501 participation: state.participation.into(),
502 movement_id: state.movement_id,
503 flow: state.flow.into(),
504 new_vtxos: state.new_vtxos.into_owned(),
505 sent_forfeit_sigs: state.sent_forfeit_sigs,
506 }
507 }
508}
509
510#[cfg(test)]
511mod test {
512 use crate::exit::{ExitState, ExitTxOrigin};
513 use crate::vtxo::VtxoState;
514 use super::SerdeAttemptState;
515
516 #[test]
517 fn test_serialized_structs() {
520 let serialised = r#"{"type":"start","tip_height":119}"#;
522 serde_json::from_str::<ExitState>(serialised).unwrap();
523 let serialised = r#"{"type":"awaiting-delta","tip_height":122,"confirmed_block":"122:3cdd30fc942301a74666c481beb82050ccd182050aee3c92d2197e8cad427b8f","claimable_height":134}"#;
524 serde_json::from_str::<ExitState>(serialised).unwrap();
525 let serialised = r#"{"type":"claimable","tip_height":134,"claimable_since": "134:71fe28f4c803a4c46a3a93d0a9937507d7c20b4bd9586ba317d1109e1aebaac9","last_scanned_block":null}"#;
526 serde_json::from_str::<ExitState>(serialised).unwrap();
527 let serialised = r#"{"type":"claimable","tip_height":140,"claimable_since": "134:71fe28f4c803a4c46a3a93d0a9937507d7c20b4bd9586ba317d1109e1aebaac9","last_scanned_block": "139:c6e9eb8c8b4d9620bbe87b94d7fb0fbb8eef1c4a8c1e60f7b3a5d80fe26b0d3e"}"#;
528 serde_json::from_str::<ExitState>(serialised).unwrap();
529 let serialised = r#"{"type":"claim-in-progress","tip_height":134, "claimable_since": "134:6585896bdda6f08d924bf45cc2b16418af56703b3c50930e4dccbc1728d3800a","claim_txid":"599347c35870bd36f7acb22b81f9ffa8b911d9b5e94834858aebd3ec09339f4c"}"#;
530 serde_json::from_str::<ExitState>(serialised).unwrap();
531 let serialised = r#"{"type":"claimed","tip_height":134,"txid":"599347c35870bd36f7acb22b81f9ffa8b911d9b5e94834858aebd3ec09339f4c","block": "122:3cdd30fc942301a74666c481beb82050ccd182050aee3c92d2197e8cad427b8f"}"#;
532 serde_json::from_str::<ExitState>(serialised).unwrap();
533 let serialised = r#"{"type":"vtxo-already-spent","tip_height":135}"#;
534 serde_json::from_str::<ExitState>(serialised).unwrap();
535 let serialised = r#"{"type":"canceled","tip_height":135}"#;
536 serde_json::from_str::<ExitState>(serialised).unwrap();
537
538 let serialised = r#"{"type":"processing","tip_height":119,"transactions":[{"txid":"9fd34b8c556dd9954bda80ba2cf3474a372702ebc31a366639483e78417c6812","status":{"type":"verify-inputs"}}]}"#;
542 serde_json::from_str::<ExitState>(serialised).unwrap();
543 let serialised = r#"{"type":"processing","tip_height":119,"transactions":[{"txid":"9fd34b8c556dd9954bda80ba2cf3474a372702ebc31a366639483e78417c6812","status":{"type":"awaiting-input-confirmation","txids":["ddfe11920358d1a1fae970dc80459c60675bf1392896f69b103fc638313751de"]}}]}"#;
544 serde_json::from_str::<ExitState>(serialised).unwrap();
545 let serialised = r#"{"type":"processing","tip_height":119,"transactions":[{"txid":"9fd34b8c556dd9954bda80ba2cf3474a372702ebc31a366639483e78417c6812","status":{"type":"awaiting-cpfp-broadcast"}}]}"#;
546 serde_json::from_str::<ExitState>(serialised).unwrap();
547 let serialised = r#"{"type":"processing","tip_height":119,"transactions":[{"txid":"9fd34b8c556dd9954bda80ba2cf3474a372702ebc31a366639483e78417c6812","status":{"type":"awaiting-confirmation","child_txid":"ddfe11920358d1a1fae970dc80459c60675bf1392896f69b103fc638313751de","origin":{"type":"wallet","confirmed_in":null}}}]}"#;
548 serde_json::from_str::<ExitState>(serialised).unwrap();
549 let serialised = r#"{"type":"processing","tip_height":119,"transactions":[{"txid":"9fd34b8c556dd9954bda80ba2cf3474a372702ebc31a366639483e78417c6812","status":{"type":"awaiting-confirmation","child_txid":"ddfe11920358d1a1fae970dc80459c60675bf1392896f69b103fc638313751de","origin":{"type":"mempool","fee_rate_kwu":25000,"total_fee":27625}}}]}"#;
550 serde_json::from_str::<ExitState>(serialised).unwrap();
551 let serialised = r#"{"type":"processing","tip_height":134,"transactions":[{"txid":"9fd34b8c556dd9954bda80ba2cf3474a372702ebc31a366639483e78417c6812","status":{"type":"confirmed","child_txid":"ddfe11920358d1a1fae970dc80459c60675bf1392896f69b103fc638313751de","block":"122:3cdd30fc942301a74666c481beb82050ccd182050aee3c92d2197e8cad427b8f","origin":{"type":"block","confirmed_in":"122:3cdd30fc942301a74666c481beb82050ccd182050aee3c92d2197e8cad427b8f"}}}]}"#;
552 serde_json::from_str::<ExitState>(serialised).unwrap();
553
554 let serialized = r#"{"type":"wallet","confirmed_in":null}"#;
556 serde_json::from_str::<ExitTxOrigin>(serialized).unwrap();
557 let serialized = r#"{"type":"wallet","confirmed_in": "134:71fe28f4c803a4c46a3a93d0a9937507d7c20b4bd9586ba317d1109e1aebaac9"}"#;
558 serde_json::from_str::<ExitTxOrigin>(serialized).unwrap();
559 let serialized = r#"{"type":"mempool"}"#;
561 serde_json::from_str::<ExitTxOrigin>(serialized).unwrap();
562 let serialized = r#"{"type":"mempool","fee_rate_kwu":25000,"total_fee":27625}"#;
564 serde_json::from_str::<ExitTxOrigin>(serialized).unwrap();
565 let serialized = r#"{"type":"block","confirmed_in": "134:71fe28f4c803a4c46a3a93d0a9937507d7c20b4bd9586ba317d1109e1aebaac9"}"#;
566 serde_json::from_str::<ExitTxOrigin>(serialized).unwrap();
567
568 let serialised = r#"{"type": "spendable"}"#;
570 serde_json::from_str::<VtxoState>(serialised).unwrap();
571 let serialised = r#"{"type": "spent"}"#;
572 serde_json::from_str::<VtxoState>(serialised).unwrap();
573 let serialised = r#"{"type": "exited"}"#;
574 serde_json::from_str::<VtxoState>(serialised).unwrap();
575 let serialised = r#"{"type": "locked", "movement_id": null}"#;
577 serde_json::from_str::<VtxoState>(serialised).unwrap();
578 let serialised = r#"{"type": "locked", "movement_id": 42}"#;
579 serde_json::from_str::<VtxoState>(serialised).unwrap();
580 let serialised = r#"{"type": "locked", "holder": null}"#;
582 serde_json::from_str::<VtxoState>(serialised).unwrap();
583 let serialised = r#"{"type": "locked", "holder": {"type": "movement", "id": 42}}"#;
584 serde_json::from_str::<VtxoState>(serialised).unwrap();
585 let serialised = r#"{"type": "locked", "holder": {"type": "action", "id": "test-action-id"}}"#;
586 serde_json::from_str::<VtxoState>(serialised).unwrap();
587
588 let serialised = r#"{"AwaitingUnsignedVtxoTree":{"cosign_keys":[],"secret_nonces":[[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]],"unlock_hash":"0000000000000000000000000000000000000000000000000000000000000000"}}"#;
591 serde_json::from_str::<SerdeAttemptState>(serialised).unwrap();
592 let serialised = r#"{"AwaitingUnsignedVtxoTree":{"cosign_keys":[],"unlock_hash":"0000000000000000000000000000000000000000000000000000000000000000"}}"#;
593 serde_json::from_str::<SerdeAttemptState>(serialised).unwrap();
594 }
595
596 #[test]
600 fn test_serialized_round_state_msgpack() {
601 use bitcoin::hex::FromHex;
602
603 let serialised = "81b84177616974696e67556e7369676e65645674786f5472656593909191dc0084000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c4200000000000000000000000000000000000000000000000000000000000000000";
605 rmp_serde::from_slice::<SerdeAttemptState>(
606 &Vec::<u8>::from_hex(serialised).unwrap(),
607 ).unwrap();
608 let serialised = "81b84177616974696e67556e7369676e65645674786f54726565939090c4200000000000000000000000000000000000000000000000000000000000000000";
610 rmp_serde::from_slice::<SerdeAttemptState>(
611 &Vec::<u8>::from_hex(serialised).unwrap(),
612 ).unwrap();
613 }
614
615 #[test]
619 fn test_serialized_non_interactive_pending_scheduled_height() {
620 use bitcoin::hashes::Hash;
621 use super::{SerdeRoundFlowState, UnlockHash};
622
623 #[derive(serde::Serialize)]
625 enum LegacyRoundFlowState {
626 NonInteractivePending { unlock_hash: UnlockHash },
627 }
628
629 let unlock_hash = UnlockHash::all_zeros();
630 let legacy = rmp_serde::to_vec(
631 &LegacyRoundFlowState::NonInteractivePending { unlock_hash },
632 ).unwrap();
633 match rmp_serde::from_slice::<SerdeRoundFlowState>(&legacy).unwrap() {
634 SerdeRoundFlowState::NonInteractivePending { unlock_hash: uh, scheduled_height } => {
635 assert_eq!(uh, unlock_hash);
636 assert_eq!(scheduled_height, None);
637 },
638 _ => panic!("wrong variant"),
639 }
640
641 let current = rmp_serde::to_vec(&SerdeRoundFlowState::NonInteractivePending {
643 unlock_hash,
644 scheduled_height: Some(123_456),
645 }).unwrap();
646 match rmp_serde::from_slice::<SerdeRoundFlowState>(¤t).unwrap() {
647 SerdeRoundFlowState::NonInteractivePending { scheduled_height, .. } => {
648 assert_eq!(scheduled_height, Some(123_456));
649 },
650 _ => panic!("wrong variant"),
651 }
652 }
653}