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)]
222pub struct StoredExit {
223 pub vtxo_id: VtxoId,
225 pub state: ExitState,
227 pub history: Vec<ExitState>,
229}
230
231impl StoredExit {
232 pub fn new(exit: &ExitVtxo) -> Self {
234 Self {
235 vtxo_id: exit.id(),
236 state: exit.state().clone(),
237 history: exit.history().clone(),
238 }
239 }
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct SerdeExitChildTx {
245 #[serde(with = "bitcoin_ext::serde::encodable")]
246 pub child_tx: Transaction,
247 pub origin: ExitTxOrigin,
248}
249
250#[derive(Debug, Clone, Deserialize, Serialize)]
251struct SerdeVtxoRequest<'a> {
252 #[serde(with = "bitcoin::amount::serde::as_sat")]
253 amount: Amount,
254 #[serde(with = "ark::encode::serde")]
255 policy: Cow<'a, VtxoPolicy>,
256}
257
258impl<'a> From<&'a VtxoRequest> for SerdeVtxoRequest<'a> {
259 fn from(v: &'a VtxoRequest) -> Self {
260 Self {
261 amount: v.amount,
262 policy: Cow::Borrowed(&v.policy),
263 }
264 }
265}
266
267impl<'a> From<SerdeVtxoRequest<'a>> for VtxoRequest {
268 fn from(v: SerdeVtxoRequest<'a>) -> Self {
269 VtxoRequest {
270 amount: v.amount,
271 policy: v.policy.into_owned(),
272 }
273 }
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
278struct SerdeRoundParticipation<'a> {
279 #[serde(with = "ark::encode::serde::cow::vec")]
280 inputs: Cow<'a, [Vtxo<Full>]>,
281 outputs: Vec<SerdeVtxoRequest<'a>>,
282 #[serde(default, skip_serializing_if = "Option::is_none", with = "ark::encode::serde::opt")]
283 unblinded_mailbox_id: Option<MailboxIdentifier>,
284}
285
286impl<'a> From<&'a RoundParticipation> for SerdeRoundParticipation<'a> {
287 fn from(v: &'a RoundParticipation) -> Self {
288 Self {
289 inputs: Cow::Borrowed(&v.inputs),
290 outputs: v.outputs.iter().map(|v| v.into()).collect(),
291 unblinded_mailbox_id: v.unblinded_mailbox_id,
292 }
293 }
294}
295
296impl<'a> From<SerdeRoundParticipation<'a>> for RoundParticipation {
297 fn from(v: SerdeRoundParticipation<'a>) -> Self {
298 Self {
299 inputs: v.inputs.into_owned(),
300 outputs: v.outputs.into_iter().map(|v| v.into()).collect(),
301 unblinded_mailbox_id: v.unblinded_mailbox_id,
302 }
303 }
304}
305
306#[derive(Debug, Default)]
309struct PersistedNoncesPlaceholder;
310
311impl ::serde::Serialize for PersistedNoncesPlaceholder {
312 fn serialize<S: ::serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
313 s.collect_seq(std::iter::empty::<()>())
314 }
315}
316
317impl<'de> ::serde::Deserialize<'de> for PersistedNoncesPlaceholder {
318 fn deserialize<D: ::serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
319 ::serde::de::IgnoredAny::deserialize(d)?;
320 Ok(PersistedNoncesPlaceholder)
321 }
322}
323
324#[derive(Debug, Serialize, Deserialize)]
326enum SerdeAttemptState<'a> {
327 AwaitingAttempt,
328 AwaitingUnsignedVtxoTree {
329 cosign_keys: Cow<'a, [Keypair]>,
330 #[serde(rename = "secret_nonces", default)]
333 _legacy_secret_nonces: PersistedNoncesPlaceholder,
334 unlock_hash: UnlockHash,
335 },
336 AwaitingFinishedRound {
337 #[serde(with = "bitcoin_ext::serde::encodable::cow")]
338 unsigned_round_tx: Cow<'a, Transaction>,
339 #[serde(with = "ark::encode::serde")]
340 vtxos_spec: Cow<'a, VtxoTreeSpec>,
341 unlock_hash: UnlockHash,
342 },
343}
344
345impl<'a> From<&'a AttemptState> for SerdeAttemptState<'a> {
346 fn from(state: &'a AttemptState) -> Self {
347 match state {
348 AttemptState::AwaitingAttempt => SerdeAttemptState::AwaitingAttempt,
349 AttemptState::AwaitingUnsignedVtxoTree { cosign_keys, unlock_hash } => {
350 SerdeAttemptState::AwaitingUnsignedVtxoTree {
351 cosign_keys: Cow::Borrowed(cosign_keys),
352 _legacy_secret_nonces: PersistedNoncesPlaceholder,
353 unlock_hash: *unlock_hash,
354 }
355 },
356 AttemptState::AwaitingFinishedRound { unsigned_round_tx, vtxos_spec, unlock_hash } => {
357 SerdeAttemptState::AwaitingFinishedRound {
358 unsigned_round_tx: Cow::Borrowed(unsigned_round_tx),
359 vtxos_spec: Cow::Borrowed(vtxos_spec),
360 unlock_hash: *unlock_hash,
361 }
362 },
363 }
364 }
365}
366
367impl<'a> From<SerdeAttemptState<'a>> for AttemptState {
368 fn from(state: SerdeAttemptState<'a>) -> Self {
369 match state {
370 SerdeAttemptState::AwaitingAttempt => AttemptState::AwaitingAttempt,
371 SerdeAttemptState::AwaitingUnsignedVtxoTree { cosign_keys, _legacy_secret_nonces: _, unlock_hash } => {
372 AttemptState::AwaitingUnsignedVtxoTree {
373 cosign_keys: cosign_keys.into_owned(),
374 unlock_hash: unlock_hash,
375 }
376 },
377 SerdeAttemptState::AwaitingFinishedRound { unsigned_round_tx, vtxos_spec, unlock_hash } => {
378 AttemptState::AwaitingFinishedRound {
379 unsigned_round_tx: unsigned_round_tx.into_owned(),
380 vtxos_spec: vtxos_spec.into_owned(),
381 unlock_hash: unlock_hash,
382 }
383 },
384 }
385 }
386}
387
388#[derive(Debug, Serialize, Deserialize)]
390enum SerdeRoundFlowState<'a> {
391 NonInteractivePending {
393 unlock_hash: UnlockHash,
394 },
395
396 InteractivePending,
398 InteractiveOngoing {
400 round_seq: RoundSeq,
401 attempt_seq: usize,
402 state: SerdeAttemptState<'a>,
403 },
404
405 Finished {
407 funding_tx: Cow<'a, Transaction>,
408 unlock_hash: UnlockHash,
409 },
410
411 Failed {
413 error: Cow<'a, str>,
414 },
415
416 Canceled,
418}
419
420impl<'a> From<&'a RoundFlowState> for SerdeRoundFlowState<'a> {
421 fn from(state: &'a RoundFlowState) -> Self {
422 match state {
423 RoundFlowState::NonInteractivePending { unlock_hash } => {
424 SerdeRoundFlowState::NonInteractivePending {
425 unlock_hash: *unlock_hash,
426 }
427 },
428 RoundFlowState::InteractivePending => SerdeRoundFlowState::InteractivePending,
429 RoundFlowState::InteractiveOngoing { round_seq, attempt_seq, state } => {
430 SerdeRoundFlowState::InteractiveOngoing {
431 round_seq: *round_seq,
432 attempt_seq: *attempt_seq,
433 state: state.into(),
434 }
435 },
436 RoundFlowState::Finished { funding_tx, unlock_hash } => {
437 SerdeRoundFlowState::Finished {
438 funding_tx: Cow::Borrowed(funding_tx),
439 unlock_hash: *unlock_hash,
440 }
441 },
442 RoundFlowState::Failed { error } => {
443 SerdeRoundFlowState::Failed {
444 error: Cow::Borrowed(error),
445 }
446 },
447 RoundFlowState::Canceled => SerdeRoundFlowState::Canceled,
448 }
449 }
450}
451
452impl<'a> From<SerdeRoundFlowState<'a>> for RoundFlowState {
453 fn from(state: SerdeRoundFlowState<'a>) -> Self {
454 match state {
455 SerdeRoundFlowState::NonInteractivePending { unlock_hash } => {
456 RoundFlowState::NonInteractivePending { unlock_hash }
457 },
458 SerdeRoundFlowState::InteractivePending => RoundFlowState::InteractivePending,
459 SerdeRoundFlowState::InteractiveOngoing { round_seq, attempt_seq, state } => {
460 RoundFlowState::InteractiveOngoing {
461 round_seq: round_seq,
462 attempt_seq: attempt_seq,
463 state: state.into(),
464 }
465 },
466 SerdeRoundFlowState::Finished { funding_tx, unlock_hash } => {
467 RoundFlowState::Finished {
468 funding_tx: funding_tx.into_owned(),
469 unlock_hash,
470 }
471 },
472 SerdeRoundFlowState::Failed { error } => {
473 RoundFlowState::Failed {
474 error: error.into_owned(),
475 }
476 },
477 SerdeRoundFlowState::Canceled => RoundFlowState::Canceled,
478 }
479 }
480}
481
482#[derive(Debug, Serialize, Deserialize)]
484pub struct SerdeRoundState<'a> {
485 done: bool,
486 participation: SerdeRoundParticipation<'a>,
487 movement_id: Option<MovementId>,
488 flow: SerdeRoundFlowState<'a>,
489 #[serde(with = "ark::encode::serde::cow::vec")]
490 new_vtxos: Cow<'a, [Vtxo<Full>]>,
491 sent_forfeit_sigs: bool,
492}
493
494impl<'a> From<&'a RoundState> for SerdeRoundState<'a> {
495 fn from(state: &'a RoundState) -> Self {
496 Self {
497 done: state.done,
498 participation: (&state.participation).into(),
499 movement_id: state.movement_id,
500 flow: (&state.flow).into(),
501 new_vtxos: Cow::Borrowed(&state.new_vtxos),
502 sent_forfeit_sigs: state.sent_forfeit_sigs,
503 }
504 }
505}
506
507impl<'a> From<SerdeRoundState<'a>> for RoundState {
508 fn from(state: SerdeRoundState<'a>) -> Self {
509 Self {
510 done: state.done,
511 participation: state.participation.into(),
512 movement_id: state.movement_id,
513 flow: state.flow.into(),
514 new_vtxos: state.new_vtxos.into_owned(),
515 sent_forfeit_sigs: state.sent_forfeit_sigs,
516 }
517 }
518}
519
520#[cfg(test)]
521mod test {
522 use crate::exit::{ExitState, ExitTxOrigin};
523 use crate::vtxo::VtxoState;
524 use super::SerdeAttemptState;
525
526 #[test]
527 fn test_serialized_structs() {
530 let serialised = r#"{"type":"start","tip_height":119}"#;
532 serde_json::from_str::<ExitState>(serialised).unwrap();
533 let serialised = r#"{"type":"awaiting-delta","tip_height":122,"confirmed_block":"122:3cdd30fc942301a74666c481beb82050ccd182050aee3c92d2197e8cad427b8f","claimable_height":134}"#;
534 serde_json::from_str::<ExitState>(serialised).unwrap();
535 let serialised = r#"{"type":"claimable","tip_height":134,"claimable_since": "134:71fe28f4c803a4c46a3a93d0a9937507d7c20b4bd9586ba317d1109e1aebaac9","last_scanned_block":null}"#;
536 serde_json::from_str::<ExitState>(serialised).unwrap();
537 let serialised = r#"{"type":"claimable","tip_height":140,"claimable_since": "134:71fe28f4c803a4c46a3a93d0a9937507d7c20b4bd9586ba317d1109e1aebaac9","last_scanned_block": "139:c6e9eb8c8b4d9620bbe87b94d7fb0fbb8eef1c4a8c1e60f7b3a5d80fe26b0d3e"}"#;
538 serde_json::from_str::<ExitState>(serialised).unwrap();
539 let serialised = r#"{"type":"claim-in-progress","tip_height":134, "claimable_since": "134:6585896bdda6f08d924bf45cc2b16418af56703b3c50930e4dccbc1728d3800a","claim_txid":"599347c35870bd36f7acb22b81f9ffa8b911d9b5e94834858aebd3ec09339f4c"}"#;
540 serde_json::from_str::<ExitState>(serialised).unwrap();
541 let serialised = r#"{"type":"claimed","tip_height":134,"txid":"599347c35870bd36f7acb22b81f9ffa8b911d9b5e94834858aebd3ec09339f4c","block": "122:3cdd30fc942301a74666c481beb82050ccd182050aee3c92d2197e8cad427b8f"}"#;
542 serde_json::from_str::<ExitState>(serialised).unwrap();
543
544 let serialised = r#"{"type":"processing","tip_height":119,"transactions":[{"txid":"9fd34b8c556dd9954bda80ba2cf3474a372702ebc31a366639483e78417c6812","status":{"type":"verify-inputs"}}]}"#;
548 serde_json::from_str::<ExitState>(serialised).unwrap();
549 let serialised = r#"{"type":"processing","tip_height":119,"transactions":[{"txid":"9fd34b8c556dd9954bda80ba2cf3474a372702ebc31a366639483e78417c6812","status":{"type":"awaiting-input-confirmation","txids":["ddfe11920358d1a1fae970dc80459c60675bf1392896f69b103fc638313751de"]}}]}"#;
550 serde_json::from_str::<ExitState>(serialised).unwrap();
551 let serialised = r#"{"type":"processing","tip_height":119,"transactions":[{"txid":"9fd34b8c556dd9954bda80ba2cf3474a372702ebc31a366639483e78417c6812","status":{"type":"awaiting-cpfp-broadcast"}}]}"#;
552 serde_json::from_str::<ExitState>(serialised).unwrap();
553 let serialised = r#"{"type":"processing","tip_height":119,"transactions":[{"txid":"9fd34b8c556dd9954bda80ba2cf3474a372702ebc31a366639483e78417c6812","status":{"type":"awaiting-confirmation","child_txid":"ddfe11920358d1a1fae970dc80459c60675bf1392896f69b103fc638313751de","origin":{"type":"wallet","confirmed_in":null}}}]}"#;
554 serde_json::from_str::<ExitState>(serialised).unwrap();
555 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}}}]}"#;
556 serde_json::from_str::<ExitState>(serialised).unwrap();
557 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"}}}]}"#;
558 serde_json::from_str::<ExitState>(serialised).unwrap();
559
560 let serialized = r#"{"type":"wallet","confirmed_in":null}"#;
562 serde_json::from_str::<ExitTxOrigin>(serialized).unwrap();
563 let serialized = r#"{"type":"wallet","confirmed_in": "134:71fe28f4c803a4c46a3a93d0a9937507d7c20b4bd9586ba317d1109e1aebaac9"}"#;
564 serde_json::from_str::<ExitTxOrigin>(serialized).unwrap();
565 let serialized = r#"{"type":"mempool"}"#;
567 serde_json::from_str::<ExitTxOrigin>(serialized).unwrap();
568 let serialized = r#"{"type":"mempool","fee_rate_kwu":25000,"total_fee":27625}"#;
570 serde_json::from_str::<ExitTxOrigin>(serialized).unwrap();
571 let serialized = r#"{"type":"block","confirmed_in": "134:71fe28f4c803a4c46a3a93d0a9937507d7c20b4bd9586ba317d1109e1aebaac9"}"#;
572 serde_json::from_str::<ExitTxOrigin>(serialized).unwrap();
573
574 let serialised = r#"{"type": "spendable"}"#;
576 serde_json::from_str::<VtxoState>(serialised).unwrap();
577 let serialised = r#"{"type": "spent"}"#;
578 serde_json::from_str::<VtxoState>(serialised).unwrap();
579 let serialised = r#"{"type": "locked", "movement_id": null}"#;
580 serde_json::from_str::<VtxoState>(serialised).unwrap();
581 let serialised = r#"{"type": "locked", "movement_id": 42}"#;
582 serde_json::from_str::<VtxoState>(serialised).unwrap();
583
584 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"}}"#;
587 serde_json::from_str::<SerdeAttemptState>(serialised).unwrap();
588 let serialised = r#"{"AwaitingUnsignedVtxoTree":{"cosign_keys":[],"unlock_hash":"0000000000000000000000000000000000000000000000000000000000000000"}}"#;
589 serde_json::from_str::<SerdeAttemptState>(serialised).unwrap();
590 }
591
592 #[test]
596 fn test_serialized_round_state_msgpack() {
597 use bitcoin::hex::FromHex;
598
599 let serialised = "81b84177616974696e67556e7369676e65645674786f5472656593909191dc0084000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c4200000000000000000000000000000000000000000000000000000000000000000";
601 rmp_serde::from_slice::<SerdeAttemptState>(
602 &Vec::<u8>::from_hex(serialised).unwrap(),
603 ).unwrap();
604 let serialised = "81b84177616974696e67556e7369676e65645674786f54726565939090c4200000000000000000000000000000000000000000000000000000000000000000";
606 rmp_serde::from_slice::<SerdeAttemptState>(
607 &Vec::<u8>::from_hex(serialised).unwrap(),
608 ).unwrap();
609 }
610}