whisky_csl/tx_parser/
certificates.rs

1use std::net::{Ipv4Addr, Ipv6Addr};
2
3use cardano_serialization_lib::{self as csl};
4use whisky_common::{
5    Anchor, Certificate, CertificateType, CommitteeColdResign, CommitteeHotAuth, DRep,
6    DRepDeregistration, DRepRegistration, DRepUpdate, DelegateStake, DeregisterStake,
7    MultiHostName, PoolMetadata, PoolParams, RegisterPool, RegisterStake, Relay, RetirePool,
8    ScriptCertificate, ScriptSource, SimpleScriptCertificate, SimpleScriptSource, SingleHostAddr,
9    SingleHostName, StakeAndVoteDelegation, StakeRegistrationAndDelegation,
10    StakeVoteRegistrationAndDelegation, VoteDelegation, VoteRegistrationAndDelegation, WError,
11};
12
13use crate::tx_parser::context::{RedeemerIndex, Script};
14
15use super::CSLParser;
16
17impl CSLParser {
18    pub fn get_certificates(&self) -> &Vec<Certificate> {
19        &self.tx_body.certificates
20    }
21
22    pub(super) fn extract_certificates(&mut self) -> Result<(), WError> {
23        let certs = self.csl_tx_body.certs();
24        if certs.is_none() {
25            return Ok(());
26        }
27
28        let certs = certs.unwrap();
29        let mut result = Vec::new();
30        let len = certs.len();
31
32        for i in 0..len {
33            let cert = certs.get(i);
34
35            let script_hash = get_script_credential_from_cert(&cert);
36            let script_witness = script_hash
37                .map(|sh| self.context.script_witness.scripts.get(&sh))
38                .flatten()
39                .cloned();
40            let redeemer = self
41                .context
42                .script_witness
43                .redeemers
44                .get(&RedeemerIndex::Cert(i))
45                .cloned();
46            let certificate_type =
47                csl_cert_to_certificate_type(&cert, &csl::NetworkInfo::mainnet())?;
48            if let Some(certificate_type) = certificate_type {
49                if let Some(script_witness) = script_witness {
50                    match script_witness {
51                        Script::ProvidedNative(native_script) => {
52                            result.push(Certificate::SimpleScriptCertificate(
53                                SimpleScriptCertificate {
54                                    cert: certificate_type,
55                                    simple_script_source: Some(
56                                        SimpleScriptSource::ProvidedSimpleScriptSource(
57                                            native_script.clone(),
58                                        ),
59                                    ),
60                                },
61                            ));
62                        }
63                        Script::ProvidedPlutus(plutus_script) => {
64                            result.push(Certificate::ScriptCertificate(ScriptCertificate {
65                                cert: certificate_type,
66                                script_source: Some(ScriptSource::ProvidedScriptSource(
67                                    plutus_script,
68                                )),
69                                redeemer,
70                            }));
71                        }
72                        Script::ReferencedNative(inline_script) => {
73                            result.push(Certificate::SimpleScriptCertificate(
74                                SimpleScriptCertificate {
75                                    cert: certificate_type,
76                                    simple_script_source: Some(
77                                        SimpleScriptSource::InlineSimpleScriptSource(
78                                            inline_script.clone(),
79                                        ),
80                                    ),
81                                },
82                            ));
83                        }
84                        Script::ReferencedPlutus(inline_script) => {
85                            result.push(Certificate::ScriptCertificate(ScriptCertificate {
86                                cert: certificate_type,
87                                script_source: Some(ScriptSource::InlineScriptSource(
88                                    inline_script,
89                                )),
90                                redeemer,
91                            }));
92                        }
93                    }
94                } else {
95                    result.push(Certificate::BasicCertificate(certificate_type));
96                }
97            };
98        }
99        self.tx_body.certificates = result;
100        Ok(())
101    }
102}
103
104fn get_script_credential_from_cert(cert: &csl::Certificate) -> Option<csl::ScriptHash> {
105    match cert.kind() {
106        csl::CertificateKind::StakeRegistration => cert
107            .as_stake_registration()
108            .map(|reg| reg.stake_credential())
109            .map(|cred| cred.to_scripthash())
110            .flatten(),
111        csl::CertificateKind::StakeDeregistration => cert
112            .as_stake_deregistration()
113            .map(|dereg| dereg.stake_credential())
114            .map(|cred| cred.to_scripthash())
115            .flatten(),
116        csl::CertificateKind::StakeDelegation => cert
117            .as_stake_delegation()
118            .map(|deleg| deleg.stake_credential())
119            .map(|cred| cred.to_scripthash())
120            .flatten(),
121        csl::CertificateKind::StakeAndVoteDelegation => cert
122            .as_stake_and_vote_delegation()
123            .map(|deleg| deleg.stake_credential())
124            .map(|cred| cred.to_scripthash())
125            .flatten(),
126        csl::CertificateKind::StakeRegistrationAndDelegation => cert
127            .as_stake_registration_and_delegation()
128            .map(|reg| reg.stake_credential())
129            .map(|cred| cred.to_scripthash())
130            .flatten(),
131        csl::CertificateKind::StakeVoteRegistrationAndDelegation => cert
132            .as_stake_vote_registration_and_delegation()
133            .map(|reg| reg.stake_credential())
134            .map(|cred| cred.to_scripthash())
135            .flatten(),
136        csl::CertificateKind::CommitteeHotAuth => cert
137            .as_committee_hot_auth()
138            .map(|auth| auth.committee_hot_credential())
139            .map(|cred| cred.to_scripthash())
140            .flatten(),
141        csl::CertificateKind::CommitteeColdResign => cert
142            .as_committee_cold_resign()
143            .map(|resign| resign.committee_cold_credential())
144            .map(|cred| cred.to_scripthash())
145            .flatten(),
146        csl::CertificateKind::DRepDeregistration => cert
147            .as_drep_deregistration()
148            .map(|dereg| dereg.voting_credential())
149            .map(|cred| cred.to_scripthash())
150            .flatten(),
151        csl::CertificateKind::DRepRegistration => cert
152            .as_drep_registration()
153            .map(|reg| reg.voting_credential())
154            .map(|cred| cred.to_scripthash())
155            .flatten(),
156        csl::CertificateKind::DRepUpdate => cert
157            .as_drep_update()
158            .map(|update| update.voting_credential())
159            .map(|cred| cred.to_scripthash())
160            .flatten(),
161        csl::CertificateKind::VoteDelegation => cert
162            .as_vote_delegation()
163            .map(|deleg| deleg.stake_credential())
164            .map(|cred| cred.to_scripthash())
165            .flatten(),
166        csl::CertificateKind::VoteRegistrationAndDelegation => cert
167            .as_vote_registration_and_delegation()
168            .map(|reg| reg.stake_credential())
169            .map(|cred| cred.to_scripthash())
170            .flatten(),
171        csl::CertificateKind::PoolRegistration => None,
172        csl::CertificateKind::PoolRetirement => None,
173        csl::CertificateKind::GenesisKeyDelegation => None,
174        csl::CertificateKind::MoveInstantaneousRewardsCert => None,
175    }
176}
177
178fn csl_drep_to_drep(csl_drep: &csl::DRep) -> Result<DRep, WError> {
179    match csl_drep.kind() {
180        csl::DRepKind::KeyHash => {
181            let drep_id = csl_drep.to_bech32(true).map_err(|e| {
182                WError::new(
183                    "csl_drep_to_drep",
184                    &format!("Failed to convert drep to bech32: {:?}", e),
185                )
186            })?;
187            Ok(DRep::DRepId(drep_id))
188        }
189        csl::DRepKind::AlwaysAbstain => Ok(DRep::AlwaysAbstain),
190        csl::DRepKind::AlwaysNoConfidence => Ok(DRep::AlwaysNoConfidence),
191        csl::DRepKind::ScriptHash => {
192            let drep_id = csl_drep.to_bech32(true).map_err(|e| {
193                WError::new(
194                    "csl_drep_to_drep",
195                    &format!("Failed to convert drep to bech32: {:?}", e),
196                )
197            })?;
198            Ok(DRep::DRepId(drep_id))
199        }
200    }
201}
202
203fn csl_cert_to_certificate_type(
204    cert: &csl::Certificate,
205    network_id: &csl::NetworkInfo,
206) -> Result<Option<CertificateType>, WError> {
207    match cert.kind() {
208        csl::CertificateKind::StakeRegistration => {
209            let reg = cert.as_stake_registration().ok_or_else(|| {
210                WError::new(
211                    "csl_cert_to_certificate_type",
212                    "Failed to get stake registration",
213                )
214            })?;
215            let stake_cred = reg.stake_credential();
216            let stake_key_address = csl::RewardAddress::new(network_id.network_id(), &stake_cred)
217                .to_address()
218                .to_bech32(None)
219                .map_err(|e| {
220                    WError::new(
221                        "csl_cert_to_certificate_type",
222                        &format!("Failed to convert address to bech32: {:?}", e),
223                    )
224                })?;
225            Ok(Some(CertificateType::RegisterStake(RegisterStake {
226                stake_key_address,
227                coin: 0,
228            })))
229        }
230        csl::CertificateKind::StakeDeregistration => {
231            let dereg = cert.as_stake_deregistration().ok_or_else(|| {
232                WError::new(
233                    "csl_cert_to_certificate_type",
234                    "Failed to get stake deregistration",
235                )
236            })?;
237            let stake_cred = dereg.stake_credential();
238            let stake_key_address = csl::RewardAddress::new(network_id.network_id(), &stake_cred)
239                .to_address()
240                .to_bech32(None)
241                .map_err(|e| {
242                    WError::new(
243                        "csl_cert_to_certificate_type",
244                        &format!("Failed to convert address to bech32: {:?}", e),
245                    )
246                })?;
247            Ok(Some(CertificateType::DeregisterStake(DeregisterStake {
248                stake_key_address,
249            })))
250        }
251        csl::CertificateKind::StakeDelegation => {
252            let deleg = cert.as_stake_delegation().ok_or_else(|| {
253                WError::new(
254                    "csl_cert_to_certificate_type",
255                    "Failed to get stake delegation",
256                )
257            })?;
258            let stake_cred = deleg.stake_credential();
259            let stake_key_address = csl::RewardAddress::new(network_id.network_id(), &stake_cred)
260                .to_address()
261                .to_bech32(None)
262                .map_err(|e| {
263                    WError::new(
264                        "csl_cert_to_certificate_type",
265                        &format!("Failed to convert address to bech32: {:?}", e),
266                    )
267                })?;
268            let pool_id = deleg.pool_keyhash().to_hex();
269            Ok(Some(CertificateType::DelegateStake(DelegateStake {
270                stake_key_address,
271                pool_id,
272            })))
273        }
274        csl::CertificateKind::PoolRegistration => {
275            let pool_reg = cert.as_pool_registration().ok_or_else(|| {
276                WError::new(
277                    "csl_cert_to_certificate_type",
278                    "Failed to get pool registration",
279                )
280            })?;
281            let pool_params = pool_reg.pool_params();
282            let mapped_pool_params = csl_pool_params_to_pool_params(&pool_params)?;
283            Ok(Some(CertificateType::RegisterPool(RegisterPool {
284                pool_params: mapped_pool_params,
285            })))
286        }
287        csl::CertificateKind::PoolRetirement => {
288            let pool_ret = cert.as_pool_retirement().ok_or_else(|| {
289                WError::new(
290                    "csl_cert_to_certificate_type",
291                    "Failed to get pool retirement",
292                )
293            })?;
294            let pool_id = pool_ret.pool_keyhash().to_hex();
295            let epoch = pool_ret.epoch();
296            Ok(Some(CertificateType::RetirePool(RetirePool {
297                pool_id,
298                epoch,
299            })))
300        }
301        csl::CertificateKind::GenesisKeyDelegation => Ok(None),
302        csl::CertificateKind::MoveInstantaneousRewardsCert => Ok(None),
303        csl::CertificateKind::CommitteeHotAuth => {
304            let committee_hot_auth = cert.as_committee_hot_auth().ok_or_else(|| {
305                WError::new(
306                    "csl_cert_to_certificate_type",
307                    "Failed to get committee hot auth",
308                )
309            })?;
310            let committee_cold_credential = committee_hot_auth.committee_cold_credential().to_hex();
311            let committee_hot_credential = committee_hot_auth.committee_hot_credential().to_hex();
312            Ok(Some(CertificateType::CommitteeHotAuth(CommitteeHotAuth {
313                committee_cold_key_address: committee_cold_credential,
314                committee_hot_key_address: committee_hot_credential,
315            })))
316        }
317        csl::CertificateKind::CommitteeColdResign => {
318            let committee_cold_resign = cert.as_committee_cold_resign().ok_or_else(|| {
319                WError::new(
320                    "csl_cert_to_certificate_type",
321                    "Failed to get committee cold resign",
322                )
323            })?;
324            let committee_cold_credential =
325                committee_cold_resign.committee_cold_credential().to_hex();
326            let anchor = committee_cold_resign.anchor().map(|a| Anchor {
327                anchor_url: a.url().url(),
328                anchor_data_hash: a.anchor_data_hash().to_hex(),
329            });
330            Ok(Some(CertificateType::CommitteeColdResign(
331                CommitteeColdResign {
332                    committee_cold_key_address: committee_cold_credential,
333                    anchor,
334                },
335            )))
336        }
337        csl::CertificateKind::DRepDeregistration => {
338            let drep_dereg = cert.as_drep_deregistration().ok_or_else(|| {
339                WError::new(
340                    "csl_cert_to_certificate_type",
341                    "Failed to get drep deregistration",
342                )
343            })?;
344            let drep = csl::DRep::new_from_credential(&drep_dereg.voting_credential());
345            let drep_id = drep.to_bech32(true).map_err(|e| {
346                WError::new(
347                    "csl_cert_to_certificate_type",
348                    &format!("Failed to convert drep to bech32: {:?}", e),
349                )
350            })?;
351            let coin = drep_dereg.coin().to_str().parse::<u64>().unwrap_or(0);
352            Ok(Some(CertificateType::DRepDeregistration(
353                DRepDeregistration { drep_id, coin },
354            )))
355        }
356        csl::CertificateKind::DRepRegistration => {
357            let drep_reg = cert.as_drep_registration().ok_or_else(|| {
358                WError::new(
359                    "csl_cert_to_certificate_type",
360                    "Failed to get drep registration",
361                )
362            })?;
363            let drep = csl::DRep::new_from_credential(&drep_reg.voting_credential());
364            let drep_id = drep.to_bech32(true).map_err(|e| {
365                WError::new(
366                    "csl_cert_to_certificate_type",
367                    &format!("Failed to convert drep to bech32: {:?}", e),
368                )
369            })?;
370            let coin = drep_reg.coin().to_str().parse::<u64>().unwrap_or(0);
371            let anchor = drep_reg.anchor().map(|a| Anchor {
372                anchor_url: a.url().url(),
373                anchor_data_hash: a.anchor_data_hash().to_hex(),
374            });
375            Ok(Some(CertificateType::DRepRegistration(DRepRegistration {
376                drep_id,
377                coin,
378                anchor,
379            })))
380        }
381        csl::CertificateKind::DRepUpdate => {
382            let drep_update = cert.as_drep_update().ok_or_else(|| {
383                WError::new("csl_cert_to_certificate_type", "Failed to get drep update")
384            })?;
385            let drep = csl::DRep::new_from_credential(&drep_update.voting_credential());
386            let drep_id = drep.to_bech32(true).map_err(|e| {
387                WError::new(
388                    "csl_cert_to_certificate_type",
389                    &format!("Failed to convert drep to bech32: {:?}", e),
390                )
391            })?;
392            let anchor = drep_update.anchor().map(|a| Anchor {
393                anchor_url: a.url().url(),
394                anchor_data_hash: a.anchor_data_hash().to_hex(),
395            });
396            Ok(Some(CertificateType::DRepUpdate(DRepUpdate {
397                drep_id,
398                anchor,
399            })))
400        }
401        csl::CertificateKind::VoteDelegation => {
402            let vote_deleg = cert.as_vote_delegation().ok_or_else(|| {
403                WError::new(
404                    "csl_cert_to_certificate_type",
405                    "Failed to get vote delegation",
406                )
407            })?;
408            let stake_cred = vote_deleg.stake_credential();
409            let stake_key_address = csl::RewardAddress::new(network_id.network_id(), &stake_cred)
410                .to_address()
411                .to_bech32(None)
412                .map_err(|e| {
413                    WError::new(
414                        "csl_cert_to_certificate_type",
415                        &format!("Failed to convert address to bech32: {:?}", e),
416                    )
417                })?;
418            let drep = csl_drep_to_drep(&vote_deleg.drep())?;
419            Ok(Some(CertificateType::VoteDelegation(VoteDelegation {
420                stake_key_address,
421                drep,
422            })))
423        }
424        csl::CertificateKind::StakeAndVoteDelegation => {
425            let stake_and_vote_deleg = cert.as_stake_and_vote_delegation().ok_or_else(|| {
426                WError::new(
427                    "csl_cert_to_certificate_type",
428                    "Failed to get stake and vote delegation",
429                )
430            })?;
431            let stake_key_address = csl::RewardAddress::new(
432                network_id.network_id(),
433                &stake_and_vote_deleg.stake_credential(),
434            )
435            .to_address()
436            .to_bech32(None)
437            .map_err(|e| {
438                WError::new(
439                    "csl_cert_to_certificate_type",
440                    &format!("Failed to convert address to bech32: {:?}", e),
441                )
442            })?;
443            let pool_key_hash = stake_and_vote_deleg.pool_keyhash().to_hex();
444            let drep = csl_drep_to_drep(&stake_and_vote_deleg.drep())?;
445            Ok(Some(CertificateType::StakeAndVoteDelegation(
446                StakeAndVoteDelegation {
447                    stake_key_address,
448                    drep,
449                    pool_key_hash,
450                },
451            )))
452        }
453        csl::CertificateKind::StakeRegistrationAndDelegation => {
454            let stake_reg_and_deleg =
455                cert.as_stake_registration_and_delegation().ok_or_else(|| {
456                    WError::new(
457                        "csl_cert_to_certificate_type",
458                        "Failed to get stake registration and delegation",
459                    )
460                })?;
461            let stake_key_address = csl::RewardAddress::new(
462                network_id.network_id(),
463                &stake_reg_and_deleg.stake_credential(),
464            )
465            .to_address()
466            .to_bech32(None)
467            .map_err(|e| {
468                WError::new(
469                    "csl_cert_to_certificate_type",
470                    &format!("Failed to convert address to bech32: {:?}", e),
471                )
472            })?;
473            let pool_key_hash = stake_reg_and_deleg.pool_keyhash().to_hex();
474            let coin = stake_reg_and_deleg
475                .coin()
476                .to_str()
477                .parse::<u64>()
478                .unwrap_or(0);
479            Ok(Some(CertificateType::StakeRegistrationAndDelegation(
480                StakeRegistrationAndDelegation {
481                    stake_key_address,
482                    pool_key_hash,
483                    coin,
484                },
485            )))
486        }
487        csl::CertificateKind::StakeVoteRegistrationAndDelegation => {
488            let stake_vote_reg_and_deleg = cert
489                .as_stake_vote_registration_and_delegation()
490                .ok_or_else(|| {
491                    WError::new(
492                        "csl_cert_to_certificate_type",
493                        "Failed to get stake vote registration and delegation",
494                    )
495                })?;
496            let stake_key_address = csl::RewardAddress::new(
497                network_id.network_id(),
498                &stake_vote_reg_and_deleg.stake_credential(),
499            )
500            .to_address()
501            .to_bech32(None)
502            .map_err(|e| {
503                WError::new(
504                    "csl_cert_to_certificate_type",
505                    &format!("Failed to convert address to bech32: {:?}", e),
506                )
507            })?;
508            let pool_key_hash = stake_vote_reg_and_deleg.pool_keyhash().to_hex();
509            let coin = stake_vote_reg_and_deleg
510                .coin()
511                .to_str()
512                .parse::<u64>()
513                .unwrap_or(0);
514            let drep = csl_drep_to_drep(&stake_vote_reg_and_deleg.drep())?;
515            Ok(Some(CertificateType::StakeVoteRegistrationAndDelegation(
516                StakeVoteRegistrationAndDelegation {
517                    stake_key_address,
518                    pool_key_hash,
519                    coin,
520                    drep,
521                },
522            )))
523        }
524        csl::CertificateKind::VoteRegistrationAndDelegation => {
525            let vote_reg_and_deleg =
526                cert.as_vote_registration_and_delegation().ok_or_else(|| {
527                    WError::new(
528                        "csl_cert_to_certificate_type",
529                        "Failed to get vote registration and delegation",
530                    )
531                })?;
532            let stake_key_address = csl::RewardAddress::new(
533                network_id.network_id(),
534                &vote_reg_and_deleg.stake_credential(),
535            )
536            .to_address()
537            .to_bech32(None)
538            .map_err(|e| {
539                WError::new(
540                    "csl_cert_to_certificate_type",
541                    &format!("Failed to convert address to bech32: {:?}", e),
542                )
543            })?;
544            let drep = csl_drep_to_drep(&vote_reg_and_deleg.drep())?;
545            let coin = vote_reg_and_deleg
546                .coin()
547                .to_str()
548                .parse::<u64>()
549                .unwrap_or(0);
550            Ok(Some(CertificateType::VoteRegistrationAndDelegation(
551                VoteRegistrationAndDelegation {
552                    stake_key_address,
553                    drep,
554                    coin,
555                },
556            )))
557        }
558    }
559}
560
561pub fn csl_pool_params_to_pool_params(pool_params: &csl::PoolParams) -> Result<PoolParams, WError> {
562    let vrf_key_hash = pool_params.vrf_keyhash().to_hex();
563    let operator = pool_params.operator().to_hex();
564    let pledge = pool_params.pledge().to_str();
565    let cost = pool_params.cost().to_str();
566    let margin = (
567        pool_params
568            .margin()
569            .numerator()
570            .to_str()
571            .parse::<u64>()
572            .unwrap_or(0),
573        pool_params
574            .margin()
575            .denominator()
576            .to_str()
577            .parse::<u64>()
578            .unwrap_or(1),
579    );
580    let reward_address = pool_params
581        .reward_account()
582        .to_address()
583        .to_bech32(None)
584        .map_err(|e| {
585            WError::new(
586                "csl_pool_params_to_pool_params",
587                &format!("Failed to convert reward address to bech32: {:?}", e),
588            )
589        })?;
590
591    let pool_owners = (0..pool_params.pool_owners().len())
592        .map(|i| pool_params.pool_owners().get(i).to_hex())
593        .collect();
594
595    let mut relays = Vec::new();
596    let csl_relays = pool_params.relays();
597    let relays_len = csl_relays.len();
598    for i in 0..relays_len {
599        let relay = csl_relays.get(i);
600        let relay = csl_relay_to_relay(&relay)?;
601        relays.push(relay);
602    }
603
604    let metadata = pool_params.pool_metadata().map(|metadata| PoolMetadata {
605        url: metadata.url().url(),
606        hash: metadata.pool_metadata_hash().to_hex(),
607    });
608
609    Ok(PoolParams {
610        vrf_key_hash,
611        operator,
612        pledge,
613        cost,
614        margin,
615        relays,
616        owners: pool_owners,
617        reward_address,
618        metadata,
619    })
620}
621
622pub fn csl_relay_to_relay(relay: &csl::Relay) -> Result<Relay, WError> {
623    match relay.kind() {
624        csl::RelayKind::SingleHostAddr => {
625            let single_host_addr = relay.as_single_host_addr().ok_or_else(|| {
626                WError::new("csl_relay_to_relay", "Failed to get single host addr")
627            })?;
628            Ok(Relay::SingleHostAddr(SingleHostAddr {
629                ipv4: single_host_addr.ipv4().map(|ipv4| {
630                    let octets = ipv4.ip();
631                    let ipv4_bytes = <[u8; 4]>::try_from(&octets[..4]).unwrap_or([0, 0, 0, 0]);
632                    Ipv4Addr::from(ipv4_bytes).to_string()
633                }),
634                ipv6: single_host_addr.ipv6().map(|ipv6| {
635                    let octets = ipv6.ip();
636                    let ipv6_bytes = <[u8; 16]>::try_from(&octets[..16]).unwrap_or([0; 16]);
637                    Ipv6Addr::from(ipv6_bytes).to_string()
638                }),
639                port: single_host_addr.port(),
640            }))
641        }
642        csl::RelayKind::SingleHostName => {
643            let single_host_name = relay.as_single_host_name().ok_or_else(|| {
644                WError::new("csl_relay_to_relay", "Failed to get single host name")
645            })?;
646            Ok(Relay::SingleHostName(SingleHostName {
647                domain_name: single_host_name.dns_name().record(),
648                port: single_host_name.port(),
649            }))
650        }
651        csl::RelayKind::MultiHostName => {
652            let multi_host_name = relay.as_multi_host_name().ok_or_else(|| {
653                WError::new("csl_relay_to_relay", "Failed to get multi host name")
654            })?;
655            Ok(Relay::MultiHostName(MultiHostName {
656                domain_name: multi_host_name.dns_name().record(),
657            }))
658        }
659    }
660}