From fdc3f89f3d90edc21418b7c18aaa4d5de9a9e392 Mon Sep 17 00:00:00 2001 From: Ivan Kalinin Date: Wed, 10 Apr 2024 20:56:29 +0200 Subject: [PATCH 1/6] feat(cli): add basic cli tools --- Cargo.lock | 7 +++++ cli/Cargo.toml | 9 +++++- cli/src/main.rs | 53 +++++++++++++++++++++++++++++-- cli/src/tools/gen_dht.rs | 68 ++++++++++++++++++++++++++++++++++++++++ cli/src/tools/gen_key.rs | 52 ++++++++++++++++++++++++++++++ cli/src/util/mod.rs | 19 +++++++++++ 6 files changed, 204 insertions(+), 4 deletions(-) create mode 100644 cli/src/tools/gen_dht.rs create mode 100644 cli/src/tools/gen_key.rs create mode 100644 cli/src/util/mod.rs diff --git a/Cargo.lock b/Cargo.lock index b13c8c6e6..2ab38c95e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2171,10 +2171,17 @@ name = "tycho-cli" version = "0.0.1" dependencies = [ "anyhow", + "base64 0.22.0", "clap", + "everscale-crypto", + "hex", + "rand", "rustc_version", + "serde_json", "tikv-jemallocator", "tokio", + "tycho-network", + "tycho-util", ] [[package]] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 9cb71e2c2..daaec36bd 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -15,14 +15,21 @@ path = "./src/main.rs" [dependencies] # crates.io deps anyhow = { workspace = true } +base64 = { workspace = true } clap = { workspace = true } -tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } +everscale-crypto = { workspace = true } +hex = { workspace = true } +rand = { workspace = true } +serde_json = { workspace = true } tikv-jemallocator = { workspace = true, features = [ "unprefixed_malloc_on_supported_platforms", "background_threads", ], optional = true } +tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } # local deps +tycho-network = { workspace = true } +tycho-util = { workspace = true } [build-dependencies] anyhow = { workspace = true } diff --git a/cli/src/main.rs b/cli/src/main.rs index 238d6467c..290c82267 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,18 +1,33 @@ +use std::process::ExitCode; use std::sync::OnceLock; +use anyhow::Result; use clap::{Parser, Subcommand}; +mod tools { + pub mod gen_dht; + pub mod gen_key; +} + +mod util; + #[cfg(feature = "jemalloc")] #[global_allocator] static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; -fn main() { +fn main() -> ExitCode { if std::env::var("RUST_BACKTRACE").is_err() { // Enable backtraces on panics by default. std::env::set_var("RUST_BACKTRACE", "1"); } - App::parse().run(); + match App::parse().run() { + Ok(()) => ExitCode::SUCCESS, + Err(err) => { + eprintln!("Error: {err}"); + ExitCode::FAILURE + } + } } /// Tycho Node @@ -26,13 +41,29 @@ struct App { } impl App { - fn run(self) {} + fn run(self) -> Result<()> { + self.cmd.run() + } } #[derive(Subcommand)] enum Cmd { Init(InitCmd), + Run(RunCmd), + + #[clap(subcommand)] + Tool(ToolCmd), +} + +impl Cmd { + fn run(self) -> Result<()> { + match self { + Cmd::Init(_cmd) => Ok(()), // todo + Cmd::Run(_cmd) => Ok(()), // todo + Cmd::Tool(cmd) => cmd.run(), + } + } } /// Initialize a node environment @@ -43,6 +74,22 @@ struct InitCmd {} #[derive(Parser)] struct RunCmd {} +/// A collection of tools +#[derive(Subcommand)] +enum ToolCmd { + GenDht(tools::gen_dht::CmdGenDht), + GenKey(tools::gen_key::CmdGenKey), +} + +impl ToolCmd { + fn run(self) -> Result<()> { + match self { + ToolCmd::GenDht(cmd) => cmd.run(), + ToolCmd::GenKey(cmd) => cmd.run(), + } + } +} + fn version_string() -> &'static str { static STRING: OnceLock = OnceLock::new(); STRING.get_or_init(|| { diff --git a/cli/src/tools/gen_dht.rs b/cli/src/tools/gen_dht.rs new file mode 100644 index 000000000..121529930 --- /dev/null +++ b/cli/src/tools/gen_dht.rs @@ -0,0 +1,68 @@ +use std::io::{IsTerminal, Read}; + +use anyhow::Result; +use everscale_crypto::ed25519; +use tycho_network::{Address, PeerId, PeerInfo}; +use tycho_util::time::now_sec; + +use crate::util::parse_secret_key; + +/// Generate a DHT entry for a node. +#[derive(clap::Parser)] +pub struct CmdGenDht { + /// a list of node addresses + #[clap(required = true)] + addr: Vec
, + + /// node secret key (reads from stdin if not provided) + #[clap(long)] + key: Option, + + /// expect a raw key input (32 bytes) + #[clap(short, long)] + raw_key: bool, + + /// time to live in seconds (default: unlimited) + #[clap(long)] + ttl: Option, +} + +impl CmdGenDht { + pub fn run(self) -> Result<()> { + // Read key + let key = match self.key { + Some(key) => key.into_bytes(), + None => { + let mut key = Vec::new(); + std::io::stdin().read_to_end(&mut key)?; + key + } + }; + let key = parse_secret_key(&key, self.raw_key)?; + let entry = make_peer_info(&key, self.addr, self.ttl); + + let output = if std::io::stdin().is_terminal() { + serde_json::to_string_pretty(&entry) + } else { + serde_json::to_string(&entry) + }?; + println!("{output}"); + Ok(()) + } +} + +fn make_peer_info(key: &ed25519::SecretKey, addresses: Vec
, ttl: Option) -> PeerInfo { + let keypair = ed25519::KeyPair::from(key); + let peer_id = PeerId::from(keypair.public_key); + + let now = now_sec(); + let mut node_info = PeerInfo { + id: peer_id, + address_list: addresses.into_boxed_slice(), + created_at: now, + expires_at: ttl.unwrap_or(u32::MAX), + signature: Box::new([0; 64]), + }; + *node_info.signature = keypair.sign(&node_info); + node_info +} diff --git a/cli/src/tools/gen_key.rs b/cli/src/tools/gen_key.rs new file mode 100644 index 000000000..93805a3ca --- /dev/null +++ b/cli/src/tools/gen_key.rs @@ -0,0 +1,52 @@ +use std::io::{IsTerminal, Read}; + +use anyhow::Result; +use everscale_crypto::ed25519; + +use crate::util::parse_secret_key; + +/// Generate a new key pair +#[derive(clap::Parser)] +pub struct CmdGenKey { + /// secret key (reads from stdin if only flag is provided) + #[clap(long)] + key: Option>, + + /// expect a raw key input (32 bytes) + #[clap(short, long, requires = "key")] + raw_key: bool, +} + +impl CmdGenKey { + pub fn run(self) -> Result<()> { + let secret = match self.key { + Some(flag) => { + let key = match flag { + Some(key) => key.into_bytes(), + None => { + let mut key = Vec::new(); + std::io::stdin().read_to_end(&mut key)?; + key + } + }; + parse_secret_key(&key, self.raw_key)? + } + None => ed25519::SecretKey::generate(&mut rand::thread_rng()), + }; + + let public = ed25519::PublicKey::from(&secret); + + let keypair = serde_json::json!({ + "public": hex::encode(public.as_bytes()), + "secret": hex::encode(secret.as_bytes()), + }); + + let output = if std::io::stdin().is_terminal() { + serde_json::to_string_pretty(&keypair) + } else { + serde_json::to_string(&keypair) + }?; + println!("{output}"); + Ok(()) + } +} diff --git a/cli/src/util/mod.rs b/cli/src/util/mod.rs new file mode 100644 index 000000000..d1ef8f0da --- /dev/null +++ b/cli/src/util/mod.rs @@ -0,0 +1,19 @@ +use anyhow::{Context, Result}; +use base64::prelude::{Engine as _, BASE64_STANDARD}; +use everscale_crypto::ed25519; + +pub fn parse_secret_key(key: &[u8], raw_key: bool) -> Result { + let key = if raw_key { + key.try_into().ok() + } else { + let key = std::str::from_utf8(key)?.trim(); + match key.len() { + 44 => BASE64_STANDARD.decode(key)?.try_into().ok(), + 64 => hex::decode(key)?.try_into().ok(), + _ => None, + } + }; + + key.map(ed25519::SecretKey::from_bytes) + .context("invalid key length") +} From d30448fd6a0de99b60852b465762ae2bc1df9577 Mon Sep 17 00:00:00 2001 From: Ivan Kalinin Date: Thu, 11 Apr 2024 19:37:03 +0200 Subject: [PATCH 2/6] feat(cli): add tool for generating multisig states --- Cargo.lock | 1 + cli/Cargo.toml | 1 + cli/src/main.rs | 10 +- cli/src/tools/gen_dht.rs | 4 +- cli/src/tools/gen_key.rs | 4 +- .../tools/gen_multisig_state/giver_state.boc | Bin 0 -> 1032 bytes cli/src/tools/gen_multisig_state/mod.rs | 258 ++++++++++++++++++ .../gen_multisig_state/safe_multisig_code.boc | Bin 0 -> 4241 bytes .../setcode_multisig_code.boc | Bin 0 -> 6682 bytes cli/src/tools/gen_zerostate.rs | 30 ++ cli/src/util/mod.rs | 14 +- 11 files changed, 313 insertions(+), 9 deletions(-) create mode 100644 cli/src/tools/gen_multisig_state/giver_state.boc create mode 100644 cli/src/tools/gen_multisig_state/mod.rs create mode 100644 cli/src/tools/gen_multisig_state/safe_multisig_code.boc create mode 100644 cli/src/tools/gen_multisig_state/setcode_multisig_code.boc create mode 100644 cli/src/tools/gen_zerostate.rs diff --git a/Cargo.lock b/Cargo.lock index 2ab38c95e..7fae3bf9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2174,6 +2174,7 @@ dependencies = [ "base64 0.22.0", "clap", "everscale-crypto", + "everscale-types", "hex", "rand", "rustc_version", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index daaec36bd..a39f6dce6 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -18,6 +18,7 @@ anyhow = { workspace = true } base64 = { workspace = true } clap = { workspace = true } everscale-crypto = { workspace = true } +everscale-types = { workspace = true } hex = { workspace = true } rand = { workspace = true } serde_json = { workspace = true } diff --git a/cli/src/main.rs b/cli/src/main.rs index 290c82267..06852b474 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -7,6 +7,8 @@ use clap::{Parser, Subcommand}; mod tools { pub mod gen_dht; pub mod gen_key; + pub mod gen_multisig_state; + pub mod gen_zerostate; } mod util; @@ -77,8 +79,10 @@ struct RunCmd {} /// A collection of tools #[derive(Subcommand)] enum ToolCmd { - GenDht(tools::gen_dht::CmdGenDht), - GenKey(tools::gen_key::CmdGenKey), + GenDht(tools::gen_dht::Cmd), + GenKey(tools::gen_key::Cmd), + GenZerostate(tools::gen_zerostate::Cmd), + GenMultisigState(tools::gen_multisig_state::Cmd), } impl ToolCmd { @@ -86,6 +90,8 @@ impl ToolCmd { match self { ToolCmd::GenDht(cmd) => cmd.run(), ToolCmd::GenKey(cmd) => cmd.run(), + ToolCmd::GenZerostate(cmd) => cmd.run(), + ToolCmd::GenMultisigState(cmd) => cmd.run(), } } } diff --git a/cli/src/tools/gen_dht.rs b/cli/src/tools/gen_dht.rs index 121529930..857f7c4b9 100644 --- a/cli/src/tools/gen_dht.rs +++ b/cli/src/tools/gen_dht.rs @@ -9,7 +9,7 @@ use crate::util::parse_secret_key; /// Generate a DHT entry for a node. #[derive(clap::Parser)] -pub struct CmdGenDht { +pub struct Cmd { /// a list of node addresses #[clap(required = true)] addr: Vec
, @@ -27,7 +27,7 @@ pub struct CmdGenDht { ttl: Option, } -impl CmdGenDht { +impl Cmd { pub fn run(self) -> Result<()> { // Read key let key = match self.key { diff --git a/cli/src/tools/gen_key.rs b/cli/src/tools/gen_key.rs index 93805a3ca..6775f8d86 100644 --- a/cli/src/tools/gen_key.rs +++ b/cli/src/tools/gen_key.rs @@ -7,7 +7,7 @@ use crate::util::parse_secret_key; /// Generate a new key pair #[derive(clap::Parser)] -pub struct CmdGenKey { +pub struct Cmd { /// secret key (reads from stdin if only flag is provided) #[clap(long)] key: Option>, @@ -17,7 +17,7 @@ pub struct CmdGenKey { raw_key: bool, } -impl CmdGenKey { +impl Cmd { pub fn run(self) -> Result<()> { let secret = match self.key { Some(flag) => { diff --git a/cli/src/tools/gen_multisig_state/giver_state.boc b/cli/src/tools/gen_multisig_state/giver_state.boc new file mode 100644 index 0000000000000000000000000000000000000000..1cbf62452dd4ed8e95256c8bab1adae34a5bbd72 GIT binary patch literal 1032 zcmbVLTTBx{6rI@?v=j=Ir&R6kRtZrF!4JjwKu}R56p3t0ks1O8ktk8CxY>{4A?p?}5rH4)XD0W|z31F>XC8cS zI1aH*5D4Y~cDhs2cetbLwK8J&xAynfTMx!%nBI=OvOK!K`_se?)t1Wmv|Xm~^Rk>3*&YG&C+rVI70Dhs6z=j%A59MZaEcbFJ4rwq?{<>2{V>Q z*wuD<42~2XOdB~3aFkpQ0V~0)R8Gd4kAMio@-c;_SDoPo5>bI(XqUq&i1rmRWxP&* zF$Q(M-ca;-vTiQb2M(h6BD0j&{Eu3vjVdh07Rz6(g$}XWP(xAFcolAr(nniJTFeo& zSglgQVv=YUWEgQau^5XX7c0@G@4&1f7xhS~v})ovJdcmwyTAB@`4 zOK=NaVSEEhX-yxT!HmafO;718--dfS%Y6+57t9d^Qnc4EnV6+S1, + + /// Number of required confirmations + #[clap(short, long)] + req_confirms: Option, + + /// Custom lifetime of the wallet + #[clap(short, long)] + lifetime: Option, + + /// Use SetcodeMultisig instead of SafeMultisig + #[clap(short, long)] + updatable: bool, +} + +impl Cmd { + pub fn run(self) -> Result<()> { + let pubkey = + parse_public_key(self.pubkey.as_bytes(), false).context("invalid deployer pubkey")?; + + let custodians = self + .custodians + .iter() + .map(|key| parse_public_key(key.as_bytes(), false)) + .collect::>>() + .context("invalid custodian pubkey")?; + + let (account, state) = MultisigBuilder { + pubkey, + custodians, + updatable: self.updatable, + required_confirms: self.req_confirms, + lifetime: self.lifetime, + balance: self.balance, + } + .build()?; + + let res = serde_json::json!({ + "account": account.to_string(), + "boc": BocRepr::encode_base64(OptionalAccount(Some(state)))?, + }); + + let output = if std::io::stdin().is_terminal() { + serde_json::to_string_pretty(&res) + } else { + serde_json::to_string(&res) + }?; + println!("{}", output); + Ok(()) + } +} + +const DEFAULT_LIFETIME: u32 = 3600; +const MIN_LIFETIME: u32 = 600; + +/// Multisig2 +const SAFE_MULTISIG_CODE: &[u8] = include_bytes!("./safe_multisig_code.boc"); +/// SetcodeMultisig (old) +const SETCODE_MULTISIG_CODE: &[u8] = include_bytes!("./setcode_multisig_code.boc"); + +struct MultisigBuilder { + pubkey: ed25519::PublicKey, + custodians: Vec, + updatable: bool, + required_confirms: Option, + lifetime: Option, + balance: Tokens, +} + +impl MultisigBuilder { + fn build(mut self) -> Result<(HashBytes, Account)> { + if let Some(lifetime) = self.lifetime { + anyhow::ensure!( + !self.updatable, + "custom lifetime is not supported by SetcodeMultisig", + ); + anyhow::ensure!( + lifetime >= MIN_LIFETIME, + "transaction lifetime is too short", + ); + } + + let code = Boc::decode(match self.updatable { + false => SAFE_MULTISIG_CODE, + true => SETCODE_MULTISIG_CODE, + }) + .expect("invalid contract code"); + + let custodian_count = match self.custodians.len() { + 0 => { + self.custodians.push(self.pubkey); + 1 // set deployer as the single custodian + } + len @ 1..=32 => len as u8, + _ => anyhow::bail!("too many custodians"), + }; + + // All confirmations are required if it wasn't explicitly specified + let required_confirms = self.required_confirms.unwrap_or(custodian_count); + + // Compute address + let data = { + let mut init_params = Dict::>::new(); + + let pubkey_cell = CellBuilder::build_from(HashBytes::wrap(self.pubkey.as_bytes()))?; + init_params.set(0, pubkey_cell.as_slice()?)?; + + let garbage_cell; + if self.updatable { + // Set some garbage for the SetcodeMultisig to match + // the commonly used `tvc` file. + garbage_cell = { + let mut garbage_dict = Dict::::new(); + garbage_dict.set(0, ())?; + CellBuilder::build_from(garbage_dict)? + }; + init_params.set(8, garbage_cell.as_slice()?)?; + } + + CellBuilder::build_from(init_params)? + }; + + let mut state_init = StateInit { + split_depth: None, + special: None, + code: Some(code), + data: Some(data), + libraries: Dict::new(), + }; + let address = *CellBuilder::build_from(&state_init)?.repr_hash(); + + // Compute runtime data + let owner_key = HashBytes::wrap(self.custodians.first().unwrap_or(&self.pubkey).as_bytes()); + + let mut custodians = Dict::::new(); + for (i, custodian) in self.custodians.iter().enumerate() { + custodians.set(HashBytes::wrap(custodian.as_bytes()), i as u8)?; + } + + let default_required_confirmations = std::cmp::min(required_confirms, custodian_count); + + let required_votes = if custodian_count <= 2 { + custodian_count + } else { + (custodian_count * 2 + 1) / 3 + }; + + let mut data = CellBuilder::new(); + + // Write headers + data.store_u256(HashBytes::wrap(self.pubkey.as_bytes()))?; + data.store_u64(0)?; // time + data.store_bit_one()?; // constructor flag + + // Write state variables + match self.updatable { + false => { + data.store_u256(owner_key)?; // m_ownerKey + data.store_u256(&HashBytes::ZERO)?; // m_requestsMask + data.store_bit_zero()?; // empty m_transactions + custodians.store_into(&mut data, &mut Cell::empty_context())?; // m_custodians + data.store_u8(custodian_count)?; // m_custodianCount + data.store_bit_zero()?; // empty m_updateRequests + data.store_u32(0)?; // m_updateRequestsMask + data.store_u8(required_votes)?; // m_requiredVotes + data.store_u8(default_required_confirmations)?; // m_defaultRequiredConfirmations + data.store_u32(self.lifetime.unwrap_or(DEFAULT_LIFETIME))?; + } + true => { + data.store_u256(owner_key)?; // m_ownerKey + data.store_u256(&HashBytes::ZERO)?; // m_requestsMask + data.store_u8(custodian_count)?; // m_custodianCount + data.store_u32(0)?; // m_updateRequestsMask + data.store_u8(required_votes)?; // m_requiredVotes + + let mut updates = CellBuilder::new(); + updates.store_bit_zero()?; // empty m_updateRequests + data.store_reference(updates.build()?)?; // sub reference + + data.store_u8(default_required_confirmations)?; // m_defaultRequiredConfirmations + data.store_bit_zero()?; // empty m_transactions + custodians.store_into(&mut data, &mut Cell::empty_context())?; // m_custodians + } + }; + + // "Deploy" wallet + state_init.data = Some(data.build()?); + + // Done + let mut account = Account { + address: StdAddr::new(-1, address).into(), + storage_stat: Default::default(), + last_trans_lt: 0, + balance: self.balance.into(), + state: AccountState::Active(state_init), + init_code_hash: None, + }; + + account.storage_stat.used = compute_storage_used(&account)?; + + Ok((address, account)) + } +} + +// TODO: move into types +fn compute_storage_used(account: &Account) -> Result { + let cell = { + let cx = &mut Cell::empty_context(); + let mut storage = CellBuilder::new(); + storage.store_u64(account.last_trans_lt)?; + account.balance.store_into(&mut storage, cx)?; + account.state.store_into(&mut storage, cx)?; + if account.init_code_hash.is_some() { + account.init_code_hash.store_into(&mut storage, cx)?; + } + storage.build_ext(cx)? + }; + + let res = cell + .compute_unique_stats(usize::MAX) + .context("max size exceeded")?; + + let res = StorageUsed { + cells: VarUint56::new(res.cell_count), + bits: VarUint56::new(res.bit_count), + public_cells: Default::default(), + }; + + anyhow::ensure!(res.bits.is_valid(), "bit count overflow"); + anyhow::ensure!(res.cells.is_valid(), "cell count overflow"); + + Ok(res) +} diff --git a/cli/src/tools/gen_multisig_state/safe_multisig_code.boc b/cli/src/tools/gen_multisig_state/safe_multisig_code.boc new file mode 100644 index 0000000000000000000000000000000000000000..058d68569ddcaa387ea587fc3c5d406a79742477 GIT binary patch literal 4241 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 QI}%0=Zt#f`nGV$Te=#D8`v3p{ literal 0 HcmV?d00001 diff --git a/cli/src/tools/gen_multisig_state/setcode_multisig_code.boc b/cli/src/tools/gen_multisig_state/setcode_multisig_code.boc new file mode 100644 index 0000000000000000000000000000000000000000..67210427e9c7d931c0f69d86d7b9650cefead694 GIT binary patch literal 6682 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, + + /// path to the zero state config + #[clap(required_unless_present = "init_config")] + config: Option, + + /// path to the output file + #[clap(short, long)] + output: PathBuf, + + /// explicit unix timestamp of the zero state + #[clap(long)] + now: Option, +} + +impl Cmd { + pub fn run(self) -> Result<()> { + // todo + Ok(()) + } +} diff --git a/cli/src/util/mod.rs b/cli/src/util/mod.rs index d1ef8f0da..37236648d 100644 --- a/cli/src/util/mod.rs +++ b/cli/src/util/mod.rs @@ -3,7 +3,16 @@ use base64::prelude::{Engine as _, BASE64_STANDARD}; use everscale_crypto::ed25519; pub fn parse_secret_key(key: &[u8], raw_key: bool) -> Result { - let key = if raw_key { + parse_hash(key, raw_key).map(ed25519::SecretKey::from_bytes) +} + +pub fn parse_public_key(key: &[u8], raw_key: bool) -> Result { + parse_hash(key, raw_key) + .and_then(|bytes| ed25519::PublicKey::from_bytes(bytes).context("invalid public key")) +} + +fn parse_hash(key: &[u8], raw: bool) -> Result<[u8; 32]> { + let key = if raw { key.try_into().ok() } else { let key = std::str::from_utf8(key)?.trim(); @@ -14,6 +23,5 @@ pub fn parse_secret_key(key: &[u8], raw_key: bool) -> Result } }; - key.map(ed25519::SecretKey::from_bytes) - .context("invalid key length") + key.context("invalid key length") } From b7727dee3c9444a59f61f3e2293695eaca832ef5 Mon Sep 17 00:00:00 2001 From: Ivan Kalinin Date: Fri, 12 Apr 2024 15:35:15 +0200 Subject: [PATCH 3/6] feat(cli): add tools for generating giver and wallet account states --- cli/Cargo.toml | 1 + cli/res/ever_wallet_code.boc | Bin 0 -> 267 bytes .../giver_state.boc | Bin .../safe_multisig_code.boc | Bin .../setcode_multisig_code.boc | Bin cli/src/main.rs | 6 +- .../mod.rs => gen_account.rs} | 211 ++++++++++++++++-- 7 files changed, 195 insertions(+), 23 deletions(-) create mode 100644 cli/res/ever_wallet_code.boc rename cli/{src/tools/gen_multisig_state => res}/giver_state.boc (100%) rename cli/{src/tools/gen_multisig_state => res}/safe_multisig_code.boc (100%) rename cli/{src/tools/gen_multisig_state => res}/setcode_multisig_code.boc (100%) rename cli/src/tools/{gen_multisig_state/mod.rs => gen_account.rs} (60%) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index a39f6dce6..1381f1c1f 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "tycho-cli" description = "Node CLI." +include = ["src/**/*.rs", "res/**/*.boc"] version.workspace = true authors.workspace = true edition.workspace = true diff --git a/cli/res/ever_wallet_code.boc b/cli/res/ever_wallet_code.boc new file mode 100644 index 0000000000000000000000000000000000000000..5a5b0cc9c6b26029c781055153cab51f0be13b34 GIT binary patch literal 267 zcmdn`ZcdRSBO4>b9|lH|{|sN22!Gl0=>#_;6QcqXGXu*d1Ey!63a>LV9$@%X)y#2S z;;qYtX7=lh*SYOa{LnZrrhH!Pkcz_$vp_O|!n-~_NV(VGaGkM10fIxsN`DG` zTJb}9_ovFVeLs{N**O+8GQU_6q_Ah>rxGTGCIO#!DLK0y^D~|}{r|N6$+saatPC>@ zn6CR=_~p>dcAXJudpC1eb4FW8ym_#<7IL#WsdR{Dm`4`jaOr;Bo=M5FFdoF0< z+&ROzfbld##Cfsl28BR|(3wXk8Gkb{*fj}=&9f8S)hGCafoZbBb+73_1FtasW_WRN Ove74-M|Njh_5%QGjB^zL literal 0 HcmV?d00001 diff --git a/cli/src/tools/gen_multisig_state/giver_state.boc b/cli/res/giver_state.boc similarity index 100% rename from cli/src/tools/gen_multisig_state/giver_state.boc rename to cli/res/giver_state.boc diff --git a/cli/src/tools/gen_multisig_state/safe_multisig_code.boc b/cli/res/safe_multisig_code.boc similarity index 100% rename from cli/src/tools/gen_multisig_state/safe_multisig_code.boc rename to cli/res/safe_multisig_code.boc diff --git a/cli/src/tools/gen_multisig_state/setcode_multisig_code.boc b/cli/res/setcode_multisig_code.boc similarity index 100% rename from cli/src/tools/gen_multisig_state/setcode_multisig_code.boc rename to cli/res/setcode_multisig_code.boc diff --git a/cli/src/main.rs b/cli/src/main.rs index 06852b474..f9d1ecf49 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -5,9 +5,9 @@ use anyhow::Result; use clap::{Parser, Subcommand}; mod tools { + pub mod gen_account; pub mod gen_dht; pub mod gen_key; - pub mod gen_multisig_state; pub mod gen_zerostate; } @@ -82,7 +82,7 @@ enum ToolCmd { GenDht(tools::gen_dht::Cmd), GenKey(tools::gen_key::Cmd), GenZerostate(tools::gen_zerostate::Cmd), - GenMultisigState(tools::gen_multisig_state::Cmd), + GenAccount(tools::gen_account::Cmd), } impl ToolCmd { @@ -91,7 +91,7 @@ impl ToolCmd { ToolCmd::GenDht(cmd) => cmd.run(), ToolCmd::GenKey(cmd) => cmd.run(), ToolCmd::GenZerostate(cmd) => cmd.run(), - ToolCmd::GenMultisigState(cmd) => cmd.run(), + ToolCmd::GenAccount(cmd) => cmd.run(), } } } diff --git a/cli/src/tools/gen_multisig_state/mod.rs b/cli/src/tools/gen_account.rs similarity index 60% rename from cli/src/tools/gen_multisig_state/mod.rs rename to cli/src/tools/gen_account.rs index 962f703a0..9b3a9a052 100644 --- a/cli/src/tools/gen_multisig_state/mod.rs +++ b/cli/src/tools/gen_account.rs @@ -10,9 +10,66 @@ use everscale_types::prelude::*; use crate::util::parse_public_key; -/// Generate a multisig wallet state. +/// Generate an account state #[derive(clap::Parser)] pub struct Cmd { + #[clap(subcommand)] + cmd: SubCmd, +} + +impl Cmd { + pub fn run(self) -> Result<()> { + self.cmd.run() + } +} + +#[derive(clap::Subcommand)] +enum SubCmd { + Wallet(WalletCmd), + Multisig(MultisigCmd), + Giver(GiverCmd), +} + +impl SubCmd { + fn run(self) -> Result<()> { + match self { + Self::Wallet(cmd) => cmd.run(), + Self::Multisig(cmd) => cmd.run(), + Self::Giver(cmd) => cmd.run(), + } + } +} + +/// Generate a simple wallet state +#[derive(clap::Parser)] +struct WalletCmd { + /// account public key + #[clap(short, long, required = true)] + pubkey: String, + + /// initial balance of the wallet (in nano) + #[clap(short, long, required = true)] + balance: Tokens, +} + +impl WalletCmd { + fn run(self) -> Result<()> { + let pubkey = + parse_public_key(self.pubkey.as_bytes(), false).context("invalid deployer pubkey")?; + + let (account, state) = WalletBuilder { + pubkey, + balance: self.balance, + } + .build()?; + + write_state(&account, &state) + } +} + +/// Generate a multisig wallet state +#[derive(clap::Parser)] +struct MultisigCmd { /// account public key #[clap(short, long, required = true)] pubkey: String, @@ -38,8 +95,8 @@ pub struct Cmd { updatable: bool, } -impl Cmd { - pub fn run(self) -> Result<()> { +impl MultisigCmd { + fn run(self) -> Result<()> { let pubkey = parse_public_key(self.pubkey.as_bytes(), false).context("invalid deployer pubkey")?; @@ -60,28 +117,87 @@ impl Cmd { } .build()?; - let res = serde_json::json!({ - "account": account.to_string(), - "boc": BocRepr::encode_base64(OptionalAccount(Some(state)))?, - }); + write_state(&account, &state) + } +} - let output = if std::io::stdin().is_terminal() { - serde_json::to_string_pretty(&res) - } else { - serde_json::to_string(&res) - }?; - println!("{}", output); - Ok(()) +/// Generate a giver state +#[derive(clap::Parser)] +struct GiverCmd { + /// account public key + #[clap(short, long, required = true)] + pubkey: String, + + /// initial balance of the giver (in nano) + #[clap(short, long, required = true)] + balance: Tokens, +} + +impl GiverCmd { + fn run(self) -> Result<()> { + let pubkey = + parse_public_key(self.pubkey.as_bytes(), false).context("invalid deployer pubkey")?; + + let (account, state) = GiverBuilder { + pubkey, + balance: self.balance, + } + .build()?; + + write_state(&account, &state) } } -const DEFAULT_LIFETIME: u32 = 3600; -const MIN_LIFETIME: u32 = 600; +fn write_state(account: &HashBytes, state: &Account) -> Result<()> { + let res = serde_json::json!({ + "account": account.to_string(), + "boc": BocRepr::encode_base64(OptionalAccount(Some(state.clone())))?, + }); + + let output = if std::io::stdin().is_terminal() { + serde_json::to_string_pretty(&res) + } else { + serde_json::to_string(&res) + }?; + println!("{}", output); + Ok(()) +} + +struct WalletBuilder { + pubkey: ed25519::PublicKey, + balance: Tokens, +} + +impl WalletBuilder { + fn build(self) -> Result<(HashBytes, Account)> { + const EVER_WALLET_CODE: &[u8] = include_bytes!("../../res/ever_wallet_code.boc"); + + let data = CellBuilder::build_from((HashBytes::wrap(self.pubkey.as_bytes()), 0u64))?; + let code = Boc::decode(EVER_WALLET_CODE)?; + + let state_init = StateInit { + split_depth: None, + special: None, + code: Some(code), + data: Some(data), + libraries: Dict::new(), + }; + let address = *CellBuilder::build_from(&state_init)?.repr_hash(); + + let mut account = Account { + address: StdAddr::new(-1, address).into(), + storage_stat: Default::default(), + last_trans_lt: 0, + balance: self.balance.into(), + state: AccountState::Active(state_init), + init_code_hash: None, + }; + + account.storage_stat.used = compute_storage_used(&account)?; -/// Multisig2 -const SAFE_MULTISIG_CODE: &[u8] = include_bytes!("./safe_multisig_code.boc"); -/// SetcodeMultisig (old) -const SETCODE_MULTISIG_CODE: &[u8] = include_bytes!("./setcode_multisig_code.boc"); + Ok((address, account)) + } +} struct MultisigBuilder { pubkey: ed25519::PublicKey, @@ -94,6 +210,14 @@ struct MultisigBuilder { impl MultisigBuilder { fn build(mut self) -> Result<(HashBytes, Account)> { + const DEFAULT_LIFETIME: u32 = 3600; + const MIN_LIFETIME: u32 = 600; + + // Multisig2 + const SAFE_MULTISIG_CODE: &[u8] = include_bytes!("../../res/safe_multisig_code.boc"); + // SetcodeMultisig (old) + const SETCODE_MULTISIG_CODE: &[u8] = include_bytes!("../../res/setcode_multisig_code.boc"); + if let Some(lifetime) = self.lifetime { anyhow::ensure!( !self.updatable, @@ -227,6 +351,53 @@ impl MultisigBuilder { } } +struct GiverBuilder { + pubkey: ed25519::PublicKey, + balance: Tokens, +} + +impl GiverBuilder { + fn build(self) -> Result<(HashBytes, Account)> { + const GIVER_STATE: &[u8] = include_bytes!("../../res/giver_state.boc"); + + let mut account = BocRepr::decode::(GIVER_STATE)? + .0 + .expect("invalid giver state"); + + let address; + match &mut account.state { + AccountState::Active(state_init) => { + let mut data = CellBuilder::new(); + + // Append pubkey first + data.store_u256(HashBytes::wrap(self.pubkey.as_bytes()))?; + + // Append everything except the pubkey + let prev_data = state_init + .data + .take() + .expect("giver state must contain data"); + let mut prev_data = prev_data.as_slice()?; + prev_data.advance(256, 0)?; + + data.store_slice(prev_data)?; + + // Update data + state_init.data = Some(data.build()?); + + // Compute address + address = *CellBuilder::build_from(&*state_init)?.repr_hash(); + } + _ => unreachable!("saved state is for the active account"), + }; + + account.balance.tokens = self.balance; + account.storage_stat.used = compute_storage_used(&account)?; + + Ok((address, account)) + } +} + // TODO: move into types fn compute_storage_used(account: &Account) -> Result { let cell = { From d2f49e306ed5b1c7da6b31eb18d2c2a49c217680 Mon Sep 17 00:00:00 2001 From: Ivan Kalinin Date: Fri, 12 Apr 2024 21:15:28 +0200 Subject: [PATCH 4/6] feat(cli): add models for zerostate config (wip) --- Cargo.lock | 212 +++++++++------------------------ cli/Cargo.toml | 1 + cli/res/config_code.boc | Bin 0 -> 2294 bytes cli/res/elector_code.boc | Bin 0 -> 4031 bytes cli/res/minter_state.boc | Bin 0 -> 246 bytes cli/src/tools/gen_account.rs | 38 +----- cli/src/tools/gen_zerostate.rs | 204 +++++++++++++++++++++++++++++++ cli/src/util/mod.rs | 33 +++++ util/Cargo.toml | 1 + util/src/serde_helpers.rs | 70 +++++++++++ 10 files changed, 366 insertions(+), 193 deletions(-) create mode 100644 cli/res/config_code.boc create mode 100644 cli/res/elector_code.boc create mode 100644 cli/res/minter_state.boc diff --git a/Cargo.lock b/Cargo.lock index 7fae3bf9d..41f2c11a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,9 +89,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" [[package]] name = "arc-swap" @@ -140,9 +140,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.79" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", @@ -245,12 +245,6 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" -[[package]] -name = "bytecount" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" - [[package]] name = "bytes" version = "1.6.0" @@ -280,37 +274,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "camino" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo-platform" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", -] - [[package]] name = "castaway" version = "0.2.3" @@ -622,15 +585,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "error-chain" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" -dependencies = [ - "version_check", -] - [[package]] name = "everscale-crypto" version = "0.2.0" @@ -648,7 +602,7 @@ dependencies = [ [[package]] name = "everscale-types" version = "0.1.0-rc.6" -source = "git+https://github.com/broxus/everscale-types.git#f93cdd2956ceb1a26c13a49e8e019a32ddfab1dd" +source = "git+https://github.com/broxus/everscale-types.git#96332943ca1942b3bee968e3c16306b330a4e974" dependencies = [ "ahash", "base64 0.21.7", @@ -668,7 +622,7 @@ dependencies = [ [[package]] name = "everscale-types-proc" version = "0.1.4" -source = "git+https://github.com/broxus/everscale-types.git#f93cdd2956ceb1a26c13a49e8e019a32ddfab1dd" +source = "git+https://github.com/broxus/everscale-types.git#96332943ca1942b3bee968e3c16306b330a4e974" dependencies = [ "proc-macro2", "quote", @@ -830,9 +784,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +checksum = "685a7d121ee3f65ae4fddd72b25a04bb36b6af81bc0828f7d5434c0fe60fa3a2" dependencies = [ "libc", ] @@ -871,7 +825,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -987,9 +941,9 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.5" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1911e88d5831f748a4097a43862d129e3c6fca831eecac9b8db6d01d93c9de2" +checksum = "87bfd249f570638bfb0b4f9d258e6b8cddd2a5a7d0ed47e8bb8b176bfc0e7a17" dependencies = [ "crossbeam-channel", "crossbeam-epoch", @@ -998,7 +952,6 @@ dependencies = [ "parking_lot", "quanta", "rustc_version", - "skeptic", "smallvec", "tagptr", "thiserror", @@ -1265,17 +1218,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "pulldown-cmark" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" -dependencies = [ - "bitflags 2.5.0", - "memchr", - "unicase", -] - [[package]] name = "quanta" version = "0.12.3" @@ -1352,9 +1294,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -1609,15 +1551,6 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - [[package]] name = "scopeguard" version = "1.2.0" @@ -1639,9 +1572,6 @@ name = "semver" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" -dependencies = [ - "serde", -] [[package]] name = "serde" @@ -1718,21 +1648,6 @@ dependencies = [ "rand_core", ] -[[package]] -name = "skeptic" -version = "0.13.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" -dependencies = [ - "bytecount", - "cargo_metadata", - "error-chain", - "glob", - "pulldown-cmark", - "tempfile", - "walkdir", -] - [[package]] name = "slab" version = "0.4.9" @@ -1834,9 +1749,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.30.9" +version = "0.30.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9a84fe4cfc513b41cb2596b624e561ec9e7e1c4b46328e496ed56a53514ef2a" +checksum = "26d7c217777061d5a2d652aea771fb9ba98b6dade657204b08c4b9604d11555b" dependencies = [ "cfg-if", "core-foundation-sys", @@ -1917,9 +1832,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", @@ -1938,9 +1853,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", @@ -2178,6 +2093,7 @@ dependencies = [ "hex", "rand", "rustc_version", + "serde", "serde_json", "tikv-jemallocator", "tokio", @@ -2349,6 +2265,7 @@ dependencies = [ "ahash", "castaway", "dashmap", + "everscale-crypto", "futures-util", "hex", "humantime", @@ -2373,15 +2290,6 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" -[[package]] -name = "unicase" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] - [[package]] name = "unicode-ident" version = "1.0.12" @@ -2439,16 +2347,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2547,15 +2445,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -2569,7 +2458,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ "windows-core", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -2578,7 +2467,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -2596,7 +2485,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -2616,17 +2505,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -2637,9 +2527,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -2649,9 +2539,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -2661,9 +2551,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -2673,9 +2569,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -2685,9 +2581,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -2697,9 +2593,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -2709,9 +2605,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "x509-parser" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 1381f1c1f..39a1a69ed 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -22,6 +22,7 @@ everscale-crypto = { workspace = true } everscale-types = { workspace = true } hex = { workspace = true } rand = { workspace = true } +serde = { workspace = true } serde_json = { workspace = true } tikv-jemallocator = { workspace = true, features = [ "unprefixed_malloc_on_supported_platforms", diff --git a/cli/res/config_code.boc b/cli/res/config_code.boc new file mode 100644 index 0000000000000000000000000000000000000000..925ee037257973408e8c7595ed2aef58ef180690 GIT binary patch literal 2294 zcmah~eNa-4D3ERApHuL40_P8 zR^n4{HQS{3$^Of-b5C+5QY5FJLP>u9$2Your#Ttk`d~%Lb!&B>ynq?gmm%xw1Mtw= zU94|nUS!MNDa@;bZu%)mezRaJ<5qBP_|aj)YJFBr1~9<|N=Kw0R8W>S@t9zIR34@! zJ>k%aq`T5fU$q#*#N*Tx=A(3MCpw_p93rn;^`o# z$gRPQQp_HLG?YUova_`0HExeJ&wbG@fI7lZop{=U=CpmADC^v8sQUviz&b9b2@1Fj z+gB%27Rh^PhM{m_aZ@3=K#^U-aDMS^e|X3 z$4gLpmZze$vtIR3i8*A3P_m>+1d&W7YQ)ed!d0(w;fxQ&$Tc_%VjZ%Sc(3qtlbT&z zW_ z9=f@b|9V2rh35Ic((MU}xuq2MH?>~-rw=%>b~2=d|nY4`_A{vXG8 z2AA7FPb3zSY2JD5t6SUSv(HJVUz-nLW^1&oMJ;fXc9Vu$TfG)Uol3&6FL_}g@RCl2 z!F!S67|h~F0M#CWeoJuK7ql*oV*ar4@eZ8iyfjyP1?GI=6@S-G$$HI6SLUuwube4r z3hBYYx*a&It?elQFV7U_^!|iYb7uuTI0`sw1xR^Z)10;(G?{ia&!bVSkdq~Ywo3eGa5)Ja)9yb> zvcX$Gt~t5+y@oL-A;Z!*z`t-TENq&S(TyhF$JecOI2j*<-O>@eOUn;f&g6*n_(UXW z7x;;V`Ni8U?oRUmP%NXsM=?~(I^u8TN_jh1E1$-lMPkwlpng@)6?bRcD#oox86)Ee zn)Wb9Gs6`54q5(@{P-nwq)LoMxo6UjQsxXfQXf8$uvBem@`eyXBHkg(U@Xy?h)i{$ z%SDj_prfkiR#@IEqR0}XsLV7n5k$M3WJ$fsi4>6*15Z5ioMr>7=lGU}k?Ej%kqcge z4q09t>QS%zF^sdoMcsr0bdO2RC@XfJ@T-(0GT@Y&#Z<|n{Eek@1>>^@6*A8+Wcu3O zzV*z1vYi z`!|znL>Yu6HmR2tS312y+mJwEM6&cm#oJ~j>*@fF*d7a~41xmT^BaP%)ub06c9Pm4 zZ;C@k>`}9r3!D?d4kfG4K&TYE)d32h&nnT;y=JbH*iJ|YVb;;-0CI;|=ow-yq9(yt z7GZa&S=b^hV}K4bE15SM)Qn*9Qs{9Em$f?~5r9QAe*+mGa@dWHKv zOxCm8`CWk*Fm6|PdmijUp0+EuO4`EAKx(>vRq2GV)?)GQR!yMaYr$N5Yr)p4dad$)GxOj3R$M57uWaYmB d&|9!DM!|*=AHXl}?lA${4WQ9Z8>^se^nXv4%f71&R+a_5p?#yVBPQv8x@|j2(>W44^f$FVK1Gt+#I0%{@2! z?6ZHq{q3DU|8#l>CN^Od(TK1phywM(z)gSGWK3X&B#`!8Feu)2cH3TJFA=$5#pU6s zJPe=sL9nTS~S!>B%CND!<1GQSiBx3J;NAc=LW*f zbycHCRw=Xi)`2P^4S^6u;zP|J_rDaO)`dK@b2-aU$XD`se&c`LUp4I8kt^E=#^`v1 z))$<7`IBk#UsP~K59LThhNdI1ZW+hx zrbfYSN;KTWF~s^SD^0b$saI@mN18fUC0D5Fy{ZNGjxJle-&8BScy&Q}GQPGS=bqY* zzNy%mPPZ0sAZA_;w?3-Pw2yKZ^mb;NYL|=CAFJbHi49lJLa6z#v=w!FiqDI(oxH_t ztP}s(+SC)%#_R3yw2A%`?vCpX|9-+htq zU4cwqwQfeCI(=i|m*p}zVueR(vP&F-Br6?w+k7rXdR#}E3F154_=7B!?DZ(kJkm_9 zSVkm6nA40LE5nAlQf3LZMT8mZfQ5F)odjTWB zrBk0&r(OT~&vSG9Co_q^A1t`;vVXVD<*>$Iz4rcNv={MmXG$Sgi3#|Bvc$wHd-}ua z*TQ2Y_Qib5=Rw<%di7NG9M2SZ$0J^_r-VFA8(I(^f!riBk4L<>W16KQXiJM+809*Z zHncUIb0<#NEu10@PsX4j!bzfq9A$e%|7A(pu;w@$iXrL`FqlixHdc8g9<=qPF+SS8^E(rrA zbBoKrO-Zp*Eod=9J8ysT`+Q~SVaaez>$l9h88eVr%0l}%9znR-2R5cB!)?wi@}rj; zyC4Rtqa0c6g|wmF;Y70U=agI`LjA+2*ar|6@Bzb5IB4aSIcIEXXkpmlhMx~V-h@CT zTQ*CcHne2cjQ04M!yj)9d6>xMd#5#a4j0efh)yIzX5TcQV;oAtt#SwTQO%C|3Nk>A zqEVE5iO`;^)68;GT`pC$D<}vcJW9Z4O;5CeVKI?KT5zS2^c_p_;YKj^g}LO<5E1?o zUU%uHf%dqJxFunpb205o-e(N;c35DEmAw?50mJ>$%`soVYdk%s{TY1K544}o;B^QL zdDYXNh4N&GH}_6kd31-MJY2tfR}b$&d02tylg0lVHr{7%l6kjyt=ncbOfuPus3l*zdZE)Q=yGv&GMB_95xXK{ToF@UDq#S1TZU762POygVvjO3 zKxy>OWM}9IqLdh9DN2yL5bFV9az-!j=p6KpJhJEvk&{9DBYQ4c?Lmj~`7aipa z+NX^YBG+p|mqg%F-R<7~6p{a8Sc4Q_1irXi_C=u3WGjp{J~+Cf2w0Ov+F|Q0HVU1A zAYAIdwxy)}<#x)U{-Y^Df0=0MpKK{9ogMz5KiJD*)Y}%*>W6g4_v75kkDg}}wU%xk zX!8`t8am#H-FW@*m$P0;s_EDh$4j{W-g_syoN1{t{M%Nf%epRTc;85wEGB)VSC7wC znj^nA(`MSDM<&y|KPVK6bX|V4zd@6oC6wdmA?5^ZPSQ^sr&NE-D3ZHXayCe(uRGRj z7oNWqp;I^UwApn@7Z=UHKxvOVwG~bq?btu_-NZ`k&PuD^2f=npbZS>n=X9wOtA(y0 zaV*tmNH`BXlVwTjas?s&8qO#ozJt_L;iN1cog)-gmdJf6I6>+-kcT|VNyiF&RDDuu zRE!qq60B7;Rj<#z~b_Ob0kV~<9pLR+U54$C-W29T+4zdrKGLf0maLfc%St9i{iV*o~ zy8u5t_-gO0gWU~fx5MR@Gha`?#1B6%*s&=zzV9DRTI1?66v`~lpdNgL-%3FDmS5Tb z`4IiLPcrRBc)T?<-VFA?dgMYyE4>#OzWa(@WjXr014o`k_8u*=!t$0S(o~xsIpta8 zqgqbefoFB6GRu4C8ju?&bNcz3WU@b*($JZX-sF(N?kfjchGtK~7vnrtt;NffzoF_q z%5K7=)HaUitJ)ZD9Ua_{)dNy!hC1GbPbE{FI_}MCW0-Zst1Y-uZJcZ!_L?kuZa(8t zhK!)4p|De5ZJcEN*WgkOy?~y%Xv6Y_WP(RYu-Nw~Q|TO9vBsk;D&B|`)|*{-xMEi@ z`mTt@IoA~&=K_}mDXskBRHpQV%$~U2&O6iV=Hk|={tA{tMo}NjW2z-++6^Qb+FDny zVy}-yDr(>T@PjACliw?^9F}nJ;r-zmRGIXh@Na%Z;U`DJ5mlCO9~L2}Mntr2XU*C5 z{bjR`?vMZGM-1ylbLyt|uU~Iai}o&RY9F|>GRjx-;hS3v7Jf&mc9d(@drOk`ww`|S zH*Vt-6yhjX2t7(~!g!}@J|b2ablD!|iSeFQSuXLZQ^y(?EArE>RV$kl$G*+YI=Vrx zE6k)$I1Z*NvH+Q08Hnh%W?7mY9~!cvhT^Si=^qo?gYOrVJtZvm5S9mRw0~={#z|&p zTQKZUNNx1$GT8BeNM-EtS6?d3dJ)GtD#|4jrw$s)Lh>wW=M9J%z+(i9$v$#ylcw2! zPU=z0+*~WuD7f0H_g`>J-~hJbn&CO^X zyhIQe38+SGYF##fYV9&Dntu-va?@Yo4)%k$Tz8NKid)i0-qI)jie}2R4`vM+v_f!A zAJuZoNScV97y1!Vser220HJ_e@@ga3WbqCscDW?#EiB0x7^+U2`bm-|l>!!5uqNR4 zV3pjo-HUhRtyqrYtn+}SM0bJIUBNpl6wktSp1-0$K)HkWRDGBY&DwGGGMtCzS`z@> zW1trJ&s&QIq;6G!>LZ*f#Ax`QQOA0VZ{5peHim3%VFxV>>wHNn(~cbFG;DAjkkN@A zrBARyvkLF9F#8;TN&?@;T6ZnMWi%SnzkXBg{nv;T$` zp9J>b=eRcG*Ujhu-{xb6-MkCs+AU69jC18x=#QXA@1!Qwq>;^f>5Z%Y8v-Y;)1nl; zU?O9{I!>VEE=WGXMje$xaG266BW3Zjtu}Eq8`l#p4OK=G5{7WU^eM%3B8b*$-1{j0 zksdz|vH6j2AMv$Z%E~$%G8-KX2A`{iur80RTh6_TyeC;3>GzMsLp3 zlmQx{2};C#=KP6%?$dTH!^FaWZR){&(YfV9v-!0aX|P z;SS1l)|O*(-?&s6D}`B|u;q1FF#gNK2uf$oDB+R-tHX!~1h39uvq$G-%Aze6FzXd! zhDQl8g`p27AjV;5Pb*lv1~WGLNm*{*+Hr%y$U-zb1U!#&Ni^ixoESiN;Oa(DJrE_f z$OF_pmNc*!7}lJVH}Qu7Wlv;?+Y=4OM2*c-E|s)BSm~00{oVK}q6`3N78VWCR@C5l zhvTHv@&&9*Y_|d`sFrb@_yM0z%gid?X#6dP^4~~6rweoDb0&L~21d_zwGBlmdBR8- qPJSI|0|;nfERo}rFVr_m=77_p@!^jR5Tx*t0WlLGWaZez_x&67 Result { - let cell = { - let cx = &mut Cell::empty_context(); - let mut storage = CellBuilder::new(); - storage.store_u64(account.last_trans_lt)?; - account.balance.store_into(&mut storage, cx)?; - account.state.store_into(&mut storage, cx)?; - if account.init_code_hash.is_some() { - account.init_code_hash.store_into(&mut storage, cx)?; - } - storage.build_ext(cx)? - }; - - let res = cell - .compute_unique_stats(usize::MAX) - .context("max size exceeded")?; - - let res = StorageUsed { - cells: VarUint56::new(res.cell_count), - bits: VarUint56::new(res.bit_count), - public_cells: Default::default(), - }; - - anyhow::ensure!(res.bits.is_valid(), "bit count overflow"); - anyhow::ensure!(res.cells.is_valid(), "cell count overflow"); - - Ok(res) -} diff --git a/cli/src/tools/gen_zerostate.rs b/cli/src/tools/gen_zerostate.rs index 2f7f77c63..cccaa48b3 100644 --- a/cli/src/tools/gen_zerostate.rs +++ b/cli/src/tools/gen_zerostate.rs @@ -1,6 +1,17 @@ +use std::collections::HashMap; use std::path::PathBuf; use anyhow::Result; +use everscale_crypto::ed25519; +use everscale_types::models::{ + Account, AccountState, CurrencyCollection, OptionalAccount, SpecialFlags, StateInit, StdAddr, +}; +use everscale_types::num::Tokens; +use everscale_types::prelude::*; +use serde::{Deserialize, Serialize}; +use tycho_util::serde_helpers; + +use crate::util::compute_storage_used; /// Generate a zero state for a network. #[derive(clap::Parser)] @@ -28,3 +39,196 @@ impl Cmd { Ok(()) } } + +#[derive(Serialize, Deserialize)] +struct ZerostateConfig { + global_id: i32, + + #[serde(with = "serde_helpers::public_key")] + config_public_key: ed25519::PublicKey, + #[serde(with = "serde_helpers::public_key")] + minter_public_key: ed25519::PublicKey, + + #[serde(with = "serde_account_states")] + accounts: HashMap, +} + +#[derive(Serialize, Deserialize)] +struct BlockchainConfig { + #[serde(with = "serde_helpers::hex_byte_array")] + config_address: HashBytes, + #[serde(with = "serde_helpers::hex_byte_array")] + elector_address: HashBytes, + #[serde(with = "serde_helpers::hex_byte_array")] + minter_address: HashBytes, + // TODO: additional currencies + global_version: u32, + global_capabilities: u64, +} + +fn build_minter_account(pubkey: &ed25519::PublicKey) -> Result { + const MINTER_STATE: &[u8] = include_bytes!("../../res/minter_state.boc"); + + let mut account = BocRepr::decode::(MINTER_STATE)? + .0 + .expect("invalid minter state"); + + match &mut account.state { + AccountState::Active(state_init) => { + let mut data = CellBuilder::new(); + + // Append pubkey first + data.store_u256(HashBytes::wrap(pubkey.as_bytes()))?; + + // Append everything except the pubkey + let prev_data = state_init + .data + .take() + .expect("minter state must contain data"); + let mut prev_data = prev_data.as_slice()?; + prev_data.advance(256, 0)?; + + data.store_slice(prev_data)?; + + // Update data + state_init.data = Some(data.build()?); + } + _ => unreachable!("saved state is for the active account"), + }; + + account.balance = CurrencyCollection::ZERO; + account.storage_stat.used = compute_storage_used(&account)?; + + Ok(account) +} + +fn build_config_account( + pubkey: &ed25519::PublicKey, + address: &HashBytes, + balance: Tokens, +) -> Result { + const CONFIG_CODE: &[u8] = include_bytes!("../../res/config_code.boc"); + + let code = Boc::decode(CONFIG_CODE)?; + + let mut data = CellBuilder::new(); + data.store_reference(Cell::empty_cell())?; + data.store_u32(0)?; + data.store_u256(HashBytes::wrap(pubkey.as_bytes()))?; + data.store_bit_zero()?; + let data = data.build()?; + + let mut account = Account { + address: StdAddr::new(-1, *address).into(), + storage_stat: Default::default(), + last_trans_lt: 0, + balance: balance.into(), + state: AccountState::Active(StateInit { + split_depth: None, + special: Some(SpecialFlags { + tick: false, + tock: true, + }), + code: Some(code), + data: Some(data), + libraries: Dict::new(), + }), + init_code_hash: None, + }; + + account.storage_stat.used = compute_storage_used(&account)?; + + Ok(account) +} + +fn build_elector_code(address: &HashBytes, balance: Tokens) -> Result { + const ELECTOR_CODE: &[u8] = include_bytes!("../../res/elector_code.boc"); + + let code = Boc::decode(ELECTOR_CODE)?; + + let mut data = CellBuilder::new(); + data.store_small_uint(0, 3)?; //empty dict, empty dict, empty dict + data.store_small_uint(0, 4)?; // tokens + data.store_u32(0)?; // elections id + data.store_zeros(256)?; // elections hash + let data = data.build()?; + + let mut account = Account { + address: StdAddr::new(-1, *address).into(), + storage_stat: Default::default(), + last_trans_lt: 0, + balance: balance.into(), + state: AccountState::Active(StateInit { + split_depth: None, + special: Some(SpecialFlags { + tick: true, + tock: false, + }), + code: Some(code), + data: Some(data), + libraries: Dict::new(), + }), + init_code_hash: None, + }; + + account.storage_stat.used = compute_storage_used(&account)?; + + Ok(account) +} + +mod serde_account_states { + use std::collections::HashMap; + + use everscale_types::boc::BocRepr; + use everscale_types::cell::HashBytes; + use everscale_types::models::OptionalAccount; + use serde::de::Deserializer; + use serde::ser::{SerializeMap, Serializer}; + use serde::{Deserialize, Serialize}; + use tycho_util::serde_helpers; + + pub fn serialize( + value: &HashMap, + serializer: S, + ) -> Result + where + S: Serializer, + { + #[derive(Serialize)] + #[repr(transparent)] + struct WrapperKey<'a>(#[serde(with = "serde_helpers::hex_byte_array")] &'a [u8; 32]); + + #[derive(Serialize)] + #[repr(transparent)] + struct WrapperValue<'a>( + #[serde(serialize_with = "BocRepr::serialize")] &'a OptionalAccount, + ); + + let mut ser = serializer.serialize_map(Some(value.len()))?; + for (key, value) in value { + ser.serialize_entry(&WrapperKey(key.as_array()), &WrapperValue(value))?; + } + ser.end() + } + + pub fn deserialize<'de, D>( + deserializer: D, + ) -> Result, D::Error> + where + D: Deserializer<'de>, + { + #[derive(Deserialize, Hash, PartialEq, Eq)] + #[repr(transparent)] + struct WrapperKey(#[serde(with = "serde_helpers::hex_byte_array")] [u8; 32]); + + #[derive(Deserialize)] + #[repr(transparent)] + struct WrappedValue(#[serde(with = "BocRepr")] OptionalAccount); + + >::deserialize(deserializer).map(|map| { + map.into_iter() + .map(|(k, v)| (HashBytes(k.0), v.0)) + .collect() + }) + } +} diff --git a/cli/src/util/mod.rs b/cli/src/util/mod.rs index 37236648d..0dad67020 100644 --- a/cli/src/util/mod.rs +++ b/cli/src/util/mod.rs @@ -1,6 +1,39 @@ use anyhow::{Context, Result}; use base64::prelude::{Engine as _, BASE64_STANDARD}; use everscale_crypto::ed25519; +use everscale_types::models::{Account, StorageUsed}; +use everscale_types::num::VarUint56; +use everscale_types::prelude::*; + +// TODO: move into types +pub fn compute_storage_used(account: &Account) -> Result { + let cell = { + let cx = &mut Cell::empty_context(); + let mut storage = CellBuilder::new(); + storage.store_u64(account.last_trans_lt)?; + account.balance.store_into(&mut storage, cx)?; + account.state.store_into(&mut storage, cx)?; + if account.init_code_hash.is_some() { + account.init_code_hash.store_into(&mut storage, cx)?; + } + storage.build_ext(cx)? + }; + + let res = cell + .compute_unique_stats(usize::MAX) + .context("max size exceeded")?; + + let res = StorageUsed { + cells: VarUint56::new(res.cell_count), + bits: VarUint56::new(res.bit_count), + public_cells: Default::default(), + }; + + anyhow::ensure!(res.bits.is_valid(), "bit count overflow"); + anyhow::ensure!(res.cells.is_valid(), "cell count overflow"); + + Ok(res) +} pub fn parse_secret_key(key: &[u8], raw_key: bool) -> Result { parse_hash(key, raw_key).map(ed25519::SecretKey::from_bytes) diff --git a/util/Cargo.toml b/util/Cargo.toml index 75be351e4..5511f9023 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -13,6 +13,7 @@ license.workspace = true ahash = { workspace = true } castaway = { workspace = true } dashmap = { workspace = true } +everscale-crypto = { workspace = true } futures-util = { workspace = true } hex = { workspace = true } humantime = { workspace = true } diff --git a/util/src/serde_helpers.rs b/util/src/serde_helpers.rs index bf38f98d5..9cee7e93d 100644 --- a/util/src/serde_helpers.rs +++ b/util/src/serde_helpers.rs @@ -5,6 +5,58 @@ use std::str::FromStr; use serde::de::{Error, Expected, Visitor}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +pub mod public_key { + use everscale_crypto::ed25519; + + use super::*; + + pub fn serialize( + value: &ed25519::PublicKey, + serializer: S, + ) -> Result { + hex_byte_array::serialize(value.as_bytes(), serializer) + } + + pub fn deserialize<'de, D: Deserializer<'de>>( + deserializer: D, + ) -> Result { + hex_byte_array::deserialize(deserializer).and_then(|bytes| { + ed25519::PublicKey::from_bytes(bytes).ok_or_else(|| Error::custom("invalid public key")) + }) + } +} + +pub mod hex_byte_array { + use super::*; + + pub fn serialize( + value: &dyn AsRef<[u8]>, + serializer: S, + ) -> Result { + if serializer.is_human_readable() { + serializer.serialize_str(&hex::encode(value.as_ref())) + } else { + serializer.serialize_bytes(value.as_ref()) + } + } + + pub fn deserialize<'de, D: Deserializer<'de>, const N: usize>( + deserializer: D, + ) -> Result<[u8; N], D::Error> { + if deserializer.is_human_readable() { + deserializer.deserialize_str(HexVisitor).and_then(|bytes| { + let len = bytes.len(); + match <[u8; N]>::try_from(bytes) { + Ok(bytes) => Ok(bytes), + Err(_) => Err(Error::invalid_length(len, &"32 bytes")), + } + }) + } else { + deserializer.deserialize_bytes(BytesVisitor::) + } + } +} + pub mod socket_addr { use std::net::SocketAddr; @@ -310,3 +362,21 @@ impl<'de, const M: usize> Visitor<'de> for BytesVisitor { array_from_iterator(SeqIter::new(seq), &self) } } + +struct HexVisitor; + +impl<'de> Visitor<'de> for HexVisitor { + type Value = Vec; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("hex-encoded byte array") + } + + fn visit_str(self, value: &str) -> Result { + hex::decode(value).map_err(|_| E::invalid_type(serde::de::Unexpected::Str(value), &self)) + } + + fn visit_bytes(self, value: &[u8]) -> Result { + Ok(value.to_vec()) + } +} From f53b510b6a72f78da40cba7eabbc8166379e90b6 Mon Sep 17 00:00:00 2001 From: Ivan Kalinin Date: Wed, 24 Apr 2024 20:33:27 +0200 Subject: [PATCH 5/6] feat(cli): add default zerostate generator --- .gitignore | 4 +- Cargo.lock | 15 +- cli/Cargo.toml | 2 +- cli/src/tools/gen_zerostate.rs | 290 +++++++++++++++++++++++++++++---- 4 files changed, 274 insertions(+), 37 deletions(-) diff --git a/.gitignore b/.gitignore index ae9a3bdcb..62e1e125a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,6 @@ target/ perf.data* .scratch -.DS_Store \ No newline at end of file +.DS_Store + +zerostate.json diff --git a/Cargo.lock b/Cargo.lock index 41f2c11a8..985930ee7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -602,7 +602,7 @@ dependencies = [ [[package]] name = "everscale-types" version = "0.1.0-rc.6" -source = "git+https://github.com/broxus/everscale-types.git#96332943ca1942b3bee968e3c16306b330a4e974" +source = "git+https://github.com/broxus/everscale-types.git#254e80a9244b6d763f7ca3a47eadbef32fb0bf3c" dependencies = [ "ahash", "base64 0.21.7", @@ -622,7 +622,7 @@ dependencies = [ [[package]] name = "everscale-types-proc" version = "0.1.4" -source = "git+https://github.com/broxus/everscale-types.git#96332943ca1942b3bee968e3c16306b330a4e974" +source = "git+https://github.com/broxus/everscale-types.git#254e80a9244b6d763f7ca3a47eadbef32fb0bf3c" dependencies = [ "proc-macro2", "quote", @@ -767,6 +767,16 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "itertools" version = "0.12.1" @@ -1599,6 +1609,7 @@ version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" dependencies = [ + "indexmap", "itoa", "ryu", "serde", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 39a1a69ed..5e33c2dfb 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -23,7 +23,7 @@ everscale-types = { workspace = true } hex = { workspace = true } rand = { workspace = true } serde = { workspace = true } -serde_json = { workspace = true } +serde_json = { workspace = true, features = ["preserve_order"] } tikv-jemallocator = { workspace = true, features = [ "unprefixed_malloc_on_supported_platforms", "background_threads", diff --git a/cli/src/tools/gen_zerostate.rs b/cli/src/tools/gen_zerostate.rs index cccaa48b3..9d47e96c8 100644 --- a/cli/src/tools/gen_zerostate.rs +++ b/cli/src/tools/gen_zerostate.rs @@ -3,9 +3,7 @@ use std::path::PathBuf; use anyhow::Result; use everscale_crypto::ed25519; -use everscale_types::models::{ - Account, AccountState, CurrencyCollection, OptionalAccount, SpecialFlags, StateInit, StdAddr, -}; +use everscale_types::models::*; use everscale_types::num::Tokens; use everscale_types::prelude::*; use serde::{Deserialize, Serialize}; @@ -25,8 +23,8 @@ pub struct Cmd { config: Option, /// path to the output file - #[clap(short, long)] - output: PathBuf, + #[clap(short, long, required_unless_present = "init_config")] + output: Option, /// explicit unix timestamp of the zero state #[clap(long)] @@ -35,8 +33,14 @@ pub struct Cmd { impl Cmd { pub fn run(self) -> Result<()> { - // todo - Ok(()) + match self.init_config { + Some(path) => { + let config = ZerostateConfig::default(); + std::fs::write(path, serde_json::to_string_pretty(&config).unwrap())?; + Ok(()) + } + None => Ok(()), + } } } @@ -51,19 +55,251 @@ struct ZerostateConfig { #[serde(with = "serde_account_states")] accounts: HashMap, + + params: BlockchainConfigParams, } -#[derive(Serialize, Deserialize)] -struct BlockchainConfig { - #[serde(with = "serde_helpers::hex_byte_array")] - config_address: HashBytes, - #[serde(with = "serde_helpers::hex_byte_array")] - elector_address: HashBytes, - #[serde(with = "serde_helpers::hex_byte_array")] - minter_address: HashBytes, - // TODO: additional currencies - global_version: u32, - global_capabilities: u64, +impl Default for ZerostateConfig { + fn default() -> Self { + Self { + global_id: 0, + config_public_key: ed25519::PublicKey::from_bytes([0; 32]).unwrap(), + minter_public_key: ed25519::PublicKey::from_bytes([0; 32]).unwrap(), + accounts: Default::default(), + params: make_default_params().unwrap(), + } + } +} + +fn make_default_params() -> Result { + let mut params = BlockchainConfig::new_empty(HashBytes([0x55; 32])).params; + + // Param 1 + params.set_elector_address(&HashBytes([0x33; 32]))?; + + // Param 2 + params.set_minter_address(&HashBytes([0x00; 32]))?; + + // Param 8 + params.set_global_version(&GlobalVersion { + version: 32, + capabilities: GlobalCapabilities::from([ + GlobalCapability::CapCreateStatsEnabled, + GlobalCapability::CapBounceMsgBody, + GlobalCapability::CapReportVersion, + GlobalCapability::CapShortDequeue, + GlobalCapability::CapFastStorageStat, + GlobalCapability::CapOffHypercube, + GlobalCapability::CapMyCode, + GlobalCapability::CapFixTupleIndexBug, + ]), + })?; + + // Param 9 + params.set_mandatory_params(&[ + 0, 1, 9, 10, 12, 14, 15, 16, 17, 18, 20, 21, 22, 23, 24, 25, 28, 34, + ])?; + + // Param 10 + params.set_critical_params(&[0, 1, 9, 10, 12, 14, 15, 16, 17, 32, 34, 36])?; + + // Param 11 + params.set::(&ConfigVotingSetup { + normal_params: Lazy::new(&ConfigProposalSetup { + min_total_rounds: 2, + max_total_rounds: 3, + min_wins: 2, + max_losses: 2, + min_store_sec: 1000000, + max_store_sec: 10000000, + bit_price: 1, + cell_price: 500, + })?, + critical_params: Lazy::new(&ConfigProposalSetup { + min_total_rounds: 4, + max_total_rounds: 7, + min_wins: 4, + max_losses: 2, + min_store_sec: 5000000, + max_store_sec: 20000000, + bit_price: 2, + cell_price: 1000, + })?, + })?; + + // Param 12 will always be overwritten + + // Param 14 + params.set_block_creation_rewards(&BlockCreationRewards { + masterchain_block_fee: Tokens::new(1700000000), + basechain_block_fee: Tokens::new(1000000000), + })?; + + // Param 15 + params.set_election_timings(&ElectionTimings { + validators_elected_for: 65536, + elections_start_before: 32768, + elections_end_before: 8192, + stake_held_for: 32768, + })?; + + // Param 16 + params.set_validator_count_params(&ValidatorCountParams { + max_validators: 1000, + max_main_validators: 100, + min_validators: 13, + })?; + + // Param 17 + params.set_validator_stake_params(&ValidatorStakeParams { + min_stake: Tokens::new(10000000000000), + max_stake: Tokens::new(10000000000000000), + min_total_stake: Tokens::new(100000000000000), + max_stake_factor: 196608, + })?; + + // Param 18 + params.set_storage_prices(&[StoragePrices { + utime_since: 0, + bit_price_ps: 1, + cell_price_ps: 500, + mc_bit_price_ps: 1000, + mc_cell_price_ps: 500000, + }])?; + + // Param 20 (masterchain) + params.set_gas_prices( + true, + &GasLimitsPrices { + gas_price: 655360000, + gas_limit: 1000000, + special_gas_limit: 100000000, + gas_credit: 10000, + block_gas_limit: 11000000, + freeze_due_limit: 100000000, + delete_due_limit: 1000000000, + flat_gas_limit: 1000, + flat_gas_price: 10000000, + }, + )?; + + // Param 21 (basechain) + params.set_gas_prices( + false, + &GasLimitsPrices { + gas_price: 65536000, + gas_limit: 1000000, + special_gas_limit: 1000000, + gas_credit: 10000, + block_gas_limit: 10000000, + freeze_due_limit: 100000000, + delete_due_limit: 1000000000, + flat_gas_limit: 1000, + flat_gas_price: 1000000, + }, + )?; + + // Param 22 (masterchain) + params.set_block_limits( + true, + &BlockLimits { + bytes: BlockParamLimits { + underload: 131072, + soft_limit: 524288, + hard_limit: 1048576, + }, + gas: BlockParamLimits { + underload: 900000, + soft_limit: 1200000, + hard_limit: 2000000, + }, + lt_delta: BlockParamLimits { + underload: 1000, + soft_limit: 5000, + hard_limit: 10000, + }, + }, + )?; + + // Param 23 (basechain) + params.set_block_limits( + true, + &BlockLimits { + bytes: BlockParamLimits { + underload: 131072, + soft_limit: 524288, + hard_limit: 1048576, + }, + gas: BlockParamLimits { + underload: 900000, + soft_limit: 1200000, + hard_limit: 2000000, + }, + lt_delta: BlockParamLimits { + underload: 1000, + soft_limit: 5000, + hard_limit: 10000, + }, + }, + )?; + + // Param 24 (masterchain) + params.set_msg_forward_prices( + true, + &MsgForwardPrices { + lump_price: 10000000, + bit_price: 655360000, + cell_price: 65536000000, + ihr_price_factor: 98304, + first_frac: 21845, + next_frac: 21845, + }, + )?; + + // Param 25 (basechain) + params.set_msg_forward_prices( + false, + &MsgForwardPrices { + lump_price: 1000000, + bit_price: 65536000, + cell_price: 6553600000, + ihr_price_factor: 98304, + first_frac: 21845, + next_frac: 21845, + }, + )?; + + // Param 28 + params.set_catchain_config(&CatchainConfig { + isolate_mc_validators: false, + shuffle_mc_validators: true, + mc_catchain_lifetime: 250, + shard_catchain_lifetime: 250, + shard_validators_lifetime: 1000, + shard_validators_num: 11, + })?; + + // Param 29 + params.set_consensus_config(&ConsensusConfig { + new_catchain_ids: true, + round_candidates: 3.try_into().unwrap(), + next_candidate_delay_ms: 2000, + consensus_timeout_ms: 16000, + fast_attempts: 3, + attempt_duration: 8, + catchain_max_deps: 4, + max_block_bytes: 2097152, + max_collated_bytes: 2097152, + })?; + + // Param 31 + params.set_fundamental_addresses(&[ + HashBytes([0x00; 32]), + HashBytes([0x33; 32]), + HashBytes([0x55; 32]), + ])?; + + Ok(params) } fn build_minter_account(pubkey: &ed25519::PublicKey) -> Result { @@ -185,7 +421,6 @@ mod serde_account_states { use serde::de::Deserializer; use serde::ser::{SerializeMap, Serializer}; use serde::{Deserialize, Serialize}; - use tycho_util::serde_helpers; pub fn serialize( value: &HashMap, @@ -194,10 +429,6 @@ mod serde_account_states { where S: Serializer, { - #[derive(Serialize)] - #[repr(transparent)] - struct WrapperKey<'a>(#[serde(with = "serde_helpers::hex_byte_array")] &'a [u8; 32]); - #[derive(Serialize)] #[repr(transparent)] struct WrapperValue<'a>( @@ -206,7 +437,7 @@ mod serde_account_states { let mut ser = serializer.serialize_map(Some(value.len()))?; for (key, value) in value { - ser.serialize_entry(&WrapperKey(key.as_array()), &WrapperValue(value))?; + ser.serialize_entry(key, &WrapperValue(value))?; } ser.end() } @@ -217,18 +448,11 @@ mod serde_account_states { where D: Deserializer<'de>, { - #[derive(Deserialize, Hash, PartialEq, Eq)] - #[repr(transparent)] - struct WrapperKey(#[serde(with = "serde_helpers::hex_byte_array")] [u8; 32]); - #[derive(Deserialize)] #[repr(transparent)] struct WrappedValue(#[serde(with = "BocRepr")] OptionalAccount); - >::deserialize(deserializer).map(|map| { - map.into_iter() - .map(|(k, v)| (HashBytes(k.0), v.0)) - .collect() - }) + >::deserialize(deserializer) + .map(|map| map.into_iter().map(|(k, v)| (k, v.0)).collect()) } } From ea4582dfcd7b972d933267e68a274da174dcaba6 Mon Sep 17 00:00:00 2001 From: Ivan Kalinin Date: Thu, 25 Apr 2024 19:01:34 +0200 Subject: [PATCH 6/6] feat(cli): complete `gen-zerostate` subcommand --- .gitignore | 1 + Cargo.lock | 22 +- Cargo.toml | 3 +- cli/Cargo.toml | 3 + cli/src/tools/gen_account.rs | 4 +- cli/src/tools/gen_zerostate.rs | 439 ++++++++++++++++++++++++++++----- util/src/serde_helpers.rs | 52 ---- 7 files changed, 405 insertions(+), 119 deletions(-) diff --git a/.gitignore b/.gitignore index 62e1e125a..638826373 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ perf.data* .DS_Store zerostate.json +zerostate.boc diff --git a/Cargo.lock b/Cargo.lock index 985930ee7..c4ef3a85f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -587,14 +587,15 @@ dependencies = [ [[package]] name = "everscale-crypto" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3b3e4fc7882223c86a7cfd8ccdb58e017b89a9f91d90114beafa0e8d35b45fb" +checksum = "0b0304a55e328ca4f354e59e6816bccb43b03f681b85b31c6bd10ea7233d62b5" dependencies = [ "curve25519-dalek", "generic-array", "hex", "rand", + "serde", "sha2", "tl-proto", ] @@ -602,7 +603,7 @@ dependencies = [ [[package]] name = "everscale-types" version = "0.1.0-rc.6" -source = "git+https://github.com/broxus/everscale-types.git#254e80a9244b6d763f7ca3a47eadbef32fb0bf3c" +source = "git+https://github.com/broxus/everscale-types.git#40f2cd862ede93943a254351fe4eea313be83233" dependencies = [ "ahash", "base64 0.21.7", @@ -622,7 +623,7 @@ dependencies = [ [[package]] name = "everscale-types-proc" version = "0.1.4" -source = "git+https://github.com/broxus/everscale-types.git#254e80a9244b6d763f7ca3a47eadbef32fb0bf3c" +source = "git+https://github.com/broxus/everscale-types.git#40f2cd862ede93943a254351fe4eea313be83233" dependencies = [ "proc-macro2", "quote", @@ -1615,6 +1616,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "sha2" version = "0.10.8" @@ -2106,6 +2117,9 @@ dependencies = [ "rustc_version", "serde", "serde_json", + "serde_path_to_error", + "sha2", + "thiserror", "tikv-jemallocator", "tokio", "tycho-network", diff --git a/Cargo.toml b/Cargo.toml index cc29690e3..1c8bcf22a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ clap = { version = "4.5.3", features = ["derive"] } crc = "3.0.1" dashmap = "5.4" ed25519 = "2.0" -everscale-crypto = { version = "0.2", features = ["tl-proto"] } +everscale-crypto = { version = "0.2", features = ["tl-proto", "serde"] } everscale-types = "0.1.0-rc.6" exponential-backoff = "1" fdlimit = "0.3.0" @@ -64,6 +64,7 @@ rustls = { version = "0.21", features = ["dangerous_configuration"] } rustls-webpki = "0.101" serde = "1.0" serde_json = "1.0.114" +serde_path_to_error = "0.1" sha2 = "0.10.8" smallvec = "1.13.1" socket2 = "0.5" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 5e33c2dfb..ce4df01fc 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -23,7 +23,10 @@ everscale-types = { workspace = true } hex = { workspace = true } rand = { workspace = true } serde = { workspace = true } +serde_path_to_error = { workspace = true } serde_json = { workspace = true, features = ["preserve_order"] } +sha2 = { workspace = true } +thiserror = { workspace = true } tikv-jemallocator = { workspace = true, features = [ "unprefixed_malloc_on_supported_platforms", "background_threads", diff --git a/cli/src/tools/gen_account.rs b/cli/src/tools/gen_account.rs index 5d73a48c0..24631f658 100644 --- a/cli/src/tools/gen_account.rs +++ b/cli/src/tools/gen_account.rs @@ -295,7 +295,7 @@ impl MultisigBuilder { let mut data = CellBuilder::new(); // Write headers - data.store_u256(HashBytes::wrap(self.pubkey.as_bytes()))?; + data.store_u256(&self.pubkey)?; data.store_u64(0)?; // time data.store_bit_one()?; // constructor flag @@ -368,7 +368,7 @@ impl GiverBuilder { let mut data = CellBuilder::new(); // Append pubkey first - data.store_u256(HashBytes::wrap(self.pubkey.as_bytes()))?; + data.store_u256(&self.pubkey)?; // Append everything except the pubkey let prev_data = state_init diff --git a/cli/src/tools/gen_zerostate.rs b/cli/src/tools/gen_zerostate.rs index 9d47e96c8..2e98af1d9 100644 --- a/cli/src/tools/gen_zerostate.rs +++ b/cli/src/tools/gen_zerostate.rs @@ -1,5 +1,7 @@ use std::collections::HashMap; +use std::io::IsTerminal; use std::path::PathBuf; +use std::sync::OnceLock; use anyhow::Result; use everscale_crypto::ed25519; @@ -7,7 +9,7 @@ use everscale_types::models::*; use everscale_types::num::Tokens; use everscale_types::prelude::*; use serde::{Deserialize, Serialize}; -use tycho_util::serde_helpers; +use sha2::Digest; use crate::util::compute_storage_used; @@ -16,7 +18,7 @@ use crate::util::compute_storage_used; pub struct Cmd { /// dump the template of the zero state config #[clap(short = 'i', long, exclusive = true)] - init_config: Option, + init_config: Option, /// path to the zero state config #[clap(required_unless_present = "init_config")] @@ -29,48 +31,336 @@ pub struct Cmd { /// explicit unix timestamp of the zero state #[clap(long)] now: Option, + + #[clap(short, long)] + force: bool, } impl Cmd { pub fn run(self) -> Result<()> { match self.init_config { - Some(path) => { - let config = ZerostateConfig::default(); - std::fs::write(path, serde_json::to_string_pretty(&config).unwrap())?; - Ok(()) - } - None => Ok(()), + Some(path) => write_default_config(&path, self.force), + None => generate_zerostate( + &self.config.unwrap(), + &self.output.unwrap(), + self.now.unwrap_or_else(tycho_util::time::now_sec), + self.force, + ), } } } +fn write_default_config(config_path: &PathBuf, force: bool) -> Result<()> { + if config_path.exists() && !force { + anyhow::bail!("config file already exists, use --force to overwrite"); + } + + let config = ZerostateConfig::default(); + std::fs::write(config_path, serde_json::to_string_pretty(&config).unwrap())?; + Ok(()) +} + +fn generate_zerostate( + config_path: &PathBuf, + output_path: &PathBuf, + now: u32, + force: bool, +) -> Result<()> { + if output_path.exists() && !force { + anyhow::bail!("output file already exists, use --force to overwrite"); + } + + let mut config = { + let data = std::fs::read_to_string(config_path)?; + let de = &mut serde_json::Deserializer::from_str(&data); + serde_path_to_error::deserialize::<_, ZerostateConfig>(de)? + }; + + config + .prepare_config_params(now) + .map_err(|e| GenError::new("validator config is invalid", e))?; + + config + .add_required_accounts() + .map_err(|e| GenError::new("failed to add required accounts", e))?; + + let state = config + .build_masterchain_state(now) + .map_err(|e| GenError::new("failed to build masterchain zerostate", e))?; + + let boc = CellBuilder::build_from(&state) + .map_err(|e| GenError::new("failed to serialize zerostate", e))?; + + let root_hash = *boc.repr_hash(); + let data = Boc::encode(&boc); + let file_hash = HashBytes::from(sha2::Sha256::digest(&data)); + + std::fs::write(output_path, data) + .map_err(|e| GenError::new("failed to write masterchain zerostate", e))?; + + let hashes = serde_json::json!({ + "root_hash": root_hash, + "file_hash": file_hash, + }); + + let output = if std::io::stdin().is_terminal() { + serde_json::to_string_pretty(&hashes) + } else { + serde_json::to_string(&hashes) + }?; + println!("{output}"); + Ok(()) +} + #[derive(Serialize, Deserialize)] struct ZerostateConfig { global_id: i32, - #[serde(with = "serde_helpers::public_key")] config_public_key: ed25519::PublicKey, - #[serde(with = "serde_helpers::public_key")] - minter_public_key: ed25519::PublicKey, + #[serde(default)] + minter_public_key: Option, + + config_balance: Tokens, + elector_balance: Tokens, #[serde(with = "serde_account_states")] accounts: HashMap, + validators: Vec, + params: BlockchainConfigParams, } +impl ZerostateConfig { + fn prepare_config_params(&mut self, now: u32) -> Result<()> { + let Some(config_address) = self.params.get::()? else { + anyhow::bail!("config address is not set (param 0)"); + }; + let Some(elector_address) = self.params.get::()? else { + anyhow::bail!("elector address is not set (param 1)"); + }; + let minter_address = self.params.get::()?; + + if self.params.get::()?.is_none() { + self.params + .set::(&ExtraCurrencyCollection::new())?; + } + + anyhow::ensure!( + self.params.get::()?.is_some(), + "required params list is required (param 9)" + ); + + { + let Some(mut workchains) = self.params.get::()? else { + anyhow::bail!("workchains are not set (param 12)"); + }; + + let mut updated = false; + for entry in workchains.clone().iter() { + let (id, mut workchain) = entry?; + anyhow::ensure!( + id != ShardIdent::MASTERCHAIN.workchain(), + "masterchain is not configurable" + ); + + if workchain.zerostate_root_hash != HashBytes::ZERO { + continue; + } + + let shard_ident = ShardIdent::new_full(id); + let shard_state = make_shard_state(self.global_id, shard_ident, now); + + let cell = CellBuilder::build_from(&shard_state)?; + workchain.zerostate_root_hash = *cell.repr_hash(); + let bytes = Boc::encode(&cell); + workchain.zerostate_file_hash = sha2::Sha256::digest(bytes).into(); + + workchains.set(id, &workchain)?; + updated = true; + } + + if updated { + self.params.set_workchains(&workchains)?; + } + } + + { + let mut fundamental_addresses = self.params.get::()?.unwrap_or_default(); + fundamental_addresses.set(config_address, ())?; + fundamental_addresses.set(elector_address, ())?; + if let Some(minter_address) = minter_address { + fundamental_addresses.set(minter_address, ())?; + } + self.params.set::(&fundamental_addresses)?; + } + + for id in 32..=37 { + anyhow::ensure!( + !self.params.contains_raw(id)?, + "config param {id} must not be set manually as it is managed by the tool" + ); + } + + { + const VALIDATOR_WEIGHT: u64 = 1; + + anyhow::ensure!(!self.validators.is_empty(), "validator set is empty"); + + let mut validator_set = ValidatorSet { + utime_since: now, + utime_until: now, + main: (self.validators.len() as u16).try_into().unwrap(), + total_weight: 0, + list: Vec::with_capacity(self.validators.len()), + }; + for pubkey in &self.validators { + validator_set.list.push(ValidatorDescription { + public_key: HashBytes::from(*pubkey.as_bytes()), + weight: VALIDATOR_WEIGHT, + adnl_addr: None, + mc_seqno_since: 0, + prev_total_weight: validator_set.total_weight, + }); + validator_set.total_weight += VALIDATOR_WEIGHT; + } + + self.params.set::(&validator_set)?; + } + + let mandatory_params = self.params.get::()?.unwrap(); + for entry in mandatory_params.keys() { + let id = entry?; + anyhow::ensure!( + self.params.contains_raw(id)?, + "required param {id} is not set" + ); + } + + Ok(()) + } + + fn add_required_accounts(&mut self) -> Result<()> { + // Config + let Some(config_address) = self.params.get::()? else { + anyhow::bail!("config address is not set (param 0)"); + }; + anyhow::ensure!( + &self.config_public_key != zero_public_key(), + "config public key is not set" + ); + self.accounts.insert( + config_address, + build_config_account( + &self.config_public_key, + &config_address, + self.config_balance, + )? + .into(), + ); + + // Elector + let Some(elector_address) = self.params.get::()? else { + anyhow::bail!("elector address is not set (param 1)"); + }; + self.accounts.insert( + elector_address, + build_elector_code(&elector_address, self.elector_balance)?.into(), + ); + + // Minter + match (&self.minter_public_key, self.params.get::()?) { + (Some(public_key), Some(minter_address)) => { + anyhow::ensure!( + public_key != zero_public_key(), + "minter public key is invalid" + ); + self.accounts.insert( + minter_address, + build_minter_account(&public_key, &minter_address)?.into(), + ); + } + (None, Some(_)) => anyhow::bail!("minter_public_key is required"), + (Some(_), None) => anyhow::bail!("minter address is not set (param 2)"), + (None, None) => {} + } + + // Done + Ok(()) + } + + fn build_masterchain_state(&self, now: u32) -> Result { + let mut state = make_shard_state(self.global_id, ShardIdent::MASTERCHAIN, now); + + for account in self.accounts.values() { + if let Some(account) = account.as_ref() { + state.total_balance = state + .total_balance + .checked_add(&account.balance) + .map_err(|e| GenError::new("failed ot compute total balance", e))?; + } + } + + state.custom = Some(Lazy::new(&McStateExtra { + shards: Default::default(), + config: BlockchainConfig { + address: self.params.get::()?.unwrap(), + params: self.params.clone(), + }, + validator_info: ValidatorInfo { + validator_list_hash_short: 0, + catchain_seqno: 0, + nx_cc_updated: true, + }, + prev_blocks: AugDict::new(), + after_key_block: true, + last_key_block: None, + block_create_stats: None, + global_balance: state.total_balance.clone(), + copyleft_rewards: Dict::new(), + })?); + + Ok(state) + } +} + impl Default for ZerostateConfig { fn default() -> Self { Self { global_id: 0, - config_public_key: ed25519::PublicKey::from_bytes([0; 32]).unwrap(), - minter_public_key: ed25519::PublicKey::from_bytes([0; 32]).unwrap(), + config_public_key: *zero_public_key(), + minter_public_key: None, + config_balance: Tokens::new(500_000_000_000), // 500 + elector_balance: Tokens::new(500_000_000_000), // 500 accounts: Default::default(), + validators: Default::default(), params: make_default_params().unwrap(), } } } +fn make_shard_state(global_id: i32, shard_ident: ShardIdent, now: u32) -> ShardStateUnsplit { + ShardStateUnsplit { + global_id, + shard_ident, + seqno: 0, + vert_seqno: 0, + gen_utime: now, + gen_lt: 0, + min_ref_mc_seqno: u32::MAX, + out_msg_queue_info: Default::default(), + before_split: false, + accounts: Lazy::new(&Default::default()).unwrap(), + overload_history: 0, + underload_history: 0, + total_balance: CurrencyCollection::ZERO, + total_validator_fees: CurrencyCollection::ZERO, + libraries: Dict::new(), + master_ref: None, + custom: None, + } +} + fn make_default_params() -> Result { let mut params = BlockchainConfig::new_empty(HashBytes([0x55; 32])).params; @@ -127,7 +417,29 @@ fn make_default_params() -> Result { })?, })?; - // Param 12 will always be overwritten + // Param 12 + { + let mut workchains = Dict::new(); + workchains.set( + 0, + WorkchainDescription { + enabled_since: 0, + actual_min_split: 0, + min_split: 0, + max_split: 3, + active: true, + accept_msgs: true, + zerostate_root_hash: HashBytes::ZERO, + zerostate_file_hash: HashBytes::ZERO, + version: 0, + format: WorkchainFormat::Basic(WorkchainFormatBasic { + vm_version: 0, + vm_mode: 0, + }), + }, + )?; + params.set::(&workchains)?; + } // Param 14 params.set_block_creation_rewards(&BlockCreationRewards { @@ -223,7 +535,7 @@ fn make_default_params() -> Result { // Param 23 (basechain) params.set_block_limits( - true, + false, &BlockLimits { bytes: BlockParamLimits { underload: 131072, @@ -302,42 +614,6 @@ fn make_default_params() -> Result { Ok(params) } -fn build_minter_account(pubkey: &ed25519::PublicKey) -> Result { - const MINTER_STATE: &[u8] = include_bytes!("../../res/minter_state.boc"); - - let mut account = BocRepr::decode::(MINTER_STATE)? - .0 - .expect("invalid minter state"); - - match &mut account.state { - AccountState::Active(state_init) => { - let mut data = CellBuilder::new(); - - // Append pubkey first - data.store_u256(HashBytes::wrap(pubkey.as_bytes()))?; - - // Append everything except the pubkey - let prev_data = state_init - .data - .take() - .expect("minter state must contain data"); - let mut prev_data = prev_data.as_slice()?; - prev_data.advance(256, 0)?; - - data.store_slice(prev_data)?; - - // Update data - state_init.data = Some(data.build()?); - } - _ => unreachable!("saved state is for the active account"), - }; - - account.balance = CurrencyCollection::ZERO; - account.storage_stat.used = compute_storage_used(&account)?; - - Ok(account) -} - fn build_config_account( pubkey: &ed25519::PublicKey, address: &HashBytes, @@ -350,7 +626,7 @@ fn build_config_account( let mut data = CellBuilder::new(); data.store_reference(Cell::empty_cell())?; data.store_u32(0)?; - data.store_u256(HashBytes::wrap(pubkey.as_bytes()))?; + data.store_u256(pubkey)?; data.store_bit_zero()?; let data = data.build()?; @@ -412,15 +688,60 @@ fn build_elector_code(address: &HashBytes, balance: Tokens) -> Result { Ok(account) } +fn build_minter_account(pubkey: &ed25519::PublicKey, address: &HashBytes) -> Result { + const MINTER_STATE: &[u8] = include_bytes!("../../res/minter_state.boc"); + + let mut account = BocRepr::decode::(MINTER_STATE)? + .0 + .expect("invalid minter state"); + + match &mut account.state { + AccountState::Active(state_init) => { + // Append everything except the pubkey + let mut data = CellBuilder::new(); + data.store_u32(0)?; + data.store_u256(pubkey)?; + + // Update data + state_init.data = Some(data.build()?); + } + _ => unreachable!("saved state is for the active account"), + }; + + account.address = StdAddr::new(-1, *address).into(); + account.balance = CurrencyCollection::ZERO; + account.storage_stat.used = compute_storage_used(&account)?; + + Ok(account) +} + +fn zero_public_key() -> &'static ed25519::PublicKey { + static KEY: OnceLock = OnceLock::new(); + KEY.get_or_init(|| ed25519::PublicKey::from_bytes([0; 32]).unwrap()) +} + +#[derive(thiserror::Error, Debug)] +#[error("{context}: {source}")] +struct GenError { + context: String, + #[source] + source: anyhow::Error, +} + +impl GenError { + fn new(context: impl Into, source: impl Into) -> Self { + Self { + context: context.into(), + source: source.into(), + } + } +} + mod serde_account_states { - use std::collections::HashMap; + use super::*; - use everscale_types::boc::BocRepr; - use everscale_types::cell::HashBytes; - use everscale_types::models::OptionalAccount; use serde::de::Deserializer; use serde::ser::{SerializeMap, Serializer}; - use serde::{Deserialize, Serialize}; pub fn serialize( value: &HashMap, @@ -431,9 +752,7 @@ mod serde_account_states { { #[derive(Serialize)] #[repr(transparent)] - struct WrapperValue<'a>( - #[serde(serialize_with = "BocRepr::serialize")] &'a OptionalAccount, - ); + struct WrapperValue<'a>(#[serde(with = "BocRepr")] &'a OptionalAccount); let mut ser = serializer.serialize_map(Some(value.len()))?; for (key, value) in value { diff --git a/util/src/serde_helpers.rs b/util/src/serde_helpers.rs index 9cee7e93d..0d2ddd9ce 100644 --- a/util/src/serde_helpers.rs +++ b/util/src/serde_helpers.rs @@ -5,58 +5,6 @@ use std::str::FromStr; use serde::de::{Error, Expected, Visitor}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -pub mod public_key { - use everscale_crypto::ed25519; - - use super::*; - - pub fn serialize( - value: &ed25519::PublicKey, - serializer: S, - ) -> Result { - hex_byte_array::serialize(value.as_bytes(), serializer) - } - - pub fn deserialize<'de, D: Deserializer<'de>>( - deserializer: D, - ) -> Result { - hex_byte_array::deserialize(deserializer).and_then(|bytes| { - ed25519::PublicKey::from_bytes(bytes).ok_or_else(|| Error::custom("invalid public key")) - }) - } -} - -pub mod hex_byte_array { - use super::*; - - pub fn serialize( - value: &dyn AsRef<[u8]>, - serializer: S, - ) -> Result { - if serializer.is_human_readable() { - serializer.serialize_str(&hex::encode(value.as_ref())) - } else { - serializer.serialize_bytes(value.as_ref()) - } - } - - pub fn deserialize<'de, D: Deserializer<'de>, const N: usize>( - deserializer: D, - ) -> Result<[u8; N], D::Error> { - if deserializer.is_human_readable() { - deserializer.deserialize_str(HexVisitor).and_then(|bytes| { - let len = bytes.len(); - match <[u8; N]>::try_from(bytes) { - Ok(bytes) => Ok(bytes), - Err(_) => Err(Error::invalid_length(len, &"32 bytes")), - } - }) - } else { - deserializer.deserialize_bytes(BytesVisitor::) - } - } -} - pub mod socket_addr { use std::net::SocketAddr;