z4_engine/
config.rs

1use ethers::prelude::{Address, Http, LocalWallet, Provider, SignerMiddleware, U256};
2use std::{path::PathBuf, sync::Arc};
3use tdn::prelude::{Config as TdnConfig, PeerKey};
4use z4_types::{
5    env_value, env_values, hex_address, Network, NetworkConfig, Result, Z4_ROOM_MARKET_GROUP,
6};
7
8use crate::contracts::{RoomMarket, Token};
9
10/// config of engine
11#[derive(Default)]
12pub struct Config {
13    /// default groups
14    pub groups: Vec<u64>,
15    /// supported games
16    pub games: Vec<String>,
17    /// main room market
18    pub room_market: String,
19    /// the server secret key (SECP256K1)
20    pub secret_key: String,
21    /// the server websocket port
22    pub ws_port: Option<u16>,
23    /// the server rpc port
24    pub http_port: u16,
25    /// the p2p port
26    pub p2p_port: u16,
27    /// the chain network name
28    pub chain_network: String,
29    /// the chain rpcs
30    pub chain_rpcs: Vec<String>,
31    /// scan start block
32    pub chain_start_block: Option<u64>,
33    /// auto stake to sequencer
34    pub auto_stake: bool,
35    /// http url for this service
36    pub url_http: String,
37    /// http url for this service
38    pub url_websocket: String,
39}
40
41impl Config {
42    /// Get config from env
43    pub fn from_env() -> Result<Self> {
44        dotenv::dotenv().ok();
45
46        let network = env_value("NETWORK", None)?;
47        let games: Vec<String> = env_values("GAMES", None)?;
48        let secret_key = env_value("SECRET_KEY", None)?;
49        let start_block = env_value("START_BLOCK", None).ok();
50
51        let chain_rpcs = env_values("RPC_ENDPOINTS", Some(vec![]))?;
52        let room_market = env_value("ROOM_MARKET", Some(games[0].clone()))?;
53        let url_http = env_value("URL_HTTP", Some("".to_owned()))?;
54        let url_websocket = env_value("URL_WEBSOCKET", Some("".to_owned()))?;
55        let http_port = env_value("HTTP_PORT", Some(8080))?;
56        let ws_port = env_value("WS_PORT", Some(8000))?;
57        let p2p_port = env_value("P2P_PORT", Some(7364))?;
58        let auto_stake = env_value("AUTO_STAKE", Some(false))?;
59
60        let mut config = Config::default();
61        config.http_port = http_port;
62        config.ws_port = Some(ws_port);
63        config.p2p_port = p2p_port;
64        config.secret_key = secret_key;
65        config.chain_network = network;
66        config.chain_rpcs = chain_rpcs;
67        config.chain_start_block = start_block;
68        config.games = games;
69        config.room_market = room_market;
70        config.auto_stake = auto_stake;
71        config.url_http = url_http;
72        config.url_websocket = url_websocket;
73
74        Ok(config)
75    }
76
77    /// Convert config to TDN config
78    pub fn to_tdn(&self) -> (TdnConfig, PeerKey) {
79        let rpc_addr = format!("0.0.0.0:{}", self.http_port).parse().unwrap();
80        let p2p_addr = format!("0.0.0.0:{}", self.p2p_port).parse().unwrap();
81        let mut config = TdnConfig::with_addr(p2p_addr, rpc_addr);
82        config.rpc_ws = match self.ws_port {
83            Some(port) => Some(format!("0.0.0.0:{}", port).parse().unwrap()),
84            None => None,
85        };
86        config.group_ids = self.groups.clone();
87        config.group_ids.push(Z4_ROOM_MARKET_GROUP);
88
89        // TODO boostrap seed
90
91        let sk_str = self.secret_key.trim_start_matches("0x");
92        let sk_bytes = hex::decode(&sk_str).expect("Invalid secret key");
93        let key = PeerKey::from_db_bytes(&sk_bytes).expect("Invalid secret key");
94
95        config.db_path = Some(PathBuf::from(&format!("./.tdn/{:?}", key.peer_id())));
96
97        (config, key)
98    }
99
100    /// Convert config to chain params
101    pub async fn to_chain(
102        &self,
103    ) -> Option<(
104        Vec<Arc<Provider<Http>>>,
105        Arc<SignerMiddleware<Arc<Provider<Http>>, LocalWallet>>,
106        Address,
107        Option<u64>,
108    )> {
109        if self.chain_network.is_empty() {
110            return None;
111        }
112
113        let network = Network::from_str(&self.chain_network);
114        let nc = NetworkConfig::from(network);
115        let rpcs = if self.chain_rpcs.is_empty() {
116            &nc.rpc_urls
117        } else {
118            &self.chain_rpcs
119        };
120        let providers: Vec<_> = rpcs
121            .iter()
122            .map(|rpc| Arc::new(Provider::<Http>::try_from(rpc).unwrap()))
123            .collect();
124        if providers.is_empty() {
125            panic!("NO RPCS");
126        }
127
128        let sk_str = self.secret_key.trim_start_matches("0x");
129        let signer = LocalWallet::from_bytes(&hex::decode(&sk_str).unwrap()).unwrap();
130        let signer_provider = Arc::new(
131            SignerMiddleware::new_with_provider_chain(providers[0].clone(), signer)
132                .await
133                .unwrap(),
134        );
135
136        let room_market = if self.room_market.is_empty() {
137            &self.games[0]
138        } else {
139            &self.room_market
140        };
141        let market_address = hex_address(room_market).expect("Invalid room market address");
142        if self.auto_stake && (!self.url_http.is_empty() || !self.url_websocket.is_empty()) {
143            // check & register sequencer
144            let contract = RoomMarket::new(market_address, signer_provider.clone());
145            let token_address = contract.token().await.unwrap();
146            let token = Token::new(token_address, signer_provider.clone());
147
148            // TODO check staking is enough
149
150            let amount = contract.min_staking().await.unwrap() * U256::from(100);
151            info!("Auto staking: {}", amount);
152            match token.approve(market_address, amount).send().await {
153                Ok(pending) => {
154                    if let Ok(_receipt) = pending.await {
155                        let _ = contract
156                            .stake_sequencer(
157                                self.url_http.clone(),
158                                self.url_websocket.clone(),
159                                amount,
160                            )
161                            .send()
162                            .await;
163                    } else {
164                        error!("Failed to approve token");
165                    }
166                }
167                Err(err) => {
168                    if let Some(rcode) = err.decode_revert::<String>() {
169                        error!("{}", rcode);
170                    } else {
171                        error!("{}", err);
172                    }
173                }
174            }
175        }
176
177        Some((
178            providers,
179            signer_provider,
180            market_address,
181            self.chain_start_block,
182        ))
183    }
184}