diff --git a/Cargo.lock b/Cargo.lock index 80b103775cdbb9..8b310573686092 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -234,11 +234,14 @@ dependencies = [ "aptos-genesis", "aptos-github-client", "aptos-global-constants", + "aptos-indexer-grpc-server-framework", + "aptos-indexer-grpc-utils", "aptos-keygen", "aptos-ledger", "aptos-logger", "aptos-network-checker", "aptos-node", + "aptos-protos", "aptos-rest-client", "aptos-sdk", "aptos-storage-interface", @@ -254,6 +257,7 @@ dependencies = [ "clap 4.3.21", "clap_complete", "codespan-reporting", + "dashmap", "dirs", "futures", "hex", @@ -273,6 +277,7 @@ dependencies = [ "move-unit-test", "move-vm-runtime", "once_cell", + "poem", "rand 0.7.3", "regex", "reqwest", @@ -286,6 +291,8 @@ dependencies = [ "thiserror", "tokio", "toml 0.7.4", + "tonic 0.10.0", + "tracing-subscriber", "walkdir", ] @@ -2004,6 +2011,7 @@ dependencies = [ "tokio", "tokio-stream", "tonic 0.10.0", + "tonic-reflection", ] [[package]] diff --git a/api/fuzzing/README.md b/api/fuzzing/README.md index b82fe0e87b5807..5bc3d4d6399ab3 100644 --- a/api/fuzzing/README.md +++ b/api/fuzzing/README.md @@ -5,7 +5,7 @@ This project uses [RESTler](https://github.com/microsoft/restler-fuzzer) (from M ## Quick start To get started with fuzzing, first make sure you have an API running locally: ``` -cargo run -p aptos -- node run-local-testnet --with-faucet --faucet-port 8081 --force-restart --assume-yes +cargo run -p aptos -- node run-local-testnet --faucet-port 8081 --force-restart --assume-yes ``` And ensure that you have Docker running on your system. Then, run the following commands from the root of `aptos-core`: diff --git a/crates/aptos-faucet/core/src/server/run.rs b/crates/aptos-faucet/core/src/server/run.rs index 54df2c8def77db..253c28dd39409f 100644 --- a/crates/aptos-faucet/core/src/server/run.rs +++ b/crates/aptos-faucet/core/src/server/run.rs @@ -48,7 +48,7 @@ pub struct HandlerConfig { #[derive(Clone, Debug, Deserialize, Serialize)] pub struct RunConfig { /// API server config. - server_config: ServerConfig, + pub server_config: ServerConfig, /// Metrics server config. metrics_server_config: MetricsServerConfig, @@ -69,7 +69,6 @@ pub struct RunConfig { impl RunConfig { pub async fn run(self) -> Result<()> { info!("Running with config: {:#?}", self); - println!("Faucet is starting, please wait..."); // Set whether we should use useful errors. // If it's already set, then we'll carry on @@ -212,11 +211,6 @@ impl RunConfig { })); } - println!( - "Faucet is running. Faucet endpoint: http://{}:{}", - self.server_config.listen_address, self.server_config.listen_port - ); - // Wait for all the futures. We expect none of them to ever end. futures::future::select_all(main_futures) .await diff --git a/crates/aptos-faucet/integration-tests/common.py b/crates/aptos-faucet/integration-tests/common.py index a0c2b61f0e2664..98e7b1e8124de8 100644 --- a/crates/aptos-faucet/integration-tests/common.py +++ b/crates/aptos-faucet/integration-tests/common.py @@ -59,5 +59,10 @@ def network_from_str(str: str, tag: Optional[str]) -> Network: return CustomNetwork(tag) -def build_image_name(image_repo_with_project: str, network: Network): - return f"{image_repo_with_project}/tools:{network.tag()}" +def build_image_name(image_repo_with_project: str, tag: str): + # If no repo is specified, leave it that way. Otherwise make sure we have a slash + # between the image repo and the image name. + image_repo_with_project = image_repo_with_project.rstrip("/") + if image_repo_with_project != "": + image_repo_with_project.append("/") + return f"{image_repo_with_project}tools:{tag}" diff --git a/crates/aptos-faucet/integration-tests/local_testnet.py b/crates/aptos-faucet/integration-tests/local_testnet.py index 7076f8f4f54075..adc060049fd25a 100644 --- a/crates/aptos-faucet/integration-tests/local_testnet.py +++ b/crates/aptos-faucet/integration-tests/local_testnet.py @@ -62,6 +62,8 @@ def run_node(network: Network, image_repo_with_project: str, external_test_dir: "run-local-testnet", "--test-dir", internal_mount_path, + "--no-faucet", + "--no-txn-stream", ], ) LOG.info(f"Running local testnet from image: {image_name}") diff --git a/crates/aptos/CHANGELOG.md b/crates/aptos/CHANGELOG.md index 7c55766ace3370..180886c4b2f611 100644 --- a/crates/aptos/CHANGELOG.md +++ b/crates/aptos/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to the Aptos CLI will be captured in this file. This project ## Unreleased +### Updated +- The `--with-faucet` flag has been removed from `aptos node run-local-testnet`, we now run a faucet by default. To disable the faucet use the `--no-faucet` flag. +- When using `aptos node run-local-testnet` we now expose a transaction stream. Learn more about the transaction stream service here: https://aptos.dev/indexer/txn-stream/. Opt out of this with `--no-txn-stream`. + ## [2.1.0] - 2023/08/24 ### Updated - Updated CLI source compilation to use rust toolchain version 1.71.1 (from 1.71.0). diff --git a/crates/aptos/Cargo.toml b/crates/aptos/Cargo.toml index ea8ce78ecec096..a61355f7261298 100644 --- a/crates/aptos/Cargo.toml +++ b/crates/aptos/Cargo.toml @@ -30,11 +30,14 @@ aptos-gas-schedule = { workspace = true } aptos-genesis = { workspace = true } aptos-github-client = { workspace = true } aptos-global-constants = { workspace = true } +aptos-indexer-grpc-server-framework = { workspace = true } +aptos-indexer-grpc-utils = { workspace = true } aptos-keygen = { workspace = true } aptos-ledger = { workspace = true } aptos-logger = { workspace = true } aptos-network-checker = { workspace = true } aptos-node = { workspace = true } +aptos-protos = { workspace = true } aptos-rest-client = { workspace = true } aptos-sdk = { workspace = true } aptos-storage-interface = { workspace = true } @@ -50,6 +53,7 @@ chrono = { workspace = true } clap = { workspace = true, features = ["env", "unstable-styles"] } clap_complete = { workspace = true } codespan-reporting = { workspace = true } +dashmap = { workspace = true } dirs = { workspace = true } futures = { workspace = true } hex = { workspace = true } @@ -68,6 +72,7 @@ move-symbol-pool = { workspace = true } move-unit-test = { workspace = true, features = [ "debugging" ] } move-vm-runtime = { workspace = true, features = [ "testing" ] } once_cell = { workspace = true } +poem = { workspace = true } rand = { workspace = true } regex = { workspace = true } reqwest = { workspace = true } @@ -80,6 +85,8 @@ termcolor = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } toml = { workspace = true } +tonic = { workspace = true } +tracing-subscriber = { workspace = true } walkdir = { workspace = true } [target.'cfg(unix)'.dependencies] diff --git a/crates/aptos/e2e/common.py b/crates/aptos/e2e/common.py index 88fd2c1dcde924..f482fe40834f18 100644 --- a/crates/aptos/e2e/common.py +++ b/crates/aptos/e2e/common.py @@ -38,7 +38,12 @@ class AccountInfo: def build_image_name(image_repo_with_project: str, tag: str): - return f"{image_repo_with_project}/tools:{tag}" + # If no repo is specified, leave it that way. Otherwise make sure we have a slash + # between the image repo and the image name. + image_repo_with_project = image_repo_with_project.rstrip("/") + if image_repo_with_project != "": + image_repo_with_project.append("/") + return f"{image_repo_with_project}tools:{tag}" # Exception to use when a test fails, for the CLI did something unexpected, an diff --git a/crates/aptos/e2e/local_testnet.py b/crates/aptos/e2e/local_testnet.py index 66d89fd43fd5b3..d18f5fe2791818 100644 --- a/crates/aptos/e2e/local_testnet.py +++ b/crates/aptos/e2e/local_testnet.py @@ -63,7 +63,6 @@ def run_node(network: Network, image_repo_with_project: str): "aptos", "node", "run-local-testnet", - "--with-faucet", ], **kwargs, ) diff --git a/crates/aptos/e2e/poetry.lock b/crates/aptos/e2e/poetry.lock index fb6928af541c09..d56783db14590a 100644 --- a/crates/aptos/e2e/poetry.lock +++ b/crates/aptos/e2e/poetry.lock @@ -1,26 +1,27 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. [[package]] name = "anyio" -version = "3.6.2" +version = "3.7.1" description = "High level compatibility layer for multiple asynchronous event loop implementations" category = "main" optional = false -python-versions = ">=3.6.2" +python-versions = ">=3.7" files = [ - {file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"}, - {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, + {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, + {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, ] [package.dependencies] +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" typing-extensions = {version = "*", markers = "python_version < \"3.8\""} [package.extras] -doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"] -trio = ["trio (>=0.16,<0.22)"] +doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] +test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (<0.22)"] [[package]] name = "aptos-sdk" @@ -77,14 +78,14 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "certifi" -version = "2022.12.7" +version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, - {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, ] [[package]] @@ -166,99 +167,99 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "3.1.0" +version = "3.2.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, - {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, - {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, - {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, - {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, - {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, - {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, + {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, + {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, ] [[package]] name = "click" -version = "8.1.3" +version = "8.1.7" description = "Composable command line interface toolkit" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, ] [package.dependencies] @@ -277,6 +278,21 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "exceptiongroup" +version = "1.1.3" +description = "Backport of PEP 654 (exception groups)" +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, + {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, +] + +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "h11" version = "0.14.0" @@ -352,14 +368,14 @@ files = [ [[package]] name = "importlib-metadata" -version = "6.0.0" +version = "6.7.0" description = "Read metadata from Python packages" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "importlib_metadata-6.0.0-py3-none-any.whl", hash = "sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad"}, - {file = "importlib_metadata-6.0.0.tar.gz", hash = "sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d"}, + {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, + {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, ] [package.dependencies] @@ -369,7 +385,7 @@ zipp = ">=0.5" [package.extras] docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] [[package]] name = "isort" @@ -448,34 +464,34 @@ files = [ [[package]] name = "pathspec" -version = "0.11.0" +version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pathspec-0.11.0-py3-none-any.whl", hash = "sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229"}, - {file = "pathspec-0.11.0.tar.gz", hash = "sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc"}, + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, ] [[package]] name = "platformdirs" -version = "3.1.0" +version = "3.10.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.1.0-py3-none-any.whl", hash = "sha256:13b08a53ed71021350c9e300d4ea8668438fb0046ab3937ac9a29913a1a1350a"}, - {file = "platformdirs-3.1.0.tar.gz", hash = "sha256:accc3665857288317f32c7bebb5a8e482ba717b474f3fc1d18ca7f9214be0cef"}, + {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, + {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, ] [package.dependencies] -typing-extensions = {version = ">=4.4", markers = "python_version < \"3.8\""} +typing-extensions = {version = ">=4.7.1", markers = "python_version < \"3.8\""} [package.extras] -docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] [[package]] name = "pycparser" @@ -582,66 +598,84 @@ files = [ [[package]] name = "typed-ast" -version = "1.5.4" +version = "1.5.5" description = "a fork of Python 2 and 3 ast modules with type comment support" category = "main" optional = false python-versions = ">=3.6" files = [ - {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, - {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, - {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, - {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, - {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, - {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, - {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, - {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, - {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, - {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, + {file = "typed_ast-1.5.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bc1efe0ce3ffb74784e06460f01a223ac1f6ab31c6bc0376a21184bf5aabe3b"}, + {file = "typed_ast-1.5.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f7a8c46a8b333f71abd61d7ab9255440d4a588f34a21f126bbfc95f6049e686"}, + {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597fc66b4162f959ee6a96b978c0435bd63791e31e4f410622d19f1686d5e769"}, + {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d41b7a686ce653e06c2609075d397ebd5b969d821b9797d029fccd71fdec8e04"}, + {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5fe83a9a44c4ce67c796a1b466c270c1272e176603d5e06f6afbc101a572859d"}, + {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d5c0c112a74c0e5db2c75882a0adf3133adedcdbfd8cf7c9d6ed77365ab90a1d"}, + {file = "typed_ast-1.5.5-cp310-cp310-win_amd64.whl", hash = "sha256:e1a976ed4cc2d71bb073e1b2a250892a6e968ff02aa14c1f40eba4f365ffec02"}, + {file = "typed_ast-1.5.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c631da9710271cb67b08bd3f3813b7af7f4c69c319b75475436fcab8c3d21bee"}, + {file = "typed_ast-1.5.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b445c2abfecab89a932b20bd8261488d574591173d07827c1eda32c457358b18"}, + {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc95ffaaab2be3b25eb938779e43f513e0e538a84dd14a5d844b8f2932593d88"}, + {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61443214d9b4c660dcf4b5307f15c12cb30bdfe9588ce6158f4a005baeb167b2"}, + {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6eb936d107e4d474940469e8ec5b380c9b329b5f08b78282d46baeebd3692dc9"}, + {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e48bf27022897577d8479eaed64701ecaf0467182448bd95759883300ca818c8"}, + {file = "typed_ast-1.5.5-cp311-cp311-win_amd64.whl", hash = "sha256:83509f9324011c9a39faaef0922c6f720f9623afe3fe220b6d0b15638247206b"}, + {file = "typed_ast-1.5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:44f214394fc1af23ca6d4e9e744804d890045d1643dd7e8229951e0ef39429b5"}, + {file = "typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:118c1ce46ce58fda78503eae14b7664163aa735b620b64b5b725453696f2a35c"}, + {file = "typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4919b808efa61101456e87f2d4c75b228f4e52618621c77f1ddcaae15904fa"}, + {file = "typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:fc2b8c4e1bc5cd96c1a823a885e6b158f8451cf6f5530e1829390b4d27d0807f"}, + {file = "typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:16f7313e0a08c7de57f2998c85e2a69a642e97cb32f87eb65fbfe88381a5e44d"}, + {file = "typed_ast-1.5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:2b946ef8c04f77230489f75b4b5a4a6f24c078be4aed241cfabe9cbf4156e7e5"}, + {file = "typed_ast-1.5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2188bc33d85951ea4ddad55d2b35598b2709d122c11c75cffd529fbc9965508e"}, + {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0635900d16ae133cab3b26c607586131269f88266954eb04ec31535c9a12ef1e"}, + {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57bfc3cf35a0f2fdf0a88a3044aafaec1d2f24d8ae8cd87c4f58d615fb5b6311"}, + {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:fe58ef6a764de7b4b36edfc8592641f56e69b7163bba9f9c8089838ee596bfb2"}, + {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d09d930c2d1d621f717bb217bf1fe2584616febb5138d9b3e8cdd26506c3f6d4"}, + {file = "typed_ast-1.5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:d40c10326893ecab8a80a53039164a224984339b2c32a6baf55ecbd5b1df6431"}, + {file = "typed_ast-1.5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fd946abf3c31fb50eee07451a6aedbfff912fcd13cf357363f5b4e834cc5e71a"}, + {file = "typed_ast-1.5.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ed4a1a42df8a3dfb6b40c3d2de109e935949f2f66b19703eafade03173f8f437"}, + {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:045f9930a1550d9352464e5149710d56a2aed23a2ffe78946478f7b5416f1ede"}, + {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:381eed9c95484ceef5ced626355fdc0765ab51d8553fec08661dce654a935db4"}, + {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bfd39a41c0ef6f31684daff53befddae608f9daf6957140228a08e51f312d7e6"}, + {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8c524eb3024edcc04e288db9541fe1f438f82d281e591c548903d5b77ad1ddd4"}, + {file = "typed_ast-1.5.5-cp38-cp38-win_amd64.whl", hash = "sha256:7f58fabdde8dcbe764cef5e1a7fcb440f2463c1bbbec1cf2a86ca7bc1f95184b"}, + {file = "typed_ast-1.5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:042eb665ff6bf020dd2243307d11ed626306b82812aba21836096d229fdc6a10"}, + {file = "typed_ast-1.5.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:622e4a006472b05cf6ef7f9f2636edc51bda670b7bbffa18d26b255269d3d814"}, + {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1efebbbf4604ad1283e963e8915daa240cb4bf5067053cf2f0baadc4d4fb51b8"}, + {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0aefdd66f1784c58f65b502b6cf8b121544680456d1cebbd300c2c813899274"}, + {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:48074261a842acf825af1968cd912f6f21357316080ebaca5f19abbb11690c8a"}, + {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:429ae404f69dc94b9361bb62291885894b7c6fb4640d561179548c849f8492ba"}, + {file = "typed_ast-1.5.5-cp39-cp39-win_amd64.whl", hash = "sha256:335f22ccb244da2b5c296e6f96b06ee9bed46526db0de38d2f0e5a6597b81155"}, + {file = "typed_ast-1.5.5.tar.gz", hash = "sha256:94282f7a354f36ef5dbce0ef3467ebf6a258e370ab33d5b40c249fa996e590dd"}, ] [[package]] name = "typing-extensions" -version = "4.5.0" +version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, - {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, ] [[package]] name = "urllib3" -version = "1.26.15" +version = "2.0.4" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = ">=3.7" files = [ - {file = "urllib3-1.26.15-py2.py3-none-any.whl", hash = "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"}, - {file = "urllib3-1.26.15.tar.gz", hash = "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305"}, + {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"}, + {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] [[package]] name = "zipp" @@ -661,5 +695,5 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" -python-versions = ">=3.7 <3.10" -content-hash = "14e2791cee9e92d4f03b7c3589d91543f762b83983f49747126aecd3a3cdcc3f" +python-versions = ">=3.7 <=3.11" +content-hash = "cd9347ffd84f9a38bf5eed92950cc9b77d6a2b6b689272eb8ff4cbb3599211fd" diff --git a/crates/aptos/e2e/pyproject.toml b/crates/aptos/e2e/pyproject.toml index 2254fcfc09189f..17401fed009bd0 100644 --- a/crates/aptos/e2e/pyproject.toml +++ b/crates/aptos/e2e/pyproject.toml @@ -6,7 +6,7 @@ authors = ["Aptos Labs "] license = "Apache-2.0" [tool.poetry.dependencies] -python = ">=3.7 <3.10" +python = ">=3.7 <=3.11" aptos-sdk = "^0.5.1" requests = "^2.31.0" diff --git a/crates/aptos/src/common/types.rs b/crates/aptos/src/common/types.rs index 0d74e737c7dbd7..654218c2864258 100644 --- a/crates/aptos/src/common/types.rs +++ b/crates/aptos/src/common/types.rs @@ -184,7 +184,7 @@ impl From for CliError { impl From for CliError { fn from(e: anyhow::Error) -> Self { - CliError::UnexpectedError(e.to_string()) + CliError::UnexpectedError(format!("{:#}", e)) } } diff --git a/crates/aptos/src/common/utils.rs b/crates/aptos/src/common/utils.rs index 1f165b96e1a0c2..4e04cf4d37c9c6 100644 --- a/crates/aptos/src/common/utils.rs +++ b/crates/aptos/src/common/utils.rs @@ -94,7 +94,7 @@ pub async fn to_common_result( } } - let result: ResultWrapper = result.into(); + let result = ResultWrapper::::from(result); let string = serde_json::to_string_pretty(&result).unwrap(); if is_err { Err(string) @@ -152,7 +152,7 @@ impl From> for ResultWrapper { fn from(result: CliTypedResult) -> Self { match result { Ok(inner) => ResultWrapper::Result(inner), - Err(inner) => ResultWrapper::Error(inner.to_string()), + Err(inner) => ResultWrapper::Error(format!("{:#}", inner)), } } } diff --git a/crates/aptos/src/move_tool/aptos_dep_example/README.md b/crates/aptos/src/move_tool/aptos_dep_example/README.md index abfa0f9956c661..89d702218476ab 100644 --- a/crates/aptos/src/move_tool/aptos_dep_example/README.md +++ b/crates/aptos/src/move_tool/aptos_dep_example/README.md @@ -12,7 +12,7 @@ To see it working: ```shell # Start a node with an account -aptos node run-local-testnet --with-faucet & +aptos node run-local-testnet & aptos account create --account default --use-faucet # Compile and publish pack2 cd pack2 @@ -21,4 +21,4 @@ aptos move publish --named-addresses project=default # Compile pack1 agains the published pack2 cd ../pack1 aptos move compile --named-addresses project=default -``` \ No newline at end of file +``` diff --git a/crates/aptos/src/node/local_testnet/health_checker.rs b/crates/aptos/src/node/local_testnet/health_checker.rs new file mode 100644 index 00000000000000..2ba6438160ba6f --- /dev/null +++ b/crates/aptos/src/node/local_testnet/health_checker.rs @@ -0,0 +1,139 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +use crate::common::types::{CliError, CliTypedResult}; +use anyhow::Context; +use aptos_protos::indexer::v1::GetTransactionsRequest; +use futures::StreamExt; +use reqwest::Url; +use serde::Serialize; +use std::time::Duration; +use tokio::time::Instant; + +const MAX_WAIT_S: u64 = 35; +const WAIT_INTERVAL_MS: u64 = 150; + +/// This provides a single place to define a variety of different healthchecks. +#[derive(Clone, Debug, Serialize)] +pub enum HealthChecker { + /// Check that an HTTP API is up. The second param is the name of the HTTP service. + Http(Url, &'static str), + /// Check that the node API is up. This is just a specific case of Http for extra + /// guarantees around liveliness. + NodeApi(Url), + /// Check that a data service GRPC stream is up. + DataServiceGrpc(Url), +} + +impl HealthChecker { + pub async fn check(&self) -> CliTypedResult<()> { + match self { + HealthChecker::Http(url, _) => { + reqwest::get(Url::clone(url)) + .await + .with_context(|| format!("Failed to GET {}", url))?; + Ok(()) + }, + HealthChecker::NodeApi(url) => { + aptos_rest_client::Client::new(Url::clone(url)) + .get_index() + .await?; + Ok(()) + }, + HealthChecker::DataServiceGrpc(url) => { + let mut client = aptos_indexer_grpc_utils::create_data_service_grpc_client( + url.clone(), + Some(Duration::from_secs(5)), + ) + .await; + let request = tonic::Request::new(GetTransactionsRequest { + starting_version: Some(0), + ..Default::default() + }); + // Make sure we can stream the first message from the stream. + client + .get_transactions(request) + .await + .map_err(|err| { + CliError::UnexpectedError(format!("GRPC connection error: {:#}", err)) + })? + .into_inner() + .next() + .await + .context("Did not receive init signal from data service GRPC stream")? + .map_err(|err| { + CliError::UnexpectedError(format!( + "Error processing first message from GRPC stream: {:#}", + err + )) + })?; + Ok(()) + }, + } + } + + /// Wait up to MAX_WAIT_S seconds for a service to start up. + pub async fn wait( + &self, + // The service, if any, waiting for this service to start up. + waiting_service: Option<&str>, + ) -> CliTypedResult<()> { + let prefix = self.to_string(); + wait_for_startup(|| self.check(), match waiting_service { + Some(waiting_service) => { + format!( + "{} at {} did not start up before {}", + prefix, + waiting_service, + self.address_str() + ) + }, + None => format!("{} at {} did not start up", prefix, self.address_str()), + }) + .await + } + + pub fn address_str(&self) -> &str { + match self { + HealthChecker::Http(url, _) => url.as_str(), + HealthChecker::NodeApi(url) => url.as_str(), + HealthChecker::DataServiceGrpc(url) => url.as_str(), + } + } +} + +impl std::fmt::Display for HealthChecker { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + HealthChecker::Http(_, name) => write!(f, "{}", name), + HealthChecker::NodeApi(_) => write!(f, "Node API"), + HealthChecker::DataServiceGrpc(_) => write!(f, "Transaction stream"), + } + } +} + +async fn wait_for_startup(check_fn: F, error_message: String) -> CliTypedResult<()> +where + F: Fn() -> Fut, + Fut: futures::Future>, +{ + let max_wait = Duration::from_secs(MAX_WAIT_S); + let wait_interval = Duration::from_millis(WAIT_INTERVAL_MS); + + let start = Instant::now(); + let mut started_successfully = false; + + while start.elapsed() < max_wait { + if check_fn().await.is_ok() { + started_successfully = true; + break; + } + tokio::time::sleep(wait_interval).await + } + + if !started_successfully { + return Err(CliError::UnexpectedError(error_message)); + } + + Ok(()) +} diff --git a/crates/aptos/src/node/local_testnet/logging.rs b/crates/aptos/src/node/local_testnet/logging.rs new file mode 100644 index 00000000000000..3752582070d941 --- /dev/null +++ b/crates/aptos/src/node/local_testnet/logging.rs @@ -0,0 +1,102 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +use dashmap::DashMap; +use std::{ + fs::{create_dir_all, File}, + path::PathBuf, + sync::{Arc, Mutex}, +}; +use tracing_subscriber::fmt::MakeWriter; + +/// This struct impls MakeWriter, a trait that returns writers for using by the +/// tracing_subscriber library. It returns a custom logger that logs to different +/// files based on the name of the worker (thread). +/// +/// To be as efficient as possible and only create + open each file once, we keep +/// track of file handles in a DashMap with this FileLock struct as the values. +/// Learn more about FileLock in the doc comment there. +pub struct ThreadNameMakeWriter { + /// The base directory to output logs to. + base_dir: PathBuf, + /// We keep open file handles here to avoid making them every time. They key is the + /// name of the file (thread name without the number). + file_handles: DashMap, +} + +impl ThreadNameMakeWriter { + pub fn new(base_dir: PathBuf) -> Self { + Self { + base_dir, + file_handles: DashMap::new(), + } + } +} + +impl<'a> MakeWriter<'a> for ThreadNameMakeWriter { + type Writer = Box; + + fn make_writer(&'a self) -> Self::Writer { + let base_dir = self.base_dir.clone(); + let thread_name = std::thread::current() + .name() + .unwrap_or("no-thread-name") + .to_string(); + let thread_name_no_number = truncate_last_segment(&thread_name, '-'); + let log_file = self + .file_handles + .entry(thread_name_no_number.clone()) + .or_insert_with(|| FileLock::new(create_file(base_dir, thread_name_no_number))) + .value() + .clone(); + Box::new(log_file) + } +} + +fn create_file(base_dir: PathBuf, thread_name_no_number: String) -> File { + let dir_path = base_dir.join(thread_name_no_number); + create_dir_all(&dir_path).expect("Failed to create log directory"); + let log_path = dir_path.join("tracing.log"); + std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(log_path) + .unwrap() +} + +fn truncate_last_segment(s: &str, delimiter: char) -> String { + s.rsplit_once(delimiter) + .map(|x| x.0) + .unwrap_or(s) + .to_string() +} + +/// This struct protects access to an open file handle. Using a Mutex is necessary +/// because we cannot allow concurrent access to the file. The Arc ensures that +/// every time we hand out a reference to the handle, it is the same handle. +#[derive(Clone)] +pub struct FileLock { + file: Arc>, +} + +impl FileLock { + pub fn new(file: File) -> Self { + Self { + file: Arc::new(Mutex::new(file)), + } + } +} + +impl std::io::Write for FileLock { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.file.lock().unwrap().write(buf) + } + + fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> { + self.file.lock().unwrap().write_all(buf) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.file.lock().unwrap().flush() + } +} diff --git a/crates/aptos/src/node/local_testnet/mod.rs b/crates/aptos/src/node/local_testnet/mod.rs new file mode 100644 index 00000000000000..575720df540917 --- /dev/null +++ b/crates/aptos/src/node/local_testnet/mod.rs @@ -0,0 +1,424 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +mod health_checker; +mod logging; +mod ready_server; +mod utils; + +use self::{ + health_checker::HealthChecker, + logging::ThreadNameMakeWriter, + ready_server::{run_ready_server, ReadyServerConfig}, + utils::socket_addr_to_url, +}; +use crate::{ + common::{ + types::{CliCommand, CliError, CliTypedResult, ConfigSearchMode, PromptOptions}, + utils::prompt_yes_with_override, + }, + config::GlobalConfig, +}; +use anyhow::Context; +use aptos_config::config::{NodeConfig, DEFAULT_GRPC_STREAM_PORT}; +use aptos_faucet_core::server::{FunderKeyEnum, RunConfig as FaucetConfig}; +use aptos_indexer_grpc_server_framework::setup_logging; +use aptos_logger::debug; +use aptos_node::create_single_node_test_config; +use async_trait::async_trait; +use clap::Parser; +use futures::{Future, FutureExt}; +use rand::{rngs::StdRng, SeedableRng}; +use reqwest::Url; +use std::{ + fs::{create_dir_all, remove_dir_all}, + path::PathBuf, + pin::Pin, + thread, + time::Duration, +}; +use tokio::task::JoinHandle; +use tracing_subscriber::fmt::MakeWriter; + +const TESTNET_FOLDER: &str = "testnet"; + +/// Run a local testnet +/// +/// This local testnet will run it's own genesis and run as a single node network +/// locally. A faucet and grpc transaction stream will run alongside the node unless +/// you specify otherwise with --no-faucet and --no-txn-stream respectively. +#[derive(Parser)] +pub struct RunLocalTestnet { + /// An overridable config template for the test node + /// + /// If provided, the config will be used, and any needed configuration for the local testnet + /// will override the config's values + #[clap(long, value_parser)] + config_path: Option, + + /// The directory to save all files for the node + /// + /// Defaults to .aptos/testnet + #[clap(long, value_parser)] + test_dir: Option, + + /// Path to node configuration file override for local test mode. + /// + /// If provided, the default node config will be overridden by the config in the given file. + /// Cannot be used with --config-path + #[clap(long, value_parser, conflicts_with("config_path"))] + test_config_override: Option, + + /// Random seed for key generation in test mode + /// + /// This allows you to have deterministic keys for testing + #[clap(long, value_parser = aptos_node::load_seed)] + seed: Option<[u8; 32]>, + + /// Clean the state and start with a new chain at genesis + /// + /// This will wipe the aptosdb in `test-dir` to remove any incompatible changes, and start + /// the chain fresh. Note, that you will need to publish the module again and distribute funds + /// from the faucet accordingly + #[clap(long)] + force_restart: bool, + + /// Port to run the faucet on. + /// + /// When running, you'll be able to use the faucet at `http://127.0.0.1:/mint` e.g. + /// `http//127.0.0.1:8081/mint` + #[clap(long, default_value_t = 8081)] + faucet_port: u16, + + /// Do not run a faucet alongside the node. + /// + /// Running a faucet alongside the node allows you to create and fund accounts + /// for testing. + #[clap(long)] + no_faucet: bool, + + /// This does nothing, we already run a faucet by default. We only keep this here + /// for backwards compatibility with tests. We will remove this once the commit + /// that added --no-faucet makes its way to the testnet branch. + #[clap(long)] + with_faucet: bool, + + /// Disable the delegation of faucet minting to a dedicated account. + #[clap(long)] + do_not_delegate: bool, + + /// Do not run a transaction stream service alongside the node. + /// + /// Note: In reality this is not the same as running a Transaction Stream Service, + /// it is just using the stream from the node, but in practice this distinction + /// shouldn't matter. + #[clap(long)] + no_txn_stream: bool, + + /// The port at which to expose the grpc transaction stream. + #[clap(long, default_value_t = DEFAULT_GRPC_STREAM_PORT)] + txn_stream_port: u16, + + #[clap(flatten)] + ready_server_config: ReadyServerConfig, + + #[clap(flatten)] + prompt_options: PromptOptions, +} + +#[derive(Debug)] +struct AllConfigs { + ready_server_config: ReadyServerConfig, + node_config: NodeConfig, + faucet_config: Option, +} + +impl AllConfigs { + pub fn get_node_api_url(&self) -> Url { + socket_addr_to_url(&self.node_config.api.address, "http").unwrap() + } +} + +impl RunLocalTestnet { + /// This function builds all the configs we need to run each of the requested + /// services. We separate creating configs and spawning services to keep the + /// code clean. This could also allow us to one day have two phases for starting + /// a local testnet, in which you can alter the configs on disk between each phase. + fn build_configs(&self, test_dir: PathBuf) -> anyhow::Result { + let rng = self + .seed + .map(StdRng::from_seed) + .unwrap_or_else(StdRng::from_entropy); + + let mut node_config = create_single_node_test_config( + &self.config_path, + &self.test_config_override, + &test_dir, + false, + false, + aptos_cached_packages::head_release_bundle(), + rng, + ) + .context("Failed to create config for node")?; + + // Enable the grpc stream on the node if we will run a txn stream service. + let run_txn_stream = !self.no_txn_stream; + node_config.indexer_grpc.enabled = run_txn_stream; + node_config.indexer_grpc.use_data_service_interface = run_txn_stream; + node_config + .indexer_grpc + .address + .set_port(self.txn_stream_port); + + // So long as the indexer relies on storage indexing tables, this must be set + // for the indexer GRPC stream on the node to work. + node_config.storage.enable_indexer = run_txn_stream; + + let node_api_url = socket_addr_to_url(&node_config.api.address, "http").unwrap(); + + let faucet_config = if self.no_faucet { + None + } else { + Some(FaucetConfig::build_for_cli( + node_api_url.clone(), + self.faucet_port, + FunderKeyEnum::KeyFile(test_dir.join("mint.key")), + self.do_not_delegate, + None, + )) + }; + + Ok(AllConfigs { + ready_server_config: self.ready_server_config.clone(), + node_config, + faucet_config, + }) + } + + // Note: These start_* functions (e.g. start_node) can run checks prior to + // returning the future for the service, for example to ensure that a prerequisite + // service has started. They cannot however do anything afterwards. For that, + // you probably want to define a HealthCheck to register with wait_for_startup. + + /// Spawn the node on a thread and then create a future that just waits for it to + /// exit (which should never happen) forever. This is necessary because there is + /// no async function we can use to run the node. + async fn start_node( + &self, + test_dir: PathBuf, + config: NodeConfig, + ) -> CliTypedResult> { + let rng = self + .seed + .map(StdRng::from_seed) + .unwrap_or_else(StdRng::from_entropy); + + let node_thread_handle = thread::spawn(move || { + let result = aptos_node::setup_test_environment_and_start_node( + None, + None, + Some(config), + Some(test_dir), + false, + false, + aptos_cached_packages::head_release_bundle(), + rng, + ); + eprintln!("Node stopped unexpectedly {:#?}", result); + }); + + // This just waits for the node thread forever. + let node_future = async move { + loop { + if node_thread_handle.is_finished() { + return; + } + tokio::time::sleep(Duration::from_millis(500)).await; + } + }; + + Ok(node_future) + } + + /// Run the faucet. + async fn start_faucet( + &self, + config: FaucetConfig, + node_api_url: Url, + ) -> CliTypedResult> { + HealthChecker::NodeApi(node_api_url) + .wait(Some("Faucet")) + .await?; + + // Start the faucet + Ok(config.run().map(|result| { + eprintln!("Faucet stopped unexpectedly {:#?}", result); + })) + } + + /// Run the ready server. + async fn start_ready_server( + &self, + health_checks: Vec, + ) -> CliTypedResult> { + let config = self.ready_server_config.clone(); + Ok(run_ready_server(health_checks, config).map(|result| { + eprintln!("Faucet stopped unexpectedly {:#?}", result); + })) + } + + /// Wait for many services to start up. This prints a message like "X is starting, + /// please wait..." for each service and then "X is running. Endpoint: " + /// when it's ready. + async fn wait_for_startup<'a>(&self, health_checks: &Vec) -> CliTypedResult<()> { + let mut futures: Vec> + Send>>> = + Vec::new(); + + for health_check in health_checks { + eprintln!("{} is starting, please wait...", health_check); + let fut = async move { + health_check.wait(None).await?; + eprintln!( + "{} is running. Endpoint: {}", + health_check, + health_check.address_str() + ); + Ok(()) + }; + futures.push(Box::pin(fut)); + } + + eprintln!(); + + // We use join_all because we expect all of these to return. + for f in futures::future::join_all(futures).await { + f.map_err(|err| { + CliError::UnexpectedError(format!( + "One of the services failed to start up: {:?}", + err + )) + })?; + } + + eprintln!("\nAll services are running, you can now use the local testnet!"); + + Ok(()) + } +} + +#[async_trait] +impl CliCommand<()> for RunLocalTestnet { + fn command_name(&self) -> &'static str { + "RunLocalTestnet" + } + + async fn execute(mut self) -> CliTypedResult<()> { + let global_config = GlobalConfig::load().context("Failed to load global config")?; + let test_dir = match &self.test_dir { + Some(test_dir) => test_dir.clone(), + None => global_config + .get_config_location(ConfigSearchMode::CurrentDirAndParents)? + .join(TESTNET_FOLDER), + }; + + // If asked, remove the current test directory and start with a new node. + if test_dir.exists() && self.force_restart { + prompt_yes_with_override( + "Are you sure you want to delete the existing local testnet data?", + self.prompt_options, + )?; + remove_dir_all(test_dir.as_path()).map_err(|err| { + CliError::IO(format!("Failed to delete {}", test_dir.display()), err) + })?; + } + + if !test_dir.exists() { + debug!("Test directory does not exist, creating it: {:?}", test_dir); + create_dir_all(test_dir.as_path()).map_err(|err| { + CliError::IO(format!("Failed to create {}", test_dir.display()), err) + })?; + debug!("Created test directory: {:?}", test_dir); + } + + // Set up logging for anything that uses tracing. These logs will go to + // different directories based on the name of the runtime. + let td = test_dir.clone(); + let make_writer = + move || ThreadNameMakeWriter::new(td.clone()).make_writer() as Box; + setup_logging(Some(Box::new(make_writer))); + + let all_configs = self + .build_configs(test_dir.clone()) + .context("Failed to build configs")?; + + let node_api_url = all_configs.get_node_api_url(); + + let AllConfigs { + ready_server_config, + node_config, + faucet_config, + } = all_configs; + + // Collect all the health checks we want to run. + let mut health_checks = Vec::new(); + health_checks.push(HealthChecker::NodeApi(node_api_url.clone())); + + if let Some(config) = &faucet_config { + let url = Url::parse(&format!( + "http://{}:{}", + config.server_config.listen_address, config.server_config.listen_port + )) + .unwrap(); + health_checks.push(HealthChecker::Http(url, "Faucet")); + } + + if !self.no_txn_stream { + let url = socket_addr_to_url(&node_config.indexer_grpc.address, "http")?; + health_checks.push(HealthChecker::DataServiceGrpc(url)); + } + + // Build tasks for each of the services. + let mut tasks: Vec> = Vec::new(); + + // Push a task to run the ready server. + tasks.push(tokio::spawn( + self.start_ready_server(health_checks.clone()) + .await + .context("Failed to create future to start the ready server")?, + )); + + // Run the node API. + tasks.push(tokio::spawn( + self.start_node(test_dir.clone(), node_config) + .await + .context("Failed to create future to start the node")?, + )); + + // If configured, run the faucet. + if let Some(config) = faucet_config { + tasks.push(tokio::spawn( + self.start_faucet(config, node_api_url.clone()) + .await + .context("Failed to create future to start the faucet")?, + )); + } + + eprintln!( + "Readiness endpoint: http://0.0.0.0:{}/\n", + ready_server_config.ready_server_listen_port + ); + + // Wait for all the services to start up. + self.wait_for_startup(&health_checks).await?; + + // Wait for all of the futures for the tasks. We should never get past this + // point unless something goes wrong or the user signals for the process to + // end. + let result = futures::future::select_all(tasks).await; + + Err(CliError::UnexpectedError(format!( + "One of the components stopped unexpectedly: {:?}", + result + ))) + } +} diff --git a/crates/aptos/src/node/local_testnet/ready_server.rs b/crates/aptos/src/node/local_testnet/ready_server.rs new file mode 100644 index 00000000000000..a516f2abeef218 --- /dev/null +++ b/crates/aptos/src/node/local_testnet/ready_server.rs @@ -0,0 +1,73 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +use crate::node::local_testnet::health_checker::HealthChecker; +use anyhow::Result; +use clap::Parser; +use poem::{ + get, handler, + http::StatusCode, + listener::TcpListener, + middleware::Tracing, + web::{Data, Json}, + EndpointExt, IntoResponse, Route, Server, +}; +use serde::Serialize; +use std::net::{Ipv4Addr, SocketAddrV4}; + +#[derive(Debug, Clone, Parser)] +pub struct ReadyServerConfig { + #[clap(long, default_value_t = 8090)] + pub ready_server_listen_port: u16, +} + +/// This returns a future that runs a web server that exposes a single unified health +/// checking port. Clients can use this to check if all the services are ready. +pub async fn run_ready_server( + health_checkers: Vec, + config: ReadyServerConfig, +) -> Result<()> { + let app = Route::new() + .at("/", get(root)) + .data(HealthCheckers { health_checkers }) + .with(Tracing); + Server::new(TcpListener::bind(SocketAddrV4::new( + Ipv4Addr::new(0, 0, 0, 0), + config.ready_server_listen_port, + ))) + .name("ready-server") + .run(app) + .await?; + Err(anyhow::anyhow!("Ready server exited unexpectedly")) +} + +#[derive(Clone, Debug)] +struct HealthCheckers { + pub health_checkers: Vec, +} + +#[derive(Serialize)] +struct ReadyData { + pub ready: Vec, + pub not_ready: Vec, +} + +#[handler] +async fn root(health_checkers: Data<&HealthCheckers>) -> impl IntoResponse { + let mut ready = vec![]; + let mut not_ready = vec![]; + for health_checker in &health_checkers.health_checkers { + match health_checker.check().await { + Ok(()) => ready.push(health_checker.clone()), + Err(_) => { + not_ready.push(health_checker.clone()); + }, + } + } + let status_code = if not_ready.is_empty() { + StatusCode::OK + } else { + StatusCode::SERVICE_UNAVAILABLE + }; + Json(ReadyData { ready, not_ready }).with_status(status_code) +} diff --git a/crates/aptos/src/node/local_testnet/utils.rs b/crates/aptos/src/node/local_testnet/utils.rs new file mode 100644 index 00000000000000..86a9f5f9d57220 --- /dev/null +++ b/crates/aptos/src/node/local_testnet/utils.rs @@ -0,0 +1,14 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +use reqwest::Url; +use std::net::SocketAddr; + +pub fn socket_addr_to_url(socket_addr: &SocketAddr, scheme: &str) -> anyhow::Result { + let host = match socket_addr { + SocketAddr::V4(v4) => format!("{}", v4.ip()), + SocketAddr::V6(v6) => format!("[{}]", v6.ip()), + }; + let full_url = format!("{}://{}:{}", scheme, host, socket_addr.port()); + Ok(Url::parse(&full_url)?) +} diff --git a/crates/aptos/src/node/mod.rs b/crates/aptos/src/node/mod.rs index 8a86c600006688..72133852e3b451 100644 --- a/crates/aptos/src/node/mod.rs +++ b/crates/aptos/src/node/mod.rs @@ -2,17 +2,17 @@ // SPDX-License-Identifier: Apache-2.0 pub mod analyze; +pub mod local_testnet; +use self::local_testnet::RunLocalTestnet; use crate::{ common::{ types::{ - CliCommand, CliError, CliResult, CliTypedResult, ConfigSearchMode, - OptionalPoolAddressArgs, PoolAddressArgs, ProfileOptions, PromptOptions, RestOptions, - TransactionOptions, TransactionSummary, + CliCommand, CliError, CliResult, CliTypedResult, OptionalPoolAddressArgs, + PoolAddressArgs, ProfileOptions, RestOptions, TransactionOptions, TransactionSummary, }, - utils::{prompt_yes_with_override, read_from_file}, + utils::read_from_file, }, - config::GlobalConfig, genesis::git::from_yaml, node::analyze::{ analyze_validators::{AnalyzeValidators, ValidatorStats}, @@ -25,9 +25,7 @@ use aptos_backup_cli::{ utils::GlobalRestoreOpt, }; use aptos_cached_packages::aptos_stdlib; -use aptos_config::config::NodeConfig; use aptos_crypto::{bls12381, bls12381::PublicKey, x25519, ValidCryptoMaterialStringExt}; -use aptos_faucet_core::server::{FunderKeyEnum, RunConfig}; use aptos_genesis::config::{HostAndPort, OperatorConfiguration}; use aptos_logger::Level; use aptos_network_checker::args::{ @@ -50,19 +48,13 @@ use async_trait::async_trait; use bcs::Result; use chrono::{DateTime, NaiveDateTime, Utc}; use clap::Parser; -use futures::FutureExt; -use rand::{rngs::StdRng, SeedableRng}; -use reqwest::Url; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, convert::{TryFrom, TryInto}, path::PathBuf, - pin::Pin, - thread, time::Duration, }; -use tokio::time::Instant; const SECS_TO_MICROSECS: u64 = 1_000_000; @@ -1047,214 +1039,6 @@ impl From<&ValidatorConfigSummary> for ValidatorConfig { } } -const MAX_WAIT_S: u64 = 30; -const WAIT_INTERVAL_MS: u64 = 100; -const TESTNET_FOLDER: &str = "testnet"; - -/// Run local testnet -/// -/// This local testnet will run it's own Genesis and run as a single node -/// network locally. Optionally, a faucet can be added for minting APT coins. -#[derive(Parser)] -pub struct RunLocalTestnet { - /// An overridable config template for the test node - /// - /// If provided, the config will be used, and any needed configuration for the local testnet - /// will override the config's values - #[clap(long, value_parser)] - config_path: Option, - - /// The directory to save all files for the node - /// - /// Defaults to .aptos/testnet - #[clap(long, value_parser)] - test_dir: Option, - - /// Path to node configuration file override for local test mode. - /// - /// If provided, the default node config will be overridden by the config in the given file. - /// Cannot be used with --config-path - #[clap(long, value_parser, conflicts_with("config_path"))] - test_config_override: Option, - - /// Random seed for key generation in test mode - /// - /// This allows you to have deterministic keys for testing - #[clap(long, value_parser = aptos_node::load_seed)] - seed: Option<[u8; 32]>, - - /// Clean the state and start with a new chain at genesis - /// - /// This will wipe the aptosdb in `test-dir` to remove any incompatible changes, and start - /// the chain fresh. Note, that you will need to publish the module again and distribute funds - /// from the faucet accordingly - #[clap(long)] - force_restart: bool, - - /// Run a faucet alongside the node - /// - /// Allows you to run a faucet alongside the node to create and fund accounts for testing - #[clap(long)] - with_faucet: bool, - - /// Port to run the faucet on - /// - /// When running, you'll be able to use the faucet at `http://localhost:/mint` e.g. - /// `http//localhost:8080/mint` - #[clap(long, default_value = "8081")] - faucet_port: u16, - - /// Disable the delegation of faucet minting to a dedicated account - #[clap(long)] - do_not_delegate: bool, - - #[clap(flatten)] - prompt_options: PromptOptions, -} - -#[async_trait] -impl CliCommand<()> for RunLocalTestnet { - fn command_name(&self) -> &'static str { - "RunLocalTestnet" - } - - async fn execute(mut self) -> CliTypedResult<()> { - let rng = self - .seed - .map(StdRng::from_seed) - .unwrap_or_else(StdRng::from_entropy); - - let global_config = GlobalConfig::load()?; - let test_dir = match self.test_dir { - Some(test_dir) => test_dir, - None => global_config - .get_config_location(ConfigSearchMode::CurrentDirAndParents)? - .join(TESTNET_FOLDER), - }; - - // Remove the current test directory and start with a new node - if self.force_restart && test_dir.exists() { - prompt_yes_with_override( - "Are you sure you want to delete the existing chain?", - self.prompt_options, - )?; - std::fs::remove_dir_all(test_dir.as_path()).map_err(|err| { - CliError::IO(format!("Failed to delete {}", test_dir.display()), err) - })?; - } - - // Spawn the node in a separate thread - let config_path = self.config_path.clone(); - let test_dir_copy = test_dir.clone(); - let node_thread_handle = thread::spawn(move || { - let result = aptos_node::setup_test_environment_and_start_node( - config_path, - self.test_config_override, - None, - Some(test_dir_copy), - false, - false, - aptos_cached_packages::head_release_bundle(), - rng, - ); - eprintln!("Node stopped unexpectedly {:#?}", result); - }); - - // Run faucet if selected - let maybe_faucet_future = if self.with_faucet { - let max_wait = Duration::from_secs(MAX_WAIT_S); - let wait_interval = Duration::from_millis(WAIT_INTERVAL_MS); - - // Load the config to get the rest port - let config_path = test_dir.join("0").join("node.yaml"); - - // We have to wait for the node to be configured above in the other thread - let mut config = None; - let start = Instant::now(); - while start.elapsed() < max_wait { - if let Ok(loaded_config) = NodeConfig::load_from_path(&config_path) { - config = Some(loaded_config); - break; - } - tokio::time::sleep(wait_interval).await; - } - - // Retrieve the port from the local node - let port = if let Some(config) = config { - config.api.address.port() - } else { - return Err(CliError::UnexpectedError( - "Failed to find node configuration to start faucet".to_string(), - )); - }; - - // Check that the REST API is ready - let rest_url = Url::parse(&format!("http://localhost:{}", port)).map_err(|err| { - CliError::UnexpectedError(format!("Failed to parse localhost URL {}", err)) - })?; - let rest_client = aptos_rest_client::Client::new(rest_url.clone()); - let start = Instant::now(); - let mut started_successfully = false; - - while start.elapsed() < max_wait { - if rest_client.get_index().await.is_ok() { - started_successfully = true; - break; - } - tokio::time::sleep(wait_interval).await - } - - if !started_successfully { - return Err(CliError::UnexpectedError(format!( - "Local node at {} did not start up before faucet", - rest_url - ))); - } - - // Build the config for the faucet service. - let faucet_config = RunConfig::build_for_cli( - rest_url, - self.faucet_port, - FunderKeyEnum::KeyFile(test_dir.join("mint.key")), - self.do_not_delegate, - None, - ); - - // Start the faucet - Some(faucet_config.run().map(|result| { - eprintln!("Faucet stopped unexpectedly {:#?}", result); - })) - } else { - None - }; - - // Collect futures that should never end. - let mut futures: Vec + Send>>> = Vec::new(); - - // This future just waits for the node thread. - let node_future = async move { - loop { - if node_thread_handle.is_finished() { - return; - } - tokio::time::sleep(Duration::from_millis(500)).await; - } - }; - - // Wait for all the futures. We should never get past this point unless - // something goes wrong or the user signals for the process to end. - futures.push(Box::pin(node_future)); - if let Some(faucet_future) = maybe_faucet_future { - futures.push(Box::pin(faucet_future)); - } - futures::future::select_all(futures).await; - - Err(CliError::UnexpectedError( - "One of the components stopped unexpectedly".to_string(), - )) - } -} - /// Update consensus key for the validator node /// /// This will take effect in the next epoch diff --git a/developer-docs-site/docs/guides/local-testnet-dev-flow.md b/developer-docs-site/docs/guides/local-testnet-dev-flow.md index fc0953ec97e2c7..bc2cdb094d1ede 100644 --- a/developer-docs-site/docs/guides/local-testnet-dev-flow.md +++ b/developer-docs-site/docs/guides/local-testnet-dev-flow.md @@ -20,7 +20,7 @@ git clone git@github.com:aptos-labs/aptos-core.git ~/aptos-core && cd ~/aptos-co Run a local testnet with a faucet: ``` -cargo run -p aptos -- node run-local-testnet --with-faucet --faucet-port 8081 --force-restart --assume-yes +cargo run -p aptos -- node run-local-testnet --faucet-port 8081 --force-restart --assume-yes ``` You may add the `--release` flag after `cargo run` if you want to build a release version of the CLI for running the local testnet. diff --git a/developer-docs-site/docs/nodes/local-testnet/using-cli-to-run-a-local-testnet.md b/developer-docs-site/docs/nodes/local-testnet/using-cli-to-run-a-local-testnet.md index 60622dc423f6ea..503bf5fe3408fa 100644 --- a/developer-docs-site/docs/nodes/local-testnet/using-cli-to-run-a-local-testnet.md +++ b/developer-docs-site/docs/nodes/local-testnet/using-cli-to-run-a-local-testnet.md @@ -21,7 +21,7 @@ If you are new to the Aptos CLI, then see this comprehensive [Aptos CLI user doc You can start a local testnet using the following Aptos CLI command: ```bash -aptos node run-local-testnet --with-faucet +aptos node run-local-testnet ``` The above command will start a local validator node and will display a terminal output similar to the following: @@ -47,6 +47,8 @@ The above command will use the default configuration for the validator node. Note that two instances of the same command cannot run at the same time. This will result in a conflict on ports for the validator node. ::: +You can choose to not run a faucet with the `--no-faucet` flag. + ## Test with your local testnet You can use the Aptos CLI for a full range of local testnet operations. See below for how to configure the CLI first. @@ -145,7 +147,7 @@ If you updated your codebase with backwards incompatible changes, or just want t the command with the `--force-restart` flag: ```bash -aptos node run-local-testnet --with-faucet --force-restart +aptos node run-local-testnet --force-restart ``` It will then prompt you if you really want to restart the chain, to ensure that you do not delete your work by accident. diff --git a/developer-docs-site/docs/tools/aptos-cli/use-cli/cli-node.md b/developer-docs-site/docs/tools/aptos-cli/use-cli/cli-node.md index f0a7f0e559cf2a..730989536be062 100644 --- a/developer-docs-site/docs/tools/aptos-cli/use-cli/cli-node.md +++ b/developer-docs-site/docs/tools/aptos-cli/use-cli/cli-node.md @@ -15,7 +15,7 @@ You can run a local testnet from the aptos CLI, that will match the version it w run a faucet side by side with the local single node testnet. ```bash -$ aptos node run-local-testnet --with-faucet +$ aptos node run-local-testnet Completed generating configuration: Log file: "/Users/greg/.aptos/testnet/validator.log" Test dir: "/Users/greg/.aptos/testnet" @@ -32,7 +32,7 @@ This will have consistent state if the node is shutdown, it will start with the If you want to restart the chain from genesis, you can add the `--force-restart` flag. ```bash -$ aptos node run-local-testnet --with-faucet --force-restart +$ aptos node run-local-testnet --force-restart Are you sure you want to delete the existing chain? [yes/no] > yes Completed generating configuration: @@ -45,4 +45,4 @@ Completed generating configuration: Fullnode network: /ip4/0.0.0.0/tcp/6181 Aptos is running, press ctrl-c to exit -``` \ No newline at end of file +``` diff --git a/docker/builder/docker-bake-rust-all.sh b/docker/builder/docker-bake-rust-all.sh index dc62f1f02ab286..c8ae35ebcceb72 100755 --- a/docker/builder/docker-bake-rust-all.sh +++ b/docker/builder/docker-bake-rust-all.sh @@ -53,7 +53,7 @@ echo "E.g. docker/builder/docker-bake-rust-all.sh forge-images" if [ "$CI" == "true" ]; then docker buildx bake --progress=plain --file docker/builder/docker-bake-rust-all.hcl --push $BUILD_TARGET else - docker buildx bake --file docker/builder/docker-bake-rust-all.hcl $BUILD_TARGET + docker buildx bake --file docker/builder/docker-bake-rust-all.hcl $BUILD_TARGET --load fi echo "Build complete. Docker buildx cache usage:" diff --git a/ecosystem/indexer-grpc/indexer-grpc-fullnode/Cargo.toml b/ecosystem/indexer-grpc/indexer-grpc-fullnode/Cargo.toml index e5966b38c95678..adbec42b6b10e6 100644 --- a/ecosystem/indexer-grpc/indexer-grpc-fullnode/Cargo.toml +++ b/ecosystem/indexer-grpc/indexer-grpc-fullnode/Cargo.toml @@ -27,6 +27,7 @@ serde_json = { workspace = true } tokio = { workspace = true } tokio-stream = { workspace = true } tonic = { workspace = true } +tonic-reflection = { workspace = true } aptos-api = { workspace = true } aptos-api-types = { workspace = true } diff --git a/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/runtime.rs b/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/runtime.rs index 40443b02d5877c..0cf82d87bbb5f6 100644 --- a/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/runtime.rs +++ b/ecosystem/indexer-grpc/indexer-grpc-fullnode/src/runtime.rs @@ -10,8 +10,12 @@ use aptos_config::config::NodeConfig; use aptos_logger::info; use aptos_mempool::MempoolClientSender; use aptos_protos::{ - indexer::v1::raw_data_server::RawDataServer, + indexer::v1::{ + raw_data_server::RawDataServer, FILE_DESCRIPTOR_SET as INDEXER_V1_FILE_DESCRIPTOR_SET, + }, internal::fullnode::v1::fullnode_data_server::FullnodeDataServer, + transaction::v1::FILE_DESCRIPTOR_SET as TRANSACTION_V1_TESTING_FILE_DESCRIPTOR_SET, + util::timestamp::FILE_DESCRIPTOR_SET as UTIL_TIMESTAMP_FILE_DESCRIPTOR_SET, }; use aptos_storage_interface::DbReader; use aptos_types::chain_id::ChainId; @@ -59,9 +63,24 @@ pub fn bootstrap( }; let localnet_data_server = LocalnetDataService { service_context }; - let mut tonic_server = Server::builder() + let reflection_service = tonic_reflection::server::Builder::configure() + // Note: It is critical that the file descriptor set is registered for every + // file that the top level API proto depends on recursively. If you don't, + // compilation will still succeed but reflection will fail at runtime. + // + // TODO: Add a test for this / something in build.rs, this is a big footgun. + .register_encoded_file_descriptor_set(INDEXER_V1_FILE_DESCRIPTOR_SET) + .register_encoded_file_descriptor_set(TRANSACTION_V1_TESTING_FILE_DESCRIPTOR_SET) + .register_encoded_file_descriptor_set(UTIL_TIMESTAMP_FILE_DESCRIPTOR_SET) + .build() + .expect("Failed to build reflection service"); + + let reflection_service_clone = reflection_service.clone(); + + let tonic_server = Server::builder() .http2_keepalive_interval(Some(std::time::Duration::from_secs(60))) - .http2_keepalive_timeout(Some(std::time::Duration::from_secs(5))); + .http2_keepalive_timeout(Some(std::time::Duration::from_secs(5))) + .add_service(reflection_service_clone); let router = match use_data_service_interface { false => tonic_server.add_service(FullnodeDataServer::new(server)), diff --git a/ecosystem/indexer-grpc/indexer-grpc-server-framework/src/lib.rs b/ecosystem/indexer-grpc/indexer-grpc-server-framework/src/lib.rs index 3594365add9b18..83c8bbe0ea628a 100644 --- a/ecosystem/indexer-grpc/indexer-grpc-server-framework/src/lib.rs +++ b/ecosystem/indexer-grpc/indexer-grpc-server-framework/src/lib.rs @@ -24,7 +24,7 @@ impl ServerArgs { C: RunnableConfig, { // Set up the server. - setup_logging(); + setup_logging(None); setup_panic_handler(); let config = load::>(&self.config_path)?; config @@ -148,20 +148,28 @@ fn handle_panic(panic_info: &PanicInfo<'_>) { process::exit(12); } -/// Set up logging for the server. -pub fn setup_logging() { +/// Set up logging for the server. By default we don't set a writer, in which case it +/// just logs to stdout. This can be overridden using the `make_writer` parameter. +/// This can be helpful for custom logging, e.g. logging to different files based on +/// the origin of the logging. +pub fn setup_logging(make_writer: Option Box + Send + Sync>>) { let env_filter = EnvFilter::try_from_default_env() .or_else(|_| EnvFilter::try_new("info")) .unwrap(); - tracing_subscriber::fmt() + + let subscriber = tracing_subscriber::fmt() .json() .with_file(true) .with_line_number(true) .with_thread_ids(true) .with_target(false) .with_thread_names(true) - .with_env_filter(env_filter) - .init(); + .with_env_filter(env_filter); + + match make_writer { + Some(w) => subscriber.with_writer(w).init(), + None => subscriber.init(), + } } /// Register readiness and liveness probes and set up metrics endpoint. diff --git a/ecosystem/indexer-grpc/indexer-grpc-utils/src/lib.rs b/ecosystem/indexer-grpc/indexer-grpc-utils/src/lib.rs index 26e97ad17462b7..fb1912f15a0d39 100644 --- a/ecosystem/indexer-grpc/indexer-grpc-utils/src/lib.rs +++ b/ecosystem/indexer-grpc/indexer-grpc-utils/src/lib.rs @@ -8,10 +8,12 @@ pub mod file_store_operator; pub mod types; use aptos_protos::{ + indexer::v1::raw_data_client::RawDataClient, internal::fullnode::v1::fullnode_data_client::FullnodeDataClient, transaction::v1::Transaction, util::timestamp::Timestamp, }; use prost::Message; +use std::time::Duration; use url::Url; pub type GrpcClientType = FullnodeDataClient; @@ -41,6 +43,41 @@ pub async fn create_grpc_client(address: Url) -> GrpcClientType { .unwrap() } +pub type GrpcDataServiceClientType = RawDataClient; + +/// Create a gRPC client for the indexer data service with exponential backoff. +/// max_elapsed_time is the maximum time to wait for the connection to be established. +pub async fn create_data_service_grpc_client( + address: Url, + max_elapsed_time: Option, +) -> GrpcDataServiceClientType { + let mut backoff = backoff::ExponentialBackoff::default(); + if let Some(max_elapsed_time) = max_elapsed_time { + backoff.max_elapsed_time = Some(max_elapsed_time); + } + backoff::future::retry(backoff, || async { + match RawDataClient::connect(address.to_string()).await { + Ok(client) => { + tracing::info!( + address = address.to_string(), + "[Indexer Cache] Connected to indexer data service gRPC server." + ); + Ok(client) + }, + Err(e) => { + tracing::error!( + address = address.to_string(), + "[Indexer Cache] Failed to connect to indexer data service gRPC server: {}", + e + ); + Err(backoff::Error::transient(e)) + }, + } + }) + .await + .unwrap() +} + // (Protobuf encoded transaction, version) pub type EncodedTransactionWithVersion = (String, u64); /// Build the EncodedTransactionWithVersion from the encoded transactions and starting version. diff --git a/ecosystem/node-checker/ts-client/README.md b/ecosystem/node-checker/ts-client/README.md index 3413cdd39b6bc0..f8fd73808c94c5 100644 --- a/ecosystem/node-checker/ts-client/README.md +++ b/ecosystem/node-checker/ts-client/README.md @@ -48,7 +48,7 @@ pnpm generate-client ### Running tests Run a local node (run from the root of the repo): ``` -cargo run -p aptos -- node run-local-testnet --with-faucet --faucet-port 8081 --force-restart --assume-yes +cargo run -p aptos -- node run-local-testnet --faucet-port 8081 --force-restart --assume-yes ``` Run a local Node Health Checker: diff --git a/ecosystem/python/sdk/README.md b/ecosystem/python/sdk/README.md index 25d67e5442ef83..814468aeae25fc 100644 --- a/ecosystem/python/sdk/README.md +++ b/ecosystem/python/sdk/README.md @@ -33,7 +33,7 @@ We of course allow you to do this a bit more manually by: First, run a local testnet (run this from the root of aptos-core): ```bash -cargo run -p aptos -- node run-local-testnet --with-faucet --faucet-port 8081 --force-restart --assume-yes +cargo run -p aptos -- node run-local-testnet --force-restart --assume-yes ``` Next, tell the end-to-end tests to talk to this locally running testnet: diff --git a/ecosystem/typescript/sdk/README.md b/ecosystem/typescript/sdk/README.md index a14bbf1f943f47..5c44918f1c0738 100644 --- a/ecosystem/typescript/sdk/README.md +++ b/ecosystem/typescript/sdk/README.md @@ -73,7 +73,7 @@ To develop in a local environment, you need to use the SDK from the [main](https Run a local node (run from the root of the [repo](https://github.com/aptos-labs/aptos-core/)): ```ts -cargo run -p aptos -- node run-local-testnet --with-faucet --faucet-port 8081 --force-restart --assume-yes +cargo run -p aptos -- node run-local-testnet --force-restart --assume-yes ``` ## Contributing