1use std::borrow::Cow;
12use std::fmt;
13
14use bitcoin::{Amount, Transaction};
15use bitcoin::secp256k1::{Keypair, PublicKey};
16use lightning_invoice::Bolt11Invoice;
17
18use ark::{Vtxo, VtxoId, VtxoPolicy, VtxoRequest};
19use ark::vtxo::Full;
20use ark::mailbox::MailboxIdentifier;
21use ark::tree::signed::{UnlockHash, VtxoTreeSpec};
22use ark::lightning::{PaymentHash, Preimage};
23use ark::rounds::RoundSeq;
24use bitcoin_ext::BlockDelta;
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}
48
49#[derive(Debug, thiserror::Error)]
50#[error("vtxo has no state")]
51pub struct MissingStateError;
52
53impl SerdeVtxo {
54 pub fn current_state(&self) -> Option<&VtxoState> {
55 self.states.last()
56 }
57
58 pub fn to_wallet_vtxo(&self) -> Result<WalletVtxo, MissingStateError> {
59 let state = self.current_state().cloned().ok_or(MissingStateError)?;
60 Ok(wallet_vtxo_from_full(&self.vtxo, state))
61 }
62}
63
64pub(crate) fn wallet_vtxo_from_full(
74 vtxo: &Vtxo<Full>,
75 state: VtxoState,
76) -> WalletVtxo {
77 WalletVtxo {
78 vtxo: vtxo.to_bare(),
79 state,
80 exit_depth: vtxo.exit_depth(),
81 exit_tx_weight: vtxo.transactions().map(|t| t.tx.weight()).sum(),
82 }
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct SerdeVtxoKey {
88 pub index: u32,
89 pub public_key: PublicKey,
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
94pub struct RoundStateId(pub u32);
95
96impl RoundStateId {
97 pub fn to_bytes(&self) -> [u8; 4] {
98 self.0.to_be_bytes()
99 }
100}
101
102impl fmt::Display for RoundStateId {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 fmt::Display::fmt(&self.0, f)
105 }
106}
107
108#[allow(unused)]
109pub struct Locked(Box<dyn LockGuard>);
110
111pub struct Unlocked;
112
113pub struct StoredRoundState<G = Locked> {
114 id: RoundStateId,
115 state: RoundState,
116 _guard: G
117}
118
119impl<G> StoredRoundState<G> {
120 pub fn id(&self) -> RoundStateId {
121 self.id
122 }
123
124 pub fn state(&self) -> &RoundState {
125 &self.state
126 }
127}
128
129impl StoredRoundState<Unlocked> {
130 pub fn new(id: RoundStateId, state: RoundState) -> Self {
131 Self { id, state, _guard: Unlocked }
132 }
133
134 pub fn lock(self, guard: Box<dyn LockGuard>) -> StoredRoundState {
135 StoredRoundState { id: self.id, state: self.state, _guard: Locked(guard) }
136 }
137}
138
139impl StoredRoundState<Locked> {
140 pub fn state_mut(&mut self) -> &mut RoundState {
141 &mut self.state
142 }
143
144 pub fn unlock(self) -> StoredRoundState<Unlocked> {
145 StoredRoundState { id: self.id, state: self.state, _guard: Unlocked }
146 }
147}
148
149#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
151pub struct PendingBoard {
152 #[serde(with = "bitcoin_ext::serde::encodable")]
155 pub funding_tx: Transaction,
156 pub vtxos: Vec<VtxoId>,
160 #[serde(with = "bitcoin::amount::serde::as_sat")]
162 pub amount: Amount,
163 pub movement_id: MovementId,
165}
166
167#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
172pub struct PendingOffboard {
173 pub movement_id: MovementId,
175 pub offboard_txid: bitcoin::Txid,
177 pub offboard_tx: Transaction,
179 pub vtxo_ids: Vec<VtxoId>,
181 pub destination: String,
183 pub created_at: chrono::DateTime<chrono::Local>,
185}
186
187#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
193pub struct PaidInvoice {
194 pub payment_hash: PaymentHash,
195 pub preimage: Preimage,
196 pub paid_at: chrono::DateTime<chrono::Local>,
197}
198
199#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
206pub struct LightningReceive {
207 pub payment_hash: PaymentHash,
208 pub payment_preimage: Preimage,
209 pub invoice: Bolt11Invoice,
210 pub preimage_revealed_at: Option<chrono::DateTime<chrono::Local>>,
211 pub htlc_vtxos: Vec<WalletVtxo>,
212 pub htlc_recv_cltv_delta: BlockDelta,
213 pub movement_id: Option<MovementId>,
214 pub finished_at: Option<chrono::DateTime<chrono::Local>>,
215}
216
217#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
223pub struct StoredExit {
224 pub vtxo_id: VtxoId,
226 pub state: ExitState,
228 pub history: Vec<ExitState>,
230 pub movement_id: Option<MovementId>,
233}
234
235impl StoredExit {
236 pub fn new(exit: &ExitVtxo) -> Self {
238 Self {
239 vtxo_id: exit.id(),
240 state: exit.state().clone(),
241 history: exit.history().clone(),
242 movement_id: exit.movement_id(),
243 }
244 }
245}
246
247#[derive(Debug, Clone, Serialize, Deserialize)]
249pub struct SerdeExitChildTx {
250 #[serde(with = "bitcoin_ext::serde::encodable")]
251 pub child_tx: Transaction,
252 pub origin: ExitTxOrigin,
253}
254
255#[derive(Debug, Clone, Deserialize, Serialize)]
256struct SerdeVtxoRequest<'a> {
257 #[serde(with = "bitcoin::amount::serde::as_sat")]
258 amount: Amount,
259 #[serde(with = "ark::encode::serde")]
260 policy: Cow<'a, VtxoPolicy>,
261}
262
263impl<'a> From<&'a VtxoRequest> for SerdeVtxoRequest<'a> {
264 fn from(v: &'a VtxoRequest) -> Self {
265 Self {
266 amount: v.amount,
267 policy: Cow::Borrowed(&v.policy),
268 }
269 }
270}
271
272impl<'a> From<SerdeVtxoRequest<'a>> for VtxoRequest {
273 fn from(v: SerdeVtxoRequest<'a>) -> Self {
274 VtxoRequest {
275 amount: v.amount,
276 policy: v.policy.into_owned(),
277 }
278 }
279}
280
281#[derive(Debug, Clone, Serialize, Deserialize)]
283struct SerdeRoundParticipation<'a> {
284 #[serde(with = "ark::encode::serde::cow::vec")]
285 inputs: Cow<'a, [Vtxo<Full>]>,
286 outputs: Vec<SerdeVtxoRequest<'a>>,
287 #[serde(default, skip_serializing_if = "Option::is_none", with = "ark::encode::serde::opt")]
288 unblinded_mailbox_id: Option<MailboxIdentifier>,
289}
290
291impl<'a> From<&'a RoundParticipation> for SerdeRoundParticipation<'a> {
292 fn from(v: &'a RoundParticipation) -> Self {
293 Self {
294 inputs: Cow::Borrowed(&v.inputs),
295 outputs: v.outputs.iter().map(|v| v.into()).collect(),
296 unblinded_mailbox_id: v.unblinded_mailbox_id,
297 }
298 }
299}
300
301impl<'a> From<SerdeRoundParticipation<'a>> for RoundParticipation {
302 fn from(v: SerdeRoundParticipation<'a>) -> Self {
303 Self {
304 inputs: v.inputs.into_owned(),
305 outputs: v.outputs.into_iter().map(|v| v.into()).collect(),
306 unblinded_mailbox_id: v.unblinded_mailbox_id,
307 }
308 }
309}
310
311#[derive(Debug, Default)]
314struct PersistedNoncesPlaceholder;
315
316impl ::serde::Serialize for PersistedNoncesPlaceholder {
317 fn serialize<S: ::serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
318 s.collect_seq(std::iter::empty::<()>())
319 }
320}
321
322impl<'de> ::serde::Deserialize<'de> for PersistedNoncesPlaceholder {
323 fn deserialize<D: ::serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
324 ::serde::de::IgnoredAny::deserialize(d)?;
325 Ok(PersistedNoncesPlaceholder)
326 }
327}
328
329#[derive(Debug, Serialize, Deserialize)]
331enum SerdeAttemptState<'a> {
332 AwaitingAttempt,
333 AwaitingUnsignedVtxoTree {
334 cosign_keys: Cow<'a, [Keypair]>,
335 #[serde(rename = "secret_nonces", default)]
338 _legacy_secret_nonces: PersistedNoncesPlaceholder,
339 unlock_hash: UnlockHash,
340 },
341 AwaitingFinishedRound {
342 #[serde(with = "bitcoin_ext::serde::encodable::cow")]
343 unsigned_round_tx: Cow<'a, Transaction>,
344 #[serde(with = "ark::encode::serde")]
345 vtxos_spec: Cow<'a, VtxoTreeSpec>,
346 unlock_hash: UnlockHash,
347 },
348}
349
350impl<'a> From<&'a AttemptState> for SerdeAttemptState<'a> {
351 fn from(state: &'a AttemptState) -> Self {
352 match state {
353 AttemptState::AwaitingAttempt => SerdeAttemptState::AwaitingAttempt,
354 AttemptState::AwaitingUnsignedVtxoTree { cosign_keys, unlock_hash } => {
355 SerdeAttemptState::AwaitingUnsignedVtxoTree {
356 cosign_keys: Cow::Borrowed(cosign_keys),
357 _legacy_secret_nonces: PersistedNoncesPlaceholder,
358 unlock_hash: *unlock_hash,
359 }
360 },
361 AttemptState::AwaitingFinishedRound { unsigned_round_tx, vtxos_spec, unlock_hash } => {
362 SerdeAttemptState::AwaitingFinishedRound {
363 unsigned_round_tx: Cow::Borrowed(unsigned_round_tx),
364 vtxos_spec: Cow::Borrowed(vtxos_spec),
365 unlock_hash: *unlock_hash,
366 }
367 },
368 }
369 }
370}
371
372impl<'a> From<SerdeAttemptState<'a>> for AttemptState {
373 fn from(state: SerdeAttemptState<'a>) -> Self {
374 match state {
375 SerdeAttemptState::AwaitingAttempt => AttemptState::AwaitingAttempt,
376 SerdeAttemptState::AwaitingUnsignedVtxoTree { cosign_keys, _legacy_secret_nonces: _, unlock_hash } => {
377 AttemptState::AwaitingUnsignedVtxoTree {
378 cosign_keys: cosign_keys.into_owned(),
379 unlock_hash: unlock_hash,
380 }
381 },
382 SerdeAttemptState::AwaitingFinishedRound { unsigned_round_tx, vtxos_spec, unlock_hash } => {
383 AttemptState::AwaitingFinishedRound {
384 unsigned_round_tx: unsigned_round_tx.into_owned(),
385 vtxos_spec: vtxos_spec.into_owned(),
386 unlock_hash: unlock_hash,
387 }
388 },
389 }
390 }
391}
392
393#[derive(Debug, Serialize, Deserialize)]
395enum SerdeRoundFlowState<'a> {
396 NonInteractivePending {
398 unlock_hash: UnlockHash,
399 },
400
401 InteractivePending,
403 InteractiveOngoing {
405 round_seq: RoundSeq,
406 attempt_seq: usize,
407 state: SerdeAttemptState<'a>,
408 },
409
410 Finished {
412 funding_tx: Cow<'a, Transaction>,
413 unlock_hash: UnlockHash,
414 },
415
416 Failed {
418 error: Cow<'a, str>,
419 },
420
421 Canceled,
423}
424
425impl<'a> From<&'a RoundFlowState> for SerdeRoundFlowState<'a> {
426 fn from(state: &'a RoundFlowState) -> Self {
427 match state {
428 RoundFlowState::NonInteractivePending { unlock_hash } => {
429 SerdeRoundFlowState::NonInteractivePending {
430 unlock_hash: *unlock_hash,
431 }
432 },
433 RoundFlowState::InteractivePending => SerdeRoundFlowState::InteractivePending,
434 RoundFlowState::InteractiveOngoing { round_seq, attempt_seq, state } => {
435 SerdeRoundFlowState::InteractiveOngoing {
436 round_seq: *round_seq,
437 attempt_seq: *attempt_seq,
438 state: state.into(),
439 }
440 },
441 RoundFlowState::Finished { funding_tx, unlock_hash } => {
442 SerdeRoundFlowState::Finished {
443 funding_tx: Cow::Borrowed(funding_tx),
444 unlock_hash: *unlock_hash,
445 }
446 },
447 RoundFlowState::Failed { error } => {
448 SerdeRoundFlowState::Failed {
449 error: Cow::Borrowed(error),
450 }
451 },
452 RoundFlowState::Canceled => SerdeRoundFlowState::Canceled,
453 }
454 }
455}
456
457impl<'a> From<SerdeRoundFlowState<'a>> for RoundFlowState {
458 fn from(state: SerdeRoundFlowState<'a>) -> Self {
459 match state {
460 SerdeRoundFlowState::NonInteractivePending { unlock_hash } => {
461 RoundFlowState::NonInteractivePending { unlock_hash }
462 },
463 SerdeRoundFlowState::InteractivePending => RoundFlowState::InteractivePending,
464 SerdeRoundFlowState::InteractiveOngoing { round_seq, attempt_seq, state } => {
465 RoundFlowState::InteractiveOngoing {
466 round_seq: round_seq,
467 attempt_seq: attempt_seq,
468 state: state.into(),
469 }
470 },
471 SerdeRoundFlowState::Finished { funding_tx, unlock_hash } => {
472 RoundFlowState::Finished {
473 funding_tx: funding_tx.into_owned(),
474 unlock_hash,
475 }
476 },
477 SerdeRoundFlowState::Failed { error } => {
478 RoundFlowState::Failed {
479 error: error.into_owned(),
480 }
481 },
482 SerdeRoundFlowState::Canceled => RoundFlowState::Canceled,
483 }
484 }
485}
486
487#[derive(Debug, Serialize, Deserialize)]
489pub struct SerdeRoundState<'a> {
490 done: bool,
491 participation: SerdeRoundParticipation<'a>,
492 movement_id: Option<MovementId>,
493 flow: SerdeRoundFlowState<'a>,
494 #[serde(with = "ark::encode::serde::cow::vec")]
495 new_vtxos: Cow<'a, [Vtxo<Full>]>,
496 sent_forfeit_sigs: bool,
497}
498
499impl<'a> From<&'a RoundState> for SerdeRoundState<'a> {
500 fn from(state: &'a RoundState) -> Self {
501 Self {
502 done: state.done,
503 participation: (&state.participation).into(),
504 movement_id: state.movement_id,
505 flow: (&state.flow).into(),
506 new_vtxos: Cow::Borrowed(&state.new_vtxos),
507 sent_forfeit_sigs: state.sent_forfeit_sigs,
508 }
509 }
510}
511
512impl<'a> From<SerdeRoundState<'a>> for RoundState {
513 fn from(state: SerdeRoundState<'a>) -> Self {
514 Self {
515 done: state.done,
516 participation: state.participation.into(),
517 movement_id: state.movement_id,
518 flow: state.flow.into(),
519 new_vtxos: state.new_vtxos.into_owned(),
520 sent_forfeit_sigs: state.sent_forfeit_sigs,
521 }
522 }
523}
524
525#[cfg(test)]
526mod test {
527 use crate::exit::{ExitState, ExitTxOrigin};
528 use crate::vtxo::VtxoState;
529 use super::SerdeAttemptState;
530
531 #[test]
532 fn test_serialized_structs() {
535 let serialised = r#"{"type":"start","tip_height":119}"#;
537 serde_json::from_str::<ExitState>(serialised).unwrap();
538 let serialised = r#"{"type":"awaiting-delta","tip_height":122,"confirmed_block":"122:3cdd30fc942301a74666c481beb82050ccd182050aee3c92d2197e8cad427b8f","claimable_height":134}"#;
539 serde_json::from_str::<ExitState>(serialised).unwrap();
540 let serialised = r#"{"type":"claimable","tip_height":134,"claimable_since": "134:71fe28f4c803a4c46a3a93d0a9937507d7c20b4bd9586ba317d1109e1aebaac9","last_scanned_block":null}"#;
541 serde_json::from_str::<ExitState>(serialised).unwrap();
542 let serialised = r#"{"type":"claimable","tip_height":140,"claimable_since": "134:71fe28f4c803a4c46a3a93d0a9937507d7c20b4bd9586ba317d1109e1aebaac9","last_scanned_block": "139:c6e9eb8c8b4d9620bbe87b94d7fb0fbb8eef1c4a8c1e60f7b3a5d80fe26b0d3e"}"#;
543 serde_json::from_str::<ExitState>(serialised).unwrap();
544 let serialised = r#"{"type":"claim-in-progress","tip_height":134, "claimable_since": "134:6585896bdda6f08d924bf45cc2b16418af56703b3c50930e4dccbc1728d3800a","claim_txid":"599347c35870bd36f7acb22b81f9ffa8b911d9b5e94834858aebd3ec09339f4c"}"#;
545 serde_json::from_str::<ExitState>(serialised).unwrap();
546 let serialised = r#"{"type":"claimed","tip_height":134,"txid":"599347c35870bd36f7acb22b81f9ffa8b911d9b5e94834858aebd3ec09339f4c","block": "122:3cdd30fc942301a74666c481beb82050ccd182050aee3c92d2197e8cad427b8f"}"#;
547 serde_json::from_str::<ExitState>(serialised).unwrap();
548
549 let serialised = r#"{"type":"processing","tip_height":119,"transactions":[{"txid":"9fd34b8c556dd9954bda80ba2cf3474a372702ebc31a366639483e78417c6812","status":{"type":"verify-inputs"}}]}"#;
553 serde_json::from_str::<ExitState>(serialised).unwrap();
554 let serialised = r#"{"type":"processing","tip_height":119,"transactions":[{"txid":"9fd34b8c556dd9954bda80ba2cf3474a372702ebc31a366639483e78417c6812","status":{"type":"awaiting-input-confirmation","txids":["ddfe11920358d1a1fae970dc80459c60675bf1392896f69b103fc638313751de"]}}]}"#;
555 serde_json::from_str::<ExitState>(serialised).unwrap();
556 let serialised = r#"{"type":"processing","tip_height":119,"transactions":[{"txid":"9fd34b8c556dd9954bda80ba2cf3474a372702ebc31a366639483e78417c6812","status":{"type":"awaiting-cpfp-broadcast"}}]}"#;
557 serde_json::from_str::<ExitState>(serialised).unwrap();
558 let serialised = r#"{"type":"processing","tip_height":119,"transactions":[{"txid":"9fd34b8c556dd9954bda80ba2cf3474a372702ebc31a366639483e78417c6812","status":{"type":"awaiting-confirmation","child_txid":"ddfe11920358d1a1fae970dc80459c60675bf1392896f69b103fc638313751de","origin":{"type":"wallet","confirmed_in":null}}}]}"#;
559 serde_json::from_str::<ExitState>(serialised).unwrap();
560 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}}}]}"#;
561 serde_json::from_str::<ExitState>(serialised).unwrap();
562 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"}}}]}"#;
563 serde_json::from_str::<ExitState>(serialised).unwrap();
564
565 let serialized = r#"{"type":"wallet","confirmed_in":null}"#;
567 serde_json::from_str::<ExitTxOrigin>(serialized).unwrap();
568 let serialized = r#"{"type":"wallet","confirmed_in": "134:71fe28f4c803a4c46a3a93d0a9937507d7c20b4bd9586ba317d1109e1aebaac9"}"#;
569 serde_json::from_str::<ExitTxOrigin>(serialized).unwrap();
570 let serialized = r#"{"type":"mempool"}"#;
572 serde_json::from_str::<ExitTxOrigin>(serialized).unwrap();
573 let serialized = r#"{"type":"mempool","fee_rate_kwu":25000,"total_fee":27625}"#;
575 serde_json::from_str::<ExitTxOrigin>(serialized).unwrap();
576 let serialized = r#"{"type":"block","confirmed_in": "134:71fe28f4c803a4c46a3a93d0a9937507d7c20b4bd9586ba317d1109e1aebaac9"}"#;
577 serde_json::from_str::<ExitTxOrigin>(serialized).unwrap();
578
579 let serialised = r#"{"type": "spendable"}"#;
581 serde_json::from_str::<VtxoState>(serialised).unwrap();
582 let serialised = r#"{"type": "spent"}"#;
583 serde_json::from_str::<VtxoState>(serialised).unwrap();
584 let serialised = r#"{"type": "locked", "movement_id": null}"#;
585 serde_json::from_str::<VtxoState>(serialised).unwrap();
586 let serialised = r#"{"type": "locked", "movement_id": 42}"#;
587 serde_json::from_str::<VtxoState>(serialised).unwrap();
588
589 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"}}"#;
592 serde_json::from_str::<SerdeAttemptState>(serialised).unwrap();
593 let serialised = r#"{"AwaitingUnsignedVtxoTree":{"cosign_keys":[],"unlock_hash":"0000000000000000000000000000000000000000000000000000000000000000"}}"#;
594 serde_json::from_str::<SerdeAttemptState>(serialised).unwrap();
595 }
596
597 #[test]
601 fn test_serialized_round_state_msgpack() {
602 use bitcoin::hex::FromHex;
603
604 let serialised = "81b84177616974696e67556e7369676e65645674786f5472656593909191dc0084000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c4200000000000000000000000000000000000000000000000000000000000000000";
606 rmp_serde::from_slice::<SerdeAttemptState>(
607 &Vec::<u8>::from_hex(serialised).unwrap(),
608 ).unwrap();
609 let serialised = "81b84177616974696e67556e7369676e65645674786f54726565939090c4200000000000000000000000000000000000000000000000000000000000000000";
611 rmp_serde::from_slice::<SerdeAttemptState>(
612 &Vec::<u8>::from_hex(serialised).unwrap(),
613 ).unwrap();
614 }
615}