Skip to content

Commit 4c6c109

Browse files
authored
change: ETCM-12160 Support generic keys in wizard register commands (#930)
1 parent 66db0f1 commit 4c6c109

File tree

5 files changed

+188
-53
lines changed

5 files changed

+188
-53
lines changed

toolkit/partner-chains-cli/src/register/mod.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use partner_chains_cardano_offchain::register::run_register;
77
use plutus_datum_derive::ToDatum;
88
use secp256k1::PublicKey;
99
use sidechain_domain::*;
10+
use sp_runtime::KeyTypeId;
1011
use std::{convert::Infallible, fmt::Display, str::FromStr};
1112

1213
use crate::cmd_traits::Register;
@@ -41,6 +42,53 @@ impl FromStr for PartnerChainPublicKeyParam {
4142
}
4243
}
4344

45+
#[derive(Clone, Debug)]
46+
pub struct CandidateKeyParam(pub CandidateKey);
47+
48+
impl CandidateKeyParam {
49+
fn new(id: [u8; 4], bytes: Vec<u8>) -> Self {
50+
Self(CandidateKey { id, bytes })
51+
}
52+
53+
fn try_new_from(id: &str, bytes: Vec<u8>) -> anyhow::Result<Self> {
54+
let id = id
55+
.bytes()
56+
.collect::<Vec<u8>>()
57+
.try_into()
58+
.expect("Incorrect key type length, must be 4");
59+
Ok(Self::new(id, bytes))
60+
}
61+
}
62+
63+
impl FromStr for CandidateKeyParam {
64+
type Err = Box<dyn std::error::Error + Send + Sync>;
65+
66+
fn from_str(s: &str) -> Result<Self, Self::Err> {
67+
let parts: Vec<_> = s.split(":").collect();
68+
if parts.len() != 2 {
69+
return Err("Incorrect format, expected: <key type>:<key hex>".into());
70+
}
71+
72+
let key_type = KeyTypeId::try_from(parts[0])
73+
.map_err(|_| format!("{} is not a correct key type", parts[0]))?;
74+
let key = hex::decode(parts[1].strip_prefix("0x").unwrap_or(parts[1]))?;
75+
76+
Ok(Self(CandidateKey::new(key_type, key)))
77+
}
78+
}
79+
80+
impl ToString for CandidateKeyParam {
81+
fn to_string(&self) -> String {
82+
format!("{}:{}", String::from_utf8_lossy(&self.0.id), hex::encode(&self.0.bytes))
83+
}
84+
}
85+
86+
impl From<CandidateKeyParam> for CandidateKey {
87+
fn from(value: CandidateKeyParam) -> Self {
88+
value.0
89+
}
90+
}
91+
4492
#[derive(Clone, Debug)]
4593
pub struct PlainPublicKeyParam(pub String);
4694

toolkit/partner-chains-cli/src/register/register1.rs

Lines changed: 60 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use super::RegisterValidatorMessage;
1+
use std::collections::BTreeMap;
2+
3+
use super::{CandidateKeyParam, RegisterValidatorMessage};
24
use crate::config::KEYS_FILE_PATH;
35
use crate::io::IOContext;
46
use crate::keystore::{CROSS_CHAIN, keystore_path};
@@ -10,9 +12,9 @@ use partner_chains_cardano_offchain::csl::NetworkTypeExt;
1012
use select_utxo::{query_utxos, select_from_utxos};
1113
use serde::de::DeserializeOwned;
1214
use serde::{Deserialize, Serialize};
15+
use sidechain_domain::byte_string::ByteString;
1316
use sidechain_domain::crypto::sc_public_key_and_signature_for_datum;
1417
use sidechain_domain::{NetworkType, SidechainPublicKey, UtxoId};
15-
use sp_core::bytes::from_hex;
1618
use sp_core::{Pair, ecdsa};
1719

1820
#[derive(Clone, Debug, clap::Parser)]
@@ -26,7 +28,7 @@ impl CmdRun for Register1Cmd {
2628
let node_data_base_path =
2729
config_fields::SUBSTRATE_NODE_DATA_BASE_PATH.load_or_prompt_and_save(context);
2830

29-
let GeneratedKeysFileContent { sidechain_pub_key: pc_pub_key, aura_pub_key, grandpa_pub_key } =
31+
let GeneratedKeysFileContent { partner_chains_key, keys } =
3032
read_generated_keys(context).map_err(|e| {
3133
context.eprint(&format!("⚠️ The keys file `{KEYS_FILE_PATH}` is missing or invalid. Please run the `generate-keys` command first"));
3234
anyhow!(e)
@@ -54,33 +56,43 @@ impl CmdRun for Register1Cmd {
5456
);
5557
context.print("");
5658

57-
let pc_pub_key_typed: SidechainPublicKey =
58-
SidechainPublicKey(from_hex(&pc_pub_key).map_err(|e| {
59-
context.eprint(&format!("⚠️ Failed to decode partner-chains public key: {e}"));
60-
anyhow!(e)
61-
})?);
59+
let pc_pub_key_typed: SidechainPublicKey = SidechainPublicKey(partner_chains_key.0.clone());
6260

6361
let registration_message = RegisterValidatorMessage {
6462
genesis_utxo,
6563
sidechain_pub_key: pc_pub_key_typed,
6664
registration_utxo,
6765
};
6866

69-
let ecdsa_pair =
70-
get_ecdsa_pair_from_file(context, &keystore_path(&node_data_base_path), &pc_pub_key)
71-
.map_err(|e| {
72-
context.eprint(&format!(
73-
"⚠️ Failed to read partner chain key from the keystore: {e}"
74-
));
75-
anyhow!(e)
76-
})?;
67+
let ecdsa_pair = get_ecdsa_pair_from_file(
68+
context,
69+
&keystore_path(&node_data_base_path),
70+
&partner_chains_key.to_hex_string(),
71+
)
72+
.map_err(|e| {
73+
context.eprint(&format!("⚠️ Failed to read partner chain key from the keystore: {e}"));
74+
anyhow!(e)
75+
})?;
76+
77+
let partner_chains_key_str = partner_chains_key.to_hex_string();
7778

7879
let pc_signature =
7980
sign_registration_message_with_sidechain_key(registration_message, ecdsa_pair)?;
8081
let executable = context.current_executable()?;
8182
context.print("Run the following command to generate signatures on the next step. It has to be executed on the machine with your SPO cold signing key.");
8283
context.print("");
83-
context.print(&format!("{executable} wizards register2 \\\n --genesis-utxo {genesis_utxo} \\\n --registration-utxo {registration_utxo} \\\n --aura-pub-key {aura_pub_key} \\\n --grandpa-pub-key {grandpa_pub_key} \\\n --partner-chain-pub-key {pc_pub_key} \\\n --partner-chain-signature {pc_signature}"));
84+
context.print(&format!(
85+
"{executable} wizards register2 \\
86+
--genesis-utxo {genesis_utxo} \\
87+
--registration-utxo {registration_utxo} \\
88+
--partner-chain-pub-key {partner_chains_key_str} \\
89+
--partner-chain-signature {pc_signature}{}",
90+
keys.iter()
91+
.map(CandidateKeyParam::to_string)
92+
.map(|arg| format!(" \\\n--keys {arg}"))
93+
.collect::<Vec<_>>()
94+
.join("")
95+
));
8496

8597
Ok(())
8698
}
@@ -111,18 +123,32 @@ fn sign_registration_message_with_sidechain_key(
111123
Ok(hex::encode(sig.serialize_compact()))
112124
}
113125

114-
#[derive(Serialize, Deserialize, Debug)]
126+
#[derive(Debug)]
115127
pub struct GeneratedKeysFileContent {
116-
pub sidechain_pub_key: String,
117-
pub aura_pub_key: String,
118-
pub grandpa_pub_key: String,
128+
pub partner_chains_key: ByteString,
129+
pub keys: Vec<CandidateKeyParam>,
119130
}
120131

121132
pub fn read_generated_keys<C: IOContext>(context: &C) -> anyhow::Result<GeneratedKeysFileContent> {
122133
let keys_file_content = context
123134
.read_file(KEYS_FILE_PATH)
124135
.ok_or_else(|| anyhow::anyhow!("failed to read keys file"))?;
125-
Ok(serde_json::from_str(&keys_file_content)?)
136+
137+
#[derive(Serialize, Deserialize, Debug)]
138+
pub struct GeneratedKeysFileContentRaw {
139+
pub partner_chains_key: ByteString,
140+
pub keys: BTreeMap<String, ByteString>,
141+
}
142+
143+
let GeneratedKeysFileContentRaw { partner_chains_key, keys: raw_keys } =
144+
serde_json::from_str(&keys_file_content)?;
145+
146+
let mut keys = vec![];
147+
for (id, bytes) in raw_keys.into_iter() {
148+
keys.push(CandidateKeyParam::try_new_from(&id, bytes.0)?)
149+
}
150+
151+
Ok(GeneratedKeysFileContent { partner_chains_key, keys })
126152
}
127153

128154
pub fn load_chain_config_field<C: IOContext, T>(
@@ -411,9 +437,11 @@ mod tests {
411437

412438
fn generated_keys_file_content() -> serde_json::Value {
413439
serde_json::json!({
414-
"sidechain_pub_key": "0x031e75acbf45ef8df98bbe24b19b28fff807be32bf88838c30c0564d7bec5301f6",
415-
"aura_pub_key": "0xdf883ee0648f33b6103017b61be702017742d501b8fe73b1d69ca0157460b777",
416-
"grandpa_pub_key": "0x5a091a06abd64f245db11d2987b03218c6bd83d64c262fe10e3a2a1230e90327"
440+
"partner_chains_key": "0x031e75acbf45ef8df98bbe24b19b28fff807be32bf88838c30c0564d7bec5301f6",
441+
"keys": {
442+
"aura": "0xdf883ee0648f33b6103017b61be702017742d501b8fe73b1d69ca0157460b777",
443+
"gran": "0x5a091a06abd64f245db11d2987b03218c6bd83d64c262fe10e3a2a1230e90327"
444+
}
417445
})
418446
}
419447

@@ -508,7 +536,13 @@ mod tests {
508536
),
509537
MockIO::print(""),
510538
MockIO::print(
511-
"<mock executable> wizards register2 \\\n --genesis-utxo 0000000000000000000000000000000000000000000000000000000000000001#0 \\\n --registration-utxo 4704a903b01514645067d851382efd4a6ed5d2ff07cf30a538acc78fed7c4c02#93 \\\n --aura-pub-key 0xdf883ee0648f33b6103017b61be702017742d501b8fe73b1d69ca0157460b777 \\\n --grandpa-pub-key 0x5a091a06abd64f245db11d2987b03218c6bd83d64c262fe10e3a2a1230e90327 \\\n --partner-chain-pub-key 0x031e75acbf45ef8df98bbe24b19b28fff807be32bf88838c30c0564d7bec5301f6 \\\n --partner-chain-signature 6e295e36a6b11d8b1c5ec01ac8a639b466fbfbdda94b39ea82b0992e303d58543341345fc705e09c7838786ba0bc746d9038036f66a36d1127d924c4a0228bec",
539+
"<mock executable> wizards register2 \\
540+
--genesis-utxo 0000000000000000000000000000000000000000000000000000000000000001#0 \\
541+
--registration-utxo 4704a903b01514645067d851382efd4a6ed5d2ff07cf30a538acc78fed7c4c02#93 \\
542+
--partner-chain-pub-key 0x031e75acbf45ef8df98bbe24b19b28fff807be32bf88838c30c0564d7bec5301f6 \\
543+
--partner-chain-signature 6e295e36a6b11d8b1c5ec01ac8a639b466fbfbdda94b39ea82b0992e303d58543341345fc705e09c7838786ba0bc746d9038036f66a36d1127d924c4a0228bec \\
544+
--keys aura:df883ee0648f33b6103017b61be702017742d501b8fe73b1d69ca0157460b777 \\
545+
--keys gran:5a091a06abd64f245db11d2987b03218c6bd83d64c262fe10e3a2a1230e90327",
512546
),
513547
]
514548
}

toolkit/partner-chains-cli/src/register/register2.rs

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use super::RegisterValidatorMessage;
2-
use super::{PartnerChainPublicKeyParam, PlainPublicKeyParam, StakePoolSigningKeyParam};
1+
use super::{CandidateKeyParam, RegisterValidatorMessage};
2+
use super::{PartnerChainPublicKeyParam, StakePoolSigningKeyParam};
33
use crate::CmdRun;
44
use crate::cardano_key::get_mc_staking_signing_key_from_file;
55
use crate::io::IOContext;
@@ -17,15 +17,21 @@ pub struct Register2Cmd {
1717
#[arg(long)]
1818
pub partner_chain_pub_key: PartnerChainPublicKeyParam,
1919
#[arg(long)]
20-
pub aura_pub_key: PlainPublicKeyParam,
21-
#[arg(long)]
22-
pub grandpa_pub_key: PlainPublicKeyParam,
20+
pub keys: Vec<CandidateKeyParam>,
2321
#[arg(long)]
2422
pub partner_chain_signature: String,
2523
}
2624

2725
impl CmdRun for Register2Cmd {
2826
fn run<C: IOContext>(&self, context: &C) -> anyhow::Result<()> {
27+
let Register2Cmd {
28+
genesis_utxo,
29+
registration_utxo,
30+
partner_chain_pub_key,
31+
keys,
32+
partner_chain_signature,
33+
} = self;
34+
2935
context.print("⚙️ Register as a committee candidate (step 2/3)");
3036
context.print(
3137
" This command will use SPO cold signing key for signing the registration message.",
@@ -50,8 +56,19 @@ impl CmdRun for Register2Cmd {
5056
let executable = context.current_executable()?;
5157
context.print("To finish the registration process, run the following command on the machine with the partner chain dependencies running:\n");
5258
context.print(&format!(
53-
"{executable} wizards register3 \\\n--genesis-utxo {} \\\n--registration-utxo {} \\\n--aura-pub-key {} \\\n--grandpa-pub-key {} \\\n--partner-chain-pub-key {} \\\n--partner-chain-signature {} \\\n--spo-public-key {} \\\n--spo-signature {}",
54-
self.genesis_utxo, self.registration_utxo, self.aura_pub_key, self.grandpa_pub_key, self.partner_chain_pub_key, self.partner_chain_signature, spo_public_key, spo_signature));
59+
"{executable} wizards register3 \\
60+
--genesis-utxo {genesis_utxo} \\
61+
--registration-utxo {registration_utxo} \\
62+
--partner-chain-pub-key {partner_chain_pub_key} \\
63+
--partner-chain-signature {partner_chain_signature} \\
64+
--spo-public-key {spo_public_key} \\
65+
--spo-signature {spo_signature}{}",
66+
keys.iter()
67+
.map(CandidateKeyParam::to_string)
68+
.map(|arg| format!(" \\\n--keys {arg}"))
69+
.collect::<Vec<_>>()
70+
.join("")
71+
));
5572
Ok(())
5673
}
5774
}
@@ -65,6 +82,8 @@ fn get_stake_pool_cold_skey<C: IOContext>(
6582

6683
#[cfg(test)]
6784
mod tests {
85+
use hex_literal::hex;
86+
6887
use super::*;
6988
use crate::tests::{MockIO, MockIOContext};
7089

@@ -122,7 +141,15 @@ mod tests {
122141
"To finish the registration process, run the following command on the machine with the partner chain dependencies running:\n",
123142
),
124143
MockIO::print(
125-
"<mock executable> wizards register3 \\\n--genesis-utxo 0000000000000000000000000000000000000000000000000000000000000001#0 \\\n--registration-utxo 7e9ebd0950ae1bec5606f0cd7ac88b3c60b1103d7feb6ffa36402edae4d1b617#0 \\\n--aura-pub-key 0xdf883ee0648f33b6103017b61be702017742d501b8fe73b1d69ca0157460b777 \\\n--grandpa-pub-key 0x5a091a06abd64f245db11d2987b03218c6bd83d64c262fe10e3a2a1230e90327 \\\n--partner-chain-pub-key 0x031e75acbf45ef8df98bbe24b19b28fff807be32bf88838c30c0564d7bec5301f6 \\\n--partner-chain-signature 7a7e3e585a5dc248d4a2772814e1b58c90313443dd99369f994e960ecc4931442a08305743db7ab42ab9b8672e00250e1cc7c08bc018b0630a8197c4f95528a301 \\\n--spo-public-key 0xcef2d1630c034d3b9034eb7903d61f419a3074a1ad01d4550cc72f2b733de6e7 \\\n--spo-signature 0xaaa39fbf163ed77c69820536f5dc22854e7e13f964f1e077efde0844a09bde64c1aab4d2b401e0fe39b43c91aa931cad26fa55c8766378462c06d86c85134801",
144+
"<mock executable> wizards register3 \\
145+
--genesis-utxo 0000000000000000000000000000000000000000000000000000000000000001#0 \\
146+
--registration-utxo 7e9ebd0950ae1bec5606f0cd7ac88b3c60b1103d7feb6ffa36402edae4d1b617#0 \\
147+
--partner-chain-pub-key 0x031e75acbf45ef8df98bbe24b19b28fff807be32bf88838c30c0564d7bec5301f6 \\
148+
--partner-chain-signature 7a7e3e585a5dc248d4a2772814e1b58c90313443dd99369f994e960ecc4931442a08305743db7ab42ab9b8672e00250e1cc7c08bc018b0630a8197c4f95528a301 \\
149+
--spo-public-key 0xcef2d1630c034d3b9034eb7903d61f419a3074a1ad01d4550cc72f2b733de6e7 \\
150+
--spo-signature 0xaaa39fbf163ed77c69820536f5dc22854e7e13f964f1e077efde0844a09bde64c1aab4d2b401e0fe39b43c91aa931cad26fa55c8766378462c06d86c85134801 \\
151+
--keys aura:df883ee0648f33b6103017b61be702017742d501b8fe73b1d69ca0157460b777 \\
152+
--keys gran:5a091a06abd64f245db11d2987b03218c6bd83d64c262fe10e3a2a1230e90327",
126153
),
127154
]
128155
}
@@ -132,8 +159,10 @@ mod tests {
132159
genesis_utxo: "0000000000000000000000000000000000000000000000000000000000000001#0".parse().unwrap(),
133160
registration_utxo: "7e9ebd0950ae1bec5606f0cd7ac88b3c60b1103d7feb6ffa36402edae4d1b617#0".parse().unwrap(),
134161
partner_chain_pub_key: "0x031e75acbf45ef8df98bbe24b19b28fff807be32bf88838c30c0564d7bec5301f6".parse().unwrap(),
135-
aura_pub_key: "0xdf883ee0648f33b6103017b61be702017742d501b8fe73b1d69ca0157460b777".parse().unwrap(),
136-
grandpa_pub_key: "0x5a091a06abd64f245db11d2987b03218c6bd83d64c262fe10e3a2a1230e90327".parse().unwrap(),
162+
keys: vec![
163+
CandidateKeyParam::new(*b"aura", hex!("df883ee0648f33b6103017b61be702017742d501b8fe73b1d69ca0157460b777").to_vec()),
164+
CandidateKeyParam::new(*b"gran", hex!("5a091a06abd64f245db11d2987b03218c6bd83d64c262fe10e3a2a1230e90327").to_vec())
165+
],
137166
partner_chain_signature: "7a7e3e585a5dc248d4a2772814e1b58c90313443dd99369f994e960ecc4931442a08305743db7ab42ab9b8672e00250e1cc7c08bc018b0630a8197c4f95528a301".parse().unwrap()
138167
}
139168
}

toolkit/partner-chains-cli/src/register/register3.rs

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ use sidechain_domain::mainchain_epoch::{MainchainEpochConfig, MainchainEpochDeri
1111
use sidechain_domain::*;
1212
use sidechain_domain::{CandidateRegistration, UtxoId};
1313

14+
use super::CandidateKeyParam;
15+
1416
#[derive(Clone, Debug, Parser)]
1517
#[command(author, version, about, long_about = None)]
1618
pub struct Register3Cmd {
@@ -23,9 +25,7 @@ pub struct Register3Cmd {
2325
#[arg(long)]
2426
pub partner_chain_pub_key: SidechainPublicKey,
2527
#[arg(long)]
26-
pub aura_pub_key: AuraPublicKey,
27-
#[arg(long)]
28-
pub grandpa_pub_key: GrandpaPublicKey,
28+
pub keys: Vec<CandidateKeyParam>,
2929
#[arg(long)]
3030
pub partner_chain_signature: SidechainSignature,
3131
#[arg(long)]
@@ -63,10 +63,7 @@ impl CmdRun for Register3Cmd {
6363
partner_chain_signature: self.partner_chain_signature.clone(),
6464
own_pkh: payment_signing_key.to_pub_key_hash(),
6565
registration_utxo: self.registration_utxo,
66-
keys: CandidateKeys(vec![
67-
self.aura_pub_key.clone().into(),
68-
self.grandpa_pub_key.clone().into(),
69-
]),
66+
keys: CandidateKeys(self.keys.iter().cloned().map(Into::into).collect()),
7067
};
7168
let offchain = context.offchain_impl(&ogmios_configuration)?;
7269

@@ -334,15 +331,31 @@ mod tests {
334331
fn mock_register3_cmd() -> Register3Cmd {
335332
Register3Cmd {
336333
common_arguments: crate::CommonArguments { retry_delay_seconds: 5, retry_count: 59 },
337-
genesis_utxo: genesis_utxo(),
338-
registration_utxo: "cdefe62b0a0016c2ccf8124d7dda71f6865283667850cc7b471f761d2bc1eb13#0".parse().unwrap(),
339-
partner_chain_pub_key: "020a1091341fe5664bfa1782d5e04779689068c916b04cb365ec3153755684d9a1".parse().unwrap(),
340-
aura_pub_key: "79c3b7fc0b7697b9414cb87adcb37317d1cab32818ae18c0e97ad76395d1fdcf".parse().unwrap(),
341-
grandpa_pub_key: "1a55db596380bc63f5ee964565359b5ea8e0096c798c3281692df097abbd9aa4b657f887915ad2a52fc85c674ef4044baeaf7149546af93a2744c379b9798f07".parse().unwrap(),
342-
partner_chain_signature: SidechainSignature(hex_literal::hex!("cb6df9de1efca7a3998a8ead4e02159d5fa99c3e0d4fd6432667390bb4726854").to_vec()),
343-
spo_public_key: StakePoolPublicKey(hex_literal::hex!("cef2d1630c034d3b9034eb7903d61f419a3074a1ad01d4550cc72f2b733de6e7")),
344-
spo_signature: MainchainSignature(hex_literal::hex!("aaa39fbf163ed77c69820536f5dc22854e7e13f964f1e077efde0844a09bde64c1aab4d2b401e0fe39b43c91aa931cad26fa55c8766378462c06d86c85134801")),
345-
}
334+
genesis_utxo: genesis_utxo(),
335+
registration_utxo: "cdefe62b0a0016c2ccf8124d7dda71f6865283667850cc7b471f761d2bc1eb13#0"
336+
.parse()
337+
.unwrap(),
338+
partner_chain_pub_key:
339+
"020a1091341fe5664bfa1782d5e04779689068c916b04cb365ec3153755684d9a1"
340+
.parse()
341+
.unwrap(),
342+
keys: vec![
343+
CandidateKeyParam::new(*b"aura", hex!("79c3b7fc0b7697b9414cb87adcb37317d1cab32818ae18c0e97ad76395d1fdcf").to_vec()),
344+
CandidateKeyParam::new(*b"gran", hex!("1a55db596380bc63f5ee964565359b5ea8e0096c798c3281692df097abbd9aa4b657f887915ad2a52fc85c674ef4044baeaf7149546af93a2744c379b9798f07").to_vec()),
345+
],
346+
partner_chain_signature: SidechainSignature(
347+
hex_literal::hex!(
348+
"cb6df9de1efca7a3998a8ead4e02159d5fa99c3e0d4fd6432667390bb4726854"
349+
)
350+
.to_vec(),
351+
),
352+
spo_public_key: StakePoolPublicKey(hex_literal::hex!(
353+
"cef2d1630c034d3b9034eb7903d61f419a3074a1ad01d4550cc72f2b733de6e7"
354+
)),
355+
spo_signature: MainchainSignature(hex_literal::hex!(
356+
"aaa39fbf163ed77c69820536f5dc22854e7e13f964f1e077efde0844a09bde64c1aab4d2b401e0fe39b43c91aa931cad26fa55c8766378462c06d86c85134801"
357+
)),
358+
}
346359
}
347360

348361
fn genesis_utxo() -> UtxoId {

0 commit comments

Comments
 (0)