diff --git a/.gitignore b/.gitignore index ae9a3bdcb..638826373 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,7 @@ target/ perf.data* .scratch -.DS_Store \ No newline at end of file +.DS_Store + +zerostate.json +zerostate.boc diff --git a/Cargo.lock b/Cargo.lock index b13c8c6e6..c4ef3a85f 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,25 +585,17 @@ 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" +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", ] @@ -648,7 +603,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#40f2cd862ede93943a254351fe4eea313be83233" dependencies = [ "ahash", "base64 0.21.7", @@ -668,7 +623,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#40f2cd862ede93943a254351fe4eea313be83233" dependencies = [ "proc-macro2", "quote", @@ -813,6 +768,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" @@ -830,9 +795,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 +836,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 +952,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 +963,6 @@ dependencies = [ "parking_lot", "quanta", "rustc_version", - "skeptic", "smallvec", "tagptr", "thiserror", @@ -1265,17 +1229,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 +1305,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 +1562,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 +1583,6 @@ name = "semver" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" -dependencies = [ - "serde", -] [[package]] name = "serde" @@ -1669,11 +1610,22 @@ version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" dependencies = [ + "indexmap", "itoa", "ryu", "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" @@ -1718,21 +1670,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 +1771,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 +1854,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 +1875,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", @@ -2171,10 +2108,22 @@ name = "tycho-cli" version = "0.0.1" dependencies = [ "anyhow", + "base64 0.22.0", "clap", + "everscale-crypto", + "everscale-types", + "hex", + "rand", "rustc_version", + "serde", + "serde_json", + "serde_path_to_error", + "sha2", + "thiserror", "tikv-jemallocator", "tokio", + "tycho-network", + "tycho-util", ] [[package]] @@ -2341,6 +2290,7 @@ dependencies = [ "ahash", "castaway", "dashmap", + "everscale-crypto", "futures-util", "hex", "humantime", @@ -2365,15 +2315,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" @@ -2431,16 +2372,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" @@ -2539,15 +2470,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" @@ -2561,7 +2483,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]] @@ -2570,7 +2492,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]] @@ -2588,7 +2510,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]] @@ -2608,17 +2530,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]] @@ -2629,9 +2552,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" @@ -2641,9 +2564,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" @@ -2653,9 +2576,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" @@ -2665,9 +2594,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" @@ -2677,9 +2606,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" @@ -2689,9 +2618,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" @@ -2701,9 +2630,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/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 9cb71e2c2..ce4df01fc 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 @@ -15,14 +16,26 @@ 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 } +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", ], 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/res/config_code.boc b/cli/res/config_code.boc new file mode 100644 index 000000000..925ee0372 Binary files /dev/null and b/cli/res/config_code.boc differ diff --git a/cli/res/elector_code.boc b/cli/res/elector_code.boc new file mode 100644 index 000000000..548a0eb46 Binary files /dev/null and b/cli/res/elector_code.boc differ diff --git a/cli/res/ever_wallet_code.boc b/cli/res/ever_wallet_code.boc new file mode 100644 index 000000000..5a5b0cc9c Binary files /dev/null and b/cli/res/ever_wallet_code.boc differ diff --git a/cli/res/giver_state.boc b/cli/res/giver_state.boc new file mode 100644 index 000000000..1cbf62452 Binary files /dev/null and b/cli/res/giver_state.boc differ diff --git a/cli/res/minter_state.boc b/cli/res/minter_state.boc new file mode 100644 index 000000000..d744abd52 Binary files /dev/null and b/cli/res/minter_state.boc differ diff --git a/cli/res/safe_multisig_code.boc b/cli/res/safe_multisig_code.boc new file mode 100644 index 000000000..058d68569 Binary files /dev/null and b/cli/res/safe_multisig_code.boc differ diff --git a/cli/res/setcode_multisig_code.boc b/cli/res/setcode_multisig_code.boc new file mode 100644 index 000000000..67210427e Binary files /dev/null and b/cli/res/setcode_multisig_code.boc differ diff --git a/cli/src/main.rs b/cli/src/main.rs index 238d6467c..f9d1ecf49 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,18 +1,35 @@ +use std::process::ExitCode; use std::sync::OnceLock; +use anyhow::Result; use clap::{Parser, Subcommand}; +mod tools { + pub mod gen_account; + pub mod gen_dht; + pub mod gen_key; + pub mod gen_zerostate; +} + +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 +43,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 +76,26 @@ struct InitCmd {} #[derive(Parser)] struct RunCmd {} +/// A collection of tools +#[derive(Subcommand)] +enum ToolCmd { + GenDht(tools::gen_dht::Cmd), + GenKey(tools::gen_key::Cmd), + GenZerostate(tools::gen_zerostate::Cmd), + GenAccount(tools::gen_account::Cmd), +} + +impl ToolCmd { + fn run(self) -> Result<()> { + match self { + ToolCmd::GenDht(cmd) => cmd.run(), + ToolCmd::GenKey(cmd) => cmd.run(), + ToolCmd::GenZerostate(cmd) => cmd.run(), + ToolCmd::GenAccount(cmd) => cmd.run(), + } + } +} + fn version_string() -> &'static str { static STRING: OnceLock = OnceLock::new(); STRING.get_or_init(|| { diff --git a/cli/src/tools/gen_account.rs b/cli/src/tools/gen_account.rs new file mode 100644 index 000000000..24631f658 --- /dev/null +++ b/cli/src/tools/gen_account.rs @@ -0,0 +1,397 @@ +use std::io::IsTerminal; + +use anyhow::{Context, Result}; +use everscale_crypto::ed25519; +use everscale_types::models::{Account, AccountState, OptionalAccount, StateInit, StdAddr}; +use everscale_types::num::Tokens; +use everscale_types::prelude::*; + +use crate::util::{compute_storage_used, parse_public_key}; + +/// 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, + + /// initial balance of the wallet (in nano) + #[clap(short, long, required = true)] + balance: Tokens, + + /// list of custodian public keys + #[clap(short, long)] + custodians: Vec, + + /// 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 MultisigCmd { + 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()?; + + write_state(&account, &state) + } +} + +/// 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) + } +} + +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)?; + + Ok((address, account)) + } +} + +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)> { + 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, + "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(&self.pubkey)?; + 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)) + } +} + +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(&self.pubkey)?; + + // 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)) + } +} diff --git a/cli/src/tools/gen_dht.rs b/cli/src/tools/gen_dht.rs new file mode 100644 index 000000000..857f7c4b9 --- /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 Cmd { + /// 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 Cmd { + 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..6775f8d86 --- /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 Cmd { + /// 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 Cmd { + 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/tools/gen_zerostate.rs b/cli/src/tools/gen_zerostate.rs new file mode 100644 index 000000000..2e98af1d9 --- /dev/null +++ b/cli/src/tools/gen_zerostate.rs @@ -0,0 +1,777 @@ +use std::collections::HashMap; +use std::io::IsTerminal; +use std::path::PathBuf; +use std::sync::OnceLock; + +use anyhow::Result; +use everscale_crypto::ed25519; +use everscale_types::models::*; +use everscale_types::num::Tokens; +use everscale_types::prelude::*; +use serde::{Deserialize, Serialize}; +use sha2::Digest; + +use crate::util::compute_storage_used; + +/// Generate a zero state for a network. +#[derive(clap::Parser)] +pub struct Cmd { + /// dump the template of the zero state config + #[clap(short = 'i', long, exclusive = true)] + init_config: Option, + + /// path to the zero state config + #[clap(required_unless_present = "init_config")] + config: Option, + + /// path to the output file + #[clap(short, long, required_unless_present = "init_config")] + output: Option, + + /// 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) => 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, + + config_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: *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; + + // 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 + { + 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 { + 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( + false, + &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_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(pubkey)?; + 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) +} + +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 super::*; + + use serde::de::Deserializer; + use serde::ser::{SerializeMap, Serializer}; + + pub fn serialize( + value: &HashMap, + serializer: S, + ) -> Result + where + S: Serializer, + { + #[derive(Serialize)] + #[repr(transparent)] + struct WrapperValue<'a>(#[serde(with = "BocRepr")] &'a OptionalAccount); + + let mut ser = serializer.serialize_map(Some(value.len()))?; + for (key, value) in value { + ser.serialize_entry(key, &WrapperValue(value))?; + } + ser.end() + } + + pub fn deserialize<'de, D>( + deserializer: D, + ) -> Result, D::Error> + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + #[repr(transparent)] + struct WrappedValue(#[serde(with = "BocRepr")] OptionalAccount); + + >::deserialize(deserializer) + .map(|map| map.into_iter().map(|(k, v)| (k, v.0)).collect()) + } +} diff --git a/cli/src/util/mod.rs b/cli/src/util/mod.rs new file mode 100644 index 000000000..0dad67020 --- /dev/null +++ b/cli/src/util/mod.rs @@ -0,0 +1,60 @@ +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) +} + +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(); + match key.len() { + 44 => BASE64_STANDARD.decode(key)?.try_into().ok(), + 64 => hex::decode(key)?.try_into().ok(), + _ => None, + } + }; + + key.context("invalid key length") +} diff --git a/storage/src/store/block/mod.rs b/storage/src/store/block/mod.rs index 1e1b45efd..bd2255e91 100644 --- a/storage/src/store/block/mod.rs +++ b/storage/src/store/block/mod.rs @@ -724,7 +724,7 @@ fn remove_blocks( // Read only prefix with shard ident and seqno let BlockIdShort { shard, seqno } = - BlockIdShort::deserialize(&mut std::convert::identity(key))?; + ::deserialize(&mut std::convert::identity(key))?; // Don't gc latest blocks if top_blocks.contains_shard_seqno(&shard, seqno) { 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..0d2ddd9ce 100644 --- a/util/src/serde_helpers.rs +++ b/util/src/serde_helpers.rs @@ -310,3 +310,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()) + } +}