From 0344571cd1de0d7d5002adcea72d41ad53d86261 Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Mon, 5 Aug 2024 07:10:50 -0700 Subject: [PATCH] Add snapshot subcommand that creates a ledger snapshot from an archive (#1368) --- .gitignore | 1 + Cargo.lock | 375 +++++++----- FULL_HELP_DOCS.md | 48 ++ .../soroban-test/tests/it/integration.rs | 1 + .../tests/it/integration/snapshot.rs | 83 +++ .../soroban-test/tests/it/integration/wrap.rs | 2 +- cmd/soroban-cli/Cargo.toml | 7 + .../src/commands/contract/deploy/asset.rs | 2 +- .../src/commands/contract/id/asset.rs | 2 +- cmd/soroban-cli/src/commands/mod.rs | 8 + .../src/commands/snapshot/create.rs | 568 ++++++++++++++++++ cmd/soroban-cli/src/commands/snapshot/mod.rs | 24 + cmd/soroban-cli/src/config/data.rs | 6 + cmd/soroban-cli/src/utils.rs | 41 +- 14 files changed, 993 insertions(+), 175 deletions(-) create mode 100644 cmd/crates/soroban-test/tests/it/integration/snapshot.rs create mode 100644 cmd/soroban-cli/src/commands/snapshot/create.rs create mode 100644 cmd/soroban-cli/src/commands/snapshot/mod.rs diff --git a/.gitignore b/.gitignore index 74abdb3f1..b59c63720 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ target/ captive-core/ .soroban/ +snapshot.json !test.toml *.sqlite test_snapshots diff --git a/Cargo.lock b/Cargo.lock index 7a54f07a3..0fd9ea18e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -194,11 +194,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ "concurrent-queue", - "event-listener-strategy", + "event-listener-strategy 0.5.2", "futures-core", "pin-project-lite", ] +[[package]] +name = "async-compression" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fec134f64e2bc57411226dfc4e52dec859ddfc7e711fc5e07b612584f000e4aa" +dependencies = [ + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", +] + [[package]] name = "async-executor" version = "1.12.0" @@ -221,7 +234,7 @@ dependencies = [ "async-channel 2.3.1", "async-executor", "async-io 2.3.3", - "async-lock 3.4.0", + "async-lock 3.3.0", "blocking", "futures-lite 2.3.0", "once_cell", @@ -253,13 +266,13 @@ version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" dependencies = [ - "async-lock 3.4.0", + "async-lock 3.3.0", "cfg-if", "concurrent-queue", "futures-io", "futures-lite 2.3.0", "parking", - "polling 3.7.2", + "polling 3.7.0", "rustix 0.38.34", "slab", "tracing", @@ -277,12 +290,12 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.4.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" dependencies = [ - "event-listener 5.3.1", - "event-listener-strategy", + "event-listener 4.0.3", + "event-listener-strategy 0.4.0", "pin-project-lite", ] @@ -319,7 +332,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "794f185324c2f00e771cd9f1ae8b5ac68be2ca7abb129a87afd6e86d228bc54d" dependencies = [ "async-io 2.3.3", - "async-lock 3.4.0", + "async-lock 3.3.0", "atomic-waker", "cfg-if", "futures-core", @@ -530,7 +543,7 @@ dependencies = [ "hex", "http 1.1.0", "http-body-util", - "hyper 1.4.0", + "hyper 1.3.1", "hyper-named-pipe", "hyper-util", "hyperlocal-next", @@ -567,7 +580,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" dependencies = [ "memchr", - "regex-automata 0.4.7", + "regex-automata 0.4.6", "serde", ] @@ -594,9 +607,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "bytes-lit" @@ -610,6 +623,12 @@ dependencies = [ "syn 2.0.39", ] +[[package]] +name = "bytesize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" + [[package]] name = "camino" version = "1.1.7" @@ -671,7 +690,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.6", + "windows-targets 0.52.5", ] [[package]] @@ -707,9 +726,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.7" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d598e88f6874d4b888ed40c71efbcbf4076f1dfbae128a08a8c9e45f710605d" +checksum = "dd79504325bf38b10165b02e89b4347300f855f273c4cb30c4a3209e6583275e" dependencies = [ "clap", ] @@ -728,9 +747,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "clru" @@ -943,15 +962,16 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.1.3" +version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", + "platforms", "rustc_version", "subtle", "zeroize", @@ -1253,7 +1273,7 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ - "curve25519-dalek 4.1.3", + "curve25519-dalek 4.1.2", "ed25519 2.2.3", "rand_core 0.6.4", "serde", @@ -1264,9 +1284,9 @@ dependencies = [ [[package]] name = "either" -version = "1.13.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" [[package]] name = "elliptic-curve" @@ -1374,22 +1394,43 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.3.1" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" dependencies = [ "concurrent-queue", "parking", "pin-project-lite", ] +[[package]] +name = "event-listener" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9944b8ca13534cdfb2800775f8dd4902ff3fc75a50101466decadfdf322a24" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +dependencies = [ + "event-listener 4.0.3", + "pin-project-lite", +] + [[package]] name = "event-listener-strategy" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ - "event-listener 5.3.1", + "event-listener 5.3.0", "pin-project-lite", ] @@ -1833,9 +1874,9 @@ dependencies = [ [[package]] name = "gix-date" -version = "0.8.7" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eed6931f21491ee0aeb922751bd7ec97b4b2fe8fbfedcb678e2a2dce5f3b8c0" +checksum = "367ee9093b0c2b04fd04c5c7c8b6a1082713534eab537597ae343663a518fa99" dependencies = [ "bstr", "itoa", @@ -1924,9 +1965,9 @@ dependencies = [ [[package]] name = "gix-glob" -version = "0.16.3" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a29ad0990cf02c48a7aac76ed0dbddeb5a0d070034b83675cc3bbf937eace4" +checksum = "682bdc43cb3c00dbedfcc366de2a849b582efd8d886215dbad2ea662ec156bb5" dependencies = [ "bitflags 2.6.0", "bstr", @@ -2116,9 +2157,9 @@ dependencies = [ [[package]] name = "gix-path" -version = "0.10.8" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca987128ffb056d732bd545db5db3d8b103d252fbf083c2567bb0796876619a4" +checksum = "23623cf0f475691a6d943f898c4d0b89f5c1a2a64d0f92bce0e0322ee6528783" dependencies = [ "bstr", "gix-trace", @@ -2170,7 +2211,7 @@ dependencies = [ "gix-utils", "maybe-async", "thiserror", - "winnow 0.6.13", + "winnow 0.6.8", ] [[package]] @@ -2419,8 +2460,8 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.6", + "regex-syntax 0.8.3", ] [[package]] @@ -2515,12 +2556,6 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" -[[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" - [[package]] name = "hex" version = "0.4.3" @@ -2621,12 +2656,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" dependencies = [ "bytes", - "futures-util", + "futures-core", "http 1.1.0", "http-body 1.0.0", "pin-project-lite", @@ -2634,9 +2669,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" @@ -2659,7 +2694,7 @@ dependencies = [ "crossbeam-utils", "form_urlencoded", "futures-util", - "hyper 0.14.29", + "hyper 0.14.28", "lazy_static", "levenshtein", "log", @@ -2680,9 +2715,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.29" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", @@ -2704,9 +2739,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.4.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4fe55fb7a772d59a5ff1dfbff4fe0258d19b89fec4b233e75d35d5d2316badc" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" dependencies = [ "bytes", "futures-channel", @@ -2728,7 +2763,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" dependencies = [ "hex", - "hyper 1.4.0", + "hyper 1.3.1", "hyper-util", "pin-project-lite", "tokio", @@ -2744,7 +2779,7 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.12", - "hyper 0.14.29", + "hyper 0.14.28", "log", "rustls 0.21.12", "rustls-native-certs", @@ -2759,7 +2794,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper 0.14.29", + "hyper 0.14.28", "native-tls", "tokio", "tokio-native-tls", @@ -2767,16 +2802,16 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.6" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" +checksum = "3d8d52be92d09acc2e01dddb7fde3ad983fc6489c7db4837e605bc3fca4cb63e" dependencies = [ "bytes", "futures-channel", "futures-util", "http 1.1.0", "http-body 1.0.0", - "hyper 1.4.0", + "hyper 1.3.1", "pin-project-lite", "socket2 0.5.7", "tokio", @@ -2793,7 +2828,7 @@ checksum = "acf569d43fa9848e510358c07b80f4adf34084ddc28c6a4a651ee8474c070dcc" dependencies = [ "hex", "http-body-util", - "hyper 1.4.0", + "hyper 1.3.1", "hyper-util", "pin-project-lite", "tokio", @@ -2849,7 +2884,7 @@ dependencies = [ "globset", "log", "memchr", - "regex-automata 0.4.7", + "regex-automata 0.4.6", "same-file", "walkdir", "winapi-util", @@ -2857,9 +2892,9 @@ dependencies = [ [[package]] name = "include_dir" -version = "0.7.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" dependencies = [ "glob", "include_dir_macros", @@ -2867,9 +2902,9 @@ dependencies = [ [[package]] name = "include_dir_macros" -version = "0.7.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" dependencies = [ "proc-macro2", "quote", @@ -2928,7 +2963,7 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi", "libc", "windows-sys 0.48.0", ] @@ -2997,7 +3032,7 @@ dependencies = [ "async-trait", "beef", "futures-util", - "hyper 0.14.29", + "hyper 0.14.28", "jsonrpsee-types", "serde", "serde_json", @@ -3013,7 +3048,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f80c17f62c7653ce767e3d7288b793dfec920f97067ceb189ebdd3570f2bc20" dependencies = [ "async-trait", - "hyper 0.14.29", + "hyper 0.14.28", "hyper-rustls", "jsonrpsee-core", "jsonrpsee-types", @@ -3093,7 +3128,7 @@ dependencies = [ "petgraph", "pico-args", "regex", - "regex-syntax 0.8.4", + "regex-syntax 0.8.3", "string_cache", "term", "tiny-keccak", @@ -3107,14 +3142,14 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" dependencies = [ - "regex-automata 0.4.7", + "regex-automata 0.4.6", ] [[package]] name = "lazy_static" -version = "1.5.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "ledger-apdu" @@ -3214,9 +3249,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" dependencies = [ "value-bag", ] @@ -3243,9 +3278,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "memmap2" @@ -3264,9 +3299,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.7.4" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" dependencies = [ "adler", ] @@ -3284,10 +3319,11 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.12" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" dependencies = [ + "lazy_static", "libc", "log", "openssl", @@ -3380,7 +3416,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi", "libc", ] @@ -3448,9 +3484,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.3.1+3.3.1" +version = "300.3.0+3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7259953d42a81bf137fbbd73bd30a8e1914d6dce43c2b90ed575783a22608b91" +checksum = "eba8804a1c5765b18c4b3f907e6897ebabeedebc9830e1a0046c4a4cf44663e1" dependencies = [ "cc", ] @@ -3516,9 +3552,9 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.2", + "redox_syscall 0.5.1", "smallvec", - "windows-targets 0.52.6", + "windows-targets 0.52.5", ] [[package]] @@ -3649,9 +3685,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.3" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391" +checksum = "464db0c665917b13ebb5d453ccdec4add5658ee1adc7affc7677615356a8afaf" dependencies = [ "atomic-waker", "fastrand 2.1.0", @@ -3674,6 +3710,12 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "platforms" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" + [[package]] name = "polling" version = "2.8.0" @@ -3692,13 +3734,13 @@ dependencies = [ [[package]] name = "polling" -version = "3.7.2" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" +checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi 0.4.0", + "hermit-abi", "pin-project-lite", "rustix 0.38.34", "tracing", @@ -3864,9 +3906,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ "bitflags 2.6.0", ] @@ -3884,14 +3926,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.5" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.6", + "regex-syntax 0.8.3", ] [[package]] @@ -3905,13 +3947,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.3", ] [[package]] @@ -3922,9 +3964,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "reqwest" @@ -3940,7 +3982,7 @@ dependencies = [ "h2", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.29", + "hyper 0.14.28", "hyper-rustls", "hyper-tls", "ipnet", @@ -4119,7 +4161,7 @@ dependencies = [ "log", "ring", "rustls-pki-types", - "rustls-webpki 0.102.5", + "rustls-webpki 0.102.4", "subtle", "zeroize", ] @@ -4163,9 +4205,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.5" +version = "0.102.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" +checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" dependencies = [ "ring", "rustls-pki-types", @@ -4417,9 +4459,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.9.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" +checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" dependencies = [ "base64 0.22.1", "chrono", @@ -4435,9 +4477,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.9.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" +checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" dependencies = [ "darling", "proc-macro2", @@ -4665,9 +4707,11 @@ version = "21.2.0" dependencies = [ "assert_cmd", "assert_fs", + "async-compression", "async-trait", "base64 0.21.7", "bollard", + "bytesize", "cargo_metadata", "chrono", "clap", @@ -4680,13 +4724,16 @@ dependencies = [ "dotenvy", "ed25519-dalek 2.1.1", "ethnum", + "flate2", + "futures", "futures-util", "gix", "heck 0.5.0", "hex", "home", "http 0.2.12", - "hyper 0.14.29", + "humantime", + "hyper 0.14.28", "hyper-tls", "itertools 0.10.5", "jsonrpsee-core", @@ -4725,6 +4772,7 @@ dependencies = [ "termcolor_output", "thiserror", "tokio", + "tokio-util", "toml 0.5.11", "toml_edit 0.21.1", "tracing", @@ -4774,7 +4822,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e25aaffe0c62eb65e0e349f725b4b8b13ad0764d78a15aab5bbccb5c4797726" dependencies = [ "backtrace", - "curve25519-dalek 4.1.3", + "curve25519-dalek 4.1.2", "ecdsa", "ed25519-dalek 2.1.1", "elliptic-curve", @@ -4977,7 +5025,7 @@ dependencies = [ "stellar-strkey", "thiserror", "tokio", - "toml 0.8.14", + "toml 0.8.13", "ulid", "walkdir", "which", @@ -5193,9 +5241,9 @@ dependencies = [ [[package]] name = "subtle" -version = "2.6.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" @@ -5491,9 +5539,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] @@ -5506,9 +5554,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -5525,9 +5573,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", @@ -5562,6 +5610,7 @@ checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", + "futures-io", "futures-sink", "pin-project-lite", "tokio", @@ -5578,14 +5627,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.14" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" +checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.14", + "toml_edit 0.22.13", ] [[package]] @@ -5610,15 +5659,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.14" +version = "0.22.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" +checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c" dependencies = [ "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.13", + "winnow 0.6.8", ] [[package]] @@ -5782,9 +5831,9 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" [[package]] name = "unicode-xid" @@ -5810,18 +5859,18 @@ dependencies = [ "once_cell", "rustls 0.22.4", "rustls-pki-types", - "rustls-webpki 0.102.5", + "rustls-webpki 0.102.4", "serde", "serde_json", "url", - "webpki-roots 0.26.3", + "webpki-roots 0.26.1", ] [[package]] name = "url" -version = "2.5.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -5830,9 +5879,9 @@ dependencies = [ [[package]] name = "utf8parse" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "valuable" @@ -6078,9 +6127,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "0.26.3" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" dependencies = [ "rustls-pki-types", ] @@ -6135,7 +6184,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.6", + "windows-targets 0.52.5", ] [[package]] @@ -6153,7 +6202,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets 0.52.5", ] [[package]] @@ -6173,18 +6222,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.6" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "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.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "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]] @@ -6195,9 +6244,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.6" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -6207,9 +6256,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.6" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -6219,15 +6268,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.6" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" [[package]] name = "windows_i686_gnullvm" -version = "0.52.6" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -6237,9 +6286,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.6" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -6249,9 +6298,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.6" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -6261,9 +6310,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.6" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -6273,9 +6322,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.6" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" @@ -6288,9 +6337,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.13" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" dependencies = [ "memchr", ] diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 6c694bce8..b3782ec02 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -47,6 +47,7 @@ Anything after the `--` double dash (the "slop") is parsed as arguments to the c * `events` — Watch the network for contract events * `keys` — Create and manage identities including keys and addresses * `network` — Start and configure networks +* `snapshot` — Download a snapshot of a ledger from an archive * `tx` — Sign, Simulate, and Send transactions * `xdr` — Decode and encode XDR * `completion` — Print shell completion code for the specified shell @@ -998,6 +999,53 @@ Stop a network container started with `network container start` +## `stellar snapshot` + +Download a snapshot of a ledger from an archive + +**Usage:** `stellar snapshot ` + +###### **Subcommands:** + +* `create` — Create a ledger snapshot using a history archive + + + +## `stellar snapshot create` + +Create a ledger snapshot using a history archive. + +Filters (address, wasm-hash) specify what ledger entries to include. + +Account addresses include the account, and trust lines. + +Contract addresses include the related wasm, contract data. + +If a contract is a Stellar asset contract, it includes the asset issuer's account and trust lines, but does not include all the trust lines of other accounts holding the asset. To include them specify the addresses of relevant accounts. + +**Usage:** `stellar snapshot create [OPTIONS] --output ` + +###### **Options:** + +* `--ledger ` — The ledger sequence number to snapshot. Defaults to latest history archived ledger +* `--address
` — Account or contract address to include in the snapshot +* `--wasm-hash ` — WASM hashes to include in the snapshot +* `--output ` — Format of the out file + + Possible values: `json` + +* `--out ` — Out path that the snapshot is written to + + Default value: `snapshot.json` +* `--global` — Use global config +* `--config-dir ` — Location of config directory, default is "." +* `--rpc-url ` — RPC server endpoint +* `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server +* `--network ` — Name of network to use from config +* `--archive-url ` — Archive URL + + + ## `stellar tx` Sign, Simulate, and Send transactions diff --git a/cmd/crates/soroban-test/tests/it/integration.rs b/cmd/crates/soroban-test/tests/it/integration.rs index b25d47313..8e2bb89a4 100644 --- a/cmd/crates/soroban-test/tests/it/integration.rs +++ b/cmd/crates/soroban-test/tests/it/integration.rs @@ -3,6 +3,7 @@ mod custom_types; mod dotenv; mod fund; mod hello_world; +mod snapshot; mod tx; mod util; mod wrap; diff --git a/cmd/crates/soroban-test/tests/it/integration/snapshot.rs b/cmd/crates/soroban-test/tests/it/integration/snapshot.rs new file mode 100644 index 000000000..ceb52985f --- /dev/null +++ b/cmd/crates/soroban-test/tests/it/integration/snapshot.rs @@ -0,0 +1,83 @@ +use assert_fs::prelude::*; +use predicates::prelude::*; +use soroban_test::{AssertExt, TestEnv}; + +#[test] +#[allow(clippy::too_many_lines)] +fn snapshot() { + let sandbox = &TestEnv::new(); + // Create a couple accounts and a couple contracts, which we'll filter on to + // make sure we only get the account and contract requested. + sandbox + .new_assert_cmd("keys") + .arg("generate") + .arg("a") + .assert() + .success(); + let account_a = sandbox + .new_assert_cmd("keys") + .arg("address") + .arg("a") + .assert() + .success() + .stdout_as_str(); + sandbox + .new_assert_cmd("keys") + .arg("generate") + .arg("b") + .assert() + .success(); + let account_b = sandbox + .new_assert_cmd("keys") + .arg("address") + .arg("b") + .assert() + .success() + .stdout_as_str(); + let contract_a = sandbox + .new_assert_cmd("contract") + .arg("asset") + .arg("deploy") + .arg(format!("--asset=A1:{account_a}")) + .assert() + .success() + .stdout_as_str(); + let contract_b = sandbox + .new_assert_cmd("contract") + .arg("asset") + .arg("deploy") + .arg(format!("--asset=A2:{account_a}")) + .assert() + .success() + .stdout_as_str(); + // Wait 8 ledgers for a checkpoint by submitting one tx per ledger, in this + // case a funding transaction. + for i in 1..=8 { + sandbox + .new_assert_cmd("keys") + .arg("generate") + .arg(format!("k{i}")) + .assert() + .success(); + } + // Create the snapshot. + sandbox + .new_assert_cmd("snapshot") + .arg("create") + .arg("--output=json") + .arg("--address") + .arg(&account_a) + .arg("--address") + .arg(&contract_b) + .assert() + .success(); + // Assert that the snapshot includes account a and contract b, but not + // account b and contract a. + sandbox + .dir() + .child("snapshot.json") + .assert(predicates::str::contains(&account_a)) + .assert(predicates::str::contains(&account_b).not()) + .assert(predicates::str::contains(&contract_b)) + .assert(predicates::str::contains(&contract_a).not()); +} diff --git a/cmd/crates/soroban-test/tests/it/integration/wrap.rs b/cmd/crates/soroban-test/tests/it/integration/wrap.rs index aa356ce99..c43f5b007 100644 --- a/cmd/crates/soroban-test/tests/it/integration/wrap.rs +++ b/cmd/crates/soroban-test/tests/it/integration/wrap.rs @@ -24,7 +24,7 @@ async fn burn() { .success(); // wrap_cmd(&asset).run().await.unwrap(); let asset = soroban_cli::utils::parsing::parse_asset(&asset).unwrap(); - let hash = contract_id_hash_from_asset(&asset, &network_passphrase).unwrap(); + let hash = contract_id_hash_from_asset(&asset, &network_passphrase); let id = stellar_strkey::Contract(hash.0).to_string(); println!("{id}, {address}"); sandbox diff --git a/cmd/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml index a1c5712dc..ca6d01a22 100644 --- a/cmd/soroban-cli/Cargo.toml +++ b/cmd/soroban-cli/Cargo.toml @@ -66,6 +66,7 @@ serde-aux = { workspace = true } hex = { workspace = true } num-bigint = "0.4" tokio = { version = "1", features = ["full"] } +tokio-util = { version = "0.7.11", features = ["io", "io-util", "compat"] } termcolor = { workspace = true } termcolor_output = { workspace = true } rand = "0.8.5" @@ -107,14 +108,20 @@ gix = { version = "0.58.0", default-features = false, features = [ "worktree-mutation", ] } ureq = { version = "2.9.1", features = ["json"] } +async-compression = { version = "0.4.12", features = [ "tokio", "gzip" ] } tempfile = "3.8.1" toml_edit = "0.21.0" rust-embed = { version = "8.2.0", features = ["debug-embed"] } bollard = { workspace=true } futures-util = "0.3.30" +futures = "0.3.30" home = "0.5.9" +flate2 = "1.0.30" +bytesize = "1.3.0" +humantime = "2.1.0" phf = { version = "0.11.2", features = ["macros"] } + # For hyper-tls [target.'cfg(unix)'.dependencies] openssl = { version = "=0.10.55", features = ["vendored"] } diff --git a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs index 98276a86d..51baa55e9 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs @@ -106,7 +106,7 @@ impl NetworkRunnable for Cmd { let account_details = client.get_account(&public_strkey).await?; let sequence: i64 = account_details.seq_num.into(); let network_passphrase = &network.network_passphrase; - let contract_id = contract_id_hash_from_asset(&asset, network_passphrase)?; + let contract_id = contract_id_hash_from_asset(&asset, network_passphrase); let tx = build_wrap_token_tx( &asset, &contract_id, diff --git a/cmd/soroban-cli/src/commands/contract/id/asset.rs b/cmd/soroban-cli/src/commands/contract/id/asset.rs index 31b55f8d1..e57385859 100644 --- a/cmd/soroban-cli/src/commands/contract/id/asset.rs +++ b/cmd/soroban-cli/src/commands/contract/id/asset.rs @@ -33,7 +33,7 @@ impl Cmd { pub fn contract_address(&self) -> Result { let asset = parse_asset(&self.asset)?; let network = self.config.get_network()?; - let contract_id = contract_id_hash_from_asset(&asset, &network.network_passphrase)?; + let contract_id = contract_id_hash_from_asset(&asset, &network.network_passphrase); Ok(stellar_strkey::Contract(contract_id.0)) } } diff --git a/cmd/soroban-cli/src/commands/mod.rs b/cmd/soroban-cli/src/commands/mod.rs index 081439254..bab7ff391 100644 --- a/cmd/soroban-cli/src/commands/mod.rs +++ b/cmd/soroban-cli/src/commands/mod.rs @@ -13,6 +13,7 @@ pub mod global; pub mod keys; pub mod network; pub mod plugin; +pub mod snapshot; pub mod tx; pub mod version; @@ -110,6 +111,7 @@ impl Root { Cmd::Events(events) => events.run().await?, Cmd::Xdr(xdr) => xdr.run()?, Cmd::Network(network) => network.run().await?, + Cmd::Snapshot(snapshot) => snapshot.run().await?, Cmd::Version(version) => version.run(), Cmd::Keys(id) => id.run().await?, Cmd::Tx(tx) => tx.run(&self.global_args).await?, @@ -143,6 +145,10 @@ pub enum Cmd { #[command(subcommand)] Network(network::Cmd), + /// Download a snapshot of a ledger from an archive. + #[command(subcommand)] + Snapshot(snapshot::Cmd), + /// Sign, Simulate, and Send transactions #[command(subcommand)] Tx(tx::Cmd), @@ -178,6 +184,8 @@ pub enum Error { #[error(transparent)] Network(#[from] network::Error), #[error(transparent)] + Snapshot(#[from] snapshot::Error), + #[error(transparent)] Tx(#[from] tx::Error), #[error(transparent)] Cache(#[from] cache::Error), diff --git a/cmd/soroban-cli/src/commands/snapshot/create.rs b/cmd/soroban-cli/src/commands/snapshot/create.rs new file mode 100644 index 000000000..ea17785ae --- /dev/null +++ b/cmd/soroban-cli/src/commands/snapshot/create.rs @@ -0,0 +1,568 @@ +use async_compression::tokio::bufread::GzipDecoder; +use bytesize::ByteSize; +use clap::{arg, Parser, ValueEnum}; +use futures::{StreamExt, TryStreamExt}; +use http::Uri; +use humantime::format_duration; +use itertools::{Either, Itertools}; +use sha2::{Digest, Sha256}; +use soroban_ledger_snapshot::LedgerSnapshot; +use std::{ + collections::HashSet, + fs::{self}, + io::{self}, + path::PathBuf, + str::FromStr, + time::{Duration, Instant}, +}; +use stellar_xdr::curr::{ + self as xdr, AccountId, Asset, BucketEntry, ConfigSettingEntry, ConfigSettingId, + ContractExecutable, Frame, Hash, LedgerEntry, LedgerEntryData, LedgerKey, LedgerKeyAccount, + LedgerKeyClaimableBalance, LedgerKeyConfigSetting, LedgerKeyContractCode, + LedgerKeyContractData, LedgerKeyData, LedgerKeyLiquidityPool, LedgerKeyOffer, + LedgerKeyTrustLine, LedgerKeyTtl, Limited, Limits, ReadXdr, ScAddress, ScContractInstance, + ScVal, +}; +use tokio::fs::OpenOptions; + +use crate::{ + commands::{config::data, HEADING_RPC}, + config::{self, locator, network::passphrase}, + utils::{get_name_from_stellar_asset_contract_storage, parsing::parse_asset}, +}; + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)] +pub enum Output { + Json, +} + +impl Default for Output { + fn default() -> Self { + Self::Json + } +} + +fn default_out_path() -> PathBuf { + PathBuf::new().join("snapshot.json") +} + +/// Create a ledger snapshot using a history archive. +/// +/// Filters (address, wasm-hash) specify what ledger entries to include. +/// +/// Account addresses include the account, and trust lines. +/// +/// Contract addresses include the related wasm, contract data. +/// +/// If a contract is a Stellar asset contract, it includes the asset issuer's +/// account and trust lines, but does not include all the trust lines of other +/// accounts holding the asset. To include them specify the addresses of +/// relevant accounts. +#[derive(Parser, Debug, Clone)] +#[group(skip)] +#[command(arg_required_else_help = true)] +pub struct Cmd { + /// The ledger sequence number to snapshot. Defaults to latest history archived ledger. + #[arg(long)] + ledger: Option, + /// Account or contract address to include in the snapshot. + #[arg(long = "address", help_heading = "Filter Options")] + address: Vec, + /// WASM hashes to include in the snapshot. + #[arg(long = "wasm-hash", help_heading = "Filter Options")] + wasm_hashes: Vec, + /// Format of the out file. + #[arg(long)] + output: Output, + /// Out path that the snapshot is written to. + #[arg(long, default_value=default_out_path().into_os_string())] + out: PathBuf, + #[command(flatten)] + locator: locator::Args, + #[command(flatten)] + network: config::network::Args, + /// Archive URL + #[arg(long, help_heading = HEADING_RPC, env = "STELLAR_ARCHIVE_URL")] + archive_url: Option, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("wasm hash invalid: {0}")] + WasmHashInvalid(String), + #[error("downloading history: {0}")] + DownloadingHistory(hyper::Error), + #[error("downloading history: got status code {0}")] + DownloadingHistoryGotStatusCode(hyper::StatusCode), + #[error("json decoding history: {0}")] + JsonDecodingHistory(serde_json::Error), + #[error("opening cached bucket to read: {0}")] + ReadOpeningCachedBucket(io::Error), + #[error("parsing bucket url: {0}")] + ParsingBucketUrl(http::uri::InvalidUri), + #[error("getting bucket: {0}")] + GettingBucket(hyper::Error), + #[error("getting bucket: got status code {0}")] + GettingBucketGotStatusCode(hyper::StatusCode), + #[error("opening cached bucket to write: {0}")] + WriteOpeningCachedBucket(io::Error), + #[error("streaming bucket: {0}")] + StreamingBucket(io::Error), + #[error("read XDR frame bucket entry: {0}")] + ReadXdrFrameBucketEntry(xdr::Error), + #[error("renaming temporary downloaded file to final destination: {0}")] + RenameDownloadFile(io::Error), + #[error("getting bucket directory: {0}")] + GetBucketDir(data::Error), + #[error("reading history http stream: {0}")] + ReadHistoryHttpStream(hyper::Error), + #[error("writing ledger snapshot: {0}")] + WriteLedgerSnapshot(soroban_ledger_snapshot::Error), + #[error(transparent)] + Join(#[from] tokio::task::JoinError), + #[error(transparent)] + Network(#[from] config::network::Error), + #[error(transparent)] + Locator(#[from] locator::Error), + #[error(transparent)] + Config(#[from] config::Error), + #[error("archive url not configured")] + ArchiveUrlNotConfigured, + #[error("parsing asset name: {0}")] + ParseAssetName(String), +} + +/// Checkpoint frequency is usually 64 ledgers, but in local test nets it'll +/// often by 8. There's no way to simply detect what frequency to expect ledgers +/// at, so it is hardcoded at 64, and this value is used only to help the user +/// select good ledger numbers when they select one that doesn't exist. +const CHECKPOINT_FREQUENCY: u32 = 64; + +impl Cmd { + #[allow(clippy::too_many_lines)] + pub async fn run(&self) -> Result<(), Error> { + let start = Instant::now(); + + let archive_url = self.archive_url()?; + let history = get_history(&archive_url, self.ledger).await?; + + let ledger = history.current_ledger; + let network_passphrase = &history.network_passphrase; + let network_id = Sha256::digest(network_passphrase); + println!("ℹ️ Ledger: {ledger}"); + println!("ℹ️ Network Passphrase: {network_passphrase}"); + println!("ℹ️ Network ID: {}", hex::encode(network_id)); + + // Prepare a flat list of buckets to read. They'll be ordered by their + // level so that they can iterated higher level to lower level. + let buckets = history + .current_buckets + .iter() + .flat_map(|h| [h.curr.clone(), h.snap.clone()]) + .filter(|b| b != "0000000000000000000000000000000000000000000000000000000000000000") + .collect::>(); + + // Pre-cache the buckets. + for (i, bucket) in buckets.iter().enumerate() { + cache_bucket(&archive_url, i, bucket).await?; + } + + // The snapshot is what will be written to file at the end. Fields will + // be updated while parsing the history archive. + let mut snapshot = LedgerSnapshot { + // TODO: Update more of the fields. + protocol_version: 0, + sequence_number: ledger, + timestamp: 0, + network_id: network_id.into(), + base_reserve: 1, + min_persistent_entry_ttl: 0, + min_temp_entry_ttl: 0, + max_entry_ttl: 0, + ledger_entries: Vec::new(), + }; + + // Track ledger keys seen, so that we can ignore old versions of + // entries. Entries can appear in both higher level and lower level + // buckets, and to get the latest version of the entry the version in + // the higher level bucket should be used. + let mut seen = HashSet::new(); + + #[allow(clippy::items_after_statements)] + #[derive(Default)] + struct SearchInputs { + account_ids: HashSet, + contract_ids: HashSet, + wasm_hashes: HashSet, + } + impl SearchInputs { + pub fn is_empty(&self) -> bool { + self.account_ids.is_empty() + && self.contract_ids.is_empty() + && self.wasm_hashes.is_empty() + } + } + + // Search the buckets using the user inputs as the starting inputs. + let (account_ids, contract_ids): (HashSet, HashSet) = + self.address.iter().cloned().partition_map(|a| match a { + ScAddress::Account(account_id) => Either::Left(account_id), + ScAddress::Contract(_) => Either::Right(a), + }); + let mut current = SearchInputs { + account_ids, + contract_ids, + wasm_hashes: self.wasm_hashes.iter().cloned().collect(), + }; + let mut next = SearchInputs::default(); + loop { + if current.is_empty() { + break; + } + println!( + "ℹ️ Searching for {} accounts, {} contracts, {} wasms", + current.account_ids.len(), + current.contract_ids.len(), + current.wasm_hashes.len(), + ); + for (i, bucket) in buckets.iter().enumerate() { + // Defined where the bucket will be read from, either from cache on + // disk, or streamed from the archive. + let cache_path = cache_bucket(&archive_url, i, bucket).await?; + let file = std::fs::OpenOptions::new() + .read(true) + .open(&cache_path) + .map_err(Error::ReadOpeningCachedBucket)?; + print!("🔎 Searching bucket {i} {bucket}"); + if let Ok(metadata) = file.metadata() { + print!(" ({})", ByteSize(metadata.len())); + } + println!(); + + // Stream the bucket entries from the bucket, identifying + // entries that match the filters, and including only the + // entries that match in the snapshot. + let limited = &mut Limited::new(file, Limits::none()); + let entries = Frame::::read_xdr_iter(limited); + let mut count_saved = 0; + for entry in entries { + let Frame(entry) = entry.map_err(Error::ReadXdrFrameBucketEntry)?; + let (key, val) = match entry { + BucketEntry::Liveentry(l) | BucketEntry::Initentry(l) => { + let k = data_into_key(&l); + (k, Some(l)) + } + BucketEntry::Deadentry(k) => (k, None), + BucketEntry::Metaentry(m) => { + snapshot.protocol_version = m.ledger_version; + continue; + } + }; + if seen.contains(&key) { + continue; + } + let keep = match &key { + LedgerKey::Account(k) => current.account_ids.contains(&k.account_id), + LedgerKey::Trustline(k) => current.account_ids.contains(&k.account_id), + LedgerKey::ContractData(k) => current.contract_ids.contains(&k.contract), + LedgerKey::ContractCode(e) => current.wasm_hashes.contains(&e.hash), + _ => false, + }; + if !keep { + continue; + } + seen.insert(key.clone()); + let Some(val) = val else { continue }; + match &val.data { + LedgerEntryData::ContractData(e) => { + // If a contract instance references contract + // executable stored in another ledger entry, add + // that ledger entry to the filter so that Wasm for + // any filtered contract is collected too in the + // second pass. + if keep && e.key == ScVal::LedgerKeyContractInstance { + match &e.val { + ScVal::ContractInstance(ScContractInstance { + executable: ContractExecutable::Wasm(hash), + .. + }) => { + if !current.wasm_hashes.contains(hash) { + next.wasm_hashes.insert(hash.clone()); + println!( + "ℹ️ Adding wasm {} to search", + hex::encode(hash) + ); + } + } + ScVal::ContractInstance(ScContractInstance { + executable: ContractExecutable::StellarAsset, + storage: Some(storage), + }) => { + if let Some(name) = + get_name_from_stellar_asset_contract_storage(storage) + { + let asset = parse_asset(&name) + .map_err(|_| Error::ParseAssetName(name))?; + if let Some(issuer) = match &asset { + Asset::Native => None, + Asset::CreditAlphanum4(a4) => { + Some(a4.issuer.clone()) + } + Asset::CreditAlphanum12(a12) => { + Some(a12.issuer.clone()) + } + } { + println!( + "ℹ️ Adding asset issuer {issuer} to search" + ); + next.account_ids.insert(issuer); + } + } + } + _ => {} + } + } + keep + } + _ => false, + }; + snapshot + .ledger_entries + .push((Box::new(key), (Box::new(val), Some(u32::MAX)))); + count_saved += 1; + } + if count_saved > 0 { + println!("ℹ️ Found {count_saved} entries"); + } + } + current = next; + next = SearchInputs::default(); + } + + // Write the snapshot to file. + snapshot + .write_file(&self.out) + .map_err(Error::WriteLedgerSnapshot)?; + println!( + "💾 Saved {} entries to {:?}", + snapshot.ledger_entries.len(), + self.out + ); + + let duration = Duration::from_secs(start.elapsed().as_secs()); + println!("✅ Completed in {}", format_duration(duration)); + + Ok(()) + } + + fn archive_url(&self) -> Result { + // Return the configured archive URL, or if one is not configured, guess + // at an appropriate archive URL given the network passphrase. + self.archive_url + .clone() + .or_else(|| { + self.network.get(&self.locator).ok().and_then(|network| { + match network.network_passphrase.as_str() { + passphrase::MAINNET => { + Some("https://history.stellar.org/prd/core-live/core_live_001") + } + passphrase::TESTNET => { + Some("https://history.stellar.org/prd/core-testnet/core_testnet_001") + } + passphrase::FUTURENET => Some("https://history-futurenet.stellar.org"), + passphrase::LOCAL => Some("http://localhost:8000/archive"), + _ => None, + } + .map(|s| Uri::from_str(s).expect("archive url valid")) + }) + }) + .ok_or(Error::ArchiveUrlNotConfigured) + } +} + +async fn get_history(archive_url: &Uri, ledger: Option) -> Result { + let archive_url = archive_url.to_string(); + let archive_url = archive_url.strip_suffix('/').unwrap_or(&archive_url); + let history_url = if let Some(ledger) = ledger { + let ledger_hex = format!("{ledger:08x}"); + let ledger_hex_0 = &ledger_hex[0..=1]; + let ledger_hex_1 = &ledger_hex[2..=3]; + let ledger_hex_2 = &ledger_hex[4..=5]; + format!("{archive_url}/history/{ledger_hex_0}/{ledger_hex_1}/{ledger_hex_2}/history-{ledger_hex}.json") + } else { + format!("{archive_url}/.well-known/stellar-history.json") + }; + let history_url = Uri::from_str(&history_url).unwrap(); + + println!("🌎 Downloading history {history_url}"); + let https = hyper_tls::HttpsConnector::new(); + let response = hyper::Client::builder() + .build::<_, hyper::Body>(https) + .get(history_url) + .await + .map_err(Error::DownloadingHistory)?; + if !response.status().is_success() { + // Check ledger is a checkpoint ledger and available in archives. + if let Some(ledger) = ledger { + let ledger_offset = (ledger + 1) % CHECKPOINT_FREQUENCY; + if ledger_offset != 0 { + println!( + "ℹ️ Ledger {ledger} may not be a checkpoint ledger, try {} or {}", + ledger - ledger_offset, + ledger + (CHECKPOINT_FREQUENCY - ledger_offset), + ); + } + } + return Err(Error::DownloadingHistoryGotStatusCode(response.status())); + } + let body = hyper::body::to_bytes(response.into_body()) + .await + .map_err(Error::ReadHistoryHttpStream)?; + serde_json::from_slice::(&body).map_err(Error::JsonDecodingHistory) +} + +async fn cache_bucket( + archive_url: &Uri, + bucket_index: usize, + bucket: &str, +) -> Result { + let bucket_dir = data::bucket_dir().map_err(Error::GetBucketDir)?; + let cache_path = bucket_dir.join(format!("bucket-{bucket}.xdr")); + if !cache_path.exists() { + let bucket_0 = &bucket[0..=1]; + let bucket_1 = &bucket[2..=3]; + let bucket_2 = &bucket[4..=5]; + let bucket_url = + format!("{archive_url}/bucket/{bucket_0}/{bucket_1}/{bucket_2}/bucket-{bucket}.xdr.gz"); + print!("🪣 Downloading bucket {bucket_index} {bucket}"); + let bucket_url = Uri::from_str(&bucket_url).map_err(Error::ParsingBucketUrl)?; + let https = hyper_tls::HttpsConnector::new(); + let response = hyper::Client::builder() + .build::<_, hyper::Body>(https) + .get(bucket_url) + .await + .map_err(Error::GettingBucket)?; + if !response.status().is_success() { + println!(); + return Err(Error::GettingBucketGotStatusCode(response.status())); + } + if let Some(val) = response.headers().get("Content-Length") { + if let Ok(str) = val.to_str() { + if let Ok(len) = str.parse::() { + print!(" ({})", ByteSize(len)); + } + } + } + println!(); + let read = response + .into_body() + .map(|result| result.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))) + .into_async_read(); + let read = tokio_util::compat::FuturesAsyncReadCompatExt::compat(read); + let mut read = GzipDecoder::new(read); + let dl_path = cache_path.with_extension("dl"); + let mut file = OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(&dl_path) + .await + .map_err(Error::WriteOpeningCachedBucket)?; + tokio::io::copy(&mut read, &mut file) + .await + .map_err(Error::StreamingBucket)?; + fs::rename(&dl_path, &cache_path).map_err(Error::RenameDownloadFile)?; + } + Ok(cache_path) +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +struct History { + current_ledger: u32, + current_buckets: Vec, + network_passphrase: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +struct HistoryBucket { + curr: String, + snap: String, +} + +fn data_into_key(d: &LedgerEntry) -> LedgerKey { + // TODO: Move this function into stellar-xdr. + match &d.data { + LedgerEntryData::Account(e) => LedgerKey::Account(LedgerKeyAccount { + account_id: e.account_id.clone(), + }), + LedgerEntryData::Trustline(e) => LedgerKey::Trustline(LedgerKeyTrustLine { + account_id: e.account_id.clone(), + asset: e.asset.clone(), + }), + LedgerEntryData::Offer(e) => LedgerKey::Offer(LedgerKeyOffer { + seller_id: e.seller_id.clone(), + offer_id: e.offer_id, + }), + LedgerEntryData::Data(e) => LedgerKey::Data(LedgerKeyData { + account_id: e.account_id.clone(), + data_name: e.data_name.clone(), + }), + LedgerEntryData::ClaimableBalance(e) => { + LedgerKey::ClaimableBalance(LedgerKeyClaimableBalance { + balance_id: e.balance_id.clone(), + }) + } + LedgerEntryData::LiquidityPool(e) => LedgerKey::LiquidityPool(LedgerKeyLiquidityPool { + liquidity_pool_id: e.liquidity_pool_id.clone(), + }), + LedgerEntryData::ContractData(e) => LedgerKey::ContractData(LedgerKeyContractData { + contract: e.contract.clone(), + key: e.key.clone(), + durability: e.durability, + }), + LedgerEntryData::ContractCode(e) => LedgerKey::ContractCode(LedgerKeyContractCode { + hash: e.hash.clone(), + }), + LedgerEntryData::ConfigSetting(e) => LedgerKey::ConfigSetting(LedgerKeyConfigSetting { + config_setting_id: match e { + ConfigSettingEntry::ContractMaxSizeBytes(_) => { + ConfigSettingId::ContractMaxSizeBytes + } + ConfigSettingEntry::ContractComputeV0(_) => ConfigSettingId::ContractComputeV0, + ConfigSettingEntry::ContractLedgerCostV0(_) => { + ConfigSettingId::ContractLedgerCostV0 + } + ConfigSettingEntry::ContractHistoricalDataV0(_) => { + ConfigSettingId::ContractHistoricalDataV0 + } + ConfigSettingEntry::ContractEventsV0(_) => ConfigSettingId::ContractEventsV0, + ConfigSettingEntry::ContractBandwidthV0(_) => ConfigSettingId::ContractBandwidthV0, + ConfigSettingEntry::ContractCostParamsCpuInstructions(_) => { + ConfigSettingId::ContractCostParamsCpuInstructions + } + ConfigSettingEntry::ContractCostParamsMemoryBytes(_) => { + ConfigSettingId::ContractCostParamsMemoryBytes + } + ConfigSettingEntry::ContractDataKeySizeBytes(_) => { + ConfigSettingId::ContractDataKeySizeBytes + } + ConfigSettingEntry::ContractDataEntrySizeBytes(_) => { + ConfigSettingId::ContractDataEntrySizeBytes + } + ConfigSettingEntry::StateArchival(_) => ConfigSettingId::StateArchival, + ConfigSettingEntry::ContractExecutionLanes(_) => { + ConfigSettingId::ContractExecutionLanes + } + ConfigSettingEntry::BucketlistSizeWindow(_) => { + ConfigSettingId::BucketlistSizeWindow + } + ConfigSettingEntry::EvictionIterator(_) => ConfigSettingId::EvictionIterator, + }, + }), + LedgerEntryData::Ttl(e) => LedgerKey::Ttl(LedgerKeyTtl { + key_hash: e.key_hash.clone(), + }), + } +} diff --git a/cmd/soroban-cli/src/commands/snapshot/mod.rs b/cmd/soroban-cli/src/commands/snapshot/mod.rs new file mode 100644 index 000000000..344b320d2 --- /dev/null +++ b/cmd/soroban-cli/src/commands/snapshot/mod.rs @@ -0,0 +1,24 @@ +use clap::Parser; + +pub mod create; + +/// Create and operate on ledger snapshots. +#[derive(Debug, Parser)] +pub enum Cmd { + Create(create::Cmd), +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Create(#[from] create::Error), +} + +impl Cmd { + pub async fn run(&self) -> Result<(), Error> { + match self { + Cmd::Create(cmd) => cmd.run().await?, + }; + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/config/data.rs b/cmd/soroban-cli/src/config/data.rs index 409ef2227..23dedc619 100644 --- a/cmd/soroban-cli/src/config/data.rs +++ b/cmd/soroban-cli/src/config/data.rs @@ -50,6 +50,12 @@ pub fn spec_dir() -> Result { Ok(dir) } +pub fn bucket_dir() -> Result { + let dir = data_local_dir()?.join("bucket"); + std::fs::create_dir_all(&dir)?; + Ok(dir) +} + pub fn write(action: Action, rpc_url: &Uri) -> Result { let data = Data { action, diff --git a/cmd/soroban-cli/src/utils.rs b/cmd/soroban-cli/src/utils.rs index 88daa9a80..24b76b24f 100644 --- a/cmd/soroban-cli/src/utils.rs +++ b/cmd/soroban-cli/src/utils.rs @@ -4,9 +4,9 @@ use stellar_strkey::ed25519::PrivateKey; use soroban_env_host::xdr::{ Asset, ContractIdPreimage, DecoratedSignature, Error as XdrError, Hash, HashIdPreimage, - HashIdPreimageContractId, Limits, Signature, SignatureHint, Transaction, TransactionEnvelope, - TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, - TransactionV1Envelope, WriteXdr, + HashIdPreimageContractId, Limits, ScMap, ScMapEntry, ScVal, Signature, SignatureHint, + Transaction, TransactionEnvelope, TransactionSignaturePayload, + TransactionSignaturePayloadTaggedTransaction, TransactionV1Envelope, WriteXdr, }; pub use soroban_spec_tools::contract as contract_spec; @@ -116,17 +116,40 @@ pub fn is_hex_string(s: &str) -> bool { s.chars().all(|s| s.is_ascii_hexdigit()) } -pub fn contract_id_hash_from_asset( - asset: &Asset, - network_passphrase: &str, -) -> Result { +pub fn contract_id_hash_from_asset(asset: &Asset, network_passphrase: &str) -> Hash { let network_id = Hash(Sha256::digest(network_passphrase.as_bytes()).into()); let preimage = HashIdPreimage::ContractId(HashIdPreimageContractId { network_id, contract_id_preimage: ContractIdPreimage::Asset(asset.clone()), }); - let preimage_xdr = preimage.to_xdr(Limits::none())?; - Ok(Hash(Sha256::digest(preimage_xdr).into())) + let preimage_xdr = preimage + .to_xdr(Limits::none()) + .expect("HashIdPreimage should not fail encoding to xdr"); + Hash(Sha256::digest(preimage_xdr).into()) +} + +pub fn get_name_from_stellar_asset_contract_storage(storage: &ScMap) -> Option { + if let Some(ScMapEntry { + val: ScVal::Map(Some(map)), + .. + }) = storage + .iter() + .find(|ScMapEntry { key, .. }| key == &ScVal::Symbol("METADATA".try_into().unwrap())) + { + if let Some(ScMapEntry { + val: ScVal::String(name), + .. + }) = map + .iter() + .find(|ScMapEntry { key, .. }| key == &ScVal::Symbol("name".try_into().unwrap())) + { + Some(name.to_string()) + } else { + None + } + } else { + None + } } pub mod rpc {