From 91d1b34a614ce3fecd48df6d5e43051c7d1881a9 Mon Sep 17 00:00:00 2001 From: yHSJ Date: Fri, 22 Nov 2024 10:39:13 -0500 Subject: [PATCH] feat: introduce network flag at terraform level --- .github/workflows/deploy.yml | 1 + bootstrap/stage2/control-plane.tf | 5 ++ bootstrap/stage2/frontend.tf | 4 ++ bootstrap/stage2/main.tf | 4 ++ crates/operator/src/config.rs | 2 + crates/operator/src/custom_resource.rs | 10 ++++ crates/rpc/src/main.rs | 16 +++++- crates/rpc/src/model/cluster/mod.rs | 9 +++- crates/rpc/src/model/cluster/node.rs | 21 ++++---- crates/rpc/src/model/tx_builder.rs | 55 +++++++++----------- crates/rpc/src/routes/add_player.rs | 2 +- crates/rpc/src/routes/cleanup.rs | 2 +- crates/rpc/src/routes/end_game.rs | 2 +- crates/rpc/src/routes/new_game.rs | 2 +- crates/rpc/src/routes/sample_transactions.rs | 2 +- crates/rpc/src/routes/start_game.rs | 2 +- 16 files changed, 90 insertions(+), 49 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 3b8a077..3a917a4 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -159,6 +159,7 @@ jobs: TF_VAR_api_key: ${{ secrets.API_KEY }} # Vars + TF_VAR_network_id: 0 TF_VAR_dmtr_port_name: preprod-4raar2 TF_VAR_dmtr_project_id: b55545f5-31e7-4e6b-81d6-22f4e6b5a144 TF_VAR_external_domain: ${{ matrix.region }}.hydra-doom.sundae.fi diff --git a/bootstrap/stage2/control-plane.tf b/bootstrap/stage2/control-plane.tf index 8697034..efe92b2 100644 --- a/bootstrap/stage2/control-plane.tf +++ b/bootstrap/stage2/control-plane.tf @@ -73,6 +73,11 @@ resource "kubernetes_deployment_v1" "control_plane" { value = var.api_key } + env { + name = "NETWORK_ID" + value = var.network_id + } + env { name = "KUBERNETES_NAMESPACE" value_from { diff --git a/bootstrap/stage2/frontend.tf b/bootstrap/stage2/frontend.tf index 4c33d28..6276622 100644 --- a/bootstrap/stage2/frontend.tf +++ b/bootstrap/stage2/frontend.tf @@ -46,6 +46,10 @@ resource "kubernetes_deployment_v1" "frontend" { value = "067d20be-8baa-49cb-b501-e004af358870" } +env { name = "VITE_NETWORK_ID" + value = var.network_id + } + resources { limits = { cpu = var.frontend_resources.limits.cpu diff --git a/bootstrap/stage2/main.tf b/bootstrap/stage2/main.tf index cd732f5..b0d594a 100644 --- a/bootstrap/stage2/main.tf +++ b/bootstrap/stage2/main.tf @@ -148,6 +148,10 @@ variable "autoscaler_max_batch" { type = number } +variable "network_id" { + type = number +} + variable "tolerations" { type = list(object({ effect = string diff --git a/crates/operator/src/config.rs b/crates/operator/src/config.rs index 089e8b5..b814328 100644 --- a/crates/operator/src/config.rs +++ b/crates/operator/src/config.rs @@ -31,6 +31,7 @@ pub struct Config { pub bucket: String, pub init_aws_access_key_id: String, pub init_aws_secret_access_key: String, + pub network_id: String, // Autoscaler pub autoscaler_delay: Duration, pub autoscaler_low_watermark: usize, @@ -85,6 +86,7 @@ impl Config { autoscaler_max_batch: env::var("AUTOSCALER_MAX_BATCH") .map(|x| x.parse().expect("Failed to parse AUTOSCALER_MAX_BATCH")) .expect("Missing AUTOSCALER_MAX_BATCH env var."), + network_id: env::var("NETWORK_ID").expect("Missing NETWORK_ID env var."), } } } diff --git a/crates/operator/src/custom_resource.rs b/crates/operator/src/custom_resource.rs index c4d738f..ad2ad0b 100644 --- a/crates/operator/src/custom_resource.rs +++ b/crates/operator/src/custom_resource.rs @@ -320,6 +320,11 @@ impl HydraDoomNode { value: Some(config.api_key.clone()), value_from: None, }, + EnvVar { + name: "NETWORK_ID".to_string(), + value: Some(config.network_id.clone()), + value_from: None, + }, ]), volume_mounts: Some(vec![VolumeMount { name: "secret".to_string(), @@ -331,6 +336,11 @@ impl HydraDoomNode { Container { name: "ai-1".to_string(), image: Some(config.ai_image.clone()), + env: Some(vec![EnvVar { + name: "NETWORK_ID".to_string(), + value: Some(config.network_id.clone()), + value_from: None, + }]), ..Default::default() }, ]; diff --git a/crates/rpc/src/main.rs b/crates/rpc/src/main.rs index 0ffd03b..e6467c1 100644 --- a/crates/rpc/src/main.rs +++ b/crates/rpc/src/main.rs @@ -1,5 +1,6 @@ use anyhow::{Context, Result}; use model::cluster::ClusterState; +use pallas::ledger::addresses::Network; use rocket::{http::Method, routes}; use rocket_cors::{AllowedOrigins, CorsOptions}; use routes::{ @@ -15,6 +16,8 @@ use routes::{ stats::{global_stats, refresh_stats, StatsState}, }; use serde::Deserialize; +use std::env; +use tracing::error; mod guards; mod model; @@ -32,12 +35,21 @@ async fn main() -> Result<()> { let rocket = rocket::build(); let figment = rocket.figment(); let config = figment.extract::().context("invalid config")?; - + let network: Network = env::var("NETWORK_ID") + .map(|network_str| { + network_str + .parse::() + .inspect_err(|_| error!("Invalid NETWORK_ID value, defaulting to 0")) + .unwrap_or_default() + }) + .inspect_err(|_| error!("Missing NETWORK_ID env var, defaulting to zero")) + .unwrap_or_default() + .into(); // This will start a reflector (aka: local cache) of the cluster state. The `try_default` // initializer assumes that this process is running within the cluster or that the local kubeconfig // context is set to the cluster. If you wanted to connect to a remote cluster, you can use the // `ClusterState::remote` initializer. - let cluster = ClusterState::try_new(&config.admin_key_file, config.remote).await?; + let cluster = ClusterState::try_new(&config.admin_key_file, config.remote, network).await?; let stats = StatsState::new( refresh_stats() .await diff --git a/crates/rpc/src/model/cluster/mod.rs b/crates/rpc/src/model/cluster/mod.rs index 8cda3b2..e346a86 100644 --- a/crates/rpc/src/model/cluster/mod.rs +++ b/crates/rpc/src/model/cluster/mod.rs @@ -7,6 +7,7 @@ use anyhow::Context; use futures_util::StreamExt as _; use kube::runtime::{reflector::ObjectRef, WatchStreamExt as _}; use pallas::crypto::key::ed25519::SecretKey; +use pallas::ledger::addresses::Network; use serde::Deserialize; mod crd; @@ -30,10 +31,15 @@ pub struct ClusterState { watcher_handle: Arc>, pub admin_sk: SecretKey, pub remote: bool, + pub network: Network, } impl ClusterState { - pub async fn try_new(admin_key_file: &str, remote: bool) -> anyhow::Result { + pub async fn try_new( + admin_key_file: &str, + remote: bool, + network: Network, + ) -> anyhow::Result { let admin_key_envelope: KeyEnvelope = serde_json::from_reader( File::open(admin_key_file).context("unable to open key file")?, )?; @@ -81,6 +87,7 @@ impl ClusterState { watcher_handle: Arc::new(watcher_handle), admin_sk, remote, + network, }) } diff --git a/crates/rpc/src/model/cluster/node.rs b/crates/rpc/src/model/cluster/node.rs index 67b6604..0599c25 100644 --- a/crates/rpc/src/model/cluster/node.rs +++ b/crates/rpc/src/model/cluster/node.rs @@ -65,7 +65,12 @@ impl TryInto> for KeyEnvelope { } impl NodeClient { - pub fn new(resource: Arc, admin_key: SecretKey, remote: bool) -> Result { + pub fn new( + resource: Arc, + admin_key: SecretKey, + remote: bool, + network: Network, + ) -> Result { let status = resource.status.as_ref().ok_or(anyhow!("no status found"))?; let (local_connection, remote_connection) = ConnectionInfo::from_resource(status)?; @@ -77,7 +82,7 @@ impl NodeClient { } else { local_connection }, - tx_builder: TxBuilder::new(admin_key), + tx_builder: TxBuilder::new(admin_key, network), }; Ok(node) @@ -95,7 +100,7 @@ impl NodeClient { let new_game_tx = self .tx_builder - .new_game(player, utxos, Network::Testnet) + .new_game(player, utxos) .context("failed to build transaction")?; // TODO: pass in network debug!("new game tx: {}", hex::encode(&new_game_tx.tx_bytes)); @@ -113,13 +118,12 @@ impl NodeClient { Ok(tx_hash) } - //TODO: don't hardcode network pub async fn start_game(&self) -> Result> { let utxos = self.fetch_utxos().await.context("failed to fetch UTxOs")?; let start_game_tx = self .tx_builder - .start_game(utxos, Network::Testnet) + .start_game(utxos) .context("failed to build transaction")?; debug!("start game tx: {}", hex::encode(&start_game_tx.tx_bytes)); @@ -138,13 +142,12 @@ impl NodeClient { Ok(tx_hash) } - //TODO: don't hardcode network pub async fn add_player(&self, player: Player) -> Result> { let utxos = self.fetch_utxos().await.context("failed to fetch UTxOs")?; let add_player_tx = self .tx_builder - .add_player(player, utxos, Network::Testnet) + .add_player(player, utxos) .context("failed to build transaction")?; debug!("add player tx: {}", hex::encode(&add_player_tx.tx_bytes)); @@ -169,7 +172,7 @@ impl NodeClient { let cleanup_tx = self .tx_builder - .cleanup_game(utxos, Network::Testnet) + .cleanup_game(utxos) .context("failed to build transaction")?; debug!("cleanup tx: {}", hex::encode(&cleanup_tx.tx_bytes)); @@ -195,7 +198,7 @@ impl NodeClient { let end_game_tx = self .tx_builder - .end_game(None, utxos, Network::Testnet) + .end_game(None, utxos) .context("failed to build transaction")?; debug!("end_game_tx tx: {}", hex::encode(&end_game_tx.tx_bytes)); diff --git a/crates/rpc/src/model/tx_builder.rs b/crates/rpc/src/model/tx_builder.rs index 3a62ef2..13cca12 100644 --- a/crates/rpc/src/model/tx_builder.rs +++ b/crates/rpc/src/model/tx_builder.rs @@ -30,23 +30,20 @@ use super::{ pub struct TxBuilder { admin_key: SecretKey, pub admin_pkh: Hash<28>, + network: Network, } impl TxBuilder { - pub fn new(admin_key: SecretKey) -> Self { + pub fn new(admin_key: SecretKey, network: Network) -> Self { let admin_pkh = admin_key.public_key().compute_hash(); TxBuilder { admin_key, admin_pkh, + network, } } - pub fn new_game( - &self, - player: Player, - utxos: Vec, - network: Network, - ) -> Result { + pub fn new_game(&self, player: Player, utxos: Vec) -> Result { let admin_utxos = self.find_admin_utxos(utxos); if admin_utxos.is_empty() { @@ -55,17 +52,16 @@ impl TxBuilder { let input_utxo = admin_utxos.first().unwrap(); - let script_address = Validator::address(network); - println!("Script Address: {:?}", script_address.to_bech32()); + let script_address = Validator::address(self.network); let player_outbound_address = player - .outbound_address(self.admin_pkh, network) + .outbound_address(self.admin_pkh, self.network) .context("failed to build player multisig outbound address")?; let mut admin_address_bytes = self.admin_pkh.to_vec(); admin_address_bytes.insert( 0, 0b1100000 - | match network { + | match self.network { Network::Testnet => 0, Network::Mainnet => 1, Network::Other(i) => i, @@ -101,16 +97,11 @@ impl TxBuilder { Ok(signed_tx) } - pub fn add_player( - &self, - player: Player, - utxos: Vec, - network: Network, - ) -> Result { + pub fn add_player(&self, player: Player, utxos: Vec) -> Result { let game_state_utxo = utxos .clone() .into_iter() - .find(|utxo| utxo.address == Validator::address(network)) + .find(|utxo| utxo.address == Validator::address(self.network)) .ok_or_else(|| anyhow!("game state UTxO not found"))?; let game_state: PlutusData = GameState::try_from(game_state_utxo.datum.clone())? @@ -126,10 +117,10 @@ impl TxBuilder { .find(|utxo| utxo.value.get("lovelace").unwrap_or(&0) > &0) .ok_or_else(|| anyhow!("No collateral utxo found"))?; - let script_address = Validator::address(network); + let script_address = Validator::address(self.network); let outbound_player_address = player - .outbound_address(self.admin_pkh, network) + .outbound_address(self.admin_pkh, self.network) .context("failed to construct player multisig outbound address")?; let redeemer: PlutusData = Redeemer::new(0, SpendAction::AddPlayer).into(); let mut redeemer_bytes = Vec::new(); @@ -185,11 +176,11 @@ impl TxBuilder { Ok(signed_tx) } - pub fn start_game(&self, utxos: Vec, network: Network) -> Result { + pub fn start_game(&self, utxos: Vec) -> Result { let game_state_utxo = utxos .clone() .into_iter() - .find(|utxo| utxo.address == Validator::address(network)) + .find(|utxo| utxo.address == Validator::address(self.network)) .ok_or_else(|| anyhow!("game state UTxO not found"))?; let game_state: PlutusData = GameState::try_from(game_state_utxo.datum.clone())? @@ -199,7 +190,7 @@ impl TxBuilder { let mut datum = Vec::new(); encode(&game_state, &mut datum)?; - let script_address = Validator::address(network); + let script_address = Validator::address(self.network); let redeemer: PlutusData = Redeemer::new(0, SpendAction::StartGame).into(); let mut redeemer_bytes = Vec::new(); encode(&redeemer, &mut redeemer_bytes)?; @@ -266,12 +257,11 @@ impl TxBuilder { // If this is Some(_, false), we are marking as finished is_player_cheater: Option<(Player, bool)>, utxos: Vec, - network: Network, ) -> Result { let game_state_utxo = utxos .clone() .into_iter() - .find(|utxo| utxo.address == Validator::address(network)) + .find(|utxo| utxo.address == Validator::address(self.network)) .ok_or_else(|| anyhow!("game state UTxO not found"))?; let mut game_state: GameState = match game_state_utxo.datum.clone() { @@ -315,7 +305,7 @@ impl TxBuilder { .input(game_state_utxo.clone().into()) .collateral_input(collateral_utxo.clone().into()) // GameState Output - .output(Output::new(Validator::address(network), 0).set_inline_datum(datum)) + .output(Output::new(Validator::address(self.network), 0).set_inline_datum(datum)) .add_spend_redeemer( game_state_utxo.into(), redeemer_bytes, @@ -361,11 +351,11 @@ impl TxBuilder { } //TODO: sooo many clones here. Let's improve that if possible - pub fn cleanup_game(&self, utxos: Vec, network: Network) -> Result { + pub fn cleanup_game(&self, utxos: Vec) -> Result { let game_state_utxo = utxos .clone() .into_iter() - .find(|utxo| utxo.address == Validator::address(network)) + .find(|utxo| utxo.address == Validator::address(self.network)) .ok_or_else(|| anyhow!("game state UTxO not found"))?; let game_state: GameState = match game_state_utxo.datum.clone() { @@ -438,7 +428,7 @@ impl TxBuilder { for player in game_state.players { let player: Player = player.into(); let outbound_address = player - .outbound_address(self.admin_pkh, network) + .outbound_address(self.admin_pkh, self.network) .context("failed to get player outbound address")?; let outbound_script = player.outbound_script(self.admin_pkh); let mut outbound_bytes = Vec::new(); @@ -517,7 +507,10 @@ mod tests { File::open("keys/preprod.sk").expect("Failed to open key file"), ) .expect("unable to parse key file"); - let tx_builder = TxBuilder::new(admin_key.try_into().expect("Failed to create SecretKey")); + let tx_builder = TxBuilder::new( + admin_key.try_into().expect("Failed to create SecretKey"), + Network::Testnet, + ); let player = match Address::from_bech32( "addr_test1qpq0htjtaygzwtj3h4akj2mvzaxgpru4yje4ca9a507jtdw5pcy8kzccynfps4ayhmtc38j6tyjrkyfccdytnxwnd6psfelznq", @@ -545,7 +538,7 @@ mod tests { }]; let tx = tx_builder - .new_game(player.into(), utxos, Network::Testnet) + .new_game(player.into(), utxos) .expect("Failed to build tx"); debug!("{}", hex::encode(tx.tx_bytes)); diff --git a/crates/rpc/src/routes/add_player.rs b/crates/rpc/src/routes/add_player.rs index ed5eb80..48035c7 100644 --- a/crates/rpc/src/routes/add_player.rs +++ b/crates/rpc/src/routes/add_player.rs @@ -25,7 +25,7 @@ pub async fn add_player( let node = state.get_node_by_id(id).ok_or(Status::NotFound)?; - let client = NodeClient::new(node, state.admin_sk.clone(), state.remote) + let client = NodeClient::new(node, state.admin_sk.clone(), state.remote, state.network) .inspect_err(|err| error!("error connecting to node: {}", err)) .map_err(|_| Status::InternalServerError)?; diff --git a/crates/rpc/src/routes/cleanup.rs b/crates/rpc/src/routes/cleanup.rs index 8f83a1d..ff82b7b 100644 --- a/crates/rpc/src/routes/cleanup.rs +++ b/crates/rpc/src/routes/cleanup.rs @@ -14,7 +14,7 @@ pub async fn cleanup( ) -> Result<(), Status> { let node = state.get_node_by_id(id).ok_or(Status::NotFound)?; - let client = NodeClient::new(node, state.admin_sk.clone(), state.remote) + let client = NodeClient::new(node, state.admin_sk.clone(), state.remote, state.network) .inspect_err(|err| error!("error connecting to node: {}", err)) .map_err(|_| Status::InternalServerError)?; diff --git a/crates/rpc/src/routes/end_game.rs b/crates/rpc/src/routes/end_game.rs index ba36b7e..1f57d97 100644 --- a/crates/rpc/src/routes/end_game.rs +++ b/crates/rpc/src/routes/end_game.rs @@ -14,7 +14,7 @@ pub async fn end_game( ) -> Result<(), Status> { let node = state.get_node_by_id(id).ok_or(Status::NotFound)?; - let client = NodeClient::new(node, state.admin_sk.clone(), state.remote) + let client = NodeClient::new(node, state.admin_sk.clone(), state.remote, state.network) .inspect_err(|err| error!("error connecting to node: {}", err)) .map_err(|_| Status::InternalServerError)?; diff --git a/crates/rpc/src/routes/new_game.rs b/crates/rpc/src/routes/new_game.rs index 6a520b6..1de7e81 100644 --- a/crates/rpc/src/routes/new_game.rs +++ b/crates/rpc/src/routes/new_game.rs @@ -28,7 +28,7 @@ pub async fn new_game(address: &str, state: &State) -> Result