Skip to content

Commit

Permalink
[CLI] move out localnet logic + add command for Aptos Workspace (apto…
Browse files Browse the repository at this point in the history
  • Loading branch information
vgao1996 authored Dec 12, 2024
1 parent d3028e9 commit 803b7fd
Show file tree
Hide file tree
Showing 25 changed files with 821 additions and 435 deletions.
2 changes: 1 addition & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
!aptos-move/framework/
!aptos-move/move-examples/hello_blockchain/
!crates/aptos/src/move_tool/*.bpl
!crates/aptos/src/node/local_testnet/hasura_metadata.json
!crates/aptos-localnet/src/hasura_metadata.json
!crates/aptos-faucet/doc/
!crates/transaction-emitter-lib/src/emitter/test_proofs_for_localnet_txn_emitter.txt
!api/doc/
Expand Down
28 changes: 26 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ members = [
"crates/aptos-jwk-consensus",
"crates/aptos-keygen",
"crates/aptos-ledger",
"crates/aptos-localnet",
"crates/aptos-log-derive",
"crates/aptos-logger",
"crates/aptos-metrics-core",
Expand Down Expand Up @@ -380,6 +381,7 @@ aptos-jwk-utils = { path = "crates/jwk-utils" }
aptos-keygen = { path = "crates/aptos-keygen" }
aptos-language-e2e-tests = { path = "aptos-move/e2e-tests" }
aptos-ledger = { path = "crates/aptos-ledger" }
aptos-localnet = { path = "crates/aptos-localnet" }
aptos-log-derive = { path = "crates/aptos-log-derive" }
aptos-logger = { path = "crates/aptos-logger" }
aptos-memory-usage-tracker = { path = "aptos-move/aptos-memory-usage-tracker" }
Expand Down Expand Up @@ -466,6 +468,7 @@ aptos-vm-logging = { path = "aptos-move/aptos-vm-logging" }
aptos-vm-genesis = { path = "aptos-move/vm-genesis" }
aptos-vm-types = { path = "aptos-move/aptos-vm-types" }
aptos-vm-validator = { path = "vm-validator" }
aptos-workspace-server = { path = "aptos-move/aptos-workspace-server" }
aptos-warp-webserver = { path = "crates/aptos-warp-webserver" }
aptos-cargo-cli = { path = "devtools/aptos-cargo-cli" }

Expand Down
2 changes: 1 addition & 1 deletion aptos-move/aptos-workspace-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ rust-version = { workspace = true }

[dependencies]
# aptos deps
aptos = { workspace = true }
aptos-cached-packages = { workspace = true }
aptos-config = { workspace = true }
aptos-faucet-core = { workspace = true }
aptos-localnet = { workspace = true }
aptos-node = { workspace = true }
aptos-types = { workspace = true }

Expand Down
183 changes: 183 additions & 0 deletions aptos-move/aptos-workspace-server/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// Copyright (c) Aptos Foundation
// SPDX-License-Identifier: Apache-2.0

//! This library runs and manages a set of services that makes up a local Aptos network.
//! - node
//! - node API
//! - indexer grpc
//! - faucet
//! - indexer
//! - postgres db
//! - processors
//! - indexer API
//!
//! The services are bound to unique OS-assigned ports to allow for multiple local networks
//! to operate simultaneously, enabling testing and development in isolated environments.
//!
//! ## Key Features:
//! - Shared Futures
//! - The code makes extensive use of shared futures across multiple services,
//! ensuring orderly startup while maximizing parallel execution.
//! - Graceful Shutdown
//! - When a `Ctrl-C` signal is received or if any of the services fail to start
//! or exit unexpectedly, the system attempts to gracefully shut down all services,
//! cleaning up resources like Docker containers, volumes and networks.
mod common;
mod services;

use anyhow::{Context, Result};
use common::make_shared;
use futures::TryFutureExt;
use services::{
docker_common::create_docker_network, indexer_api::start_indexer_api,
processors::start_all_processors,
};
use tokio_util::sync::CancellationToken;
use uuid::Uuid;

pub async fn run_all_services() -> Result<()> {
let test_dir = tempfile::tempdir()?;
let test_dir = test_dir.path();
println!("Created test directory: {}", test_dir.display());

let instance_id = Uuid::new_v4();

// Phase 0: Register the signal handler for ctrl-c.
let shutdown = CancellationToken::new();
{
// TODO: Find a way to register the signal handler in a blocking manner without
// waiting for it to trigger.
let shutdown = shutdown.clone();
tokio::spawn(async move {
tokio::signal::ctrl_c().await.unwrap();

println!("\nCtrl-C received. Shutting down services. This may take a while.\n");

shutdown.cancel();
});
}

// Phase 1: Start all services.
// Node
let (fut_node_api, fut_indexer_grpc, fut_node_finish) = services::node::start_node(test_dir)?;

let fut_node_api = make_shared(fut_node_api);
let fut_indexer_grpc = make_shared(fut_indexer_grpc);

// Faucet
let (fut_faucet, fut_faucet_finish) = services::faucet::start_faucet(
test_dir.to_owned(),
fut_node_api.clone(),
fut_indexer_grpc.clone(),
);

// Docker Network
let docker_network_name = format!("aptos-workspace-{}", instance_id);
let (fut_docker_network, fut_docker_network_clean_up) =
create_docker_network(shutdown.clone(), docker_network_name);

// Indexer part 1: postgres db
let (fut_postgres, fut_postgres_finish, fut_postgres_clean_up) =
services::postgres::start_postgres(
shutdown.clone(),
fut_docker_network.clone(),
instance_id,
);
let fut_postgres = make_shared(fut_postgres);

// Indexer part 2: processors
let (fut_all_processors_ready, fut_any_processor_finish) = start_all_processors(
fut_node_api.clone(),
fut_indexer_grpc.clone(),
fut_postgres.clone(),
);
let fut_all_processors_ready = make_shared(fut_all_processors_ready);

// Indexer part 3: indexer API
let (fut_indexer_api, fut_indexer_api_finish, fut_indexer_api_clean_up) = start_indexer_api(
instance_id,
shutdown.clone(),
fut_docker_network.clone(),
fut_postgres.clone(),
fut_all_processors_ready.clone(),
);

// Phase 2: Wait for all services to be up.
let all_services_up = async move {
tokio::try_join!(
fut_node_api.map_err(anyhow::Error::msg),
fut_indexer_grpc.map_err(anyhow::Error::msg),
fut_faucet,
fut_postgres.map_err(anyhow::Error::msg),
fut_all_processors_ready.map_err(anyhow::Error::msg),
fut_indexer_api,
)
};
let clean_up_all = async move {
eprintln!("Running shutdown steps");
fut_indexer_api_clean_up.await;
fut_postgres_clean_up.await;
fut_docker_network_clean_up.await;
};
tokio::select! {
_ = shutdown.cancelled() => {
clean_up_all.await;

return Ok(())
}
res = all_services_up => {
match res.context("one or more services failed to start") {
Ok(_) => println!("ALL SERVICES UP"),
Err(err) => {
eprintln!("\nOne or more services failed to start, will run shutdown steps\n");
clean_up_all.await;

return Err(err)
}
}
}
}

// Phase 3: Wait for services to stop, which should only happen in case of an error, or
// the shutdown signal to be received.
tokio::select! {
_ = shutdown.cancelled() => (),
res = fut_node_finish => {
eprintln!("Node exited unexpectedly");
if let Err(err) = res {
eprintln!("Error: {}", err);
}
}
res = fut_faucet_finish => {
eprintln!("Faucet exited unexpectedly");
if let Err(err) = res {
eprintln!("Error: {}", err);
}
}
res = fut_postgres_finish => {
eprintln!("Postgres exited unexpectedly");
if let Err(err) = res {
eprintln!("Error: {}", err);
}
}
res = fut_any_processor_finish => {
eprintln!("One of the processors exited unexpectedly");
if let Err(err) = res {
eprintln!("Error: {}", err);
}
}
res = fut_indexer_api_finish => {
eprintln!("Indexer API exited unexpectedly");
if let Err(err) = res {
eprintln!("Error: {}", err);
}
}
}

clean_up_all.await;

println!("Finished running all services");

Ok(())
}
Loading

0 comments on commit 803b7fd

Please sign in to comment.