diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 0000000..90eac8d --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,6 @@ +coverage: + status: + project: + default: + target: 70% + threshold: 5% \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 87beb42..58125bb 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -126,9 +126,10 @@ jobs: cargo tarpaulin --out lcov --features k8s_tests - name: Upload to codecov.io - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: token: ${{secrets.CODECOV_TOKEN}} + codecov_yml_path: .github/codecov.yml build-publish-release: runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index 70c8e73..d4808eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -507,8 +507,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chainhook-types" -version = "1.3.3" -source = "git+https://github.com/hirosystems/chainhook.git?branch=chore/update-clarinet-and-clarity#160fd65a66a679efa4cf19cbe33c685eebaa9c2d" +version = "1.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63f0d358e5c530dd6888e4678ef4ba3f7782525653a1012d33e96a48020c418d" dependencies = [ "hex", "schemars", @@ -600,8 +601,8 @@ dependencies = [ [[package]] name = "clarinet-deployments" -version = "2.4.0" -source = "git+https://github.com/hirosystems/clarinet.git?rev=a5f9fea72230b893a7d1f90bdfda3a68aa48e908#a5f9fea72230b893a7d1f90bdfda3a68aa48e908" +version = "2.6.0" +source = "git+https://github.com/hirosystems/clarinet.git?rev=f06d087c5762fa06f7080b5c38994e7437048222#f06d087c5762fa06f7080b5c38994e7437048222" dependencies = [ "base58 0.2.0", "base64 0.21.4", @@ -618,14 +619,15 @@ dependencies = [ "serde_derive", "serde_json", "serde_yaml 0.8.26", + "stacks-codec", "stacks-rpc-client", "tiny-hderive", ] [[package]] name = "clarinet-files" -version = "2.4.0" -source = "git+https://github.com/hirosystems/clarinet.git?rev=a5f9fea72230b893a7d1f90bdfda3a68aa48e908#a5f9fea72230b893a7d1f90bdfda3a68aa48e908" +version = "2.6.0" +source = "git+https://github.com/hirosystems/clarinet.git?rev=f06d087c5762fa06f7080b5c38994e7437048222#f06d087c5762fa06f7080b5c38994e7437048222" dependencies = [ "bip39", "bitcoin", @@ -644,7 +646,7 @@ dependencies = [ [[package]] name = "clarinet-utils" version = "1.0.0" -source = "git+https://github.com/hirosystems/clarinet.git?rev=a5f9fea72230b893a7d1f90bdfda3a68aa48e908#a5f9fea72230b893a7d1f90bdfda3a68aa48e908" +source = "git+https://github.com/hirosystems/clarinet.git?rev=f06d087c5762fa06f7080b5c38994e7437048222#f06d087c5762fa06f7080b5c38994e7437048222" dependencies = [ "hmac 0.12.1", "pbkdf2", @@ -675,8 +677,8 @@ dependencies = [ [[package]] name = "clarity-repl" -version = "2.4.0" -source = "git+https://github.com/hirosystems/clarinet.git?rev=a5f9fea72230b893a7d1f90bdfda3a68aa48e908#a5f9fea72230b893a7d1f90bdfda3a68aa48e908" +version = "2.6.0" +source = "git+https://github.com/hirosystems/clarinet.git?rev=f06d087c5762fa06f7080b5c38994e7437048222#f06d087c5762fa06f7080b5c38994e7437048222" dependencies = [ "ansi_term", "atty", @@ -687,7 +689,7 @@ dependencies = [ "debug_types", "futures", "getrandom 0.2.10", - "hiro-system-kit 0.1.0 (git+https://github.com/hirosystems/clarinet.git?rev=a5f9fea72230b893a7d1f90bdfda3a68aa48e908)", + "hiro-system-kit 0.1.0 (git+https://github.com/hirosystems/clarinet.git?rev=f06d087c5762fa06f7080b5c38994e7437048222)", "httparse", "integer-sqrt", "lazy_static", @@ -705,7 +707,6 @@ dependencies = [ "sha2 0.10.7", "tokio", "tokio-util", - "wsts", ] [[package]] @@ -1642,11 +1643,12 @@ dependencies = [ [[package]] name = "hiro-system-kit" version = "0.1.0" -source = "git+https://github.com/hirosystems/clarinet.git?rev=a5f9fea72230b893a7d1f90bdfda3a68aa48e908#a5f9fea72230b893a7d1f90bdfda3a68aa48e908" +source = "git+https://github.com/hirosystems/clarinet.git?rev=f06d087c5762fa06f7080b5c38994e7437048222#f06d087c5762fa06f7080b5c38994e7437048222" dependencies = [ "ansi_term", "atty", "lazy_static", + "time", "tokio", ] @@ -3238,8 +3240,9 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.12" -source = "git+https://github.com/hirosystems/schemars.git?branch=feat-chainhook-fixes#15fdd4711700114d57c090aad62516593bd4ca6d" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f55c82c700538496bdc329bb4918a81f87cc8888811bd123cf325a0f2f8d309" dependencies = [ "dyn-clone", "schemars_derive", @@ -3249,13 +3252,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.12" -source = "git+https://github.com/hirosystems/schemars.git?branch=feat-chainhook-fixes#15fdd4711700114d57c090aad62516593bd4ca6d" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83263746fe5e32097f06356968a077f96089739c927a61450efa069905eec108" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 1.0.109", + "syn 2.0.53", ] [[package]] @@ -3342,13 +3346,13 @@ dependencies = [ [[package]] name = "serde_derive_internals" -version = "0.26.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.53", ] [[package]] @@ -3672,6 +3676,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "stacks-codec" +version = "2.6.0" +source = "git+https://github.com/hirosystems/clarinet.git?rev=f06d087c5762fa06f7080b5c38994e7437048222#f06d087c5762fa06f7080b5c38994e7437048222" +dependencies = [ + "clarity", + "serde", + "wsts", +] + [[package]] name = "stacks-common" version = "0.0.2" @@ -3704,7 +3718,7 @@ dependencies = [ [[package]] name = "stacks-devnet-api" -version = "1.1.0" +version = "1.2.0" dependencies = [ "chainhook-types", "clarinet-deployments", @@ -3732,10 +3746,9 @@ dependencies = [ [[package]] name = "stacks-rpc-client" -version = "2.4.0" -source = "git+https://github.com/hirosystems/clarinet.git?rev=a5f9fea72230b893a7d1f90bdfda3a68aa48e908#a5f9fea72230b893a7d1f90bdfda3a68aa48e908" +version = "2.6.0" +source = "git+https://github.com/hirosystems/clarinet.git?rev=f06d087c5762fa06f7080b5c38994e7437048222#f06d087c5762fa06f7080b5c38994e7437048222" dependencies = [ - "clarity-repl", "hmac 0.12.1", "libsecp256k1 0.7.1", "pbkdf2", @@ -3744,6 +3757,7 @@ dependencies = [ "serde_derive", "serde_json", "sha2 0.10.7", + "stacks-codec", "tiny-hderive", ] diff --git a/Cargo.toml b/Cargo.toml index c80e435..2badd20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stacks-devnet-api" -version = "1.1.0" +version = "1.2.0" edition = "2021" authors = ["Micaiah Reid "] description = "The Stacks Devnet API runs a server that can be used to deploy, delete, manage, and make requests to Stacks Devnets run on Kubernetes." @@ -27,15 +27,15 @@ strum_macros = "0.24.3" strum = "0.24.1" # clarity-repl = "1.8.0" # clarity-repl = {version = "2.2.0", path = "../clarinet/components/clarity-repl" } -clarity-repl = {version = "2.3.1", git = "https://github.com/hirosystems/clarinet.git", rev="a5f9fea72230b893a7d1f90bdfda3a68aa48e908" } +clarity-repl = {version = "2.3.1", git = "https://github.com/hirosystems/clarinet.git", rev="f06d087c5762fa06f7080b5c38994e7437048222" } # clarinet-files = {version = "1.0.3" } # clarinet-files = {version = "2.2.0", path = "../clarinet/components/clarinet-files" } -clarinet-files = {version = "2.3.1", git = "https://github.com/hirosystems/clarinet.git", rev="a5f9fea72230b893a7d1f90bdfda3a68aa48e908" } +clarinet-files = {version = "2.3.1", git = "https://github.com/hirosystems/clarinet.git", rev="f06d087c5762fa06f7080b5c38994e7437048222" } # clarinet-deployments = {version = "1.0.3" } # clarinet-deployments = {version = "2.2.0", path = "../clarinet/components/clarinet-deployments" } -clarinet-deployments = {version = "2.3.1", git = "https://github.com/hirosystems/clarinet.git", rev="a5f9fea72230b893a7d1f90bdfda3a68aa48e908" } +clarinet-deployments = {version = "2.3.1", git = "https://github.com/hirosystems/clarinet.git", rev="f06d087c5762fa06f7080b5c38994e7437048222" } # chainhook-types = "1.0" -chainhook-types = { version = "1.3", git = "https://github.com/hirosystems/chainhook.git", branch="chore/update-clarinet-and-clarity" } +chainhook-types = { version = "1.3" } toml = "0.5.9" [dev-dependencies] diff --git a/Dockerfile b/Dockerfile index f046520..c9192d1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN rustup component add rustfmt RUN cargo build --release --manifest-path ./Cargo.toml RUN cp target/release/stacks-devnet-api /out -FROM gcr.io/distroless/cc +FROM gcr.io/distroless/cc-debian11 COPY --from=builder /out/ /bin/ CMD ["stacks-devnet-api"] \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index a1a7017..db39f8a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -37,7 +37,7 @@ impl StacksDevnetConfig { pub fn to_validated_config( self, user_id: &str, - ctx: Context, + ctx: &Context, ) -> Result { let context = format!( "failed to validate config for NAMESPACE: {}", @@ -232,7 +232,7 @@ mod tests { let _guard = hiro_system_kit::log::setup_global_logger(logger.clone()); let ctx = Context::empty(); let validated_config = template - .to_validated_config(user_id, ctx) + .to_validated_config(user_id, &ctx) .unwrap_or_else(|e| panic!("config validation test failed: {}", e.message)); let expected_project_manifest = read_file("src/tests/fixtures/project-manifest.yaml"); @@ -278,7 +278,7 @@ mod tests { template.network_manifest.devnet = None; let user_id = template.clone().namespace; template - .to_validated_config(&user_id, ctx) + .to_validated_config(&user_id, &ctx) .unwrap_or_else(|e| panic!("config validation test failed: {}", e.message)); } @@ -287,7 +287,7 @@ mod tests { let template = get_template_config("src/tests/fixtures/stacks-devnet-config.json"); let namespace = template.namespace.clone(); let user_id = "wrong"; - match template.to_validated_config(user_id, Context::empty()) { + match template.to_validated_config(user_id, &Context::empty()) { Ok(_) => { panic!("config validation with non-matching user_id should have been rejected") } diff --git a/src/lib.rs b/src/lib.rs index a34b47b..e3d5e90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,5 @@ use chainhook_types::StacksNetwork; -use clarinet_files::{ - compute_addresses, DEFAULT_STACKS_API_IMAGE_NAKA, DEFAULT_STACKS_NODE_IMAGE_NAKA, -}; +use clarinet_files::compute_addresses; use futures::future::try_join4; use hiro_system_kit::{slog, Logger}; use hyper::{body::Bytes, Body, Client as HttpClient, Request, Response, Uri}; @@ -223,21 +221,19 @@ impl StacksDevnetApiK8sManager { sleep(Duration::from_secs(5)); self.deploy_stacks_blockchain(&config).await?; - if config.devnet_config.use_nakamoto { - self.deploy_stacks_signer( - &config, - SignerIdx::Signer0, - "7287ba251d44a4d3fd9276c88ce34c5c52a038955511cccaf77e61068649c17801", - ) - .await?; + self.deploy_stacks_signer( + &config, + SignerIdx::Signer0, + "7287ba251d44a4d3fd9276c88ce34c5c52a038955511cccaf77e61068649c17801", + ) + .await?; - self.deploy_stacks_signer( - &config, - SignerIdx::Signer1, - "530d9f61984c888536871c6573073bdfc0058896dc1adfe9a6a10dfacadc209101", - ) - .await?; - } + self.deploy_stacks_signer( + &config, + SignerIdx::Signer1, + "530d9f61984c888536871c6573073bdfc0058896dc1adfe9a6a10dfacadc209101", + ) + .await?; if !config.disable_stacks_api { self.deploy_stacks_blockchain_api(&config).await?; @@ -269,23 +265,6 @@ impl StacksDevnetApiK8sManager { .collect(); for stateful_set in stateful_sets { - // todo: remove once Clarinet removes `use_nakamoto` - // becuase the devnet api is stateless, we don't have access to the config - // that was used to create the devnet originally. So, we don't know if this - // devnet has the nakamoto assets deployed or not. - // So when deleting the signer assets, just skip instead of erroring if they don't exist - if &stateful_set.as_str() == &"stacks-signer-0" - || &stateful_set.as_str() == &"stacks-signer-1" - { - match self - .check_resource_exists::(namespace, &stateful_set) - .await - { - Ok(true) | Err(_) => {} - Ok(false) => continue, - } - }; - if let Err(e) = self .delete_resource::(namespace, &stateful_set) .await @@ -298,19 +277,6 @@ impl StacksDevnetApiK8sManager { .map(|c| c.to_string()) .collect(); for configmap in configmaps { - // todo: remove once Clarinet removes `use_nakamoto` - if &configmap.as_str() == &"stacks-signer-0" - || &configmap.as_str() == &"stacks-signer-1" - { - match self - .check_resource_exists::(namespace, &configmap) - .await - { - Ok(true) | Err(_) => {} - Ok(false) => continue, - } - }; - if let Err(e) = self .delete_resource::(namespace, &configmap) .await @@ -322,18 +288,6 @@ impl StacksDevnetApiK8sManager { let services: Vec = StacksDevnetService::iter().map(|s| s.to_string()).collect(); for service in services { - // todo: remove once Clarinet removes `use_nakamoto` - if &service.as_str() == &"stacks-signer-0" - || &service.as_str() == &"stacks-signer-1" - { - match self - .check_resource_exists::(namespace, &service) - .await - { - Ok(true) | Err(_) => {} - Ok(false) => continue, - } - }; if let Err(e) = self.delete_resource::(namespace, &service).await { errors.push(e); } @@ -342,18 +296,6 @@ impl StacksDevnetApiK8sManager { let pvcs: Vec = StacksDevnetPvc::iter().map(|pvc| pvc.to_string()).collect(); for pvc in pvcs { - // todo: remove once Clarinet removes `use_nakamoto` - if &pvc.as_str() == &"stacks-signer-0" || &pvc.as_str() == &"stacks-signer-1" { - match self - .check_resource_exists_by_label::( - namespace, &pvc, user_id, - ) - .await - { - Ok(true) | Err(_) => {} - Ok(false) => continue, - } - }; if let Err(e) = self .delete_resource_by_label::(namespace, &pvc, user_id) .await @@ -455,13 +397,8 @@ impl StacksDevnetApiK8sManager { return Ok(true); } } - // todo: we are not checking for assets that are only deployed if use_nakamoto is true (remove filter once `use_nakamoto` is no longer around) - for stateful_set in StacksDevnetStatefulSet::iter().filter(|sts| match sts { - StacksDevnetStatefulSet::StacksSigner0 | StacksDevnetStatefulSet::StacksSigner1 => { - false - } - _ => true, - }) { + + for stateful_set in StacksDevnetStatefulSet::iter() { if self .check_resource_exists::(namespace, &stateful_set.to_string()) .await? @@ -469,11 +406,8 @@ impl StacksDevnetApiK8sManager { return Ok(true); } } - // todo: remove filter when nakamoto is stable - for pod in StacksDevnetPod::iter().filter(|pod| match pod { - StacksDevnetPod::StacksSigner0 | StacksDevnetPod::StacksSigner1 => false, - _ => true, - }) { + + for pod in StacksDevnetPod::iter() { if self .check_resource_exists_by_label::(namespace, &pod.to_string(), user_id) .await? @@ -482,11 +416,7 @@ impl StacksDevnetApiK8sManager { } } - // todo: remove filter when nakamoto is stable - for configmap in StacksDevnetConfigmap::iter().filter(|c| match c { - StacksDevnetConfigmap::StacksSigner0 | StacksDevnetConfigmap::StacksSigner1 => false, - _ => true, - }) { + for configmap in StacksDevnetConfigmap::iter() { if self .check_resource_exists::(namespace, &configmap.to_string()) .await? @@ -495,11 +425,7 @@ impl StacksDevnetApiK8sManager { } } - // todo: remove filter when nakamoto is stable - for service in StacksDevnetService::iter().filter(|svc| match svc { - StacksDevnetService::StacksSigner0 | StacksDevnetService::StacksSigner1 => false, - _ => true, - }) { + for service in StacksDevnetService::iter() { if self .check_resource_exists::(namespace, &service.to_string()) .await? @@ -508,11 +434,7 @@ impl StacksDevnetApiK8sManager { } } - // todo: remove filter when nakamoto is stable - for pvc in StacksDevnetPvc::iter().filter(|pvc| match pvc { - StacksDevnetPvc::StacksSigner0 | StacksDevnetPvc::StacksSigner1 => false, - _ => true, - }) { + for pvc in StacksDevnetPvc::iter() { if self .check_resource_exists_by_label::( namespace, @@ -546,13 +468,8 @@ impl StacksDevnetApiK8sManager { return Ok(false); } } - // todo: we are not checking for assets that are only deployed if use_nakamoto is true (remove filter once `use_nakamoto` is no longer around) - for stateful_set in StacksDevnetStatefulSet::iter().filter(|sts| match sts { - StacksDevnetStatefulSet::StacksSigner0 | StacksDevnetStatefulSet::StacksSigner1 => { - false - } - _ => true, - }) { + + for stateful_set in StacksDevnetStatefulSet::iter() { if !self .check_resource_exists::(namespace, &stateful_set.to_string()) .await? @@ -561,11 +478,7 @@ impl StacksDevnetApiK8sManager { } } - // todo: remove filter when nakamoto is stable - for configmap in StacksDevnetConfigmap::iter().filter(|c| match c { - StacksDevnetConfigmap::StacksSigner0 | StacksDevnetConfigmap::StacksSigner1 => false, - _ => true, - }) { + for configmap in StacksDevnetConfigmap::iter() { if !self .check_resource_exists::(namespace, &configmap.to_string()) .await? @@ -574,11 +487,7 @@ impl StacksDevnetApiK8sManager { } } - // todo: remove filter when nakamoto is stable - for service in StacksDevnetService::iter().filter(|svc| match svc { - StacksDevnetService::StacksSigner0 | StacksDevnetService::StacksSigner1 => false, - _ => true, - }) { + for service in StacksDevnetService::iter() { if !self .check_resource_exists::(namespace, &service.to_string()) .await? @@ -1046,7 +955,6 @@ impl StacksDevnetApiK8sManager { deployment_type: StacksDevnetDeployment, namespace: &str, user_id: &str, - use_nakamoto: bool, ) -> Result<(), DevNetError> { let deployment_type_moved = deployment_type.clone(); let mut deployment: Deployment = @@ -1089,36 +997,6 @@ impl StacksDevnetApiK8sManager { template.metadata = Some(metadata); } - if use_nakamoto { - match &deployment_type { - StacksDevnetDeployment::StacksBlockchain => { - if let Some(mut pod_spec) = template.spec { - for container in &mut pod_spec.containers { - if container.name == "stacks-blockchain" { - container.image = - Some(DEFAULT_STACKS_NODE_IMAGE_NAKA.to_owned()); - } - } - template.spec = Some(pod_spec); - } else { - let msg = format!( - "failed to set nakamoto image for RESOURCE: deployment, NAME: {}, NAMESPACE: {}", - deployment_type, namespace - ); - self.ctx.try_log(|logger| slog::error!(logger, "{}", msg)); - return Err(DevNetError { - message: msg, - code: 500, - }); - } - } - // note: initially the plan was to conditionally upgrade the version of clarinet we're using - // however, when the platform sends us a config from `clarinet devnet package`, it will contain - // new fields that the devnet needs to handle; if we have multiple clarinet versions here, - // we need multiple clarinet versions at the platform level, and multiple config types - StacksDevnetDeployment::BitcoindNode => {} - } - } spec.template = template; deployment.spec = Some(spec); @@ -1134,7 +1012,6 @@ impl StacksDevnetApiK8sManager { stateful_set_type: StacksDevnetStatefulSet, namespace: &str, user_id: &str, - use_nakamoto: bool, ) -> Result<(), DevNetError> { let stateful_set_type_moved = stateful_set_type.clone(); let mut stateful_set: StatefulSet = self @@ -1174,30 +1051,6 @@ impl StacksDevnetApiK8sManager { template.metadata = Some(metadata); } - if use_nakamoto { - match &stateful_set_type { - StacksDevnetStatefulSet::StacksBlockchainApi => { - if let Some(mut pod_spec) = template.spec { - for container in &mut pod_spec.containers { - if container.name == "stacks-blockchain-api" { - container.image = - Some(DEFAULT_STACKS_API_IMAGE_NAKA.to_owned()); - } - } - template.spec = Some(pod_spec); - } else { - let msg = format!("failed to set nakamoto image for RESOURCE: deployment, NAME: {}, NAMESPACE: {}", stateful_set_type, namespace); - self.ctx.try_log(|logger| slog::error!(logger, "{}", msg)); - return Err(DevNetError { - message: msg, - code: 500, - }); - } - } - StacksDevnetStatefulSet::StacksSigner0 - | StacksDevnetStatefulSet::StacksSigner1 => {} - } - } spec.template = template; stateful_set.spec = Some(spec); } @@ -1354,13 +1207,8 @@ impl StacksDevnetApiK8sManager { ) .await?; - self.deploy_deployment( - StacksDevnetDeployment::BitcoindNode, - &namespace, - &user_id, - config.devnet_config.use_nakamoto, - ) - .await?; + self.deploy_deployment(StacksDevnetDeployment::BitcoindNode, &namespace, &user_id) + .await?; self.deploy_service(StacksDevnetService::BitcoindNode, namespace, &user_id) .await?; @@ -1395,11 +1243,13 @@ impl StacksDevnetApiK8sManager { data_url = "http://127.0.0.1:{}" p2p_address = "127.0.0.1:{}" miner = true + stacker = true seed = "{}" local_peer_seed = "{}" pox_sync_sample_secs = 0 wait_time_for_blocks = 0 wait_time_for_microblocks = 0 + next_initiative_delay = 4000 mine_microblocks = false microblock_frequency = 1000 @@ -1420,7 +1270,6 @@ impl StacksDevnetApiK8sManager { block_reward_recipient = "{}" wait_for_block_download = false microblock_attempt_time_ms = 10 - self_signing_seed = 1 mining_key = "19ec1c3e31d139c989a23a27eac60d1abfad5277d3ae9604242514c738258efa01" "#, get_service_port(StacksDevnetService::StacksBlockchain, ServicePort::RPC).unwrap(), @@ -1484,29 +1333,22 @@ impl StacksDevnetApiK8sManager { .unwrap(), )); - if devnet_config.use_nakamoto { - for signer_idx in SignerIdx::iter() { - let (url, port) = match signer_idx { - SignerIdx::Signer0 => ( - get_service_url(&namespace, StacksDevnetService::StacksSigner0), - get_service_port( - StacksDevnetService::StacksSigner0, - ServicePort::Event, - ) + for signer_idx in SignerIdx::iter() { + let (url, port) = match signer_idx { + SignerIdx::Signer0 => ( + get_service_url(&namespace, StacksDevnetService::StacksSigner0), + get_service_port(StacksDevnetService::StacksSigner0, ServicePort::Event) .unwrap(), - ), - SignerIdx::Signer1 => ( - get_service_url(&namespace, StacksDevnetService::StacksSigner1), - get_service_port( - StacksDevnetService::StacksSigner1, - ServicePort::Event, - ) + ), + SignerIdx::Signer1 => ( + get_service_url(&namespace, StacksDevnetService::StacksSigner1), + get_service_port(StacksDevnetService::StacksSigner1, ServicePort::Event) .unwrap(), - ), - }; + ), + }; - stacks_conf.push_str(&format!( - r#" + stacks_conf.push_str(&format!( + r#" # Add stacks-signer-{} as an event observer [[events_observer]] endpoint = "{}:{}" @@ -1514,21 +1356,21 @@ impl StacksDevnetApiK8sManager { include_data_events = false events_keys = ["stackerdb", "block_proposal", "burn_blocks"] "#, - signer_idx.to_string(), - url, - port, - )); - } + signer_idx.to_string(), + url, + port, + )); } stacks_conf.push_str(&format!( r#" [burnchain] chain = "bitcoin" - mode = "{}" + mode = "nakamoto-neon" magic_bytes = "T3" - pox_prepare_length = 4 - pox_reward_length = 10 + first_burn_block_height = 100 + pox_prepare_length = 5 + pox_reward_length = 20 burn_fee_cap = 20_000 poll_time_secs = 1 timeout = 30 @@ -1540,11 +1382,6 @@ impl StacksDevnetApiK8sManager { rpc_port = {} peer_port = {} "#, - if devnet_config.use_nakamoto { - "nakamoto-neon" - } else { - "krypton" - }, bitcoind_chain_coordinator_host, devnet_config.miner_wallet_name, devnet_config.bitcoin_node_username, @@ -1582,27 +1419,24 @@ impl StacksDevnetApiK8sManager { [[burnchain.epochs]] epoch_name = "2.4" start_height = {} + + [[burnchain.epochs]] + epoch_name = "2.5" + start_height = {} + + [[burnchain.epochs]] + epoch_name = "3.0" + start_height = {} "#, devnet_config.epoch_2_0, devnet_config.epoch_2_05, devnet_config.epoch_2_1, devnet_config.epoch_2_2, devnet_config.epoch_2_3, - devnet_config.epoch_2_4 + devnet_config.epoch_2_4, + devnet_config.epoch_2_5, + devnet_config.epoch_3_0, )); - if devnet_config.use_nakamoto { - stacks_conf.push_str(&format!( - r#" - [[burnchain.epochs]] - epoch_name = "2.5" - start_height = {} - [[burnchain.epochs]] - epoch_name = "3.0" - start_height = {} - "#, - devnet_config.epoch_2_5, devnet_config.epoch_3_0, - )); - } stacks_conf }; @@ -1617,7 +1451,6 @@ impl StacksDevnetApiK8sManager { StacksDevnetDeployment::StacksBlockchain, &namespace, &user_id, - config.devnet_config.use_nakamoto, ) .await?; @@ -1685,7 +1518,6 @@ impl StacksDevnetApiK8sManager { StacksDevnetStatefulSet::StacksBlockchainApi, &namespace, user_id, - config.devnet_config.use_nakamoto, ) .await?; @@ -1757,8 +1589,7 @@ impl StacksDevnetApiK8sManager { ) .await?; - self.deploy_stateful_set(sts, &namespace, user_id, config.devnet_config.use_nakamoto) - .await?; + self.deploy_stateful_set(sts, &namespace, user_id).await?; self.deploy_service(service, &namespace, &user_id).await?; @@ -1876,7 +1707,7 @@ impl StacksDevnetApiK8sManager { namespace.map_left(|del| { assert_eq!(del.name_any(), namespace_str); self.ctx - .try_log(|logger| slog::error!(logger, "Deleting namespace started")); + .try_log(|logger| slog::info!(logger, "Deleting namespace started")); }); Ok(()) } diff --git a/src/main.rs b/src/main.rs index 491eebb..584575b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,14 +8,17 @@ use stacks_devnet_api::routes::{ handle_get_status, handle_new_devnet, handle_try_proxy_service, API_PATH, }; use stacks_devnet_api::{Context, StacksDevnetApiK8sManager}; +use std::collections::HashMap; use std::env; +use std::sync::{Arc, Mutex}; +use std::time::{SystemTime, UNIX_EPOCH}; use std::{convert::Infallible, net::SocketAddr}; #[tokio::main] async fn main() { const HOST: &str = "0.0.0.0"; - const PORT: &str = "8477"; - let endpoint: String = HOST.to_owned() + ":" + PORT; + let port: &str = &env::var("PORT").unwrap_or("8477".to_string()); + let endpoint: String = HOST.to_owned() + ":" + port; let addr: SocketAddr = endpoint.parse().expect("Could not parse ip:port."); let logger = hiro_system_kit::log::setup_logger(); @@ -36,14 +39,22 @@ async fn main() { } }; let config = ApiConfig::from_path(&config_path); + let request_store = Arc::new(Mutex::new(HashMap::new())); let make_svc = make_service_fn(|_| { let k8s_manager = k8s_manager.clone(); let ctx = ctx.clone(); let config = config.clone(); + let request_store = request_store.clone(); async move { Ok::<_, Infallible>(service_fn(move |req| { - handle_request(req, k8s_manager.clone(), config.clone(), ctx.clone()) + handle_request( + req, + k8s_manager.clone(), + config.clone(), + request_store.clone(), + ctx.clone(), + ) })) } }); @@ -64,6 +75,7 @@ async fn handle_request( http_response_config, auth_config, }: ApiConfig, + request_store: Arc>>, ctx: Context, ) -> Result, Infallible> { let uri = request.uri(); @@ -109,10 +121,23 @@ async fn handle_request( None => return responder.err_bad_request("missing required auth header".into()), }; + let request_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Could not get current time in secs") + .as_secs() as u64; if path == "/api/v1/networks" { return match method { &Method::POST => { - handle_new_devnet(request, &user_id, k8s_manager, responder, ctx).await + handle_new_devnet( + request, + &user_id, + k8s_manager, + responder, + request_store, + request_time, + &ctx, + ) + .await } _ => responder.err_method_not_allowed("network creation must be a POST request".into()), }; @@ -149,10 +174,25 @@ async fn handle_request( if path_parts.subroute.is_none() { return match method { &Method::DELETE => { + match request_store.lock() { + Ok(mut store) => { + store.remove(&user_id); + } + Err(_) => {} + } handle_delete_devnet(k8s_manager, &network, &user_id, responder).await } &Method::GET => { - handle_get_devnet(k8s_manager, &network, &user_id, responder, ctx).await + handle_get_devnet( + k8s_manager, + &network, + &user_id, + responder, + request_store, + request_time, + ctx, + ) + .await } &Method::HEAD => { handle_check_devnet(k8s_manager, &network, &user_id, responder).await @@ -161,6 +201,16 @@ async fn handle_request( .err_method_not_allowed("can only GET/DELETE/HEAD at provided route".into()), }; } + // the above methods with no subroute are initiated from our infra, + // but any remaning requests would come from the actual user, so we'll + // track this request as the last time a user made a request + match request_store.lock() { + Ok(mut store) => { + store.insert(user_id.to_string(), request_time); + } + Err(_) => {} + } + let subroute = path_parts.subroute.unwrap(); if subroute == "commands" { return responder.err_not_implemented("commands route in progress".into()); diff --git a/src/routes.rs b/src/routes.rs index a940e8a..1ad6913 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -1,13 +1,19 @@ use hiro_system_kit::slog; use hyper::{Body, Client, Request, Response, Uri}; +use serde::{Deserialize, Serialize}; use serde_json::json; -use std::{convert::Infallible, str::FromStr}; +use std::{ + collections::HashMap, + convert::Infallible, + str::FromStr, + sync::{Arc, Mutex}, +}; use crate::{ config::StacksDevnetConfig, resources::service::{get_service_from_path_part, get_service_url, get_user_facing_port}, responder::Responder, - Context, StacksDevnetApiK8sManager, + Context, StacksDevnetApiK8sManager, StacksDevnetInfoResponse, }; const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -36,7 +42,9 @@ pub async fn handle_new_devnet( user_id: &str, k8s_manager: StacksDevnetApiK8sManager, responder: Responder, - ctx: Context, + request_store: Arc>>, + request_time: u64, + ctx: &Context, ) -> Result, Infallible> { let body = hyper::body::to_bytes(request.into_body()).await; if body.is_err() { @@ -49,7 +57,15 @@ pub async fn handle_new_devnet( match config { Ok(config) => match config.to_validated_config(user_id, ctx) { Ok(config) => match k8s_manager.deploy_devnet(config).await { - Ok(_) => responder.ok(), + Ok(_) => { + match request_store.lock() { + Ok(mut store) => { + store.insert(user_id.to_string(), request_time); + } + Err(_) => {} + } + responder.ok() + } Err(e) => responder.respond(e.code, e.message), }, Err(e) => responder.respond(e.code, e.message), @@ -75,26 +91,58 @@ pub async fn handle_delete_devnet( } } +#[derive(Serialize, Deserialize, Debug)] +pub struct DevnetMetadata { + pub secs_since_last_request: u64, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct StacksDevnetInfoWithMetadata { + #[serde(flatten)] + pub data: StacksDevnetInfoResponse, + pub metadata: DevnetMetadata, +} + pub async fn handle_get_devnet( k8s_manager: StacksDevnetApiK8sManager, network: &str, user_id: &str, responder: Responder, + request_store: Arc>>, + request_time: u64, ctx: Context, ) -> Result, Infallible> { match k8s_manager.get_devnet_info(&network, user_id).await { - Ok(devnet_info) => match serde_json::to_vec(&devnet_info) { - Ok(body) => responder.ok_with_json(Body::from(body)), - Err(e) => { - let msg = format!( - "failed to form response body: NAMESPACE: {}, ERROR: {}", - &network, - e.to_string() - ); - ctx.try_log(|logger: &hiro_system_kit::Logger| slog::error!(logger, "{}", msg)); - responder.err_internal(msg) + Ok(devnet_info) => { + let last_request_time = match request_store.lock() { + Ok(mut store) => match store.get(user_id) { + Some(last_request_time) => *last_request_time, + None => { + store.insert(user_id.to_string(), request_time); + request_time + } + }, + Err(_) => 0, + }; + let devnet_info_with_metadata = StacksDevnetInfoWithMetadata { + data: devnet_info, + metadata: DevnetMetadata { + secs_since_last_request: request_time.saturating_sub(last_request_time), + }, + }; + match serde_json::to_vec(&devnet_info_with_metadata) { + Ok(body) => responder.ok_with_json(Body::from(body)), + Err(e) => { + let msg = format!( + "failed to form response body: NAMESPACE: {}, ERROR: {}", + &network, + e.to_string() + ); + ctx.try_log(|logger: &hiro_system_kit::Logger| slog::error!(logger, "{}", msg)); + responder.err_internal(msg) + } } - }, + } Err(e) => responder.respond(e.code, e.message), } } diff --git a/src/tests/fixtures/network-manifest.yaml b/src/tests/fixtures/network-manifest.yaml index 82d5ebe..1998a0b 100644 --- a/src/tests/fixtures/network-manifest.yaml +++ b/src/tests/fixtures/network-manifest.yaml @@ -181,4 +181,3 @@ devnet_settings: epoch_3_0: 121 use_docker_gateway_routing: false docker_platform: linux/amd64 - use_nakamoto: false diff --git a/src/tests/fixtures/project-manifest.yaml b/src/tests/fixtures/project-manifest.yaml index 127246e..2f08b5c 100644 --- a/src/tests/fixtures/project-manifest.yaml +++ b/src/tests/fixtures/project-manifest.yaml @@ -21,5 +21,3 @@ repl: trusted_sender: false trusted_caller: false callee_filter: false - clarity_wasm_mode: false - show_timings: false diff --git a/src/tests/fixtures/stacks-devnet-config.json b/src/tests/fixtures/stacks-devnet-config.json index d5913f2..25b1015 100644 --- a/src/tests/fixtures/stacks-devnet-config.json +++ b/src/tests/fixtures/stacks-devnet-config.json @@ -335,8 +335,7 @@ "epoch_2_5": 105, "epoch_3_0": 121, "use_docker_gateway_routing": false, - "docker_platform": "linux/amd64", - "use_nakamoto": false + "docker_platform": "linux/amd64" } }, "project_manifest": { diff --git a/src/tests/mod.rs b/src/tests/mod.rs index d37789a..87e7eba 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -20,7 +20,10 @@ use stacks_devnet_api::{ get_service_from_path_part, get_service_port, get_service_url, ServicePort, StacksDevnetService, }, - routes::{get_standardized_path_parts, mutate_request_for_proxy, PathParts}, + routes::{ + get_standardized_path_parts, mutate_request_for_proxy, PathParts, + StacksDevnetInfoWithMetadata, + }, StacksDevnetInfoResponse, }; use test_case::test_case; @@ -31,7 +34,7 @@ const PRJ_NAME: &str = env!("CARGO_PKG_NAME"); fn get_version_info() -> String { format!("{{\"version\":\"{PRJ_NAME} v{VERSION}\"}}") } -fn get_template_config(use_nakamoto: bool) -> StacksDevnetConfig { +fn get_template_config() -> StacksDevnetConfig { let file_path = "src/tests/fixtures/stacks-devnet-config.json"; let file = File::open(file_path) .unwrap_or_else(|e| panic!("unable to read file {}\n{:?}", file_path, e)); @@ -43,15 +46,7 @@ fn get_template_config(use_nakamoto: bool) -> StacksDevnetConfig { let config_file: StacksDevnetConfig = match serde_json::from_slice::(&file_buffer) { - Ok(mut s) => { - if use_nakamoto { - if let Some(mut devnet) = s.network_manifest.devnet { - devnet.use_nakamoto = true; - s.network_manifest.devnet = Some(devnet); - } - }; - s - } + Ok(s) => s, Err(e) => { panic!("Config file malformatted {}", e.to_string()); } @@ -62,7 +57,10 @@ fn get_template_config(use_nakamoto: bool) -> StacksDevnetConfig { async fn get_k8s_manager() -> (StacksDevnetApiK8sManager, Context) { let logger = hiro_system_kit::log::setup_logger(); let _guard = hiro_system_kit::log::setup_global_logger(logger.clone()); - let ctx = Context::empty(); + let ctx = Context { + logger: Some(logger), + tracer: false, + }; let k8s_manager = StacksDevnetApiK8sManager::new(&ctx).await; (k8s_manager, ctx) } @@ -121,13 +119,12 @@ enum TestBody { CreateNetwork, } -#[test_case("/api/v1/network/{namespace}", Method::DELETE, None, false, None => is equal_to (StatusCode::OK, "Ok".to_string()); "200 for network DELETE request")] -#[test_case("/api/v1/network/{namespace}", Method::DELETE, None, true, None => using assert_cannot_delete_devnet_multiple_errs; "500 for network DELETE request with multiple errors")] -#[test_case("/api/v1/networks", Method::POST, Some(TestBody::CreateNetwork), true, None => using assert_cannot_create_devnet_err; "409 for create network POST request if devnet exists")] -#[test_case("/api/v1/network/{namespace}", Method::GET, None, true, None => using assert_get_network; "200 for network GET request to existing network")] -#[test_case("/api/v1/network/{namespace}", Method::HEAD, None, true, None => is equal_to (StatusCode::OK, "Ok".to_string()); "200 for network HEAD request to existing network")] -#[test_case("/api/v1/network/{namespace}", Method::HEAD, None, true, Some(true) => is equal_to (StatusCode::OK, "Ok".to_string()); "200 for network HEAD request to existing network; use_nakamoto")] -#[test_case("/api/v1/network/{namespace}/stacks-blockchain/v2/info/", Method::GET, None, true, None => using assert_failed_proxy; "proxies requests to downstream nodes")] +#[test_case("/api/v1/network/{namespace}", Method::DELETE, None, false => is equal_to (StatusCode::OK, "Ok".to_string()); "200 for network DELETE request")] +#[test_case("/api/v1/network/{namespace}", Method::DELETE, None, true => using assert_cannot_delete_devnet_multiple_errs; "500 for network DELETE request with multiple errors")] +#[test_case("/api/v1/networks", Method::POST, Some(TestBody::CreateNetwork), true => using assert_cannot_create_devnet_err; "409 for create network POST request if devnet exists")] +#[test_case("/api/v1/network/{namespace}", Method::GET, None, true => using assert_get_network; "200 for network GET request to existing network")] +#[test_case("/api/v1/network/{namespace}", Method::HEAD, None, true => is equal_to (StatusCode::OK, "Ok".to_string()); "200 for network HEAD request to existing network")] +#[test_case("/api/v1/network/{namespace}/stacks-blockchain/v2/info/", Method::GET, None, true => using assert_failed_proxy; "proxies requests to downstream nodes")] #[serial_test::serial] #[tokio::test] #[cfg_attr(not(feature = "k8s_tests"), ignore)] @@ -136,7 +133,6 @@ async fn it_responds_to_valid_requests_with_deploy( method: Method, body: Option, tear_down: bool, - use_nakamoto: Option, ) -> (StatusCode, String) { let namespace = &get_random_namespace(); @@ -152,9 +148,9 @@ async fn it_responds_to_valid_requests_with_deploy( let _ = k8s_manager.deploy_namespace(&namespace).await.unwrap(); - let mut config = get_template_config(use_nakamoto.unwrap_or(false)); + let mut config = get_template_config(); config.namespace = namespace.to_owned(); - let validated_config = config.to_validated_config(&namespace, ctx.clone()).unwrap(); + let validated_config = config.to_validated_config(&namespace, &ctx).unwrap(); let user_id = &namespace; let _ = k8s_manager.deploy_devnet(validated_config).await.unwrap(); // short delay to allow assets to start @@ -163,16 +159,23 @@ async fn it_responds_to_valid_requests_with_deploy( let body = match body { None => Body::empty(), Some(TestBody::CreateNetwork) => { - let mut config = get_template_config(use_nakamoto.unwrap_or(false)); + let mut config = get_template_config(); config.namespace = namespace.to_owned(); Body::from(serde_json::to_string(&config).unwrap()) } }; let request: Request = request_builder.body(body).unwrap(); - let mut response = handle_request(request, k8s_manager.clone(), ApiConfig::default(), ctx) - .await - .unwrap(); + let request_store = Arc::new(Mutex::new(HashMap::new())); + let mut response = handle_request( + request, + k8s_manager.clone(), + ApiConfig::default(), + request_store, + ctx, + ) + .await + .unwrap(); let body = response.body_mut(); let bytes = body::to_bytes(body).await.unwrap().to_vec(); @@ -223,9 +226,16 @@ async fn it_responds_to_valid_requests( } let request: Request = request_builder.body(Body::empty()).unwrap(); - let mut response = handle_request(request, k8s_manager.clone(), ApiConfig::default(), ctx) - .await - .unwrap(); + let request_store = Arc::new(Mutex::new(HashMap::new())); + let mut response = handle_request( + request, + k8s_manager.clone(), + ApiConfig::default(), + request_store, + ctx, + ) + .await + .unwrap(); let body = response.body_mut(); let bytes = body::to_bytes(body).await.unwrap().to_vec(); @@ -238,6 +248,202 @@ async fn it_responds_to_valid_requests( (response.status(), body_str) } +async fn deploy_devnet( + namespace: &str, + k8s_manager: StacksDevnetApiK8sManager, + request_store: Arc>>, + ctx: &Context, +) { + let mut config = get_template_config(); + config.namespace = namespace.to_owned(); + let body = Body::from(serde_json::to_string(&config).unwrap()); + + let request: Request = get_request_builder("/api/v1/networks", Method::POST, &namespace) + .body(body) + .unwrap(); + let _ = handle_request( + request, + k8s_manager.clone(), + ApiConfig::default(), + request_store.clone(), + ctx.clone(), + ) + .await + .unwrap(); +} + +async fn get_devnet_info( + namespace: &str, + k8s_manager: StacksDevnetApiK8sManager, + request_store: Arc>>, + ctx: &Context, +) -> StacksDevnetInfoWithMetadata { + let request: Request = get_request_builder( + &format!("/api/v1/network/{namespace}"), + Method::GET, + &namespace, + ) + .body(Body::empty()) + .unwrap(); + let mut response = handle_request( + request, + k8s_manager.clone(), + ApiConfig::default(), + request_store.clone(), + ctx.clone(), + ) + .await + .unwrap(); + + let body = response.body_mut(); + let bytes = body::to_bytes(body).await.unwrap().to_vec(); + serde_json::from_slice(&bytes[..]).unwrap() +} + +async fn delete_devnet( + namespace: &str, + k8s_manager: StacksDevnetApiK8sManager, + request_store: Arc>>, + ctx: &Context, +) { + let request: Request = get_request_builder( + &format!("/api/v1/network/{namespace}"), + Method::DELETE, + &namespace, + ) + .body(Body::empty()) + .unwrap(); + let _ = handle_request( + request, + k8s_manager.clone(), + ApiConfig::default(), + request_store.clone(), + ctx.clone(), + ) + .await + .unwrap(); +} + +#[tokio::test] +#[cfg_attr(not(feature = "k8s_tests"), ignore)] +async fn it_tracks_requests_time_for_user() { + let namespace = &get_random_namespace(); + let namespace2 = &get_random_namespace(); + + let (k8s_manager, ctx) = get_k8s_manager().await; + let _ = k8s_manager.deploy_namespace(&namespace).await.unwrap(); + let _ = k8s_manager.deploy_namespace(&namespace2).await.unwrap(); + + let request_store = Arc::new(Mutex::new(HashMap::new())); + // create one devnet and assert request time is stored + let created_time = { + deploy_devnet(&namespace, k8s_manager.clone(), request_store.clone(), &ctx).await; + let store = request_store.lock().unwrap(); + store.get(namespace).unwrap().clone() + }; + // create another devnet and assert request time is stored + { + deploy_devnet( + &namespace2, + k8s_manager.clone(), + request_store.clone(), + &ctx, + ) + .await; + // after creating a devnet, there should be an entry + assert!(request_store.lock().unwrap().get(namespace).is_some()); + } + // wait some time so we have time elapsed since last request + sleep(Duration::new(1, 0)); + + let secs_after_first_get1 = { + let info = + get_devnet_info(&namespace, k8s_manager.clone(), request_store.clone(), &ctx).await; + // time should have elapsed since our last request + let secs_after_first_get = info.metadata.secs_since_last_request; + assert!(secs_after_first_get > 0); + // getting the devnet should not update our last request time + assert_eq!( + &created_time, + request_store.lock().unwrap().get(namespace).unwrap() + ); + secs_after_first_get + }; + + // confirm time has elapsed since our last request + let secs_after_first_get2 = { + let info = get_devnet_info( + &namespace2, + k8s_manager.clone(), + request_store.clone(), + &ctx, + ) + .await; + let secs_after_first_get = info.metadata.secs_since_last_request; + assert!(secs_after_first_get > 0); + secs_after_first_get + }; + + // send a request that should reset the time since last request + { + let request: Request = get_request_builder( + &format!("/api/v1/network/{namespace}/some-path"), + Method::GET, + &namespace, + ) + .body(Body::empty()) + .unwrap(); + let _ = handle_request( + request, + k8s_manager.clone(), + ApiConfig::default(), + request_store.clone(), + ctx.clone(), + ) + .await + .unwrap(); + } + + // immediately make another get request to confirm that the time since last request was updated + { + let info = + get_devnet_info(&namespace, k8s_manager.clone(), request_store.clone(), &ctx).await; + assert!(secs_after_first_get1 > info.metadata.secs_since_last_request); + } + + // and verify that the time since the last request wasn't updated for our other namespace + { + let info = get_devnet_info( + &namespace2, + k8s_manager.clone(), + request_store.clone(), + &ctx, + ) + .await; + assert!(info.metadata.secs_since_last_request >= secs_after_first_get2); + } + + // clear out the block store to emulate a service restart + let request_store = Arc::new(Mutex::new(HashMap::new())); + assert_eq!(request_store.lock().unwrap().keys().len(), 0); + // confirm that our infrastructure pinging will insert request times if none exist + { + let _ = get_devnet_info(&namespace, k8s_manager.clone(), request_store.clone(), &ctx).await; + assert_eq!(request_store.lock().unwrap().keys().len(), 1); + } + + // confirm that deleting a devnet removes our entry for request times + { + delete_devnet(namespace, k8s_manager.clone(), request_store.clone(), &ctx).await; + assert_eq!(request_store.lock().unwrap().keys().len(), 0); + } + + // clean up + delete_devnet(namespace2, k8s_manager.clone(), request_store.clone(), &ctx).await; + let _ = k8s_manager.delete_namespace(&namespace).await.unwrap(); + let _ = k8s_manager.delete_namespace(&namespace2).await.unwrap(); +} + async fn mock_k8s_handler(handle: &mut Handle, Response>) { let (request, send) = handle.next_request().await.expect("Service not called"); @@ -317,9 +523,16 @@ async fn it_responds_to_invalid_requests( let request_builder = get_request_builder(request_path, method, &user_id); let request: Request = request_builder.body(Body::empty()).unwrap(); - let mut response = handle_request(request, k8s_manager.clone(), ApiConfig::default(), ctx) - .await - .unwrap(); + let request_store = Arc::new(Mutex::new(HashMap::new())); + let mut response = handle_request( + request, + k8s_manager.clone(), + ApiConfig::default(), + request_store, + ctx, + ) + .await + .unwrap(); let body = response.body_mut(); let bytes = body::to_bytes(body).await.unwrap().to_vec(); let body_str = String::from_utf8(bytes).unwrap(); @@ -334,10 +547,12 @@ async fn it_responds_to_invalid_request_header() { .uri("/api/v1/network/test") .method(Method::GET); let request: Request = request_builder.body(Body::empty()).unwrap(); + let request_store = Arc::new(Mutex::new(HashMap::new())); let mut response = handle_request( request, k8s_manager.clone(), ApiConfig::default(), + request_store, ctx.clone(), ) .await @@ -358,10 +573,12 @@ async fn it_ignores_request_header_for_some_requests(request_path: &str, method: let request_builder = Request::builder().uri(request_path).method(method); let request: Request = request_builder.body(Body::empty()).unwrap(); + let request_store = Arc::new(Mutex::new(HashMap::new())); let mut response = handle_request( request, k8s_manager.clone(), ApiConfig::default(), + request_store, ctx.clone(), ) .await @@ -461,9 +678,16 @@ async fn namespace_prefix_config_prepends_header() { user_id, ); let request: Request = request_builder.body(Body::empty()).unwrap(); - let mut response = handle_request(request, k8s_manager.clone(), api_config, ctx.clone()) - .await - .unwrap(); + let request_store = Arc::new(Mutex::new(HashMap::new())); + let mut response = handle_request( + request, + k8s_manager.clone(), + api_config, + request_store, + ctx.clone(), + ) + .await + .unwrap(); let body = response.body_mut(); let bytes = body::to_bytes(body).await.unwrap().to_vec(); diff --git a/templates/deployments/bitcoind-chain-coordinator.template.yaml b/templates/deployments/bitcoind-chain-coordinator.template.yaml index 9dcd577..6b8d0c8 100644 --- a/templates/deployments/bitcoind-chain-coordinator.template.yaml +++ b/templates/deployments/bitcoind-chain-coordinator.template.yaml @@ -71,7 +71,7 @@ spec: fieldRef: apiVersion: v1 fieldPath: metadata.namespace - image: hirosystems/stacks-network-orchestrator:clarinet-2.4.0-beta2 + image: hirosystems/stacks-network-orchestrator:clarinet-2.6.0 imagePullPolicy: IfNotPresent name: chain-coordinator ports: diff --git a/templates/deployments/stacks-blockchain.template.yaml b/templates/deployments/stacks-blockchain.template.yaml index ff8d70a..103c889 100644 --- a/templates/deployments/stacks-blockchain.template.yaml +++ b/templates/deployments/stacks-blockchain.template.yaml @@ -43,7 +43,7 @@ spec: value: "1" - name: BLOCKSTACK_USE_TEST_GENESIS_CHAINSTATE value: "1" - image: quay.io/hirosystems/stacks-node:devnet-2.4.0.0.0 + image: quay.io/hirosystems/stacks-node:devnet-2.5 imagePullPolicy: IfNotPresent name: stacks-blockchain ports: diff --git a/templates/stateful-sets/stacks-blockchain-api.template.yaml b/templates/stateful-sets/stacks-blockchain-api.template.yaml index cb0ba35..6c21c2d 100644 --- a/templates/stateful-sets/stacks-blockchain-api.template.yaml +++ b/templates/stateful-sets/stacks-blockchain-api.template.yaml @@ -39,7 +39,7 @@ spec: - configMapRef: name: stacks-blockchain-api optional: false - image: hirosystems/stacks-blockchain-api + image: hirosystems/stacks-blockchain-api:master imagePullPolicy: IfNotPresent ports: - containerPort: 3999 diff --git a/templates/stateful-sets/stacks-signer-0.template.yaml b/templates/stateful-sets/stacks-signer-0.template.yaml index 2df4988..71f33cd 100644 --- a/templates/stateful-sets/stacks-signer-0.template.yaml +++ b/templates/stateful-sets/stacks-signer-0.template.yaml @@ -39,7 +39,7 @@ spec: - stacks-signer - run - --config=/src/stacks-signer-0/Signer.toml - image: quay.io/hirosystems/stacks-node:devnet-with-signer-beta4 + image: quay.io/hirosystems/stacks-signer:devnet-2.5 imagePullPolicy: IfNotPresent ports: - containerPort: 30001 diff --git a/templates/stateful-sets/stacks-signer-1.template.yaml b/templates/stateful-sets/stacks-signer-1.template.yaml index 511d5ad..e25ed3b 100644 --- a/templates/stateful-sets/stacks-signer-1.template.yaml +++ b/templates/stateful-sets/stacks-signer-1.template.yaml @@ -39,7 +39,7 @@ spec: - stacks-signer - run - --config=/src/stacks-signer-1/Signer.toml - image: quay.io/hirosystems/stacks-node:devnet-with-signer-beta4 + image: quay.io/hirosystems/stacks-signer:devnet-2.5 imagePullPolicy: IfNotPresent ports: - containerPort: 30001