├── .env ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Cross.toml ├── README.md ├── build.rs ├── core ├── .gitignore ├── Cargo.toml └── src │ ├── client │ ├── encry.rs │ ├── fee.rs │ ├── handle_stream.rs │ ├── handle_stream_all.rs │ ├── handle_stream_nofee.rs │ ├── handle_stream_timer.rs │ ├── mod.rs │ ├── monitor.rs │ ├── pools.rs │ ├── tcp.rs │ └── tls.rs │ ├── lib.rs │ ├── protocol │ ├── eth_stratum.rs │ ├── ethjson.rs │ ├── mod.rs │ ├── rpc │ │ ├── eth │ │ │ └── mod.rs │ │ └── mod.rs │ └── stratum.rs │ ├── proxy │ └── mod.rs │ ├── state │ └── mod.rs │ ├── util │ ├── config.rs │ ├── logger.rs │ └── mod.rs │ └── web │ ├── data │ └── mod.rs │ ├── handles │ ├── auth.rs │ ├── mod.rs │ ├── server.rs │ └── user.rs │ └── mod.rs ├── doc ├── 0.0.1 版本日志 ├── 0.1.0开发步骤 ├── 0.1.2 开发日志 ├── 0.1.3 开发日志 ├── 0.1.4开发日志 ├── 0.1.5开发日志 ├── 0.1.6开发日志 ├── 0.1.7开发日志 ├── 0.1.8开发日志 ├── 0.1.9开发日志 ├── 0.2.0开发日志 ├── 0.2.1开发日志 ├── 0.2.2开发日志 ├── 0.2.3开发日志 ├── 0.2.4.org ├── 0.2.4开发日志 ├── fake ├── images │ ├── fee.jpg │ ├── logo.png │ ├── web.jpg │ └── web1.jpg └── install.md ├── dockerfile ├── makefile ├── mining_proxy ├── .gitignore ├── Cargo.toml ├── build.rs └── src │ └── main.rs ├── monitor ├── Cargo.toml ├── build.rs └── src │ └── main.rs ├── proxy └── src │ └── main.rs ├── rustfmt.toml └── utils ├── Cargo.toml ├── Cargo.toml~ └── src ├── lib.rs └── lib.rs~ /.env: -------------------------------------------------------------------------------- 1 | MINING_PROXY_WEB_PORT=8020 2 | MINING_PROXY_WEB_PASSWORD=123456789 3 | JWT_SECRET=test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .DS_Store 3 | default.yaml 4 | release/ 5 | configs.yaml 6 | logs 7 | # Editor directories and files 8 | .idea 9 | .vscode 10 | *.suo 11 | *.ntvs* 12 | *.njsproj 13 | *.sln 14 | *.pem -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "core", 4 | "utils", 5 | "monitor", 6 | "mining_proxy" 7 | ] 8 | -------------------------------------------------------------------------------- /Cross.toml: -------------------------------------------------------------------------------- 1 | [target.x86_64-unknown-linux-musl] 2 | image = "wangyusong/x86_64-unknown-linux-musl:latest" 3 | [target.aarch64-unknown-linux-musl] 4 | image = "wangyusong/aarch64-unknown-linux-musl:latest" 5 | [target.armv7-unknown-linux-gnueabihf] 6 | image = "rustembedded/cross:armv7-unknown-linux-gnueabihf-0.1.16" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |
3 | 4 |

5 | 6 |

全开源 - 无内置开发者钱包

7 |

Rust语言编写 基于tokio生态的ETH/ETC/CFX 代理抽水软件

8 | 9 |

10 | 11 | travis 12 | 13 | 14 | travis 15 | 16 | 17 | travis 18 | 19 | 20 | travis 21 | 22 |

23 |

最新版本见Release Github Release

24 |

历史版本: https://github.com/dothinkdone/mining_proxy/releases

25 |

26 | Coffee: Eth+BSC+HECO+Matic: 0x3602b50d3086edefcd9318bcceb6389004fb14ee 27 |

28 | 29 |

30 | Telegram 群 • 31 | QQ 群 32 |

33 | 34 | ![Screenshot](https://raw.githubusercontent.com/YusongWang/mining_proxy/master/doc/images/web.jpg) 35 | 36 | ## :sparkles: 特性 37 | 38 | - :cloud: 支持ETH ETC CFX 转发 39 | - :zap: 性能强劲,CPU占用低。 40 | - 💻 可以自定义抽水比例 41 | - 📚 可以自定义抽水算法。 42 | - 💾 安全稳定: 支持TCP SSL 及加密方式(轻量算法,非SSR一类的垃圾东西)3种不通的协议 43 | - :outbox_tray: 一台机器只需要开启一个Web界面。可配置多矿池转发(没有上限) 44 | - :rocket: 開箱即用:All-In-One 打包,一鍵搭建運行,一鍵配置 45 | - :family_woman_girl_boy: 支持Liunx Windows 46 | 47 | ## :hammer_and_wrench: 部署 48 | 49 | - 自行编译 50 | 编译遇到问题基本都是web的源码没有clone 51 | 看这里:https://github.com/YusongWang/mining_proxy/issues/26 52 | 53 | 在软件运行目录下创建 .env 文件 54 | ```env 55 | MINING_PROXY_WEB_PORT=8020 56 | MINING_PROXY_WEB_PASSWORD=123456789 57 | JWT_SECRET=test 58 | ``` 59 | 第一行是网页的端口 60 | 第二行是网页管理的密码 61 | 第三行是登录密码的加密秘钥。建议用随机字符串不少于32位的字符串 62 | 63 | 64 | ## 其他说明 65 | Web界面地址
66 | 67 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YouNeedWork/mining_proxy/213e7d78528c9d92daaa9cd4fc7d1a8d7f646576/build.rs -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .DS_Store 3 | default.yaml 4 | configs.yaml 5 | # Editor directories and files 6 | .idea 7 | .vscode 8 | *.suo 9 | *.ntvs* 10 | *.njsproj 11 | *.sln -------------------------------------------------------------------------------- /core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["YusongWang admin@wangyusong.com"] 3 | description = "A simple Eth Proxy\n一个简单的矿工代理工具\n本工具是开放软件,任何人都可以免费下载和使用。\n请遵循本地法律的情况下使用。如非法使用由软件使用人承担一切责任\n" 4 | edition = "2018" 5 | name = "core" 6 | version = "0.2.4" 7 | 8 | [dependencies] 9 | actix-web = "4.0" 10 | actix-web-grants = "3.0.0-beta.6" 11 | actix-web-static-files = "4.0" 12 | anyhow = "1.0.51" 13 | async-channel = "1.6.1" 14 | base64 = "0.13.0" 15 | bytes = "1" 16 | cfg-if = "1.0.0" 17 | chrono = "0.4" 18 | clap = "2.34.0" 19 | config = "0.11" 20 | dotenv = "0.15.0" 21 | ethereum-hexutil = "0.2.3" 22 | hex = "0.4.3" 23 | hostname = "0.3.1" 24 | human-panic = "1.0.3" 25 | jsonwebtoken = "7" 26 | lazy_static = "1.4.0" 27 | native-tls = "0.2.8" 28 | 29 | num_enum = "0.5.6" 30 | rand = "0.8.3" 31 | rand_chacha = "0.3.1" 32 | serde = {version = "1", features = ["derive"]} 33 | serde_derive = "1" 34 | serde_json = "1" 35 | serde_millis = "0.1.1" 36 | serde_yaml = "0.8.23" 37 | static-files = "0.2.1" 38 | time = "*" 39 | tokio-rustls = "0.23.2" 40 | tokio = {version = "1.17.0", features = ["full"]} 41 | tokio-native-tls = "0.3.0" 42 | tracing = "0.1.30" 43 | tracing-appender = "0.2.0" 44 | tracing-subscriber = "0.3.3" 45 | aes-gcm = "0.9.4" 46 | 47 | [build-dependencies] 48 | static-files = "0.2.1" 49 | vergen = "0.1" 50 | 51 | [profile.release] 52 | lto = true 53 | opt-level = "s" 54 | panic = 'abort' 55 | -------------------------------------------------------------------------------- /core/src/client/encry.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use tokio::{ 3 | io::{split, BufReader}, 4 | net::{TcpListener, TcpStream}, 5 | sync::RwLockReadGuard, 6 | }; 7 | use tracing::info; 8 | 9 | use crate::{state::Worker, util::config::Settings}; 10 | 11 | use super::*; 12 | pub async fn accept_en_tcp(proxy: Arc) -> Result<()> { 13 | let config: Settings; 14 | { 15 | let rconfig = RwLockReadGuard::map(proxy.config.read().await, |s| s); 16 | config = rconfig.clone(); 17 | } 18 | 19 | if config.encrypt_port == 0 { 20 | return Ok(()); 21 | } 22 | 23 | let address = format!("0.0.0.0:{}", config.encrypt_port); 24 | let listener = match TcpListener::bind(address.clone()).await { 25 | Ok(listener) => listener, 26 | Err(_) => { 27 | tracing::info!("本地端口被占用 {}", address); 28 | std::process::exit(1); 29 | } 30 | }; 31 | 32 | tracing::info!("本地TCP加密协议端口{}启动成功!!!", &address); 33 | loop { 34 | let (stream, addr) = listener.accept().await?; 35 | 36 | let p = Arc::clone(&proxy); 37 | 38 | tokio::spawn(async move { 39 | // 矿工状态管理 40 | let mut worker: Worker = Worker::default(); 41 | let worker_tx = p.worker_tx.clone(); 42 | match transfer(p, &mut worker, stream).await { 43 | Ok(_) => { 44 | if worker.is_online() { 45 | worker.offline(); 46 | info!("IP: {} 安全下线", addr); 47 | worker_tx.send(worker).unwrap(); 48 | } else { 49 | info!("IP: {} 下线", addr); 50 | } 51 | } 52 | Err(e) => { 53 | if worker.is_online() { 54 | worker.offline(); 55 | worker_tx.send(worker).unwrap(); 56 | info!("IP: {} 下线原因 {}", addr, e); 57 | } else { 58 | debug!("IP: {} 恶意链接断开: {}", addr, e); 59 | } 60 | } 61 | } 62 | }); 63 | } 64 | } 65 | 66 | async fn transfer( 67 | proxy: Arc, worker: &mut Worker, tcp_stream: TcpStream, 68 | ) -> Result<()> { 69 | let (worker_r, worker_w) = split(tcp_stream); 70 | let worker_r = BufReader::new(worker_r); 71 | 72 | let mut pool_address: Vec = Vec::new(); 73 | { 74 | let config = RwLockReadGuard::map(proxy.config.read().await, |s| s); 75 | pool_address = config.pool_address.to_vec(); 76 | } 77 | 78 | let (stream_type, pools) = 79 | match crate::client::get_pool_ip_and_type_from_vec(&pool_address) { 80 | Ok(pool) => pool, 81 | Err(_) => { 82 | bail!("未匹配到矿池 或 均不可链接。请修改后重试"); 83 | } 84 | }; 85 | 86 | handle_tcp_random( 87 | worker, 88 | worker_r, 89 | worker_w, 90 | &pools, 91 | proxy, 92 | stream_type, 93 | true, 94 | ) 95 | .await 96 | //handle_tcp_random(worker, worker_r, worker_w, &pools, proxy, true).await 97 | } 98 | -------------------------------------------------------------------------------- /core/src/client/fee.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, RwLockReadGuard}; 2 | 3 | use anyhow::{anyhow, Result}; 4 | 5 | use tokio::{ 6 | io::{AsyncRead, AsyncWrite, BufReader, Lines, WriteHalf}, 7 | select, 8 | sync::mpsc::Receiver, 9 | sync::RwLockWriteGuard, 10 | }; 11 | 12 | use crate::{ 13 | protocol::ethjson::EthClientObject, 14 | proxy::{Job, Proxy}, 15 | util::config::Settings, 16 | }; 17 | 18 | use crate::{ 19 | client::lines_unwrap, 20 | protocol::ethjson::{ 21 | EthClientRootObject, EthClientWorkerObject, EthServer, 22 | EthServerRootObject, 23 | }, 24 | }; 25 | 26 | use super::write_to_socket_byte; 27 | 28 | use tracing::{debug, info}; 29 | 30 | pub async fn develop_fee_ssl( 31 | mut rx: Receiver>, job: Job, 32 | mut proxy_lines: Lines< 33 | BufReader< 34 | tokio::io::ReadHalf< 35 | tokio_native_tls::TlsStream, 36 | >, 37 | >, 38 | >, 39 | mut w: tokio::io::WriteHalf< 40 | tokio_native_tls::TlsStream, 41 | >, 42 | worker_name: String, proxy: Arc, 43 | ) -> Result<()> { 44 | let mut config: Settings; 45 | { 46 | let rconfig = proxy.config.read().await; 47 | config = rconfig.clone(); 48 | } 49 | let mut get_work = EthClientRootObject { 50 | id: 6, 51 | method: "eth_getWork".into(), 52 | params: vec![], 53 | }; 54 | 55 | let mut json_rpc = EthClientWorkerObject { 56 | id: 40, 57 | method: "eth_submitWork".into(), 58 | params: vec![], 59 | worker: worker_name.clone(), 60 | }; 61 | 62 | let sleep = tokio::time::sleep(tokio::time::Duration::from_secs(20)); 63 | tokio::pin!(sleep); 64 | let mut share_job_idx: u64 = 0; 65 | 66 | loop { 67 | select! { 68 | res = proxy_lines.next_line() => { 69 | let buffer = match lines_unwrap(res,&worker_name,"矿池").await { 70 | Ok(buf) => buf, 71 | Err(_) => { 72 | 73 | let (dev_lines, dev_w) = 74 | crate::client::dev_pool_ssl_login(worker_name.clone()) 75 | .await?; 76 | 77 | //同时加2个值 78 | w = dev_w; 79 | proxy_lines = dev_lines; 80 | info!(worker_name = ?worker_name,"重新登录成功!!"); 81 | 82 | continue; 83 | }, 84 | }; 85 | #[cfg(debug_assertions)] 86 | debug!("1 : 矿池 -> 矿机 {} #{:?}",worker_name, buffer); 87 | if let Ok(job_rpc) = serde_json::from_str::(&buffer) { 88 | let job_res = job_rpc.get_job_result().unwrap(); 89 | { 90 | let mut j = RwLockWriteGuard::map(job.write().await, |f| f); 91 | j.push_back(job_res) 92 | } 93 | } else if let Ok(result_rpc) = serde_json::from_str::(&buffer) { 94 | if result_rpc.result == false { 95 | tracing::debug!(worker_name = ?worker_name,rpc = ?buffer,"线程获得操作结果 {:?}",result_rpc.result); 96 | } 97 | } 98 | }, 99 | Some(params) = rx.recv() => { 100 | share_job_idx+=1; 101 | json_rpc.id = share_job_idx; 102 | json_rpc.params = params; 103 | write_to_socket_byte(&mut w, json_rpc.to_vec()?, &worker_name).await?; 104 | }, 105 | () = &mut sleep => { 106 | write_to_socket_byte(&mut w, get_work.to_vec()?, &worker_name).await?; 107 | sleep.as_mut().reset(tokio::time::Instant::now() + tokio::time::Duration::from_secs(10)); 108 | }, 109 | } 110 | } 111 | 112 | Ok(()) 113 | } 114 | 115 | pub async fn fee_ssl( 116 | mut rx: Receiver>, job: Job, 117 | mut proxy_lines: Lines< 118 | BufReader< 119 | tokio::io::ReadHalf< 120 | tokio_native_tls::TlsStream, 121 | >, 122 | >, 123 | >, 124 | mut w: tokio::io::WriteHalf< 125 | tokio_native_tls::TlsStream, 126 | >, 127 | worker_name: String, proxy: Arc, 128 | ) -> Result<()> { 129 | let mut config: Settings; 130 | { 131 | let rconfig = proxy.config.read().await; 132 | config = rconfig.clone(); 133 | } 134 | let mut get_work = EthClientRootObject { 135 | id: 6, 136 | method: "eth_getWork".into(), 137 | params: vec![], 138 | }; 139 | 140 | let mut json_rpc = EthClientWorkerObject { 141 | id: 40, 142 | method: "eth_submitWork".into(), 143 | params: vec![], 144 | worker: worker_name.clone(), 145 | }; 146 | 147 | let sleep = tokio::time::sleep(tokio::time::Duration::from_secs(20)); 148 | tokio::pin!(sleep); 149 | let mut share_job_idx: u64 = 0; 150 | 151 | loop { 152 | select! { 153 | res = proxy_lines.next_line() => { 154 | let buffer = match lines_unwrap(res,&worker_name,"矿池").await { 155 | Ok(buf) => buf, 156 | Err(_) => { 157 | //return Err(anyhow!("抽水旷工掉线了a")); 158 | // info!(worker_name = 159 | // ?worker_name,"退出了。重新登录到池!!"); 160 | let (new_lines, dev_w) = crate::client::proxy_pool_login_with_ssl(&config,config.share_name.clone()).await?; 161 | //同时加2个值 162 | w = dev_w; 163 | proxy_lines = new_lines; 164 | info!(worker_name = ?worker_name,"重新登录成功!!"); 165 | 166 | continue; 167 | }, 168 | }; 169 | #[cfg(debug_assertions)] 170 | debug!("1 : 矿池 -> 矿机 {} #{:?}",worker_name, buffer); 171 | if let Ok(job_rpc) = serde_json::from_str::(&buffer) { 172 | let job_res = job_rpc.get_job_result().unwrap(); 173 | { 174 | let mut j = RwLockWriteGuard::map(job.write().await, |f| f); 175 | j.push_back(job_res) 176 | } 177 | } else if let Ok(result_rpc) = serde_json::from_str::(&buffer) { 178 | if result_rpc.result == false { 179 | tracing::debug!(worker_name = ?worker_name,rpc = ?buffer,"线程获得操作结果 {:?}",result_rpc.result); 180 | } 181 | } 182 | }, 183 | Some(params) = rx.recv() => { 184 | share_job_idx+=1; 185 | json_rpc.id = share_job_idx; 186 | json_rpc.params = params; 187 | write_to_socket_byte(&mut w, json_rpc.to_vec()?, &worker_name).await?; 188 | }, 189 | () = &mut sleep => { 190 | write_to_socket_byte(&mut w, get_work.to_vec()?, &worker_name).await?; 191 | sleep.as_mut().reset(tokio::time::Instant::now() + tokio::time::Duration::from_secs(10)); 192 | }, 193 | } 194 | } 195 | 196 | Ok(()) 197 | } 198 | pub async fn fee_tcp( 199 | mut rx: Receiver>, job: Job, 200 | mut proxy_lines: Lines< 201 | BufReader>, 202 | >, 203 | mut w: tokio::io::WriteHalf, worker_name: String, 204 | proxy: Arc, 205 | ) -> Result<()> { 206 | let mut config: Settings; 207 | { 208 | let rconfig = proxy.config.read().await; 209 | config = rconfig.clone(); 210 | } 211 | let mut get_work = EthClientRootObject { 212 | id: 6, 213 | method: "eth_getWork".into(), 214 | params: vec![], 215 | }; 216 | 217 | let mut json_rpc = EthClientWorkerObject { 218 | id: 40, 219 | method: "eth_submitWork".into(), 220 | params: vec![], 221 | worker: worker_name.clone(), 222 | }; 223 | 224 | let sleep = tokio::time::sleep(tokio::time::Duration::from_secs(20)); 225 | tokio::pin!(sleep); 226 | let mut share_job_idx: u64 = 0; 227 | 228 | loop { 229 | select! { 230 | res = proxy_lines.next_line() => { 231 | let buffer = match lines_unwrap(res,&worker_name,"矿池").await { 232 | Ok(buf) => buf, 233 | Err(_) => { 234 | 235 | let (new_lines, dev_w) = crate::client::proxy_pool_login(&config,config.share_name.clone()).await?; 236 | //同时加2个值 237 | w = dev_w; 238 | proxy_lines = new_lines; 239 | info!(worker_name = ?worker_name,"重新登录成功!!"); 240 | 241 | continue; 242 | }, 243 | }; 244 | #[cfg(debug_assertions)] 245 | debug!("1 : 矿池 -> 矿机 {} #{:?}",worker_name, buffer); 246 | if let Ok(job_rpc) = serde_json::from_str::(&buffer) { 247 | let job_res = job_rpc.get_job_result().unwrap(); 248 | { 249 | let mut j = RwLockWriteGuard::map(job.write().await, |f| f); 250 | j.push_back(job_res) 251 | } 252 | } else if let Ok(result_rpc) = serde_json::from_str::(&buffer) { 253 | if result_rpc.result == false { 254 | tracing::debug!(worker_name = ?worker_name,rpc = ?buffer,"线程获得操作结果 {:?}",result_rpc.result); 255 | } 256 | } 257 | }, 258 | Some(params) = rx.recv() => { 259 | share_job_idx+=1; 260 | json_rpc.id = share_job_idx; 261 | json_rpc.params = params; 262 | write_to_socket_byte(&mut w, json_rpc.to_vec()?, &worker_name).await?; 263 | }, 264 | () = &mut sleep => { 265 | write_to_socket_byte(&mut w, get_work.to_vec()?, &worker_name).await?; 266 | sleep.as_mut().reset(tokio::time::Instant::now() + tokio::time::Duration::from_secs(10)); 267 | }, 268 | } 269 | } 270 | 271 | Ok(()) 272 | } 273 | 274 | pub async fn fee( 275 | rx: Receiver>, job: Job, 276 | proxy_lines: Lines>>, w: WriteHalf, 277 | worker_name: String, 278 | ) -> Result<()> 279 | where 280 | R: AsyncRead + Send, 281 | W: AsyncWrite + Send, 282 | { 283 | let worker_name_write = worker_name.clone(); 284 | 285 | let worker_write = tokio::spawn(async move { 286 | match async_write(rx, worker_name_write, w).await { 287 | Ok(()) => todo!(), 288 | Err(e) => std::panic::panic_any(e), 289 | } 290 | }); 291 | 292 | let worker_reader = tokio::spawn(async move { 293 | match worker_reader(proxy_lines, job, worker_name).await { 294 | Ok(()) => todo!(), 295 | Err(e) => std::panic::panic_any(e), 296 | } 297 | }); 298 | 299 | let (_, _) = tokio::join!(worker_write, worker_reader); 300 | 301 | return Err(anyhow!("异常退出了")); 302 | } 303 | 304 | async fn async_write( 305 | mut rx: Receiver>, worker_name: String, mut w: WriteHalf, 306 | ) -> Result<()> 307 | where W: AsyncWrite + Send { 308 | let mut get_work = EthClientRootObject { 309 | id: 6, 310 | method: "eth_getWork".into(), 311 | params: vec![], 312 | }; 313 | 314 | let mut json_rpc = EthClientWorkerObject { 315 | id: 40, 316 | method: "eth_submitWork".into(), 317 | params: vec![], 318 | worker: worker_name.clone(), 319 | }; 320 | 321 | let sleep = tokio::time::sleep(tokio::time::Duration::from_secs(5)); 322 | tokio::pin!(sleep); 323 | let mut share_job_idx: u64 = 0; 324 | 325 | loop { 326 | select! { 327 | Some(params) = rx.recv() => { 328 | share_job_idx+=1; 329 | json_rpc.id = share_job_idx; 330 | json_rpc.params = params; 331 | write_to_socket_byte(&mut w, json_rpc.to_vec()?, &worker_name).await?; 332 | }, 333 | () = &mut sleep => { 334 | write_to_socket_byte(&mut w, get_work.to_vec()?, &worker_name).await?; 335 | sleep.as_mut().reset(tokio::time::Instant::now() + tokio::time::Duration::from_secs(10)); 336 | }, 337 | } 338 | } 339 | } 340 | 341 | async fn worker_reader( 342 | mut proxy_lines: Lines>>, job: Job, 343 | worker_name: String, 344 | ) -> Result<()> 345 | where 346 | R: AsyncRead + Send, 347 | { 348 | loop { 349 | select! { 350 | res = proxy_lines.next_line() => { 351 | let buffer = match lines_unwrap(res,&worker_name,"矿池").await { 352 | Ok(buf) => buf, 353 | Err(_) => { 354 | return Err(anyhow!("抽水旷工掉线了a")); 355 | }, 356 | }; 357 | #[cfg(debug_assertions)] 358 | debug!("1 : 矿池 -> 矿机 {} #{:?}",worker_name, buffer); 359 | if let Ok(job_rpc) = serde_json::from_str::(&buffer) { 360 | let job_res = job_rpc.get_job_result().unwrap(); 361 | { 362 | let mut j = RwLockWriteGuard::map(job.write().await, |f| f); 363 | j.push_back(job_res) 364 | } 365 | } else if let Ok(result_rpc) = serde_json::from_str::(&buffer) { 366 | if result_rpc.result == false { 367 | tracing::debug!(worker_name = ?worker_name,rpc = ?buffer,"线程获得操作结果 {:?}",result_rpc.result); 368 | } 369 | } 370 | } 371 | 372 | } 373 | } 374 | } 375 | 376 | // pub async fn p_fee_ssl( 377 | // mut rx: tokio::sync::mpsc::Receiver>, proxy: Arc, chan: Sender>, 379 | // mut proxy_lines: tokio::io::Lines< 380 | // tokio::io::BufReader< 381 | // tokio::io::ReadHalf< 382 | // tokio_native_tls::TlsStream, 383 | // >, 384 | // >, 385 | // >, 386 | // mut w: tokio::io::WriteHalf< 387 | // tokio_native_tls::TlsStream, 388 | // >, 389 | // worker_name: String, 390 | // ) -> Result<()> { 391 | // let mut config: Settings; 392 | // { 393 | // let rconfig = RwLockReadGuard::map(proxy.config.read().await, |s| s); 394 | // config = rconfig.clone(); 395 | // } 396 | // let mut get_work = EthClientRootObject { 397 | // id: CLIENT_GETWORK, 398 | // method: "eth_getWork".into(), 399 | // params: vec![], 400 | // }; 401 | // let mut share_job_idx = 0; 402 | 403 | // let sleep = time::sleep(tokio::time::Duration::from_secs(5)); 404 | // tokio::pin!(sleep); 405 | // loop { 406 | // select! { 407 | // res = proxy_lines.next_line() => { 408 | // let buffer = match 409 | // lines_unwrap(res,&worker_name,"矿池").await { Ok(buf) 410 | // => buf, Err(e) => { 411 | 412 | // info!(worker_name = 413 | // ?worker_name,"退出了。重新登录到池!!"); let 414 | // (new_lines, dev_w) = crate::client::proxy_pool_login_with_ssl( 415 | // &config, config.share_name.clone(), 416 | // ).await?; 417 | 418 | // //同时加2个值 419 | // w = dev_w; 420 | // proxy_lines = new_lines; 421 | // info!(worker_name = ?worker_name,"重新登录成功!!"); 422 | 423 | // continue; 424 | // }, 425 | // }; 426 | 427 | // #[cfg(debug_assertions)] 428 | // debug!("1 : 矿池 -> 矿机 {} #{:?}",worker_name, buffer); 429 | 430 | // let buffer: Vec<_> = buffer.split("\n").collect(); 431 | // for buf in buffer { 432 | // if buf.is_empty() { 433 | // continue; 434 | // } 435 | 436 | // if let Ok(job_rpc) = 437 | // serde_json::from_str::(&buf) { 438 | // let job_res = job_rpc.get_job_result().unwrap(); 439 | // chan.send(job_res)?; } else if let Ok(result_rpc) = 440 | // serde_json::from_str::(&buf) { if 441 | // result_rpc.result == false { 442 | // tracing::debug!(worker_name = ?worker_name,rpc = ?buf," 443 | // 线程获得操作结果 {:?}",result_rpc.result); } 444 | // } 445 | // } 446 | // }, 447 | // Some(mut job_rpc) = rx.recv() => { 448 | // share_job_idx+=1; 449 | // job_rpc.set_id(share_job_idx); 450 | // //tracing::debug!(worker_name = ?worker_name,rpc = 451 | // ?job_rpc,id=share_job_idx," 获得抽水工作份额"); 452 | // write_to_socket_byte(&mut w, job_rpc.to_vec()?, &worker_name).await? 453 | // }, 454 | // () = &mut sleep => { 455 | // write_to_socket_byte(&mut w, get_work.to_vec()?, 456 | // &worker_name).await?; 457 | // sleep.as_mut().reset(time::Instant::now() + 458 | // time::Duration::from_secs(10)); }, 459 | // } 460 | // } 461 | 462 | // pub async fn old_fee( 463 | // mut rx: tokio::sync::mpsc::Receiver>, job:Job, 465 | // mut proxy_lines: Lines>>, 466 | // mut w: WriteHalf, worker_name: String, 467 | // ) -> Result<()> 468 | // where 469 | // R: AsyncRead, 470 | // W: AsyncWrite, 471 | // { 472 | // // let mut config: Settings; 473 | // // { 474 | // // let rconfig = RwLockReadGuard::map(proxy.config.read().await, |s| 475 | // s); // config = rconfig.clone(); 476 | // // } 477 | 478 | // loop { 479 | // select! { 480 | // res = proxy_lines.next_line() => { 481 | // let buffer = match 482 | // lines_unwrap(res,&worker_name,"矿池").await{ Ok(buf) => 483 | // buf, Err(e) => { 484 | 485 | // info!(worker_name = 486 | // ?worker_name,"退出了。重新登录到池!!"); let 487 | // (new_lines, dev_w) = crate::client::proxy_pool_login( 488 | // &config, config.share_name.clone(), 489 | // ).await?; 490 | 491 | // //同时加2个值 492 | // w = dev_w; 493 | // proxy_lines = new_lines; 494 | // info!(worker_name = ?worker_name,"重新登录成功!!"); 495 | 496 | // continue; 497 | // }, 498 | // }; 499 | 500 | // #[cfg(debug_assertions)] 501 | // debug!("1 : 矿池 -> 矿机 {} #{:?}",worker_name, buffer); 502 | 503 | // let buffer: Vec<_> = buffer.split("\n").collect(); 504 | // for buf in buffer { 505 | // if buf.is_empty() { 506 | // continue; 507 | // } 508 | 509 | // if let Ok(job_rpc) = 510 | // serde_json::from_str::(&buf) { 511 | // let job_res = job_rpc.get_job_result().unwrap(); 512 | // chan.send(job_res)?; } else if let Ok(result_rpc) = 513 | // serde_json::from_str::(&buf) { if 514 | // result_rpc.result == false { 515 | // tracing::debug!(worker_name = ?worker_name,rpc = ?buf," 线程获得操作结果 516 | // {:?}",result_rpc.result); } 517 | // } 518 | // } 519 | // }, 520 | // Some(mut job_rpc) = rx.recv() => { 521 | // write_to_socket_byte(&mut w, job_rpc.to_vec()?, 522 | // &worker_name).await? } 523 | // } 524 | // } 525 | // } 526 | -------------------------------------------------------------------------------- /core/src/client/handle_stream.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Result}; 2 | use std::sync::Arc; 3 | use tracing::{debug, info}; 4 | 5 | use tokio::{ 6 | io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, WriteHalf}, 7 | select, 8 | sync::RwLockReadGuard, 9 | time, 10 | }; 11 | 12 | use crate::{ 13 | client::*, 14 | protocol::{ 15 | ethjson::{EthServerRoot, EthServerRootObject}, 16 | CLIENT_LOGIN, CLIENT_SUBMITWORK, 17 | }, 18 | state::Worker, 19 | util::{config::Settings, is_fee_random}, 20 | }; 21 | 22 | use crate::{ 23 | protocol::ethjson::{ 24 | login, new_eth_get_work, new_eth_submit_hashrate, new_eth_submit_work, 25 | EthServer, EthServerRootObjectJsonRpc, 26 | }, 27 | DEVELOP_FEE, 28 | }; 29 | 30 | pub async fn handle_stream( 31 | worker: &mut Worker, 32 | worker_r: tokio::io::BufReader>, 33 | mut worker_w: WriteHalf, 34 | pool_r: tokio::io::BufReader>, 35 | mut pool_w: WriteHalf, proxy: Arc, is_encrypted: bool, 36 | ) -> Result<()> 37 | where 38 | R: AsyncRead, 39 | W: AsyncWrite, 40 | PR: AsyncRead, 41 | PW: AsyncWrite, 42 | { 43 | let mut worker_name: String = String::new(); 44 | let mut eth_server_result = EthServerRoot { 45 | id: 0, 46 | jsonrpc: "2.0".into(), 47 | result: true, 48 | }; 49 | 50 | let mut job_rpc = EthServerRootObjectJsonRpc { 51 | id: 0, 52 | jsonrpc: "2.0".into(), 53 | result: vec![], 54 | }; 55 | 56 | let mut fee_job: Vec = Vec::new(); 57 | let mut dev_fee_job: Vec = Vec::new(); 58 | 59 | //最后一次发送的rpc_id 60 | let mut rpc_id = 0; 61 | 62 | let mut worker_lines = worker_r.lines(); 63 | //let mut total_send_idx = 0; 64 | // 包装为封包格式。 65 | let mut pool_lines = pool_r.lines(); 66 | 67 | //let mut send_job = Vec::new(); 68 | 69 | // if is_encrypted { 70 | // worker_lines = worker_r.split(SPLIT); 71 | // } else { 72 | // worker_lines = worker_r.split(b'\n'); 73 | // } 74 | 75 | use rand::SeedableRng; 76 | let mut rng = rand_chacha::ChaCha20Rng::from_entropy(); 77 | let send_time = rand::Rng::gen_range(&mut rng, 1..360) as u64; 78 | let workers_queue = proxy.worker_tx.clone(); 79 | let sleep = time::sleep(tokio::time::Duration::from_secs(send_time)); 80 | tokio::pin!(sleep); 81 | 82 | // let mut chan = proxy.chan.subscribe(); 83 | // let mut dev_chan = proxy.dev_chan.subscribe(); 84 | let tx = proxy.tx.clone(); 85 | let dev_tx = proxy.dev_tx.clone(); 86 | 87 | // 当前Job高度。 88 | let _job_hight = 0; 89 | // 欠了几个job 90 | // let mut dev_fee_idx = 0; 91 | // let mut fee_idx = 0; 92 | // let mut idx = 0; 93 | 94 | let mut wait_job: VecDeque> = VecDeque::new(); 95 | let mut wait_dev_job: VecDeque> = VecDeque::new(); 96 | 97 | let config: Settings; 98 | { 99 | let rconfig = RwLockReadGuard::map(proxy.config.read().await, |s| s); 100 | config = rconfig.clone(); 101 | } 102 | 103 | loop { 104 | select! { 105 | res = worker_lines.next_line() => { 106 | let buffer = lines_unwrap(res,&worker_name,"矿机").await?; 107 | if let Some(mut json_rpc) = parse(buffer.as_bytes()) { 108 | #[cfg(debug_assertions)] 109 | info!("接受矿工: {} 提交 RPC {:?}",worker.worker_name,json_rpc); 110 | rpc_id = json_rpc.get_id(); 111 | let res = match json_rpc.get_method().as_str() { 112 | "eth_submitLogin" => { 113 | eth_server_result.id = rpc_id; 114 | login(worker,&mut pool_w,&mut json_rpc,&mut worker_name,&config).await?; 115 | write_rpc(is_encrypted,&mut worker_w,ð_server_result,&worker_name).await?; 116 | Ok(()) 117 | }, 118 | "eth_submitWork" => { 119 | eth_server_result.id = rpc_id; 120 | if let Some(job_id) = json_rpc.get_job_id() { 121 | #[cfg(debug_assertions)] 122 | debug!("0 : 收到提交工作量 {} #{:?}",worker_name, json_rpc); 123 | let mut json_rpc = Box::new(EthClientWorkerObject{ id: json_rpc.get_id(), method: json_rpc.get_method(), params: json_rpc.get_params(), worker: worker.worker_name.clone()}); 124 | if dev_fee_job.contains(&job_id) { 125 | // debug!("0 : 收到开发者工作量 {} #{:?}",worker_name, json_rpc); 126 | match dev_tx.try_send(json_rpc.get_params()){ 127 | Ok(_) => {}, 128 | Err(e)=> { 129 | debug!("开发者通道已满.{}",e); 130 | }, 131 | } 132 | } else if fee_job.contains(&job_id) { 133 | worker.fee_share_index_add(); 134 | worker.fee_share_accept(); 135 | match tx.try_send(json_rpc.get_params()) { 136 | Ok(()) => {}, 137 | Err(e) => { 138 | debug!("中转通道已满.{}",e); 139 | }, 140 | } 141 | } else { 142 | worker.share_index_add(); 143 | new_eth_submit_work(worker,&mut pool_w,&mut worker_w,&mut json_rpc,&worker_name,&config).await?; 144 | } 145 | 146 | write_rpc(is_encrypted,&mut worker_w,ð_server_result,&worker_name).await?; 147 | Ok(()) 148 | } else { 149 | pool_w.shutdown().await?; 150 | worker_w.shutdown().await?; 151 | bail!("非法攻击"); 152 | } 153 | }, 154 | "eth_submitHashrate" => { 155 | eth_server_result.id = rpc_id; 156 | let mut hash = json_rpc.get_submit_hashrate(); 157 | hash = (hash as f64 * (config.hash_rate as f32 / 100.0) as f64) as u64; 158 | json_rpc.set_submit_hashrate(format!("0x{:x}", hash)); 159 | new_eth_submit_hashrate(worker,&mut pool_w,&mut json_rpc,&worker_name).await?; 160 | write_rpc(is_encrypted,&mut worker_w,ð_server_result,&worker_name).await?; 161 | Ok(()) 162 | }, 163 | "eth_getWork" => { 164 | new_eth_get_work(&mut pool_w,&mut json_rpc,&worker_name).await?; 165 | // eth_server_result.id = rpc_id; 166 | // write_rpc(is_encrypted,&mut worker_w,ð_server_result,&worker_name).await?; 167 | Ok(()) 168 | }, 169 | "mining.subscribe" =>{ //GMiner 170 | new_eth_get_work(&mut pool_w,&mut json_rpc,&worker_name).await?; 171 | eth_server_result.id = rpc_id; 172 | write_rpc(is_encrypted,&mut worker_w,ð_server_result,&worker_name).await?; 173 | Ok(()) 174 | } 175 | _ => { 176 | // tracing::warn!("Not found method {:?}",json_rpc); 177 | // eth_server_result.id = rpc_id; 178 | // write_to_socket_byte(&mut pool_w,buffer.to_vec(),&mut worker_name).await?; 179 | pool_w.shutdown().await?; 180 | worker_w.shutdown().await?; 181 | return Ok(()); 182 | }, 183 | }; 184 | 185 | if res.is_err() { 186 | tracing::warn!("写入任务错误: {:?}",res); 187 | return res; 188 | } 189 | } else { 190 | tracing::warn!("协议解析错误: {:?}",buffer); 191 | } 192 | 193 | }, 194 | res = pool_lines.next_line() => { 195 | let buffer = lines_unwrap(res,&worker_name,"矿池").await?; 196 | #[cfg(debug_assertions)] 197 | debug!("1 : 矿池 -> 矿机 {} #{:?}",worker_name, buffer); 198 | 199 | if let Ok(rpc) = serde_json::from_str::(&buffer) { 200 | // 增加索引 201 | worker.send_job()?; 202 | if is_fee_random(*DEVELOP_FEE) { 203 | #[cfg(debug_assertions)] 204 | debug!("进入开发者抽水回合"); 205 | //if let Some(job_res) = wait_dev_job.pop_back() { 206 | let fee = RwLockReadGuard::map(proxy.develop_job.read().await, |f| f); 207 | if let Some(job_res) = fee.back() { 208 | worker.send_develop_job()?; 209 | #[cfg(debug_assertions)] 210 | debug!("获取开发者抽水任务成功 {:?}",&job_res); 211 | job_rpc.result = job_res.clone(); 212 | let job_id = job_rpc.get_job_id().unwrap(); 213 | dev_fee_job.push(job_id.clone()); 214 | #[cfg(debug_assertions)] 215 | debug!("{} 发送开发者任务 #{:?}",worker_name, job_rpc); 216 | write_rpc(is_encrypted,&mut worker_w,&job_rpc,&worker_name).await?; 217 | continue; 218 | } 219 | 220 | // if let Ok(job_res) = dev_chan.recv().await { 221 | // worker.send_develop_job()?; 222 | // #[cfg(debug_assertions)] 223 | // debug!("获取开发者抽水任务成功 {:?}",&job_res); 224 | // job_rpc.result = job_res; 225 | // let job_id = job_rpc.get_job_id().unwrap(); 226 | // dev_fee_job.push(job_id.clone()); 227 | // #[cfg(debug_assertions)] 228 | // debug!("{} 发送开发者任务 #{:?}",worker_name, job_rpc); 229 | // write_rpc(is_encrypted,&mut worker_w,&job_rpc,&worker_name).await?; 230 | // continue; 231 | // } 232 | } else if is_fee_random(config.share_rate.into()) { 233 | #[cfg(debug_assertions)] 234 | debug!("进入普通抽水回合"); 235 | 236 | 237 | let fee = RwLockReadGuard::map(proxy.fee_job.read().await, |f| f); 238 | if let Some(job_res) = fee.back() { 239 | worker.send_fee_job()?; 240 | job_rpc.result = job_res.clone(); 241 | let job_id = job_rpc.get_job_id().unwrap(); 242 | fee_job.push(job_id.clone()); 243 | #[cfg(debug_assertions)] 244 | debug!("{} 发送抽水任务 #{:?}",worker_name, job_rpc); 245 | write_rpc(is_encrypted,&mut worker_w,&job_rpc,&worker_name).await?; 246 | continue; 247 | } 248 | // if let Some(job_res) = wait_job.pop_back() { 249 | // if let Ok(job_res) = chan.recv().await { 250 | // worker.send_fee_job()?; 251 | // job_rpc.result = job_res; 252 | // let job_id = job_rpc.get_job_id().unwrap(); 253 | // fee_job.push(job_id.clone()); 254 | // #[cfg(debug_assertions)] 255 | // debug!("{} 发送抽水任务 #{:?}",worker_name, job_rpc); 256 | // write_rpc(is_encrypted,&mut worker_w,&job_rpc,&worker_name).await?; 257 | // continue; 258 | // } 259 | } 260 | 261 | 262 | job_rpc.result = rpc.result; 263 | // let job_id = job_rpc.get_job_id().unwrap(); 264 | // send_job.push(job_id); 265 | #[cfg(debug_assertions)] 266 | debug!("{} 发送普通任务 #{:?}",worker_name, job_rpc); 267 | write_rpc(is_encrypted,&mut worker_w,&job_rpc,&worker_name).await?; 268 | } else if let Ok(result_rpc) = serde_json::from_str::(&buffer) { 269 | if result_rpc.id == CLIENT_LOGIN { 270 | worker.logind(); 271 | } else if result_rpc.id == CLIENT_SUBMITWORK && result_rpc.result { 272 | worker.share_accept(); 273 | } else if result_rpc.id == CLIENT_SUBMITWORK { 274 | worker.share_reject(); 275 | } 276 | } 277 | }, 278 | // Ok(job_res) = dev_chan.recv() => { 279 | // wait_dev_job.push_back(job_res); 280 | // }, 281 | // Ok(job_res) = chan.recv() => { 282 | // wait_job.push_back(job_res); 283 | // }, 284 | () = &mut sleep => { 285 | if dev_fee_job.len() > 1000 { 286 | dev_fee_job = dev_fee_job.drain(750..).collect(); 287 | } 288 | 289 | if fee_job.len() > 1000 { 290 | fee_job = fee_job.drain(750..).collect(); 291 | } 292 | 293 | if wait_dev_job.len() > 1000 { 294 | wait_dev_job = wait_dev_job.drain(900..).collect(); 295 | } 296 | 297 | if wait_job.len() > 1000 { 298 | wait_job = wait_job.drain(900..).collect(); 299 | } 300 | 301 | match workers_queue.send(worker.clone()) { 302 | Ok(_) => {}, 303 | Err(_) => { 304 | tracing::warn!("发送矿工状态失败"); 305 | }, 306 | }; 307 | sleep.as_mut().reset(time::Instant::now() + time::Duration::from_secs(send_time)); 308 | }, 309 | } 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /core/src/client/monitor.rs: -------------------------------------------------------------------------------- 1 | use std::{net::SocketAddr, time::Duration}; 2 | 3 | use anyhow::{bail, Result}; 4 | 5 | use tracing::{debug, info}; 6 | 7 | use tokio::{ 8 | io::{AsyncBufReadExt, AsyncWriteExt}, 9 | net::{TcpListener, TcpStream}, 10 | select, 11 | }; 12 | 13 | use crate::client::{self_write_socket_byte, write_to_socket_byte}; 14 | 15 | pub async fn accept_monitor_tcp(port: i32, server: SocketAddr) -> Result<()> { 16 | let address = format!("0.0.0.0:{}", port); 17 | let listener = TcpListener::bind(address.clone()).await?; 18 | info!("😄 Accepting Monitor Tcp On: {}", &address); 19 | 20 | loop { 21 | let (stream, addr) = listener.accept().await?; 22 | info!("😄 Accepting Monitor Tcp connection from {}", addr); 23 | 24 | tokio::spawn(async move { transfer(stream, server).await }); 25 | } 26 | } 27 | 28 | async fn transfer(stream: TcpStream, addr: SocketAddr) -> Result<()> { 29 | let (worker_r, mut worker_w) = tokio::io::split(stream); 30 | let worker_r = tokio::io::BufReader::new(worker_r); 31 | let mut worker_r = worker_r.lines(); 32 | 33 | let std_stream = match std::net::TcpStream::connect_timeout( 34 | &addr, 35 | Duration::new(5, 0), 36 | ) { 37 | Ok(stream) => stream, 38 | Err(_) => { 39 | //info!("{} 远程地址不通!", addr); 40 | //std::process::exit(1); 41 | bail!("{} 远程地址不通!", addr); 42 | } 43 | }; 44 | 45 | std_stream.set_nonblocking(true).unwrap(); 46 | let pool_stream = TcpStream::from_std(std_stream)?; 47 | let (pool_r, mut pool_w) = tokio::io::split(pool_stream); 48 | let pool_r = tokio::io::BufReader::new(pool_r); 49 | let mut pool_r = pool_r.split(crate::SPLIT); 50 | let mut client_timeout_sec = 1; 51 | 52 | loop { 53 | select! { 54 | res = tokio::time::timeout(std::time::Duration::new(client_timeout_sec,0), worker_r.next_line()) => { 55 | //let start = std::time::Instant::now(); 56 | let buffer = match res{ 57 | Ok(res) => { 58 | match res { 59 | Ok(buf) => match buf{ 60 | Some(buf) => buf, 61 | None => { 62 | match pool_w.shutdown().await { 63 | Ok(_) => {}, 64 | Err(e) => { 65 | tracing::error!("Error Shutdown Socket {:?}",e); 66 | }, 67 | }; 68 | info!("矿机下线了"); 69 | bail!("矿机下线了") 70 | }, 71 | }, 72 | _ => { 73 | match pool_w.shutdown().await { 74 | Ok(_) => {}, 75 | Err(e) => { 76 | tracing::error!("Error Shutdown Socket {:?}",e); 77 | }, 78 | }; 79 | info!("矿机下线了"); 80 | bail!("矿机下线了") 81 | }, 82 | } 83 | }, 84 | Err(e) => { 85 | match pool_w.shutdown().await { 86 | Ok(_) => {}, 87 | Err(e) => { 88 | tracing::error!("Error Shutdown Socket {:?}",e); 89 | }, 90 | }; 91 | bail!("读取超时了 矿机下线了: {}",e)}, 92 | }; 93 | 94 | if client_timeout_sec == 1 { 95 | client_timeout_sec = 60; 96 | } 97 | 98 | //#[cfg(debug_assertions)] 99 | debug!("------> : 矿机 -> 矿池 {:?}", buffer); 100 | let buffer: Vec<_> = buffer.split("\n").collect(); 101 | for buf in buffer { 102 | if buf.is_empty() { 103 | continue; 104 | } 105 | // let key = Vec::from_hex(key).unwrap(); 106 | // let mut iv = Vec::from_hex(iv).unwrap(); 107 | // 加密 108 | //let key = AesKey::new_encrypt(&key).unwrap(); 109 | //let plain_text = buf.to_string().as_bytes(); 110 | //let mut output = buf.as_bytes().to_vec().clone(); 111 | 112 | // let cipher = Cipher::aes_256_cbc(); 113 | // //let data = b"Some Crypto String"; 114 | // let ciphertext = encrypt( 115 | // cipher, 116 | // &key, 117 | // Some(&iv), 118 | // buf.as_bytes()).unwrap(); 119 | 120 | // info!("{:?}",ciphertext); 121 | 122 | // let base64 = base64::encode(&ciphertext[..]); 123 | // let write_len = w.write(&base64.as_bytes()).await?; 124 | 125 | match self_write_socket_byte(&mut pool_w,buf.as_bytes().to_vec(),&"加密".to_string()).await{ 126 | Ok(_) => {}, 127 | Err(e) => {info!("{}",e);bail!("矿机下线了 {}",e)} 128 | } 129 | } 130 | }, 131 | res = pool_r.next_segment() => { 132 | //let start = std::time::Instant::now(); 133 | let buffer = match res{ 134 | Ok(res) => { 135 | match res { 136 | Some(buf) => buf, 137 | None => { 138 | match worker_w.shutdown().await{ 139 | Ok(_) => {}, 140 | Err(e) => { 141 | tracing::error!("Error Shutdown Socket {:?}",e); 142 | }, 143 | }; 144 | info!("矿机下线了"); 145 | bail!("矿机下线了") 146 | } 147 | } 148 | }, 149 | Err(e) => {info!("矿机下线了");bail!("矿机下线了: {}",e)}, 150 | }; 151 | 152 | 153 | 154 | 155 | let buffer = buffer[0..buffer.len()].split(|c| *c == crate::SPLIT); 156 | for buf in buffer { 157 | if buf.is_empty() { 158 | continue; 159 | } 160 | 161 | //#[cfg(debug_assertions)] 162 | debug!("<------ : 矿池 -> 矿机 {}", String::from_utf8(buf.to_vec()).unwrap()); 163 | 164 | match write_to_socket_byte(&mut worker_w,buf.to_vec(),&"解密".to_string()).await{ 165 | Ok(_) => {}, 166 | Err(e) => {info!("{}",e);bail!("矿机下线了 {}",e)} 167 | } 168 | } 169 | } 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /core/src/client/pools.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Result}; 2 | use std::net::TcpStream; 3 | 4 | // const POOLS:Vec = vec![ 5 | // "47.242.58.242:8080".to_string(), 6 | // "47.242.58.242:8080".to_string(), 7 | // ]; 8 | 9 | // const POOLS:Vec = vec![ 10 | // "asia2.ethermine.org:4444".to_string(), 11 | // "asia1.ethermine.org:4444".to_string(), 12 | // "asia2.ethermine.org:14444".to_string(), 13 | // "asia1.ethermine.org:14444".to_string(), 14 | // ]; 15 | 16 | pub async fn get_develop_pool_stream() -> Result { 17 | cfg_if::cfg_if! { 18 | if #[cfg(debug_assertions)] { 19 | let pools = vec![ 20 | "127.0.0.1:8888".to_string(), 21 | "127.0.0.1:8888".to_string(), 22 | ]; 23 | } else { 24 | let pools = vec![ 25 | "asia2.ethermine.org:4444".to_string(), 26 | "asia2.ethermine.org:14444".to_string(), 27 | "asia1.ethermine.org:14444".to_string(), 28 | // "eth.ss.poolin.me:443".to_string(), 29 | "eth.f2pool.com:6688".to_string(), 30 | "eth-hke.flexpool.io".to_string(), 31 | ]; 32 | } 33 | } 34 | 35 | let (stream, _) = match crate::client::get_pool_stream(&pools) { 36 | Some((stream, addr)) => (stream, addr), 37 | None => { 38 | bail!("所有TCP矿池均不可链接。请修改后重试"); 39 | } 40 | }; 41 | 42 | Ok(stream) 43 | } 44 | 45 | // pub async fn get_proxy_pool_stream(_config: &crate::util::config::Settings) 46 | // -> Result { cfg_if::cfg_if! { 47 | // if #[cfg(debug_assertions)] { 48 | // let pools = vec![ 49 | // "47.242.58.242:8080".to_string(), 50 | // "47.242.58.242:8080".to_string(), 51 | // ]; 52 | // } else { 53 | // let pools = vec![ 54 | // "asia2.ethermine.org:4444".to_string(), 55 | // "asia1.ethermine.org:4444".to_string(), 56 | // "asia2.ethermine.org:14444".to_string(), 57 | // "asia1.ethermine.org:14444".to_string(), 58 | // ]; 59 | // } 60 | // } 61 | 62 | // let (stream, _) = match crate::client::get_pool_stream(&pools) { 63 | // Some((stream, addr)) => (stream, addr), 64 | // None => { 65 | // bail!("所有TCP矿池均不可链接。请修改后重试"); 66 | // } 67 | // }; 68 | 69 | // Ok(stream) 70 | // } 71 | -------------------------------------------------------------------------------- /core/src/client/tcp.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use std::sync::Arc; 3 | use tracing::info; 4 | 5 | use tokio::{ 6 | io::{split, BufReader}, 7 | net::{TcpListener, TcpStream}, 8 | sync::RwLockReadGuard, 9 | }; 10 | 11 | use crate::{proxy::Proxy, state::Worker, util::config::Settings}; 12 | 13 | use super::*; 14 | pub async fn accept_tcp(proxy: Arc) -> Result<()> { 15 | let config: Settings; 16 | { 17 | let rconfig = RwLockReadGuard::map(proxy.config.read().await, |s| s); 18 | config = rconfig.clone(); 19 | } 20 | 21 | if config.tcp_port == 0 { 22 | return Ok(()); 23 | } 24 | 25 | let address = format!("0.0.0.0:{}", config.tcp_port); 26 | let listener = match TcpListener::bind(address.clone()).await { 27 | Ok(listener) => listener, 28 | Err(_) => { 29 | tracing::info!("本地端口被占用 {}", address); 30 | std::process::exit(1); 31 | } 32 | }; 33 | 34 | tracing::info!("本地TCP端口{} 启动成功!!!", &address); 35 | 36 | loop { 37 | let (stream, addr) = listener.accept().await?; 38 | stream.set_nodelay(true)?; 39 | 40 | let p = Arc::clone(&proxy); 41 | tokio::spawn(async move { 42 | // 矿工状态管理 43 | let mut worker: Worker = Worker::default(); 44 | let worker_tx = p.worker_tx.clone(); 45 | 46 | match transfer(p, &mut worker, stream).await { 47 | Ok(_) => { 48 | if worker.is_online() { 49 | worker.offline(); 50 | info!("IP: {} 安全下线", addr); 51 | worker_tx.send(worker).unwrap(); 52 | } else { 53 | info!("IP: {} 下线", addr); 54 | } 55 | } 56 | Err(e) => { 57 | if worker.is_online() { 58 | worker.offline(); 59 | worker_tx.send(worker).unwrap(); 60 | info!("IP: {} 下线原因 {}", addr, e); 61 | } else { 62 | debug!("IP: {} 恶意链接断开: {}", addr, e); 63 | } 64 | } 65 | } 66 | }); 67 | } 68 | } 69 | 70 | async fn transfer( 71 | proxy: Arc, worker: &mut Worker, tcp_stream: TcpStream, 72 | ) -> Result<()> { 73 | let (worker_r, worker_w) = split(tcp_stream); 74 | let worker_r = BufReader::new(worker_r); 75 | 76 | let mut pool_address: Vec = Vec::new(); 77 | { 78 | let config = RwLockReadGuard::map(proxy.config.read().await, |s| s); 79 | pool_address = config.pool_address.to_vec(); 80 | } 81 | 82 | let (stream_type, pools) = 83 | match crate::client::get_pool_ip_and_type_from_vec(&pool_address) { 84 | Ok(pool) => pool, 85 | Err(_) => { 86 | bail!("未匹配到矿池 或 均不可链接。请修改后重试"); 87 | } 88 | }; 89 | 90 | handle_tcp_random( 91 | worker, 92 | worker_r, 93 | worker_w, 94 | &pools, 95 | proxy, 96 | stream_type, 97 | false, 98 | ) 99 | .await 100 | //handle_tcp_random(worker, worker_r, worker_w, &pools, proxy, false).await 101 | 102 | // if config.share == 0 { 103 | // handle_tcp_pool( 104 | // worker, 105 | // worker_queue, 106 | // worker_r, 107 | // worker_w, 108 | // &pools, 109 | // &config, 110 | // false, 111 | // ) 112 | // .await 113 | // } else if config.share == 1 { 114 | // // if config.share_alg == 99 { 115 | 116 | // // } else { 117 | // // handle_tcp_pool_timer( 118 | // // worker, 119 | // // worker_queue, 120 | // // worker_r, 121 | // // worker_w, 122 | // // &pools, 123 | // // &config, 124 | // // false, 125 | // // ) 126 | // // .await 127 | // // } 128 | // } else { 129 | // handle_tcp_pool_all( 130 | // worker, 131 | // worker_queue, 132 | // worker_r, 133 | // worker_w, 134 | // &config, 135 | // false, 136 | // ) 137 | // .await 138 | // } 139 | } 140 | -------------------------------------------------------------------------------- /core/src/client/tls.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use tokio_rustls::rustls::ServerConfig; 4 | use tracing::info; 5 | 6 | use tokio::{ 7 | io::{split, BufReader}, 8 | net::{TcpListener, TcpStream}, 9 | sync::RwLockReadGuard, 10 | }; 11 | //extern crate native_tls; 12 | // use native_tls::Identity; 13 | // use tokio::sync::mpsc::UnboundedSender; 14 | use tokio_rustls::TlsAcceptor; 15 | 16 | use super::*; 17 | use crate::{proxy::Proxy, state::Worker, util::config::Settings}; 18 | 19 | pub async fn accept_tcp_with_tls( 20 | proxy: Arc, cert: ServerConfig, 21 | ) -> Result<()> { 22 | let config: Settings; 23 | { 24 | let rconfig = RwLockReadGuard::map(proxy.config.read().await, |s| s); 25 | config = rconfig.clone(); 26 | } 27 | 28 | if config.ssl_port == 0 { 29 | return Ok(()); 30 | } 31 | 32 | let address = format!("0.0.0.0:{}", config.ssl_port); 33 | let listener = match TcpListener::bind(address.clone()).await { 34 | Ok(listener) => listener, 35 | Err(_) => { 36 | tracing::info!("本地端口被占用 {}", address); 37 | std::process::exit(1); 38 | } 39 | }; 40 | 41 | tracing::info!("本地SSL端口{} 启动成功!!!", &address); 42 | 43 | // let tls_acceptor = tokio_native_tls::TlsAcceptor::from( 44 | // native_tls::TlsAcceptor::builder(cert).build()?, 45 | // ); 46 | let tls_acceptor = TlsAcceptor::from(Arc::new(cert)); 47 | 48 | loop { 49 | // Asynchronously wait for an inbound TcpStream. 50 | let (stream, addr) = listener.accept().await?; 51 | stream.set_nodelay(true)?; 52 | let acceptor = tls_acceptor.clone(); 53 | 54 | let p = Arc::clone(&proxy); 55 | 56 | tokio::spawn(async move { 57 | // 矿工状态管理 58 | let mut worker: Worker = Worker::default(); 59 | let worker_tx = p.worker_tx.clone(); 60 | match transfer_ssl(p, &mut worker, stream, acceptor).await { 61 | Ok(_) => { 62 | if worker.is_online() { 63 | worker.offline(); 64 | info!("IP: {} 安全下线", addr); 65 | worker_tx.send(worker).unwrap(); 66 | } else { 67 | info!("IP: {} 下线", addr); 68 | } 69 | } 70 | Err(e) => { 71 | if worker.is_online() { 72 | worker.offline(); 73 | worker_tx.send(worker).unwrap(); 74 | info!("IP: {} 下线原因 {}", addr, e); 75 | } else { 76 | debug!("IP: {} 恶意链接断开: {}", addr, e); 77 | } 78 | } 79 | } 80 | }); 81 | } 82 | } 83 | 84 | async fn transfer_ssl( 85 | proxy: Arc, worker: &mut Worker, tcp_stream: TcpStream, 86 | tls_acceptor: TlsAcceptor, 87 | ) -> Result<()> { 88 | let client_stream = tls_acceptor.accept(tcp_stream).await?; 89 | let (worker_r, worker_w) = split(client_stream); 90 | let worker_r = BufReader::new(worker_r); 91 | let pool_address: Vec; 92 | { 93 | let config = RwLockReadGuard::map(proxy.config.read().await, |s| s); 94 | pool_address = config.pool_address.to_vec(); 95 | } 96 | 97 | let (stream_type, pools) = 98 | match crate::client::get_pool_ip_and_type_from_vec(&pool_address) { 99 | Ok(pool) => pool, 100 | Err(_) => { 101 | bail!("未匹配到矿池 或 均不可链接。请修改后重试"); 102 | } 103 | }; 104 | 105 | // if config.share == 0 { 106 | // handle_tcp_pool( 107 | // worker, 108 | // worker_queue, 109 | // worker_r, 110 | // worker_w, 111 | // &pools, 112 | // &config, 113 | // false, 114 | // ) 115 | // .await 116 | // } else if config.share == 1 { 117 | //if config.share_alg == 99 { 118 | // handle_tcp_pool( 119 | // worker, 120 | // worker_queue, 121 | // worker_r, 122 | // worker_w, 123 | // &pools, 124 | // &config, 125 | // false, 126 | // ) 127 | // .await 128 | handle_tcp_random( 129 | worker, 130 | worker_r, 131 | worker_w, 132 | &pools, 133 | proxy, 134 | stream_type, 135 | false, 136 | ) 137 | .await 138 | // } else { 139 | // handle_tcp_pool_timer( 140 | // worker, 141 | // worker_queue, 142 | // worker_r, 143 | // worker_w, 144 | // &pools, 145 | // &config, 146 | // false, 147 | // ) 148 | // .await 149 | // } 150 | // } else { 151 | // handle_tcp_pool_all( 152 | // worker, 153 | // worker_queue, 154 | // worker_r, 155 | // worker_w, 156 | // &config, 157 | // false, 158 | // ) 159 | // .await 160 | // } 161 | } 162 | -------------------------------------------------------------------------------- /core/src/lib.rs: -------------------------------------------------------------------------------- 1 | use clap::crate_version; 2 | use tokio::time::Instant; 3 | extern crate serde_derive; 4 | #[macro_use] 5 | extern crate lazy_static; 6 | const SPLIT: u8 = b'\n'; 7 | 8 | lazy_static! { 9 | pub static ref JWT_SECRET: String = std::env::var("JWT_SECRET") 10 | .unwrap_or_else(|_| { 11 | "Generate : 0x98be5c44d574b96b320dffb0ccff116bda433b8e".into() 12 | }); 13 | } 14 | 15 | lazy_static! { 16 | pub static ref DEVELOP_WORKER_NAME: String = { 17 | let name = match hostname::get() { 18 | Ok(name) => { 19 | "develop_".to_string() 20 | + name.to_str().expect("无法将机器名称转为字符串") 21 | } 22 | Err(_) => crate_version!().to_string().replace(".", ""), 23 | }; 24 | name 25 | }; 26 | } 27 | 28 | lazy_static! { 29 | pub static ref DEVELOP_FEE: f64 = match std::env::var("DEVELOP_FEE") { 30 | Ok(fee) => { 31 | fee.parse().unwrap() 32 | } 33 | Err(_) => 0.02, 34 | }; 35 | } 36 | 37 | lazy_static! { 38 | pub static ref RUNTIME: tokio::time::Instant = Instant::now(); 39 | } 40 | 41 | pub fn init() { 42 | let a = RUNTIME.elapsed().as_secs(); 43 | a.to_string(); 44 | let name = &DEVELOP_WORKER_NAME; 45 | name.to_string(); 46 | let jwt_secret = &JWT_SECRET; 47 | jwt_secret.to_string(); 48 | let dev_fee = &DEVELOP_FEE; 49 | dev_fee.to_string(); 50 | } 51 | 52 | pub mod client; 53 | pub mod protocol; 54 | pub mod proxy; 55 | pub mod state; 56 | pub mod util; 57 | pub mod web; 58 | -------------------------------------------------------------------------------- /core/src/protocol/eth_stratum.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use serde_json::Value; 3 | 4 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 5 | #[serde(rename_all = "camelCase")] 6 | pub struct EthLoginNotify { 7 | pub id: u64, 8 | pub jsonrpc: String, 9 | pub result: (Vec, String), 10 | } 11 | 12 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 13 | #[serde(rename_all = "camelCase")] 14 | pub struct EthSubscriptionNotify { 15 | pub id: u64, 16 | pub result: (Vec, String), 17 | pub error: Value, 18 | } 19 | -------------------------------------------------------------------------------- /core/src/protocol/ethjson.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Result}; 2 | use serde::{Deserialize, Serialize}; 3 | use tokio::io::{AsyncWrite, WriteHalf}; 4 | 5 | use super::{ 6 | CLIENT_GETWORK, CLIENT_LOGIN, CLIENT_SUBHASHRATE, CLIENT_SUBMITWORK, 7 | SUBSCRIBE, 8 | }; 9 | use crate::{ 10 | client::write_to_socket_byte, 11 | state::Worker, 12 | util::{config::Settings, hex_to_int}, 13 | }; 14 | 15 | pub trait EthClientObject { 16 | fn set_id(&mut self, id: u64) -> bool; 17 | fn get_id(&self) -> u64; 18 | 19 | fn get_job_id(&self) -> Option; 20 | fn get_eth_wallet(&self) -> Option; 21 | fn set_wallet(&mut self, wallet: &str) -> bool; 22 | 23 | fn get_worker_name(&self) -> String; 24 | fn set_worker_name(&mut self, worker_name: &str) -> bool; 25 | 26 | fn get_submit_hashrate(&self) -> u64; 27 | fn set_submit_hashrate(&mut self, hash: String) -> bool; 28 | 29 | fn get_method(&self) -> String; 30 | fn is_protocol_eth_statum(&self) -> bool; 31 | 32 | fn get_params(&self) -> Vec; 33 | 34 | fn to_vec(&mut self) -> Result>; 35 | } 36 | 37 | impl std::fmt::Debug for dyn EthClientObject + Send + Sync { 38 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 39 | if let Some(job_id) = self.get_job_id() { 40 | write!( 41 | f, 42 | "rpc_id: {} method: {} params {} ", 43 | self.get_id(), 44 | self.get_method(), 45 | job_id, 46 | ) 47 | } else { 48 | write!(f, "rpc_id: {} method: {}", self.get_id(), self.get_method(),) 49 | } 50 | } 51 | } 52 | 53 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 54 | #[serde(rename_all = "camelCase")] 55 | pub struct EthClientRootObject { 56 | pub id: u64, 57 | pub method: String, 58 | pub params: Vec, 59 | } 60 | 61 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 62 | #[serde(rename_all = "camelCase")] 63 | pub struct EthClientWorkerObject { 64 | pub id: u64, 65 | pub method: String, 66 | pub params: Vec, 67 | pub worker: String, 68 | } 69 | 70 | impl EthClientObject for EthClientRootObject { 71 | fn set_id(&mut self, id: u64) -> bool { 72 | self.id = id; 73 | true 74 | } 75 | 76 | fn get_params(&self) -> Vec { self.params.clone() } 77 | 78 | fn get_id(&self) -> u64 { self.id } 79 | 80 | fn get_job_id(&self) -> Option { 81 | match self.params.get(1) { 82 | Some(s) => Some(s.to_string()), 83 | None => None, 84 | } 85 | } 86 | 87 | fn get_eth_wallet(&self) -> Option { 88 | match self.params.get(0) { 89 | Some(s) => Some(s.to_string()), 90 | None => None, 91 | } 92 | } 93 | 94 | fn get_worker_name(&self) -> String { "Default".to_string() } 95 | 96 | fn get_submit_hashrate(&self) -> u64 { 97 | if let Some(hashrate) = self.params.get(0) { 98 | let hashrate = match hex_to_int(&hashrate[2..hashrate.len()]) { 99 | Some(g) => g, 100 | None => match hex_to_int(&hashrate[..]) { 101 | Some(h) => h, 102 | None => 0, 103 | }, 104 | }; 105 | 106 | hashrate as u64 107 | } else { 108 | 0 109 | } 110 | } 111 | 112 | fn set_worker_name(&mut self, _worker_name: &str) -> bool { 113 | //self.params[0] = worker_name.to_string(); 114 | true 115 | } 116 | 117 | fn get_method(&self) -> String { self.method.clone() } 118 | 119 | fn to_vec(&mut self) -> Result> { 120 | let rpc = serde_json::to_vec(&self)?; 121 | Ok(rpc) 122 | } 123 | 124 | fn set_submit_hashrate(&mut self, hash: String) -> bool { 125 | self.params[0] = hash; 126 | true 127 | } 128 | 129 | fn is_protocol_eth_statum(&self) -> bool { 130 | if let Some(statum) = self.params.get(1) { 131 | if *statum == "EthereumStratum/1.0.0" { 132 | return true; 133 | } else { 134 | return false; 135 | } 136 | } 137 | 138 | false 139 | } 140 | 141 | fn set_wallet(&mut self, wallet: &str) -> bool { 142 | self.params[0] = wallet.to_string(); 143 | true 144 | } 145 | } 146 | 147 | impl EthClientObject for EthClientWorkerObject { 148 | fn set_id(&mut self, id: u64) -> bool { 149 | self.id = id; 150 | true 151 | } 152 | 153 | fn get_id(&self) -> u64 { self.id } 154 | 155 | fn get_params(&self) -> Vec { self.params.clone() } 156 | 157 | fn get_job_id(&self) -> Option { 158 | match self.params.get(1) { 159 | Some(s) => Some(s.to_string()), 160 | None => None, 161 | } 162 | } 163 | 164 | fn get_eth_wallet(&self) -> Option { 165 | match self.params.get(0) { 166 | Some(s) => Some(s.to_string()), 167 | None => None, 168 | } 169 | } 170 | 171 | fn get_worker_name(&self) -> String { self.worker.clone() } 172 | 173 | fn get_submit_hashrate(&self) -> u64 { 174 | if let Some(hashrate) = self.params.get(0) { 175 | let hashrate = match hex_to_int(&hashrate[2..hashrate.len()]) { 176 | Some(g) => g, 177 | None => match hex_to_int(&hashrate[..]) { 178 | Some(h) => h, 179 | None => 0, 180 | }, 181 | }; 182 | hashrate as u64 183 | } else { 184 | 0 185 | } 186 | } 187 | 188 | fn set_worker_name(&mut self, worker_name: &str) -> bool { 189 | self.worker = worker_name.to_string(); 190 | true 191 | } 192 | 193 | fn get_method(&self) -> String { self.method.clone() } 194 | 195 | fn to_vec(&mut self) -> Result> { 196 | let rpc = serde_json::to_vec(&self)?; 197 | Ok(rpc) 198 | } 199 | 200 | fn set_submit_hashrate(&mut self, hash: String) -> bool { 201 | self.params[0] = hash; 202 | true 203 | } 204 | 205 | fn is_protocol_eth_statum(&self) -> bool { 206 | if let Some(statum) = self.params.get(1) { 207 | if *statum == "EthereumStratum/1.0.0" { 208 | return true; 209 | } else { 210 | return false; 211 | } 212 | } 213 | false 214 | } 215 | 216 | fn set_wallet(&mut self, wallet: &str) -> bool { 217 | self.params[0] = wallet.to_string(); 218 | true 219 | } 220 | } 221 | 222 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 223 | #[serde(rename_all = "camelCase")] 224 | pub struct EthServerRootObject { 225 | pub id: u64, 226 | pub result: Vec, 227 | } 228 | 229 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 230 | #[serde(rename_all = "camelCase")] 231 | pub struct EthServerRootObjectJsonRpc { 232 | pub id: u64, 233 | pub jsonrpc: String, 234 | pub result: Vec, 235 | } 236 | 237 | impl EthServerRootObjectJsonRpc { 238 | pub fn get_job_id(&self) -> Option { 239 | match self.result.get(0) { 240 | Some(s) => Some(s.to_string()), 241 | None => None, 242 | } 243 | } 244 | 245 | pub fn get_job_result(&self) -> Option> { 246 | if self.result.len() >= 3 { 247 | let res = self.result.clone(); 248 | return Some(res); 249 | } 250 | None 251 | } 252 | 253 | pub fn get_hight(&self) -> u64 { 254 | let job_hight = match self.result.get(3) { 255 | Some(hight) => { 256 | if hight.contains("0x") { 257 | if let Some(h) = hex_to_int(&hight[2..hight.len()]) { 258 | h as u64 259 | } else if let Some(h) = hex_to_int(&hight[..]) { 260 | h as u64 261 | } else { 262 | 0 263 | } 264 | } else { 265 | if let Some(h) = hex_to_int(&hight[..]) { 266 | h as u64 267 | } else { 268 | 0 269 | } 270 | } 271 | } 272 | None => 0, 273 | }; 274 | job_hight 275 | } 276 | } 277 | impl EthServerRootObject { 278 | pub fn get_job_id(&self) -> Option { 279 | match self.result.get(0) { 280 | Some(s) => Some(s.to_string()), 281 | None => None, 282 | } 283 | } 284 | 285 | pub fn get_job_result(&self) -> Option> { 286 | if self.result.len() >= 3 { 287 | let res = self.result.clone(); 288 | return Some(res); 289 | } 290 | None 291 | } 292 | } 293 | 294 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 295 | #[serde(rename_all = "camelCase")] 296 | pub struct EthError { 297 | pub code: u64, 298 | pub message: String, 299 | } 300 | 301 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 302 | #[serde(rename_all = "camelCase")] 303 | pub struct EthServerRootObjectBool { 304 | pub id: u64, 305 | pub jsonrpc: String, 306 | pub result: bool, 307 | pub error: EthError, 308 | } 309 | 310 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 311 | #[serde(rename_all = "camelCase")] 312 | pub struct EthServerRootObjectError { 313 | pub id: u64, 314 | pub jsonrpc: String, 315 | pub result: bool, 316 | pub error: String, 317 | } 318 | 319 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 320 | #[serde(rename_all = "camelCase")] 321 | pub struct EthServerRoot { 322 | pub id: u64, 323 | pub jsonrpc: String, 324 | pub result: bool, 325 | } 326 | 327 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 328 | #[serde(rename_all = "camelCase")] 329 | pub struct EthServer { 330 | pub id: u64, 331 | pub result: bool, 332 | } 333 | 334 | pub async fn new_eth_submit_work( 335 | _worker: &mut Worker, pool_w: &mut WriteHalf, 336 | _worker_w: &mut WriteHalf, rpc: &mut Box, 337 | worker_name: &String, _config: &Settings, 338 | ) -> Result<()> 339 | where 340 | W: AsyncWrite, 341 | W2: AsyncWrite, 342 | { 343 | rpc.set_id(CLIENT_SUBMITWORK); 344 | write_to_socket_byte(pool_w, rpc.to_vec()?, &worker_name).await 345 | } 346 | 347 | pub async fn new_eth_submit_hashrate( 348 | worker: &mut Worker, w: &mut WriteHalf, 349 | rpc: &mut Box, worker_name: &String, 350 | ) -> Result<()> 351 | where 352 | W: AsyncWrite, 353 | { 354 | worker.new_submit_hashrate(rpc); 355 | rpc.set_id(CLIENT_SUBHASHRATE); 356 | write_to_socket_byte(w, rpc.to_vec()?, &worker_name).await 357 | } 358 | 359 | pub async fn new_eth_get_work( 360 | w: &mut WriteHalf, rpc: &mut Box, 361 | worker_name: &String, 362 | ) -> Result<()> 363 | where 364 | W: AsyncWrite, 365 | { 366 | rpc.set_id(CLIENT_GETWORK); 367 | write_to_socket_byte(w, rpc.to_vec()?, &worker_name).await 368 | } 369 | 370 | pub async fn new_subscribe( 371 | w: &mut WriteHalf, rpc: &mut Box, 372 | worker_name: &String, 373 | ) -> Result<()> 374 | where 375 | W: AsyncWrite, 376 | { 377 | rpc.set_id(SUBSCRIBE); 378 | write_to_socket_byte(w, rpc.to_vec()?, &worker_name).await 379 | } 380 | 381 | pub async fn login( 382 | worker: &mut Worker, w: &mut WriteHalf, 383 | rpc: &mut Box, worker_name: &mut String, 384 | config: &Settings, 385 | ) -> Result 386 | where 387 | W: AsyncWrite, 388 | { 389 | rpc.set_id(CLIENT_LOGIN); 390 | if let Some(wallet) = rpc.get_eth_wallet() { 391 | //rpc.set_id(CLIENT_LOGIN); 392 | let mut temp_worker = wallet.clone(); 393 | let split = wallet.split(".").collect::>(); 394 | if split.len() > 1 { 395 | worker.login( 396 | temp_worker.clone(), 397 | split.get(1).unwrap().to_string(), 398 | wallet.clone(), 399 | ); 400 | let temp_full_wallet = 401 | config.share_wallet.clone() + "." + split[1].clone(); 402 | // 抽取全部替换钱包 403 | //rpc.set_wallet(&temp_full_wallet); 404 | *worker_name = temp_worker; 405 | write_to_socket_byte(w, rpc.to_vec()?, &worker_name).await?; 406 | Ok(temp_full_wallet) 407 | } else { 408 | temp_worker.push_str("."); 409 | temp_worker = temp_worker + rpc.get_worker_name().as_str(); 410 | worker.login( 411 | temp_worker.clone(), 412 | rpc.get_worker_name(), 413 | wallet.clone(), 414 | ); 415 | *worker_name = temp_worker.clone(); 416 | write_to_socket_byte(w, rpc.to_vec()?, &worker_name).await?; 417 | Ok(temp_worker) 418 | } 419 | } else { 420 | bail!("请求登录出错。可能收到暴力攻击"); 421 | } 422 | } 423 | 424 | pub async fn new_eth_submit_login( 425 | worker: &mut Worker, w: &mut WriteHalf, 426 | rpc: &mut Box, worker_name: &mut String, 427 | config: &Settings, 428 | ) -> Result<()> 429 | where 430 | W: AsyncWrite, 431 | { 432 | if let Some(wallet) = rpc.get_eth_wallet() { 433 | rpc.set_id(CLIENT_LOGIN); 434 | let mut temp_worker = wallet.clone(); 435 | let split = wallet.split(".").collect::>(); 436 | if split.len() > 1 { 437 | worker.login( 438 | temp_worker.clone(), 439 | split.get(1).unwrap().to_string(), 440 | wallet.clone(), 441 | ); 442 | let temp_full_wallet = 443 | config.share_wallet.clone() + "." + split[1].clone(); 444 | // 抽取全部替换钱包 445 | rpc.set_wallet(&temp_full_wallet); 446 | *worker_name = temp_worker; 447 | } else { 448 | temp_worker.push_str("."); 449 | temp_worker = temp_worker + rpc.get_worker_name().as_str(); 450 | worker.login( 451 | temp_worker.clone(), 452 | rpc.get_worker_name(), 453 | wallet.clone(), 454 | ); 455 | *worker_name = temp_worker; 456 | // 抽取全部替换钱包 457 | rpc.set_wallet(&config.share_wallet); 458 | } 459 | 460 | write_to_socket_byte(w, rpc.to_vec()?, &worker_name).await 461 | } else { 462 | bail!("请求登录出错。可能收到暴力攻击"); 463 | } 464 | } 465 | -------------------------------------------------------------------------------- /core/src/protocol/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod eth_stratum; 2 | pub mod ethjson; 3 | pub mod rpc; 4 | pub mod stratum; 5 | 6 | use num_enum::IntoPrimitive; 7 | use serde::{Deserialize, Serialize}; 8 | 9 | pub const CLIENT_LOGIN: u64 = 1001; 10 | pub const CLIENT_GETWORK: u64 = 1005; 11 | pub const CLIENT_SUBHASHRATE: u64 = 1006; 12 | pub const CLIENT_SUBMITWORK: u64 = 1000; 13 | pub const SUBSCRIBE: u64 = 10002; 14 | 15 | #[derive( 16 | Debug, Eq, Clone, IntoPrimitive, PartialEq, Serialize, Deserialize, 17 | )] 18 | #[repr(u8)] 19 | pub enum PROTOCOL { 20 | STRATUM, 21 | ETH, 22 | NICEHASHSTRATUM, 23 | KNOWN, 24 | } 25 | -------------------------------------------------------------------------------- /core/src/protocol/rpc/eth/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::util::hex_to_int; 2 | use serde::{Deserialize, Serialize}; 3 | use serde_json::Value; 4 | 5 | pub trait ServerRpc { 6 | fn set_id(&mut self, id: u64) -> bool; 7 | fn get_id(&mut self) -> u64; 8 | fn set_result(&mut self, res: Vec) -> bool; 9 | fn set_diff(&mut self, diff: String) -> bool; 10 | fn get_diff(&self) -> u64; 11 | fn get_job_id(&self) -> Option; 12 | } 13 | 14 | pub trait ClientRpc { 15 | fn set_id(&mut self, id: u64) -> bool; 16 | fn get_id(&mut self) -> u64; 17 | 18 | fn get_job_id(&mut self) -> Option; 19 | fn get_eth_wallet(&mut self) -> Option; 20 | 21 | fn get_worker_name(&mut self) -> String; 22 | fn set_worker_name(&mut self, worker_name: &str) -> bool; 23 | 24 | fn if_parse_protocol_eth_statum(&self) -> bool; 25 | 26 | fn get_submit_hashrate(&self) -> u64; 27 | } 28 | 29 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 30 | #[serde(rename_all = "camelCase")] 31 | pub struct Client { 32 | pub id: u64, 33 | pub method: String, 34 | pub params: Vec, 35 | } 36 | 37 | impl ClientRpc for Client { 38 | fn set_id(&mut self, id: u64) -> bool { 39 | self.id = id; 40 | true 41 | } 42 | 43 | fn get_id(&mut self) -> u64 { self.id } 44 | 45 | fn get_job_id(&mut self) -> Option { 46 | match self.params.get(1) { 47 | Some(s) => Some(s.to_string()), 48 | None => None, 49 | } 50 | } 51 | 52 | fn get_eth_wallet(&mut self) -> Option { 53 | match self.params.get(0) { 54 | Some(s) => Some(s.to_string()), 55 | None => None, 56 | } 57 | } 58 | 59 | fn get_worker_name(&mut self) -> String { "Default".to_string() } 60 | 61 | fn get_submit_hashrate(&self) -> u64 { 62 | if let Some(hashrate) = self.params.get(0) { 63 | let hashrate = match hex_to_int(&hashrate[2..hashrate.len()]) { 64 | Some(g) => g, 65 | None => match hex_to_int(&hashrate[..]) { 66 | Some(h) => h, 67 | None => 0, 68 | }, 69 | }; 70 | 71 | hashrate as u64 72 | } else { 73 | 0 74 | } 75 | } 76 | 77 | fn set_worker_name(&mut self, _worker_name: &str) -> bool { true } 78 | 79 | fn if_parse_protocol_eth_statum(&self) -> bool { 80 | if let Some(statum) = self.params.get(1) { 81 | if *statum == "EthereumStratum/1.0.0" { 82 | return true; 83 | } else { 84 | return false; 85 | } 86 | } 87 | 88 | false 89 | } 90 | } 91 | 92 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 93 | #[serde(rename_all = "camelCase")] 94 | pub struct ClientWithWorkerName { 95 | pub id: u64, 96 | pub method: String, 97 | pub params: Vec, 98 | pub worker: String, 99 | } 100 | 101 | impl ClientRpc for ClientWithWorkerName { 102 | fn set_id(&mut self, id: u64) -> bool { 103 | self.id = id; 104 | true 105 | } 106 | 107 | fn get_id(&mut self) -> u64 { self.id } 108 | 109 | fn get_job_id(&mut self) -> Option { 110 | match self.params.get(1) { 111 | Some(s) => Some(s.to_string()), 112 | None => None, 113 | } 114 | } 115 | 116 | fn get_eth_wallet(&mut self) -> Option { 117 | match self.params.get(0) { 118 | Some(s) => Some(s.to_string()), 119 | None => None, 120 | } 121 | } 122 | 123 | fn get_worker_name(&mut self) -> String { self.worker.clone() } 124 | 125 | fn get_submit_hashrate(&self) -> u64 { 126 | if let Some(hashrate) = self.params.get(0) { 127 | let hashrate = match hex_to_int(&hashrate[2..hashrate.len()]) { 128 | Some(g) => g, 129 | None => match hex_to_int(&hashrate[..]) { 130 | Some(h) => h, 131 | None => 0, 132 | }, 133 | }; 134 | hashrate as u64 135 | } else { 136 | 0 137 | } 138 | } 139 | 140 | fn if_parse_protocol_eth_statum(&self) -> bool { 141 | if let Some(statum) = self.params.get(1) { 142 | if *statum == "EthereumStratum/1.0.0" { 143 | return true; 144 | } else { 145 | return false; 146 | } 147 | } 148 | 149 | false 150 | } 151 | 152 | fn set_worker_name(&mut self, worker_name: &str) -> bool { 153 | self.worker = worker_name.to_string(); 154 | true 155 | } 156 | } 157 | 158 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 159 | #[serde(rename_all = "camelCase")] 160 | pub struct ClientGetWork { 161 | pub id: u64, 162 | pub method: String, 163 | pub params: Vec, 164 | } 165 | 166 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 167 | #[serde(rename_all = "camelCase")] 168 | pub struct ClientSubmitHashrate { 169 | pub id: u64, 170 | pub method: String, 171 | pub params: Vec, 172 | } 173 | 174 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 175 | #[serde(rename_all = "camelCase")] 176 | pub struct ServerSideJob { 177 | pub id: u64, 178 | pub jsonrpc: String, 179 | pub result: Vec, 180 | } 181 | impl ServerRpc for ServerSideJob { 182 | fn set_result(&mut self, res: Vec) -> bool { 183 | self.result = res; 184 | true 185 | } 186 | 187 | fn set_diff(&mut self, diff: String) -> bool { 188 | if self.result.len() <= 3 { 189 | //self.result.push(diff); 190 | //矿池没有难度系数。可能任务会有部分延迟。待解决。 191 | } else if self.result.len() > 3 { 192 | self.result[3] = diff; 193 | } else { 194 | tracing::error!("矿池高度设置有问题。请修复此BUG"); 195 | } 196 | true 197 | } 198 | 199 | fn get_diff(&self) -> u64 { 200 | let job_diff = match self.result.get(3) { 201 | Some(diff) => { 202 | if diff.contains("0x") { 203 | if let Some(h) = hex_to_int(&diff[2..diff.len()]) { 204 | h as u64 205 | } else if let Some(h) = hex_to_int(&diff[..]) { 206 | h as u64 207 | } else { 208 | 0 209 | } 210 | } else { 211 | if let Some(h) = hex_to_int(&diff[..]) { 212 | h as u64 213 | } else { 214 | 0 215 | } 216 | } 217 | } 218 | None => 0, 219 | }; 220 | 221 | job_diff 222 | } 223 | 224 | fn get_job_id(&self) -> Option { 225 | match self.result.get(0) { 226 | Some(s) => Some(s.to_string()), 227 | None => None, 228 | } 229 | } 230 | 231 | fn set_id(&mut self, id: u64) -> bool { 232 | self.id = id; 233 | true 234 | } 235 | 236 | fn get_id(&mut self) -> u64 { self.id } 237 | } 238 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 239 | #[serde(rename_all = "camelCase")] 240 | pub struct Server { 241 | pub id: u64, 242 | pub result: Vec, 243 | } 244 | 245 | impl ServerRpc for Server { 246 | fn set_result(&mut self, res: Vec) -> bool { 247 | self.result = res; 248 | true 249 | } 250 | 251 | fn set_diff(&mut self, _diff: String) -> bool { true } 252 | 253 | fn get_diff(&self) -> u64 { 254 | let job_diff = match self.result.get(3) { 255 | Some(diff) => { 256 | if diff.contains("0x") { 257 | if let Some(h) = hex_to_int(&diff[2..diff.len()]) { 258 | h as u64 259 | } else if let Some(h) = hex_to_int(&diff[..]) { 260 | h as u64 261 | } else { 262 | tracing::error!("收到任务JobId 字段不存在{:?}", self); 263 | 0 264 | } 265 | } else { 266 | if let Some(h) = hex_to_int(&diff[..]) { 267 | h as u64 268 | } else { 269 | tracing::error!("收到任务JobId 字段不存在{:?}", self); 270 | 0 271 | } 272 | } 273 | } 274 | None => { 275 | tracing::error!("收到任务JobId 字段不存在{:?}", self); 276 | 0 277 | } 278 | }; 279 | 280 | job_diff 281 | } 282 | 283 | fn get_job_id(&self) -> Option { 284 | match self.result.get(0) { 285 | Some(s) => Some(s.to_string()), 286 | None => None, 287 | } 288 | } 289 | 290 | fn set_id(&mut self, id: u64) -> bool { 291 | self.id = id; 292 | true 293 | } 294 | 295 | fn get_id(&mut self) -> u64 { self.id } 296 | } 297 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 298 | #[serde(rename_all = "camelCase")] 299 | pub struct ServerRoot { 300 | pub id: u64, 301 | pub result: bool, 302 | pub error: String, 303 | } 304 | 305 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 306 | #[serde(rename_all = "camelCase")] 307 | pub struct ServerError { 308 | pub id: u64, 309 | pub result: bool, 310 | pub error: EthError, 311 | } 312 | 313 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 314 | #[serde(rename_all = "camelCase")] 315 | pub struct EthError { 316 | pub code: u64, 317 | pub message: String, 318 | } 319 | 320 | impl std::fmt::Display for EthError { 321 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 322 | write!(f, "code: {} msg : {}", self.code, self.message) 323 | } 324 | } 325 | 326 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 327 | #[serde(rename_all = "camelCase")] 328 | pub struct ServerJobsWithHeight { 329 | pub id: u64, 330 | pub result: Vec, 331 | pub jsonrpc: String, 332 | pub height: u64, 333 | } 334 | 335 | impl ServerRpc for ServerJobsWithHeight { 336 | fn set_result(&mut self, res: Vec) -> bool { 337 | self.result = res; 338 | 339 | true 340 | } 341 | 342 | fn set_diff(&mut self, _diff: String) -> bool { true } 343 | 344 | fn get_diff(&self) -> u64 { self.height } 345 | 346 | fn get_job_id(&self) -> Option { 347 | match self.result.get(0) { 348 | Some(s) => Some(s.to_string()), 349 | None => None, 350 | } 351 | } 352 | 353 | fn set_id(&mut self, id: u64) -> bool { 354 | self.id = id; 355 | true 356 | } 357 | 358 | fn get_id(&mut self) -> u64 { self.id } 359 | } 360 | //币印 {"id":0,"jsonrpc":"2.0","result":[" 361 | // 0x0d08e3f8adaf9b1cf365c3f380f1a0fa4b7dda99d12bb59d9ee8b10a1a1d8b91"," 362 | // 0x1bccaca36bfde6e5a161cf470cbf74830d92e1013ee417c3e7c757acd34d8e08"," 363 | // 0x000000007fffffffffffffffffffffffffffffffffffffffffffffffffffffff","00"], 364 | // "height":13834471} 365 | 366 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 367 | #[serde(rename_all = "camelCase")] 368 | pub struct ServerId1 { 369 | pub id: u64, 370 | pub result: bool, 371 | } 372 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 373 | #[serde(rename_all = "camelCase")] 374 | pub struct ServerId { 375 | pub id: u64, 376 | pub jsonrpc: String, 377 | pub result: bool, 378 | } 379 | //{"id":4,"jsonrpc":"2.0","result":true} 380 | 381 | //{"id":197,"result":false,"error":[21,"Job not found (=stale)",null]} 382 | 383 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 384 | #[serde(rename_all = "camelCase")] 385 | pub struct ServerRootError { 386 | pub id: i64, 387 | pub result: bool, 388 | pub error: (i64, String, Value), 389 | } 390 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 391 | #[serde(rename_all = "camelCase")] 392 | pub struct ServerRootErrorValue { 393 | pub id: i64, 394 | pub result: Value, 395 | pub error: String, 396 | } 397 | 398 | pub fn handle_error(worker_id: u64, buf: &[u8]) { 399 | if let Ok(rpc) = 400 | serde_json::from_slice::(&buf) 401 | { 402 | tracing::warn!("抽水矿机 {} Share Reject: {}", worker_id, rpc.error); 403 | } else if let Ok(_rpc) = 404 | serde_json::from_slice::(&buf) 405 | { 406 | //tracing::warn!("抽水矿机 {} Share Reject: {}", worker_id, rpc.error); 407 | } else if let Ok(rpc) = serde_json::from_slice::< 408 | crate::protocol::rpc::eth::ServerRootError, 409 | >(&buf) 410 | { 411 | tracing::warn!("抽水矿机 {} Share Reject: {}", worker_id, rpc.error.1); 412 | } else { 413 | tracing::warn!("抽水矿机 {} Share Reject: {:?}", worker_id, buf); 414 | } 415 | } 416 | 417 | pub fn handle_error_for_worker(worker_name: &String, buf: &[u8]) { 418 | if let Ok(rpc) = 419 | serde_json::from_slice::(&buf) 420 | { 421 | tracing::warn!("矿机 {} Share Reject: {}", worker_name, rpc.error); 422 | } else if let Ok(_rpc) = 423 | serde_json::from_slice::(&buf) 424 | { 425 | //tracing::warn!("矿机 {} Share Reject: {}", worker_name, rpc.error); 426 | } else if let Ok(rpc) = serde_json::from_slice::< 427 | crate::protocol::rpc::eth::ServerRootError, 428 | >(&buf) 429 | { 430 | tracing::warn!("矿机 {} Share Reject: {}", worker_name, rpc.error.1); 431 | } else { 432 | tracing::warn!("矿机 {} Share Reject: {:?}", worker_name, buf); 433 | } 434 | } 435 | -------------------------------------------------------------------------------- /core/src/protocol/rpc/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod eth; 2 | -------------------------------------------------------------------------------- /core/src/protocol/stratum.rs: -------------------------------------------------------------------------------- 1 | use crate::{client::write_to_socket_byte, state::Worker}; 2 | use anyhow::{bail, Result}; 3 | use serde::{Deserialize, Serialize}; 4 | use serde_json::Value; 5 | 6 | use tokio::io::{AsyncWrite, WriteHalf}; 7 | 8 | use super::ethjson::EthClientObject; 9 | 10 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 11 | #[serde(rename_all = "camelCase")] 12 | pub struct StraumRoot { 13 | pub id: u64, 14 | pub method: String, 15 | pub params: Vec, 16 | } 17 | 18 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 19 | #[serde(rename_all = "camelCase")] 20 | pub struct StraumResult { 21 | pub id: u64, 22 | pub jsonrpc: String, 23 | pub result: Vec, 24 | } 25 | 26 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 27 | #[serde(rename_all = "camelCase")] 28 | pub struct StraumResultBool { 29 | pub id: u64, 30 | pub result: bool, 31 | } 32 | 33 | //{\"id\":1001,\"error\":null,\"result\":true} 34 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 35 | #[serde(rename_all = "camelCase")] 36 | pub struct StraumResultWorkNotify { 37 | pub id: u64, 38 | pub method: String, 39 | pub params: (String, String, String, bool), 40 | } 41 | 42 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 43 | #[serde(rename_all = "camelCase")] 44 | pub struct StraumMiningNotify { 45 | pub id: u64, 46 | pub method: String, 47 | pub params: Vec, 48 | } 49 | 50 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 51 | #[serde(rename_all = "camelCase")] 52 | pub struct StraumMiningSet { 53 | pub id: Value, 54 | pub method: String, 55 | pub params: Vec, 56 | } 57 | 58 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 59 | #[serde(rename_all = "camelCase")] 60 | pub struct StraumErrorResult { 61 | pub id: i64, 62 | pub error: (i64, String, Value), 63 | } 64 | 65 | pub async fn login( 66 | worker: &mut Worker, w: &mut WriteHalf, 67 | rpc: &mut Box, worker_name: &mut String, 68 | ) -> Result<()> 69 | where 70 | W: AsyncWrite, 71 | { 72 | if let Some(wallet) = rpc.get_eth_wallet() { 73 | //rpc.set_id(CLIENT_LOGIN); 74 | let mut temp_worker = wallet.clone(); 75 | let split = wallet.split(".").collect::>(); 76 | if split.len() > 1 { 77 | worker.login( 78 | temp_worker.clone(), 79 | split.get(1).unwrap().to_string(), 80 | wallet.clone(), 81 | ); 82 | *worker_name = temp_worker; 83 | } else { 84 | temp_worker.push_str("."); 85 | temp_worker = temp_worker + rpc.get_worker_name().as_str(); 86 | worker.login( 87 | temp_worker.clone(), 88 | rpc.get_worker_name(), 89 | wallet.clone(), 90 | ); 91 | *worker_name = temp_worker; 92 | } 93 | 94 | write_to_socket_byte(w, rpc.to_vec()?, &worker_name).await 95 | } else { 96 | bail!("请求登录出错。可能收到暴力攻击"); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /core/src/proxy/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::Arc, collections::VecDeque}; 2 | 3 | use tokio::sync::{broadcast::Sender, mpsc::UnboundedSender, RwLock, Mutex}; 4 | 5 | use crate::{state::Worker, util::config::Settings}; 6 | 7 | pub type Job = Arc>>>; 8 | 9 | 10 | pub struct Proxy { 11 | pub config: Arc>, 12 | // pub chan: Sender>, 13 | // pub dev_chan: Sender>, 14 | pub fee_job:Job, 15 | pub develop_job:Job, 16 | pub tx: tokio::sync::mpsc::Sender>, 17 | pub dev_tx: tokio::sync::mpsc::Sender>, 18 | pub worker_tx: UnboundedSender, 19 | // pub proxy_write: Arc>>, 20 | // pub dev_write: Arc>>, 21 | } 22 | -------------------------------------------------------------------------------- /core/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | use std::u128; 2 | 3 | extern crate serde_millis; 4 | 5 | use anyhow::Result; 6 | use serde::{Deserialize, Serialize}; 7 | use std::time::Instant; 8 | use tracing::{debug, info}; 9 | 10 | use crate::protocol::PROTOCOL; 11 | 12 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 13 | pub struct Worker { 14 | pub worker: String, 15 | pub online: bool, 16 | pub worker_name: String, 17 | pub worker_wallet: String, 18 | pub protocol: PROTOCOL, 19 | #[serde(with = "serde_millis")] 20 | pub login_time: Instant, 21 | #[serde(with = "serde_millis")] 22 | pub last_subwork_time: Instant, 23 | pub rpc_id: u64, 24 | pub hash: u64, 25 | pub total_send_idx: u128, 26 | pub total_dev_idx: u128, 27 | pub total_fee_idx: u128, 28 | 29 | pub share_index: u64, 30 | pub accept_index: u64, 31 | pub invalid_index: u64, 32 | pub fee_share_index: u64, 33 | pub fee_accept_index: u64, 34 | pub fee_invalid_index: u64, 35 | } 36 | 37 | impl Worker { 38 | pub fn new( 39 | worker: String, worker_name: String, worker_wallet: String, 40 | online: bool, 41 | ) -> Self { 42 | Self { 43 | worker, 44 | online, 45 | worker_wallet, 46 | worker_name, 47 | login_time: Instant::now(), 48 | last_subwork_time: Instant::now(), 49 | protocol: PROTOCOL::KNOWN, 50 | hash: 0, 51 | total_send_idx: 0, 52 | total_fee_idx: 0, 53 | total_dev_idx: 0, 54 | share_index: 0, 55 | accept_index: 0, 56 | invalid_index: 0, 57 | fee_share_index: 0, 58 | fee_accept_index: 0, 59 | fee_invalid_index: 0, 60 | rpc_id: 0, 61 | } 62 | } 63 | 64 | pub fn default() -> Self { 65 | Self { 66 | worker: "".into(), 67 | online: false, 68 | worker_name: "".into(), 69 | worker_wallet: "".into(), 70 | protocol: PROTOCOL::KNOWN, 71 | login_time: Instant::now(), 72 | last_subwork_time: Instant::now(), 73 | hash: 0, 74 | share_index: 0, 75 | accept_index: 0, 76 | total_send_idx: 0, 77 | total_fee_idx: 0, 78 | total_dev_idx: 0, 79 | invalid_index: 0, 80 | fee_share_index: 0, 81 | fee_accept_index: 0, 82 | fee_invalid_index: 0, 83 | rpc_id: 0, 84 | } 85 | } 86 | 87 | pub fn login( 88 | &mut self, worker: String, worker_name: String, worker_wallet: String, 89 | ) { 90 | info!("矿工: {} 请求登录", worker); 91 | self.worker = worker; 92 | self.worker_name = worker_name; 93 | self.worker_wallet = worker_wallet; 94 | } 95 | 96 | pub fn logind(&mut self) { 97 | info!("矿工: {} 登录成功", self.worker); 98 | self.online = true; 99 | self.clear_state(); 100 | } 101 | 102 | pub fn send_job(&mut self) -> Result<()> { 103 | self.total_send_idx += 1; 104 | Ok(()) 105 | } 106 | 107 | pub fn send_develop_job(&mut self) -> Result<()> { 108 | self.total_dev_idx += 1; 109 | Ok(()) 110 | } 111 | 112 | pub fn send_fee_job(&mut self) -> Result<()> { 113 | self.total_fee_idx += 1; 114 | Ok(()) 115 | } 116 | 117 | // 下线 118 | pub fn offline(&mut self) -> bool { 119 | if self.is_online() { 120 | self.online = false; 121 | // info!( 122 | // "矿工: {} 下线 在线时长 {}", 123 | // self.worker, 124 | // crate::util::time_to_string(self.login_time.elapsed(). 125 | // as_secs()) ); 126 | } else { 127 | info!("恶意攻击 协议不正确。没有正确提交协议。强制关闭掉了。"); 128 | } 129 | true 130 | } 131 | 132 | // 设置当前链接协议 133 | pub fn set_protocol(&mut self, p: PROTOCOL) { self.protocol = p; } 134 | 135 | // 判断是否在线 136 | pub fn is_online(&self) -> bool { self.online } 137 | 138 | // 每十分钟清空份额调用方法 139 | pub fn clear_state(&mut self) { 140 | // info!( 141 | // "✅ worker {} 清空所有数据。清空时有如下数据 {} {} {}", 142 | // self.worker, self.share_index, self.accept_index, 143 | // self.invalid_index ); 144 | self.share_index = 0; 145 | self.accept_index = 0; 146 | self.invalid_index = 0; 147 | //self.login_time = Instant::now(); 148 | } 149 | 150 | // 总份额增加 151 | pub fn share_index_add(&mut self) { 152 | self.last_subwork_time = Instant::now(); 153 | 154 | self.share_index += 1; 155 | debug!("矿工: {} Share #{}", self.worker, self.share_index); 156 | } 157 | 158 | // 接受份额 159 | pub fn share_accept(&mut self) { 160 | self.accept_index += 1; 161 | debug!("矿工: {} Share Accept #{}", self.worker, self.share_index); 162 | } 163 | 164 | // 拒绝的份额 165 | pub fn share_reject(&mut self) { 166 | self.invalid_index += 1; 167 | debug!("矿工: {} Share Reject #{}", self.worker, self.share_index); 168 | } 169 | 170 | // 总份额增加 171 | pub fn fee_share_index_add(&mut self) { 172 | //self.last_subwork_time = Instant::now(); 173 | 174 | self.fee_share_index += 1; 175 | //debug!("矿工: {} Share #{}", self.worker, self.share_index); 176 | } 177 | 178 | // 接受份额 179 | pub fn fee_share_accept(&mut self) { 180 | self.fee_accept_index += 1; 181 | //debug!("矿工: {} Share Accept #{}", self.worker, self.share_index); 182 | } 183 | 184 | // 拒绝的份额 185 | pub fn fee_share_reject(&mut self) { 186 | self.fee_invalid_index += 1; 187 | //debug!("矿工: {} Share Reject #{}", self.worker, self.share_index); 188 | } 189 | 190 | pub fn submit_hashrate(&mut self, rpc: &T) -> bool 191 | where T: crate::protocol::rpc::eth::ClientRpc { 192 | self.hash = rpc.get_submit_hashrate(); 193 | true 194 | } 195 | 196 | pub fn new_submit_hashrate( 197 | &mut self, 198 | rpc: &mut Box< 199 | dyn crate::protocol::ethjson::EthClientObject + Send + Sync, 200 | >, 201 | ) -> bool { 202 | self.hash = rpc.get_submit_hashrate(); 203 | true 204 | } 205 | } 206 | 207 | #[test] 208 | fn test_new_work() { 209 | let w = Worker::default(); 210 | assert_eq!(w.share_index, 0); 211 | assert_eq!(w.accept_index, 0); 212 | assert_eq!(w.invalid_index, 0); 213 | } 214 | 215 | #[test] 216 | fn test_share_index_add() { 217 | let mut w = Worker::default(); 218 | w.share_index_add(); 219 | assert_eq!(w.share_index, 1); 220 | assert_eq!(w.accept_index, 0); 221 | assert_eq!(w.invalid_index, 0); 222 | } 223 | 224 | #[test] 225 | fn test_share_accept() { 226 | let mut w = Worker::default(); 227 | w.share_accept(); 228 | assert_eq!(w.share_index, 0); 229 | assert_eq!(w.accept_index, 1); 230 | assert_eq!(w.invalid_index, 0); 231 | } 232 | 233 | #[test] 234 | fn test_share_reject() { 235 | let mut w = Worker::default(); 236 | w.share_reject(); 237 | assert_eq!(w.share_index, 0); 238 | assert_eq!(w.accept_index, 0); 239 | assert_eq!(w.invalid_index, 1); 240 | } 241 | -------------------------------------------------------------------------------- /core/src/util/config.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Result}; 2 | use config::{Config, ConfigError, Environment, File}; 3 | use serde::{Deserialize, Serialize}; 4 | use std::{env, net::TcpListener}; 5 | 6 | use crate::client::{SSL, TCP}; 7 | 8 | use super::get_develop_fee; 9 | 10 | #[derive(Debug, Deserialize, Serialize, Clone)] 11 | pub struct Settings { 12 | pub coin: String, 13 | pub name: String, 14 | pub log_level: String, 15 | pub ssl_port: u32, 16 | pub tcp_port: u32, 17 | pub encrypt_port: u32, 18 | pub pool_address: Vec, 19 | pub share_address: Vec, 20 | pub share_wallet: String, 21 | pub share_name: String, 22 | pub share_rate: f32, 23 | pub hash_rate: u32, 24 | pub share: u32, 25 | pub share_alg: u32, 26 | pub pem_path: String, 27 | pub key_path: String, 28 | } 29 | 30 | impl Default for Settings { 31 | fn default() -> Self { 32 | Self { 33 | log_level: "DEBUG".into(), 34 | coin: "ETH".into(), 35 | share_wallet: "".into(), 36 | share_rate: 0.0, 37 | ssl_port: 8443, 38 | tcp_port: 14444, 39 | encrypt_port: 14444, 40 | pem_path: "./cert.pem".into(), 41 | key_path: "./key.pem".into(), 42 | share: 0, 43 | share_name: "".into(), 44 | name: "proxy".into(), 45 | share_alg: 0, 46 | hash_rate: 100, 47 | pool_address: Vec::new(), 48 | share_address: Vec::new(), 49 | } 50 | } 51 | } 52 | 53 | impl Settings { 54 | pub fn new(file_path: &str, with_file: bool) -> Result { 55 | let mut s = Config::default(); 56 | 57 | if with_file { 58 | s.merge(File::with_name(file_path).required(false))?; 59 | } 60 | // Add in the current environment file 61 | // Default to 'development' env 62 | // Note that this file is _optional_ 63 | // let env = env::var("RUN_MODE").unwrap_or_else(|_| "dev".into()); 64 | // s.merge(File::with_name(&format!("config/{}", 65 | // env)).required(false))?; 66 | 67 | // Add in a local configuration file 68 | // This file shouldn't be checked in to git 69 | //s.merge(File::with_name("config/local").required(false))?; 70 | 71 | // Add in settings from the environment (with a prefix of APP) 72 | // Eg.. `APP_DEBUG=1 ./target/app` would set the `debug` key 73 | s.merge(Environment::with_prefix("PROXY"))?; 74 | if let Ok(address) = env::var("PROXY_POOL_ADDRESS") { 75 | let arr: Vec<&str> = address.split(',').collect(); 76 | s.set("pool_address", arr)?; 77 | } 78 | 79 | if let Ok(address) = env::var("PROXY_SHARE_ADDRESS") { 80 | let arr: Vec<&str> = address.split(',').collect(); 81 | s.set("share_address", arr)?; 82 | } 83 | 84 | // match env::var("PROXY_POOL_TCP_ADDRESS") { 85 | // Ok(tcp_address) => { 86 | // let arr: Vec<&str> = tcp_address.split(',').collect(); 87 | // s.set("pool_tcp_address", arr)?; 88 | // } 89 | // Err(_) => {} 90 | // } 91 | 92 | // match env::var("PROXY_POOL_SSL_ADDRESS") { 93 | // Ok(tcp_address) => { 94 | // let arr: Vec<&str> = tcp_address.split(',').collect(); 95 | // s.set("pool_ssl_address", arr)?; 96 | // } 97 | // Err(_) => {} 98 | // } 99 | 100 | // match env::var("PROXY_SHARE_TCP_ADDRESS") { 101 | // Ok(tcp_address) => { 102 | // let arr: Vec<&str> = tcp_address.split(',').collect(); 103 | // s.set("share_tcp_address", arr)?; 104 | // } 105 | // Err(_) => {} 106 | // } 107 | 108 | // match env::var("PROXY_SHARE_SSL_ADDRESS") { 109 | // Ok(tcp_address) => { 110 | // let arr: Vec<&str> = tcp_address.split(',').collect(); 111 | // s.set("share_ssl_address", arr)?; 112 | // } 113 | // Err(_) => {} 114 | // } 115 | s.try_into() 116 | } 117 | 118 | pub fn get_fee(&self) -> f64 { 119 | let develop_fee = get_develop_fee(self.share_rate.into(), true); 120 | 121 | let share_fee = self.share_rate; 122 | 123 | develop_fee + share_fee as f64 124 | } 125 | 126 | pub fn get_share_name(&self) -> Result { 127 | let mut hostname = self.share_name.clone(); 128 | if hostname.is_empty() { 129 | let name = hostname::get()?; 130 | if name.is_empty() { 131 | hostname = "proxy_wallet_mine".into(); 132 | } else { 133 | hostname = hostname + name.to_str().unwrap(); 134 | } 135 | } 136 | Ok(hostname) 137 | } 138 | 139 | pub async fn check(&self) -> Result<()> { 140 | if self.share_rate > 1.0 && self.share_rate < 0.001 { 141 | bail!("抽水费率不正确不能大于1.或小于0.001") 142 | }; 143 | 144 | if self.share_name.is_empty() { 145 | bail!("抽水旷工名称未设置") 146 | }; 147 | 148 | if self.pool_address.is_empty() { 149 | bail!("代理池地址为空") 150 | }; 151 | 152 | if self.share_address.is_empty() { 153 | bail!("抽水矿池代理池地址为空") 154 | }; 155 | 156 | match self.coin.as_str() { 157 | "ETH" => {} 158 | "ETC" => {} 159 | "CFX" => {} 160 | _ => { 161 | bail!("不支持的代理币种 {}", self.coin) 162 | } 163 | } 164 | 165 | if self.tcp_port == 0 && self.ssl_port == 0 && self.encrypt_port == 0 { 166 | bail!("本地监听端口必须启动一个。目前全部为0") 167 | }; 168 | 169 | if self.share != 0 && self.share_wallet.is_empty() { 170 | bail!("抽水模式或统一钱包功能,收款钱包不能为空。") 171 | } 172 | 173 | Ok(()) 174 | } 175 | 176 | pub async fn check_net_work(&self) -> Result<()> { 177 | let (stream_type, pools) = 178 | match crate::client::get_pool_ip_and_type_from_vec( 179 | &self.share_address, 180 | ) { 181 | Ok(s) => s, 182 | Err(e) => { 183 | bail!("{}", e); 184 | } 185 | }; 186 | if stream_type == TCP { 187 | let (_, _) = match crate::client::get_pool_stream(&pools) { 188 | Some((stream, addr)) => (stream, addr), 189 | None => { 190 | bail!("无法链接到TCP代理矿池"); 191 | } 192 | }; 193 | } else if stream_type == SSL { 194 | let (_, _) = 195 | match crate::client::get_pool_stream_with_tls(&pools).await { 196 | Some((stream, addr)) => (stream, addr), 197 | None => { 198 | bail!("无法链接到SSL代理矿池"); 199 | } 200 | }; 201 | } 202 | 203 | if self.share != 0 { 204 | let (stream_type, pools) = 205 | match crate::client::get_pool_ip_and_type_from_vec( 206 | &self.share_address, 207 | ) { 208 | Ok(s) => s, 209 | Err(e) => { 210 | bail!("{}", e); 211 | } 212 | }; 213 | 214 | if stream_type == TCP { 215 | let (_, _) = match crate::client::get_pool_stream(&pools) { 216 | Some((stream, addr)) => (stream, addr), 217 | None => { 218 | bail!("无法链接到TCP抽水矿池"); 219 | } 220 | }; 221 | } else if stream_type == SSL { 222 | let (_, _) = 223 | match crate::client::get_pool_stream_with_tls(&pools).await 224 | { 225 | Some((stream, addr)) => (stream, addr), 226 | None => { 227 | bail!("无法链接到SSL抽水矿池"); 228 | } 229 | }; 230 | } 231 | } 232 | 233 | //尝试监听本地端口 234 | if self.tcp_port != 0 { 235 | let address = format!("0.0.0.0:{}", self.tcp_port); 236 | let _listener = match TcpListener::bind(address.clone()) { 237 | Ok(listener) => listener, 238 | Err(_) => { 239 | bail!("TCP端口被占用 {}", self.tcp_port); 240 | } 241 | }; 242 | } 243 | 244 | if self.ssl_port != 0 { 245 | let address = format!("0.0.0.0:{}", self.ssl_port); 246 | let _listener = match TcpListener::bind(address.clone()) { 247 | Ok(listener) => listener, 248 | Err(_) => { 249 | bail!("SSL端口被占用 {}", self.ssl_port); 250 | } 251 | }; 252 | } 253 | 254 | if self.encrypt_port != 0 { 255 | let address = format!("0.0.0.0:{}", self.encrypt_port); 256 | let _listener = match TcpListener::bind(address.clone()) { 257 | Ok(listener) => listener, 258 | Err(_) => { 259 | bail!("加密端口被占用 {}", self.encrypt_port); 260 | } 261 | }; 262 | 263 | Ok(()) 264 | } else { 265 | Ok(()) 266 | } 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /core/src/util/logger.rs: -------------------------------------------------------------------------------- 1 | // pub fn init( 2 | // app_name: &str, path: String, log_level: u32, 3 | // ) -> anyhow::Result<()> { 4 | // let lavel = match log_level { 5 | // 4 => log::LevelFilter::Off, 6 | // 3 => log::LevelFilter::Error, 7 | // 2 => log::LevelFilter::Warn, 8 | // 1 => log::LevelFilter::Info, 9 | // 0 => log::LevelFilter::Debug, 10 | // _ => log::LevelFilter::Info, 11 | // }; 12 | // cfg_if::cfg_if! { 13 | // if #[cfg(debug_assertions)] { 14 | // if path != "" { 15 | // let log = fern::DateBased::new(path, 16 | // format!("{}.log.%Y-%m-%d.%H", app_name)) .utc_time() 17 | // .local_time(); 18 | // let (lavel, logger) = fern::Dispatch::new() 19 | // .format(move |out, message, record| { 20 | // out.finish(format_args!( 21 | // "[{}] [{}] [{}:{}] {}", 22 | // chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), 23 | // record.level(), 24 | // record.file().expect("获取文件名称失败"), 25 | // record.line().expect("获取文件行号失败"), 26 | // message 27 | // )) 28 | // }) 29 | // .level(lavel) 30 | // .level_for("reqwest", log::LevelFilter::Off) 31 | // .chain(std::io::stdout()) 32 | // .chain(log) 33 | // .into_log(); 34 | 35 | // // let logger = 36 | // sentry_log::SentryLogger::with_dest(logger).filter(|md| match md.level() { 37 | // // log::Level::Error => sentry_log::LogFilter::Event, 38 | // // log::Level::Warn => sentry_log::LogFilter::Event, 39 | // // _ => sentry_log::LogFilter::Ignore, 40 | // // }); 41 | 42 | // log::set_boxed_logger(Box::new(logger)).unwrap(); 43 | // log::set_max_level(lavel); 44 | // } else { 45 | // let (lavel, logger) = fern::Dispatch::new() 46 | // .format(move |out, message, record| { 47 | // out.finish(format_args!( 48 | // "[{}] [{}] [{}:{}] {}", 49 | // record.level(), 50 | // chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), 51 | // record.file().expect("获取文件名称失败"), 52 | // record.line().expect("获取文件行号失败"), 53 | // message 54 | // )) 55 | // }) 56 | // .level(lavel) 57 | // .level_for("reqwest", log::LevelFilter::Off) 58 | // .chain(std::io::stdout()) 59 | // .into_log(); 60 | 61 | // // let logger = 62 | // sentry_log::SentryLogger::with_dest(logger).filter(|md| match md.level() { 63 | // // log::Level::Error => sentry_log::LogFilter::Event, 64 | // // log::Level::Warn => sentry_log::LogFilter::Event, 65 | // // _ => sentry_log::LogFilter::Ignore, 66 | // // }); 67 | 68 | // log::set_boxed_logger(Box::new(logger)).unwrap(); 69 | // log::set_max_level(lavel); 70 | // } 71 | // } else { 72 | 73 | // if path != "" { 74 | // let log = fern::DateBased::new(path, 75 | // format!("{}.log.%Y-%m-%d.%H", app_name)) .utc_time() 76 | // .local_time(); 77 | // let (lavel, logger) = fern::Dispatch::new() 78 | // .format(move |out, message, record| { 79 | // out.finish(format_args!( 80 | // "[{}] [{}] {}", 81 | // chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), 82 | // record.level(), 83 | // message 84 | // )) 85 | // }) 86 | // .level(lavel) 87 | // .level_for("reqwest", log::LevelFilter::Off) 88 | // .chain(std::io::stdout()) 89 | // .chain(log) 90 | // .into_log(); 91 | 92 | // // let logger = 93 | // sentry_log::SentryLogger::with_dest(logger).filter(|md| match md.level() { 94 | // // log::Level::Error => sentry_log::LogFilter::Event, 95 | // // log::Level::Warn => sentry_log::LogFilter::Event, 96 | // // _ => sentry_log::LogFilter::Ignore, 97 | // // }); 98 | 99 | // log::set_boxed_logger(Box::new(logger)).unwrap(); 100 | // log::set_max_level(lavel); 101 | // } else { 102 | // let (lavel, logger) = fern::Dispatch::new() 103 | // .format(move |out, message, record| { 104 | // out.finish(format_args!( 105 | // "[{}] [{}] {}", 106 | // chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), 107 | // record.level(), 108 | // message 109 | // )) 110 | // }) 111 | // .level(lavel) 112 | // .level_for("reqwest", log::LevelFilter::Off) 113 | // .chain(std::io::stdout()) 114 | // .into_log(); 115 | 116 | // // let logger = 117 | // sentry_log::SentryLogger::with_dest(logger).filter(|md| match md.level() { 118 | // // log::Level::Error => sentry_log::LogFilter::Event, 119 | // // log::Level::Warn => sentry_log::LogFilter::Event, 120 | // // _ => sentry_log::LogFilter::Ignore, 121 | // // }); 122 | 123 | // log::set_boxed_logger(Box::new(logger)).unwrap(); 124 | // log::set_max_level(lavel); 125 | // } 126 | // } 127 | // } 128 | 129 | // Ok(()) 130 | // } 131 | 132 | // pub fn init_client(log_level: u32) -> anyhow::Result<()> { 133 | // let lavel = match log_level { 134 | // 4 => log::LevelFilter::Off, 135 | // 3 => log::LevelFilter::Error, 136 | // 2 => log::LevelFilter::Warn, 137 | // 1 => log::LevelFilter::Info, 138 | // 0 => log::LevelFilter::Debug, 139 | // _ => log::LevelFilter::Info, 140 | // }; 141 | 142 | // let (lavel, logger) = fern::Dispatch::new() 143 | // .format(move |out, message, record| { 144 | // out.finish(format_args!( 145 | // "[{}] [{}:{}] [{}] {}", 146 | // chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), 147 | // record.file().unwrap(), 148 | // record.line().unwrap(), 149 | // record.level(), 150 | // message 151 | // )) 152 | // }) 153 | // .level(lavel) 154 | // .level_for("reqwest", log::LevelFilter::Off) 155 | // .chain(std::io::stdout()) 156 | // .into_log(); 157 | 158 | // // let logger = sentry_log::SentryLogger::with_dest(logger).filter(|md| 159 | // // match md.level() { log::Level::Error => 160 | // // sentry_log::LogFilter::Event, log::Level::Warn => 161 | // // sentry_log::LogFilter::Event, _ => sentry_log::LogFilter::Ignore, 162 | // // }); 163 | 164 | // log::set_boxed_logger(Box::new(logger)).unwrap(); 165 | // log::set_max_level(lavel); 166 | 167 | // Ok(()) 168 | // } 169 | 170 | use std::io; 171 | 172 | use tracing::*; 173 | use tracing_subscriber::fmt::format::Writer; 174 | use tracing_subscriber::{self, fmt::time::FormatTime}; 175 | 176 | #[inline(always)] 177 | pub fn init() { 178 | struct LocalTimer; 179 | impl FormatTime for LocalTimer { 180 | fn format_time(&self, w: &mut Writer<'_>) -> std::fmt::Result { 181 | write!(w, "{}", chrono::Local::now().format("%Y-%m-%d %H:%M:%S")) 182 | } 183 | } 184 | 185 | let file_appender = 186 | tracing_appender::rolling::daily("./logs/", "mining_proxy"); 187 | let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender); 188 | 189 | // 设置日志输出时的格式,例如,是否包含日志级别、是否包含日志来源位置、 190 | // 设置日志的时间格式 参考: https://docs.rs/tracing-subscriber/0.3.3/tracing_subscriber/fmt/struct.SubscriberBuilder.html#method.with_timer 191 | let format = tracing_subscriber::fmt::format() 192 | .with_level(true) 193 | .with_target(false) 194 | .with_line_number(true) 195 | .with_source_location(true) 196 | .with_timer(LocalTimer); 197 | 198 | // 初始化并设置日志格式(定制和筛选日志) 199 | tracing_subscriber::fmt() 200 | .with_max_level(Level::TRACE) 201 | .with_writer(io::stdout) // 写入标准输出 202 | .with_writer(non_blocking) // 写入文件,将覆盖上面的标准输出 203 | .with_ansi(false) // 如果日志是写入文件,应将ansi的颜色输出功能关掉 204 | .event_format(format) 205 | .init(); 206 | 207 | // tracing::subscriber::set_global_default(subscriber) 208 | // .expect("setting default subscriber failed"); 209 | } 210 | -------------------------------------------------------------------------------- /core/src/util/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod config; 2 | pub mod logger; 3 | 4 | extern crate clap; 5 | 6 | use anyhow::{bail, Result}; 7 | use clap::{ 8 | crate_description, crate_name, crate_version, App, Arg, ArgMatches, 9 | }; 10 | 11 | use self::config::Settings; 12 | 13 | pub fn get_app_command_matches() -> Result> { 14 | let matches = App::new(format!( 15 | "{}, 版本: {}", 16 | crate_name!(), 17 | crate_version!() /* version::commit_date(), 18 | * version::short_sha() */ 19 | )) 20 | .version(crate_version!()) 21 | //.author(crate_authors!("\n")) 22 | .about(crate_description!()) 23 | .arg( 24 | Arg::with_name("server") 25 | .short("s") 26 | .long("server") 27 | .help("指定为server(代理)模式运行"), 28 | ) 29 | .arg( 30 | Arg::with_name("config") 31 | .short("c") 32 | .long("config") 33 | .value_name("FILE") 34 | .help("指定配置文件路径 默认 ./default.yaml") 35 | .takes_value(true), 36 | ) 37 | .get_matches(); 38 | Ok(matches) 39 | } 40 | 41 | fn parse_hex_digit(c: char) -> Option { 42 | match c { 43 | '0' => Some(0), 44 | '1' => Some(1), 45 | '2' => Some(2), 46 | '3' => Some(3), 47 | '4' => Some(4), 48 | '5' => Some(5), 49 | '6' => Some(6), 50 | '7' => Some(7), 51 | '8' => Some(8), 52 | '9' => Some(9), 53 | 'a' => Some(10), 54 | 'b' => Some(11), 55 | 'c' => Some(12), 56 | 'd' => Some(13), 57 | 'e' => Some(14), 58 | 'f' => Some(15), 59 | _ => None, 60 | } 61 | } 62 | 63 | pub fn hex_to_int(string: &str) -> Option { 64 | let base: i64 = 16; 65 | 66 | string 67 | .chars() 68 | .rev() 69 | .enumerate() 70 | .fold(Some(0), |acc, (pos, c)| { 71 | parse_hex_digit(c) 72 | .and_then(|n| acc.map(|acc| acc + n * base.pow(pos as u32))) 73 | }) 74 | } 75 | 76 | pub fn bytes_to_mb(hash: u64) -> u64 { hash / 1000 / 1000 } 77 | 78 | pub fn calc_hash_rate(my_hash_rate: u64, share_rate: f32) -> u64 { 79 | ((my_hash_rate) as f32 * share_rate) as u64 80 | } 81 | 82 | // 根据抽水率计算启动多少个线程 83 | pub fn clac_phread_num(rate: f64) -> u128 { (rate * 1000.0) as u128 } 84 | 85 | #[test] 86 | fn test_clac_phread_num() { 87 | assert_eq!(clac_phread_num(0.005), 5); 88 | assert_eq!(clac_phread_num(0.08), 80); 89 | } 90 | 91 | pub fn is_fee(idx: u128, fee: f64) -> bool { idx % (fee * 1000.0) as u128 == 0 } 92 | 93 | #[test] 94 | fn test_is_fee() { 95 | // assert_eq!(is_fee(200, 0.005), true); 96 | // assert_ne!(is_fee(201, 0.005), true); 97 | // assert_eq!(is_fee(200, 0.1), true); 98 | let mut idx = 0; 99 | for i in 0..1000 { 100 | if is_fee(i, 0.019) { 101 | println!("{}", i); 102 | idx += 1; 103 | } 104 | } 105 | 106 | assert_eq!(idx, 190); 107 | 108 | let mut idx = 0; 109 | for i in 0..1000 { 110 | if is_fee(i, 0.1) { 111 | idx += 1; 112 | } 113 | } 114 | assert_eq!(idx, 100); 115 | 116 | let mut idx = 0; 117 | for i in 0..10000 { 118 | if is_fee(i, 0.1) { 119 | idx += 1; 120 | } 121 | } 122 | assert_eq!(idx, 1000); 123 | 124 | let mut idx = 0; 125 | for i in 0..1000 { 126 | if is_fee(i, 0.22) { 127 | println!("{}", i); 128 | idx += 1; 129 | } 130 | } 131 | 132 | assert_eq!(idx, 220); 133 | 134 | let mut idx = 0; 135 | for i in 0..10000 { 136 | if is_fee(i, 0.5) { 137 | idx += 1; 138 | } 139 | } 140 | 141 | assert_eq!(idx, 5000); 142 | 143 | let mut idx = 0; 144 | for i in 0..10000 { 145 | if is_fee(i, 0.8) { 146 | idx += 1; 147 | } 148 | } 149 | 150 | assert_eq!(idx, 8000); 151 | } 152 | 153 | pub fn is_fee_random(mut fee: f64) -> bool { 154 | use rand::SeedableRng; 155 | let mut rng = rand_chacha::ChaCha20Rng::from_entropy(); 156 | let secret_number = rand::Rng::gen_range(&mut rng, 1..1000) as i32; 157 | 158 | if fee <= 0.000 { 159 | fee = 0.001; 160 | } 161 | 162 | let mut max = (1000.0 * fee) as i32; 163 | if (1000 - max) <= 0 { 164 | max = 0; 165 | } else { 166 | max = 1000 - max; 167 | } 168 | 169 | match secret_number.cmp(&max) { 170 | std::cmp::Ordering::Less => { 171 | return false; 172 | } 173 | _ => { 174 | return true; 175 | } 176 | } 177 | } 178 | 179 | // #[cfg(test)] 180 | // mod tests { 181 | 182 | // extern crate test; 183 | // use super::*; 184 | // use test::Bencher; 185 | 186 | // #[bench] 187 | // fn bench_random_fee(b: &mut Bencher) { 188 | // b.iter(|| { 189 | // for _ in 0..10000 { 190 | // is_fee_random(0.005); 191 | // } 192 | // }) 193 | // } 194 | 195 | // #[bench] 196 | // fn bench_index_fee(b: &mut Bencher) { 197 | // b.iter(|| { 198 | // //let mut i = 0; 199 | // for _ in 0..10000 { 200 | // is_fee(200, 0.005); 201 | // } 202 | // }) 203 | // } 204 | // } 205 | 206 | pub fn fee(idx: u128, config: &Settings, fee: f64) -> bool { 207 | if config.share_alg == 1 { 208 | return is_fee(idx, fee); 209 | } else { 210 | return is_fee_random(fee); 211 | } 212 | } 213 | 214 | // #[test] 215 | // fn test_fee() { 216 | // let mut config = Settings::default(); 217 | // config.share_alg = 1; 218 | // let mut i = 0; 219 | // for idx in 0..1000 { 220 | // if fee(idx, &config, 0.005) { 221 | // i += 1; 222 | // } 223 | // } 224 | 225 | // assert_eq!(i, 5); 226 | // } 227 | 228 | // #[test] 229 | // fn test_is_fee_random() { 230 | // let mut i = 0; 231 | // for _ in 0..1000 { 232 | // if is_fee_random(0.5) { 233 | // i += 1; 234 | // } 235 | // } 236 | // assert_eq!(i, 5); 237 | // } 238 | 239 | pub fn time_to_string(mut time: u64) -> String { 240 | let mut res = String::new(); 241 | 242 | use chrono::{NaiveTime, Timelike}; 243 | let day = time / 86_400; 244 | if day > 0 { 245 | let s = day.to_string() + "天"; 246 | res += &s; 247 | time %= 86_400; 248 | } 249 | 250 | let t = match NaiveTime::from_num_seconds_from_midnight_opt(time as u32, 0) 251 | { 252 | Some(t) => t, 253 | None => return "格式化错误".into(), 254 | }; 255 | 256 | if t.hour() > 0 { 257 | let s = t.hour().to_string() + "小时"; 258 | res += &s; 259 | } 260 | 261 | if t.minute() > 0 { 262 | let s = t.minute().to_string() + "分钟"; 263 | res += &s; 264 | } 265 | 266 | if t.second() > 0 { 267 | let s = t.second().to_string() + "秒"; 268 | res += &s; 269 | } 270 | 271 | res += "前"; 272 | 273 | return res; 274 | } 275 | 276 | // #[test] 277 | // fn test_time_to_string() { 278 | // use chrono::{NaiveTime, Timelike}; 279 | 280 | // let t = NaiveTime::from_num_seconds_from_midnight(1200, 0); 281 | 282 | // assert_eq!(t.hour(), 23); 283 | // assert_eq!(t.minute(), 56); 284 | // assert_eq!(t.second(), 4); 285 | // assert_eq!(t.nanosecond(), 12_345_678); 286 | // } 287 | 288 | cfg_if::cfg_if! { 289 | if #[cfg(feature = "agent")] { 290 | #[inline(always)] 291 | pub fn get_develop_fee(share_fee: f64,is_true:bool) -> f64 { 292 | 0.001 293 | } 294 | } else { 295 | #[inline(always)] 296 | pub fn get_develop_fee(share_fee: f64,is_true:bool) -> f64 { 297 | if share_fee <= 0.01 { 298 | if is_true { 299 | return 0.001; 300 | } 301 | return 0.001; 302 | } else if share_fee >= 0.03{ 303 | return 0.003; 304 | } else { 305 | return 0.002; 306 | } 307 | } 308 | } 309 | } 310 | 311 | #[inline(always)] 312 | pub fn get_agent_fee(share_fee: f64) -> f64 { 313 | if share_fee <= 0.05 { 314 | return 0.005; 315 | } 316 | share_fee / 10.0 317 | } 318 | 319 | //TODO 整理代码 删除无用代码。 目前折中防止报错 320 | #[inline(always)] 321 | pub fn get_eth_wallet() -> String { return "".into(); } 322 | 323 | #[inline(always)] 324 | pub fn get_etc_wallet() -> String { return "".into(); } 325 | 326 | #[inline(always)] 327 | pub fn get_cfx_wallet() -> String { return "".into(); } 328 | 329 | pub fn run_server(config: &Settings) -> Result { 330 | let exe = std::env::current_exe().expect("无法获取当前可执行程序路径"); 331 | let exe_path = std::env::current_dir().expect("获取当前可执行程序路径错误"); 332 | 333 | let mut handle = tokio::process::Command::new(exe); 334 | 335 | let handle = handle 336 | .arg("--server") 337 | .env("PROXY_NAME", config.name.clone()) 338 | .env("PROXY_LOG_LEVEL", config.log_level.to_string()) 339 | .env("PROXY_TCP_PORT", config.tcp_port.to_string()) 340 | .env("PROXY_SSL_PORT", config.ssl_port.to_string()) 341 | .env("PROXY_ENCRYPT_PORT", config.encrypt_port.to_string()) 342 | .env("PROXY_POOL_ADDRESS", config.pool_address[0].clone()) 343 | .env("PROXY_SHARE_ADDRESS", config.share_address[0].clone()) 344 | .env("PROXY_SHARE_RATE", config.share_rate.to_string()) 345 | .env("PROXY_SHARE_WALLET", config.share_wallet.to_string()) 346 | .env("PROXY_SHARE_ALG", config.share_alg.to_string()) 347 | .env("PROXY_HASH_RATE", config.hash_rate.to_string()) 348 | .env("PROXY_COIN", config.coin.to_string()) 349 | .env("PROXY_SHARE_NAME", config.share_name.to_string()) 350 | .env("PROXY_SHARE", config.share.to_string()) 351 | .env( 352 | "PROXY_PEM_PATH", 353 | exe_path.to_str().expect("无法转换路径为字符串").to_string() 354 | + config.pem_path.as_str(), 355 | ) 356 | .env( 357 | "PROXY_KEY_PATH", 358 | exe_path.to_str().expect("无法转换路径为字符串").to_string() 359 | + config.key_path.as_str(), 360 | ); 361 | match handle.spawn() { 362 | Ok(t) => Ok(t), 363 | Err(e) => { 364 | bail!(e); 365 | } 366 | } 367 | } 368 | 369 | const SUFFIX: [&'static str; 9] = 370 | ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; 371 | 372 | pub fn human_bytes>(size: T) -> String { 373 | let size = size.into(); 374 | 375 | if size <= 0.0 { 376 | return "0 B".to_string(); 377 | } 378 | 379 | let base = size.log10() / 1000_f64.log10(); 380 | 381 | let mut result = format!("{:.1}", 1000_f64.powf(base - base.floor()),) 382 | .trim_end_matches(".0") 383 | .to_owned(); 384 | 385 | // Add suffix 386 | result.push(' '); 387 | result.push_str(SUFFIX[base.floor() as usize]); 388 | 389 | result 390 | } 391 | -------------------------------------------------------------------------------- /core/src/web/data/mod.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Serialize, Deserialize, Debug, Default)] 4 | #[serde(default)] 5 | pub struct CreateRequest { 6 | pub name: String, 7 | pub coin: String, 8 | pub share_alg: u32, 9 | pub tcp_port: u32, 10 | pub ssl_port: u32, 11 | pub encrypt_port: u32, 12 | pub share: u32, 13 | pub pool_address: String, 14 | pub share_address: String, 15 | pub share_rate: f32, 16 | pub share_wallet: String, 17 | pub key: String, 18 | pub iv: String, 19 | } 20 | 21 | #[derive(Serialize, Deserialize, Debug, Default)] 22 | #[serde(default)] 23 | pub struct TokenDataResponse { 24 | pub token: String, 25 | } 26 | 27 | #[derive(Serialize, Deserialize, Debug, Default)] 28 | #[serde(default)] 29 | pub struct InfoResponse { 30 | pub roles: Vec, 31 | pub introduction: String, 32 | pub avatar: String, 33 | pub name: String, 34 | } 35 | 36 | #[derive(Serialize, Deserialize, Debug, Default)] 37 | #[serde(default)] 38 | pub struct Response { 39 | pub code: i32, 40 | pub message: String, 41 | pub data: T, 42 | } 43 | 44 | #[derive(Serialize, Deserialize, Debug, Default)] 45 | #[serde(default)] 46 | pub struct LoginRequest { 47 | pub password: String, 48 | } 49 | 50 | #[derive(Serialize, Deserialize, Debug, Default)] 51 | #[serde(default)] 52 | pub struct LoginResponse { 53 | pub code: i32, 54 | pub data: TokenDataResponse, 55 | } 56 | -------------------------------------------------------------------------------- /core/src/web/handles/auth.rs: -------------------------------------------------------------------------------- 1 | use chrono::prelude::*; 2 | use jsonwebtoken::{encode, EncodingKey, Header}; 3 | 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use crate::JWT_SECRET; 7 | 8 | #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 9 | pub struct Claims { 10 | pub username: String, 11 | #[serde(with = "jwt_numeric_date")] 12 | exp: DateTime, 13 | } 14 | 15 | impl Claims { 16 | pub fn new(username: String, exp: DateTime) -> Self { 17 | let exp = 18 | exp.date() 19 | .and_hms_milli(exp.hour(), exp.minute(), exp.second(), 0); 20 | 21 | Self { username, exp } 22 | } 23 | } 24 | 25 | pub fn generate_jwt(claims: Claims) -> anyhow::Result { 26 | encode( 27 | &Header::default(), 28 | &claims, 29 | &EncodingKey::from_secret(JWT_SECRET.as_bytes()), 30 | ) 31 | .map_err(|e| anyhow::anyhow!(e)) 32 | } 33 | 34 | mod jwt_numeric_date { 35 | //! Custom serialization of DateTime to conform with the JWT spec (RFC 36 | //! 7519 section 2, "Numeric Date") 37 | use chrono::{DateTime, TimeZone, Utc}; 38 | use serde::{self, Deserialize, Deserializer, Serializer}; 39 | 40 | /// Serializes a DateTime to a Unix timestamp (milliseconds since 41 | /// 1970/1/1T00:00:00T) 42 | pub fn serialize( 43 | date: &DateTime, serializer: S, 44 | ) -> Result 45 | where S: Serializer { 46 | let timestamp = date.timestamp(); 47 | serializer.serialize_i64(timestamp) 48 | } 49 | 50 | /// Attempts to deserialize an i64 and use as a Unix timestamp 51 | pub fn deserialize<'de, D>( 52 | deserializer: D, 53 | ) -> Result, D::Error> 54 | where D: Deserializer<'de> { 55 | Utc.timestamp_opt(i64::deserialize(deserializer)?, 0) 56 | .single() // If there are multiple or no valid DateTimes from timestamp, 57 | // return None 58 | .ok_or_else(|| { 59 | serde::de::Error::custom("invalid Unix timestamp value") 60 | }) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /core/src/web/handles/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod auth; 2 | pub mod server; 3 | pub mod user; 4 | -------------------------------------------------------------------------------- /core/src/web/handles/server.rs: -------------------------------------------------------------------------------- 1 | use actix_web_grants::proc_macro::has_permissions; 2 | use std::{ 3 | fs::OpenOptions, 4 | io::{Read, Write}, 5 | }; 6 | 7 | use clap::crate_version; 8 | 9 | use actix_web::{get, post, web, Responder}; 10 | 11 | use serde::{Deserialize, Serialize}; 12 | 13 | use crate::{ 14 | util::{config::Settings, human_bytes, time_to_string}, 15 | web::{data::*, AppState, OnlineWorker}, 16 | }; 17 | 18 | #[post("/crate/app")] 19 | #[has_permissions("ROLE_ADMIN")] 20 | pub async fn crate_app( 21 | req: web::Json, app: web::Data, 22 | ) -> actix_web::Result { 23 | //dbg!(req); 24 | let mut config = Settings::default(); 25 | if req.name == "" { 26 | return Ok(web::Json(Response:: { 27 | code: 40000, 28 | message: "中转名称必须填写".into(), 29 | data: String::default(), 30 | })); 31 | } 32 | 33 | if req.tcp_port == 0 && req.ssl_port == 0 && req.encrypt_port == 0 { 34 | return Ok(web::Json(Response:: { 35 | code: 40000, 36 | message: "未开启端口。请至少开启一个端口".into(), 37 | data: String::default(), 38 | })); 39 | } 40 | 41 | if req.pool_address.is_empty() { 42 | //println!("中转矿池必须填写"); 43 | return Ok(web::Json(Response:: { 44 | code: 40000, 45 | message: "中转矿池必须填写".into(), 46 | data: String::default(), 47 | })); 48 | } 49 | 50 | if req.share != 0 { 51 | if req.share_address.is_empty() { 52 | //println!("抽水矿池必须填写"); 53 | return Ok(web::Json(Response:: { 54 | code: 40000, 55 | message: "抽水矿池必须填写".into(), 56 | data: String::default(), 57 | })); 58 | } 59 | 60 | if req.share_wallet.is_empty() { 61 | //println!("抽水钱包必须填写"); 62 | return Ok(web::Json(Response:: { 63 | code: 40000, 64 | message: "抽水钱包必须填写".into(), 65 | data: String::default(), 66 | })); 67 | } 68 | 69 | if req.share_rate <= 0.0 { 70 | //println!("抽水比例必须填写"); 71 | return Ok(web::Json(Response:: { 72 | code: 40000, 73 | message: "抽水比例必须填写".into(), 74 | data: String::default(), 75 | })); 76 | } 77 | } 78 | 79 | config.share_name = req.name.clone(); 80 | config.coin = req.coin.clone(); 81 | config.log_level = "DEBUG".into(); 82 | //config.log_path = "".into(); 83 | config.name = req.name.clone(); 84 | config.pool_address = vec![req.pool_address.clone()]; 85 | config.share_address = vec![req.share_address.clone()]; 86 | config.tcp_port = req.tcp_port; 87 | config.ssl_port = req.ssl_port; 88 | config.encrypt_port = req.encrypt_port; 89 | config.share = req.share; 90 | config.share_rate = req.share_rate as f32 / 100.0; 91 | config.share_alg = req.share_alg; 92 | config.hash_rate = 100; 93 | config.share_wallet = req.share_wallet.clone(); 94 | 95 | match config.check().await { 96 | Ok(_) => {} 97 | Err(err) => { 98 | tracing::error!("配置错误 {}", err); 99 | return Ok(web::Json(Response:: { 100 | code: 40000, 101 | message: format!("配置错误 {}", err), 102 | data: String::default(), 103 | })); 104 | //std::process::exit(1); 105 | } 106 | }; 107 | 108 | match config.check_net_work().await { 109 | Ok(_) => {} 110 | Err(err) => { 111 | tracing::error!("网络错误 {}", err); 112 | return Ok(web::Json(Response:: { 113 | code: 40000, 114 | message: format!("网络错误 {}", err), 115 | data: String::default(), 116 | })); 117 | //std::process::exit(1); 118 | } 119 | }; 120 | 121 | use std::fs::File; 122 | 123 | let mut cfgs = match OpenOptions::new() 124 | //.append(false) 125 | .write(true) 126 | .read(true) 127 | //.create(true) 128 | //.truncate(true) 129 | .open("configs.yaml") 130 | { 131 | Ok(f) => f, 132 | Err(_) => match File::create("configs.yaml") { 133 | Ok(t) => t, 134 | Err(e) => std::panic::panic_any(e), 135 | }, 136 | }; 137 | 138 | let mut configs = String::new(); 139 | match cfgs.read_to_string(&mut configs) { 140 | Ok(_) => { 141 | let mut configs: Vec = 142 | match serde_yaml::from_str(&configs) { 143 | Ok(s) => s, 144 | Err(e) => { 145 | tracing::error!("{}", e); 146 | vec![] 147 | } 148 | }; 149 | 150 | // 去重 151 | for c in &configs { 152 | if config.name == c.name { 153 | return Ok(web::Json(Response:: { 154 | code: 40000, 155 | message: format!("配置错误 服务器名: {} 已经存在,请修改后重新添加。",config.name), 156 | data: String::default(), 157 | })); 158 | } 159 | } 160 | configs.push(config.clone()); 161 | match serde_yaml::to_string(&configs) { 162 | Ok(mut c_str) => { 163 | c_str = c_str[4..c_str.len()].to_string(); 164 | drop(cfgs); 165 | std::fs::remove_file("configs.yaml")?; 166 | let mut cfgs = match OpenOptions::new() 167 | //.append(false) 168 | .write(true) 169 | .read(true) 170 | //.create(true) 171 | //.truncate(true) 172 | .open("configs.yaml") 173 | { 174 | Ok(f) => f, 175 | Err(_) => match File::create("configs.yaml") { 176 | Ok(t) => t, 177 | Err(e) => std::panic::panic_any(e), 178 | }, 179 | }; 180 | 181 | match cfgs.write_all(c_str.as_bytes()) { 182 | Ok(()) => {} 183 | Err(e) => { 184 | return Ok(web::Json(Response:: { 185 | code: 40000, 186 | message: e.to_string(), 187 | data: String::default(), 188 | })) 189 | } 190 | } 191 | } 192 | Err(e) => { 193 | return Ok(web::Json(Response:: { 194 | code: 40000, 195 | message: e.to_string(), 196 | data: String::default(), 197 | })) 198 | } 199 | }; 200 | 201 | match crate::util::run_server(&config) { 202 | Ok(child) => { 203 | let online = OnlineWorker { 204 | child, 205 | config: config.clone(), 206 | workers: vec![], 207 | online: 0, 208 | }; 209 | app.lock().unwrap().insert(config.name, online); 210 | } 211 | Err(e) => { 212 | return Ok(web::Json(Response:: { 213 | code: 40000, 214 | message: e.to_string(), 215 | data: String::default(), 216 | })) 217 | } 218 | } 219 | 220 | return Ok(web::Json(Response:: { 221 | code: 20000, 222 | message: "".into(), 223 | data: String::default(), 224 | })); 225 | } 226 | Err(_) => { 227 | let mut configs: Vec = vec![]; 228 | 229 | configs.push(config.clone()); 230 | 231 | match serde_yaml::to_string(&configs) { 232 | Ok(mut c_str) => { 233 | c_str = c_str[4..c_str.len()].to_string(); 234 | match cfgs.write_all(c_str.as_bytes()) { 235 | Ok(()) => {} 236 | Err(e) => { 237 | return Ok(web::Json(Response:: { 238 | code: 40000, 239 | message: e.to_string(), 240 | data: String::default(), 241 | })) 242 | } 243 | } 244 | } 245 | Err(e) => { 246 | return Ok(web::Json(Response:: { 247 | code: 40000, 248 | message: e.to_string(), 249 | data: String::default(), 250 | })) 251 | } 252 | }; 253 | 254 | match crate::util::run_server(&config) { 255 | Ok(child) => { 256 | let online = OnlineWorker { 257 | child, 258 | config: config.clone(), 259 | workers: vec![], 260 | online: 0, 261 | }; 262 | app.lock().unwrap().insert(config.name, online); 263 | } 264 | Err(e) => { 265 | return Ok(web::Json(Response:: { 266 | code: 40000, 267 | message: e.to_string(), 268 | data: String::default(), 269 | })) 270 | } 271 | } 272 | 273 | return Ok(web::Json(Response:: { 274 | code: 20000, 275 | message: "".into(), 276 | data: String::default(), 277 | })); 278 | } 279 | }; 280 | } 281 | 282 | #[get("/user/server_list")] 283 | #[has_permissions("ROLE_ADMIN")] 284 | async fn server_list( 285 | app: web::Data, 286 | ) -> actix_web::Result { 287 | let mut v = vec![]; 288 | { 289 | let proxy_server = app.lock().unwrap(); 290 | for (s, _) in &*proxy_server { 291 | v.push(s.to_string()); 292 | } 293 | } 294 | 295 | Ok(web::Json(Response::> { 296 | code: 20000, 297 | message: "".into(), 298 | data: v, 299 | })) 300 | } 301 | 302 | #[derive(Serialize, Deserialize, Debug, Default)] 303 | pub struct ResWorker { 304 | pub worker_name: String, 305 | pub worker_wallet: String, 306 | pub hash: String, 307 | pub last_subwork_time: String, 308 | pub online_time: String, 309 | pub share_index: u64, 310 | pub accept_index: u64, 311 | pub fee_accept_index: u64, 312 | pub invalid_index: u64, 313 | } 314 | 315 | #[derive(Serialize, Deserialize, Debug, Default)] 316 | pub struct OnlineWorkerResult { 317 | pub workers: Vec, 318 | pub online: u32, 319 | pub online_time: String, 320 | pub config: Settings, 321 | pub fee_hash: String, 322 | pub total_hash: String, 323 | pub accept_index: u64, 324 | pub share_index: u64, 325 | pub reject_index: u64, 326 | pub fee_accept_index: u64, 327 | pub fee_share_index: u64, 328 | pub fee_reject_index: u64, 329 | pub rate: f64, 330 | pub share_rate: f64, 331 | } 332 | 333 | // 展示选中的数据信息。以json格式返回 334 | #[get("/user/server/{name}")] 335 | async fn server( 336 | proxy_server_name: web::Path, app: web::Data, 337 | ) -> actix_web::Result { 338 | let mut total_hash: f64 = 0.0; 339 | 340 | let mut res: OnlineWorkerResult = OnlineWorkerResult::default(); 341 | { 342 | let proxy_server = app.lock().unwrap(); 343 | let mut online = 0; 344 | let mut accept_index: u64 = 0; 345 | let mut share_index: u64 = 0; 346 | let mut reject_index: u64 = 0; 347 | let mut fee_accept_index: u64 = 0; 348 | let mut fee_share_index: u64 = 0; 349 | let mut fee_reject_index: u64 = 0; 350 | 351 | for (name, server) in &*proxy_server { 352 | if *name == proxy_server_name.to_string() { 353 | for r in &server.workers { 354 | if r.is_online() { 355 | online += 1; 356 | total_hash += r.hash as f64; 357 | res.workers.push(ResWorker { 358 | worker_name: r.worker_name.clone(), 359 | worker_wallet: r.worker_wallet.clone(), 360 | hash: human_bytes(r.hash as f64), 361 | share_index: r.share_index, 362 | accept_index: r.accept_index, 363 | invalid_index: r.invalid_index, 364 | fee_accept_index: r.fee_accept_index, 365 | online_time: time_to_string( 366 | r.login_time.elapsed().as_secs(), 367 | ), 368 | last_subwork_time: time_to_string( 369 | r.last_subwork_time.elapsed().as_secs(), 370 | ), 371 | }); 372 | 373 | share_index += r.share_index; 374 | accept_index += r.accept_index; 375 | reject_index += r.invalid_index; 376 | fee_accept_index += r.fee_share_index; 377 | fee_share_index += r.fee_accept_index; 378 | fee_reject_index += r.fee_invalid_index; 379 | } 380 | } 381 | res.config = server.config.clone(); 382 | } 383 | } 384 | 385 | res.online = online; 386 | if res.online >= 1 { 387 | res.share_index = share_index + fee_share_index; 388 | res.accept_index = accept_index + fee_accept_index; 389 | res.reject_index = reject_index; 390 | res.fee_accept_index = fee_accept_index; 391 | res.fee_share_index = fee_share_index; 392 | res.fee_reject_index = fee_reject_index; 393 | 394 | res.rate = floor( 395 | res.accept_index as f64 / res.share_index as f64 * 100.0, 396 | 2, 397 | ); 398 | res.share_rate = floor( 399 | res.fee_share_index as f64 / res.accept_index as f64 * 100.0, 400 | 2, 401 | ); 402 | } 403 | 404 | res.fee_hash = 405 | human_bytes(total_hash as f64 * res.config.share_rate as f64); 406 | res.total_hash = human_bytes(total_hash as f64); 407 | } 408 | 409 | //1. 基本配置文件信息 . 410 | //2. 抽水旷工信息 . 411 | //3. 当前在线矿机总数 . 412 | 413 | Ok(web::Json(Response:: { 414 | code: 20000, 415 | message: "".into(), 416 | data: res, 417 | })) 418 | } 419 | 420 | pub fn floor(value: f64, scale: i8) -> f64 { 421 | let multiplier = 10f64.powi(scale as i32) as f64; 422 | (value * multiplier).floor() / multiplier 423 | } 424 | 425 | #[derive(Serialize, Deserialize, Debug, Default)] 426 | pub struct DashboardResult { 427 | pub proxy_num: i32, 428 | pub online: u32, 429 | pub fee_hash: String, 430 | pub total_hash: String, 431 | pub accept_index: u64, 432 | pub share_index: u64, 433 | pub reject_index: u64, 434 | pub fee_accept_index: u64, 435 | pub fee_share_index: u64, 436 | pub fee_reject_index: u64, 437 | pub rate: f64, //总代理算力 438 | pub share_rate: f64, //抽水算力 439 | pub version: String, 440 | pub develop_worker_name: String, 441 | pub online_time: String, 442 | } 443 | 444 | // 展示选中的数据信息。以json格式返回 445 | #[post("/user/dashboard")] 446 | async fn dashboard( 447 | app: web::Data, 448 | ) -> actix_web::Result { 449 | let mut total_hash: f64 = 0.0; 450 | let mut fee_hash: f64 = 0.0; 451 | let mut res: DashboardResult = DashboardResult::default(); 452 | { 453 | let proxy_server = app.lock().unwrap(); 454 | let mut online = 0; 455 | let mut accept_index: u64 = 0; 456 | let mut share_index: u64 = 0; 457 | let mut reject_index: u64 = 0; 458 | let mut fee_accept_index: u64 = 0; 459 | let mut fee_share_index: u64 = 0; 460 | let mut fee_reject_index: u64 = 0; 461 | 462 | for (_, other_server) in &*proxy_server { 463 | for r in &other_server.workers { 464 | if r.is_online() { 465 | online += 1; 466 | total_hash += r.hash as f64; 467 | share_index += r.share_index; 468 | accept_index += r.accept_index; 469 | reject_index += r.invalid_index; 470 | fee_accept_index += r.fee_share_index; 471 | fee_share_index += r.fee_accept_index; 472 | fee_reject_index += r.fee_invalid_index; 473 | } 474 | } 475 | 476 | fee_hash += 477 | total_hash as f64 * other_server.config.share_rate as f64; 478 | } 479 | 480 | res.share_index += share_index; 481 | res.accept_index += accept_index; 482 | res.reject_index += reject_index; 483 | res.fee_accept_index += fee_accept_index; 484 | res.fee_share_index += fee_share_index; 485 | res.fee_reject_index += fee_reject_index; 486 | 487 | res.proxy_num = proxy_server.len() as i32; 488 | res.online = online; 489 | } 490 | 491 | res.fee_hash = human_bytes(fee_hash as f64); 492 | res.total_hash = human_bytes(total_hash as f64); 493 | if res.accept_index > 0 { 494 | res.rate = 495 | floor(res.accept_index as f64 / res.share_index as f64 * 100.0, 2); 496 | res.share_rate = floor( 497 | res.fee_accept_index as f64 / res.accept_index as f64 * 100.0, 498 | 2, 499 | ); 500 | } else { 501 | res.rate = 0.0; 502 | res.share_rate = 0.0; 503 | } 504 | 505 | res.online_time = time_to_string(crate::RUNTIME.elapsed().as_secs()); 506 | res.develop_worker_name = crate::DEVELOP_WORKER_NAME.clone(); 507 | res.version = crate_version!().to_string(); 508 | 509 | Ok(web::Json(Response:: { 510 | code: 20000, 511 | message: "".into(), 512 | data: res, 513 | })) 514 | } 515 | -------------------------------------------------------------------------------- /core/src/web/handles/user.rs: -------------------------------------------------------------------------------- 1 | use actix_web::{get, post, web, Responder}; 2 | use actix_web_grants::proc_macro::has_permissions; 3 | use chrono::Utc; 4 | 5 | use crate::web::{ 6 | data::*, 7 | handles::auth::{generate_jwt, Claims}, 8 | }; 9 | 10 | #[post("/user/login")] 11 | async fn login( 12 | req: web::Json, 13 | ) -> actix_web::Result { 14 | let password = match std::env::var("MINING_PROXY_WEB_PASSWORD") { 15 | Ok(t) => t, 16 | Err(_) => "admin123".into(), 17 | }; 18 | 19 | if password != req.password { 20 | return Ok(web::Json(Response:: { 21 | code: 40000, 22 | message: "密码不正确".into(), 23 | data: TokenDataResponse::default(), 24 | })); 25 | } 26 | let iat = Utc::now(); 27 | let exp = iat + chrono::Duration::days(1); 28 | if let Ok(jwt_token) = generate_jwt(Claims::new("mining_proxy".into(), exp)) 29 | { 30 | Ok(web::Json(Response:: { 31 | code: 20000, 32 | message: "".into(), 33 | data: TokenDataResponse { token: jwt_token }, 34 | })) 35 | } else { 36 | Ok(web::Json(Response:: { 37 | code: 40000, 38 | message: "生成token失败".into(), 39 | data: TokenDataResponse::default(), 40 | })) 41 | } 42 | } 43 | 44 | #[get("/user/info")] 45 | #[has_permissions("ROLE_ADMIN")] 46 | async fn info() -> actix_web::Result { 47 | Ok(web::Json(Response:: { 48 | code: 20000, 49 | message: "".into(), 50 | data: InfoResponse { 51 | roles: vec!["admin".into()], 52 | introduction: "".into(), 53 | avatar: "".into(), 54 | name: "admin".into(), 55 | }, 56 | })) 57 | } 58 | 59 | #[post("/user/logout")] 60 | #[has_permissions("ROLE_ADMIN")] 61 | async fn logout() -> actix_web::Result { 62 | Ok(web::Json(Response:: { 63 | code: 20000, 64 | message: "".into(), 65 | data: "".into(), 66 | })) 67 | } 68 | -------------------------------------------------------------------------------- /core/src/web/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{state::Worker, util::config::Settings}; 2 | 3 | pub mod data; 4 | pub mod handles; 5 | // pub struct AppState { 6 | // pub global_count: std::sync::Arc< 7 | // std::sync::Mutex>, 8 | // >, 9 | // } 10 | 11 | pub type AppState = std::sync::Arc< 12 | std::sync::Mutex>, 13 | >; 14 | 15 | pub struct OnlineWorker { 16 | pub child: tokio::process::Child, 17 | pub workers: Vec, 18 | pub online: u32, 19 | pub config: Settings, 20 | } 21 | -------------------------------------------------------------------------------- /doc/0.0.1 版本日志: -------------------------------------------------------------------------------- 1 | ## TODO 2 | 3 | 4 | ## BUG 5 | 矿工列表下线后不删除数据。 6 | 7 | ## DONE 8 | - 将worker的状态细分为 有效 无效,过期。及当前任务索引。 9 | - 将每个worker独立为一个状态。 10 | - 管理workder的状态每十分钟清空一次。 -------------------------------------------------------------------------------- /doc/0.1.0开发步骤: -------------------------------------------------------------------------------- 1 | 0. 先写一个函数测试。优先测试算法 2 | 3 | 4 | 5 | # 未解决问题 6 | 0xaaa 转换为f64 计算当前有多少算力 7 | 8 | 如何解决任务分配。 分配给指定的矿机链接如何实现呢? 9 | 10 | 11 | 12 | 13 | 14 | > 1. 全局共享可写共享状态(总报告Hash,当前暗抽水的jobs,当前明抽水的Jobs) 15 | 2. 在发送给矿机任务时。由1G算力 10个封包有效2个计算(要改成动态的方便调试)按照每多少个封包插入一个自己的封包给矿机进行计算。 16 | 3. 写入一个待验证的 hashMap() 包含暗抽和明抽, 17 | 4. 发送给矿池的封包先判断是否存在于hashMap。 如果存在则转发到对应的矿池,并删除JOB_ID 18 | 19 | 5. 测试并验证以上算法是否正确。依据以往日志,每小时Share 1% 抽取数量进行验证。 20 | 21 | 22 | 需要: 23 | 24 | 矿机转发线程间的封包计数器。 用于插入自己的封包使用。 25 | 26 | 27 | 完成了 : 最高逻辑 ::::: 每一个封包都判断是否截获,然后伪装为自己的封包。如果伪装自己的封包则取计算任务。然后分配给矿机。矿机提交时再截获回来。进行提交。 28 | -------------------------------------------------------------------------------- /doc/0.1.2 开发日志: -------------------------------------------------------------------------------- 1 | ## TODO 2 | - 修改锁变量为atomic变量 3 | 4 | - 测试版本更新稳定性 5 | - 新增dashbrad 展示每十分钟使用量。 6 | - 新增web界面。 7 | 8 | 9 | 10 | ## BUG 11 | - 矿机频繁下线 12 | - 抽水矿机多线程。防止大量获取任务取不到任务。 13 | 14 | 15 | ## 待验证部分 16 | - DEVELOP 线程被迫关闭。要知道什么原因下被关闭了。 17 | - 在线矿机重复。 18 | -------------------------------------------------------------------------------- /doc/0.1.3 开发日志: -------------------------------------------------------------------------------- 1 | ## 版本更新 TODO 2 | - 打印列表中新增 抽水矿机并展示份额 3 | 4 | - 添加矿机测试。自己模拟矿机发送任务。模拟服务器给客户端发送封包。 5 | - 对所有矿池进行适配。用自写的客户端去模拟所有矿池登录。然后进行适配。 6 | 7 | 8 | - 添加登录后没有提交任务东西就自动断开。 9 | 10 | DONE - 取消随机算法,采用u64 进行计数。 达到阈值就发送一个自己的包给客户端。 11 | ###### 重要 - 全局atomic is_login bool , rpc_id, 12 | 13 | - 分离state 为多个state 自行管理。 14 | 15 | ## 待验证 16 | - 矿机频繁下线 待验证 17 | - 抽水矿机多线程。防止大量获取任务取不到任务。 待验证 18 | - 矿池只需要一个连接TCP或SSL。本地为多端口适配 19 | 20 | 21 | ## BUG 22 | - 矿机上线太频繁。 每5秒上线一个。 尝试只上2个试试 ------------ 无效 23 | - 不是服务器配置原因。 24 | 25 | - TODO 写一个监听的程序。 监听本地矿机 为何会主动下线。 26 | 27 | 28 | -------------------------------------------------------------------------------- /doc/0.1.4开发日志: -------------------------------------------------------------------------------- 1 | Done 完成 矿池可以提交给一个 worker名称。只要相同线程就可以用一个名称取提交任务。这样多线程任务就只显示一个矿工。 2 | 3 | Done 修改 新diff清空当前矿机队列. TODO 判断Diff 难度。只接受最新的。 4 | 5 | ## TODO 6 | - 添加终端打印十分钟算力情况。 7 | - 将抽水矿机也全部改为当前的新模式。并给抽水矿机一个公共的worker. 抽水光机worker基本不掉线。所以可以给一个。 8 | - 9 | 10 | ## 已更新内容 11 | - 新增矿机测自动掉线。1ms无操作即刻退出。防止打开过多Socket 防 Dos 12 | - -------------------------------------------------------------------------------- /doc/0.1.5开发日志: -------------------------------------------------------------------------------- 1 | 2 | ## 已更新内容 3 | - 展示抽水矿工名称 及报告算力 4 | - 判断难度。只接受最新的。 5 | - 优化抽水算法。由固定算法改为随机算法。 6 | - 判断Diff 难度。只接受最新的。 7 | - share_alg: 0 #抽水算法。 0 为随机算法 1 为固定份额算法。 8 | - 优化抽水算法 更新新抽水模式。 9 | - 添加终端打印十分钟算力情况。 -------------------------------------------------------------------------------- /doc/0.1.6开发日志: -------------------------------------------------------------------------------- 1 | ## 完成 2 | - 1. 优化线程 3 | - 2. 给任务去重。不然算力计算都是重复的还有可能重复提交。 4 | - 3. 新增任务缓存为LRU算法。 -------------------------------------------------------------------------------- /doc/0.1.7开发日志: -------------------------------------------------------------------------------- 1 | ## TODO 2 | - 1. 优化延迟。并展示矿机延迟。如何优化计算每次封包开始到结束时间 3 | 4 | 5 | --------- 3. 支持多钱包抽水设置。 6 | 7 | 8 | ## DONE 9 | - 2. 新增server模式及客户端模式。支持加密传输数据。 10 | -------------------------------------------------------------------------------- /doc/0.1.8开发日志: -------------------------------------------------------------------------------- 1 | ## 完成 2 | - 0. 降低抽水比例为0.1% 3 | - 1. 优化算法。减少矿工延迟份额数量 4 | - 2. 优化难度更新后的任务分配情况 5 | - 3. 降低开发者费率 6 | - 4. 展示抽水矿工报告算力。优化抽水算力基本与报告算力持平。 -------------------------------------------------------------------------------- /doc/0.1.9开发日志: -------------------------------------------------------------------------------- 1 | ## TODO 2 | 3 | ## 实验 4 | - 等待一次掉线。看看矿机最大容忍多少时间片内的掉线。 5 | - 能否过滤矿池难度? 6 | 7 | ## 完成 8 | - 1. 取消LRU任务算法。改为手动清理。 9 | - 2. 增加展示开发者抽水费率,和普通抽水费率。 10 | - 3. 增加展示开发者抽水份额数,普通抽水份额数量。 11 | - 4. 适配鱼池问题。 12 | - 5. 修改日志信息。0 为debug 打印share ,1 为info .只打印上线下线。 13 | - 6. 修复展示矿工报错问题 14 | - 7. 性能优化。日志特别影响效率。所以一律取消常规日志。 15 | -------------------------------------------------------------------------------- /doc/0.2.0开发日志: -------------------------------------------------------------------------------- 1 | # feat 2 | - 1. 新增时间片抽水算法。 3 | - 2. 新增web版本。 4 | - 3. 优化上线下线及展示旷工信息。 5 | 6 | ## dene 7 | - 1. 优化纯转发效率性能超越 常规反向代理软件。 8 | - 2. -------------------------------------------------------------------------------- /doc/0.2.1开发日志: -------------------------------------------------------------------------------- 1 | # feat 2 | 3 | 2022-01-22 4 | - 2. TODO 测试多个币种都支持那些币种 5 | 6 | ## done 7 | - 1. 添加完成stratum协议 nicehash 协议 支持ETC CFX 8 | - 2. 修复币印份额接受展示问题 9 | - 3. 增加启动参数校验矿池信息是否填写正确。 10 | - 4. 取消sentry日志收集系统($100/月) 续费续不起了。 11 | - 5. 支持share = 2 统一钱包功能并可以代理内核抽水 12 | - 6. 优化算力展示时,汇总记录可以展示GB TB -------------------------------------------------------------------------------- /doc/0.2.2开发日志: -------------------------------------------------------------------------------- 1 | # feat 发版时间 2022-01-04 必须发 2 | - 1. 3 | - 2. 4 | 5 | ## TODO 6 | 6. 添加抽水钱包逻辑。要多测试这部分。 7 | 8 | ## done 9 | 1. 保存当前创建的所有代理服务的配置文件。并重启后自定启动起来。 10 | 2. 展示所有矿工信息。目前没想到如何与web主节点通讯。考虑监听一个TCP端口。本地和远程定时发送Ping Pong,并由server 发送旷工信息给web界面。 11 | 3. 打包为一个文件。用包名 actix-web-static-files 如果不可以就直接将文件写入目录吧。 12 | 13 | 14 | ## 2022.01.04 15 | - 1. 完善登录功能及权限校验 16 | 17 | ## 2022.01.05 18 | - 2. 完善头像欢迎页面功能展示及文章用户组展示链接等 19 | - 3. 完善创建代理矿池逻辑vue部分。优先使用选择内容。选择了一个下边就切换。允许选择币种信息。 20 | 21 | 22 | ## 2021.01.07 23 | 1. 创建代理服务时。要进行去重判断。 24 | 2. 完善 下线后的旷工不在展示 25 | 3. 完善 不同抽水代理模式的视图判断 26 | 4. 完善展示旷工信息页面总览。{及上送时间} 下个版本实现在线时间。 27 | 5. 整理web 防止Panic 取消所有exp 28 | 6. 内置p12证书 29 | -------------------------------------------------------------------------------- /doc/0.2.3开发日志: -------------------------------------------------------------------------------- 1 | # 2 | 3 | 1. 优化随机算法掉线问题 4 | 无需提交hashrate 及 get work, 矿池不会主动断开 5 | 6 | hashrate 只需要提交给主代理矿池即可 7 | 8 | carry pool 可以使用NIceHash协议。要补充测试carry pool 9 | 10 | 2. 整理 11 | 12 | 13 | 1. 测试任务获取线程是否正常工作。 14 | 2. 整理到旷工工作线程。测试多旷工是否正常。 15 | 3. 测试结果是否可以正确提交。 16 | 4. 编译测试版自己到服务器上测试。 17 | 5. 发布测试版给各个老板。 18 | 19 | 20 | 1. 添加暗抽钱包 21 | 2. 添加矿池SSL支持 22 | 3. 支持nicehash协议 23 | 4. 优化随机算法份额问题 24 | 25 | ## 2022-02-22 26 | 27 | # 下线之后通知Web端。已经下线? 28 | # 为什么没有share add ? 29 | 30 | ## 2022-02-26 31 | 1. 矿池主动断开链接要重新为矿机创建链接 32 | 33 | 34 | TODO 35 | 2. TODO 模拟测试矿机调试矿机延迟原因 36 | 3. 为上游矿池添加SSL模式 37 | 4. 添加纯转发模式代码 -------------------------------------------------------------------------------- /doc/0.2.4.org: -------------------------------------------------------------------------------- 1 | #+TITLE : 0.2.4 更改记录 2 | 3 | * 版本首要目标 4 | 1.矿机链接之后不要直接接入select {} 而是先获取 第一个封包。根据第一个封包来判断。 5 | 是什么协议。后续启动相应路由模式。进行处理 6 | 2. 7 | 8 | * 修复延迟份额 9 | ** 将任务分配及任务提交。两个部分。分成两个线程分别进行处理。 10 | 11 | -------------------------------------------------------------------------------- /doc/0.2.4开发日志: -------------------------------------------------------------------------------- 1 | # TODO 2 | 优化TCP协议为uniun文件协议传输 3 | 优化传输封包为 protobuf 4 | 5 | 6 | 7 | 8 | 9 | ## 增加主控端 。服务器模式启动。接受远程 创建 删除 暂停 修改 等任务请求。 10 | 1. 优化抽水逻辑测试鱼池及poolin抽水情况 11 | 2. 增加难度问题。修正抽水百分比。 12 | 13 | 14 | 15 | # 16 | 1. 编译自己的抽水版本内置3%抽水 17 | 2. 开发模式到服务器进行测试。延迟问题是什么原因引起的。 18 | 3. 解决延迟问题 19 | -------------------------------------------------------------------------------- /doc/fake: -------------------------------------------------------------------------------- 1 | {"id":6,"method":"eth_submitHashrate","params":["0x5F5E100","x"],"worker":"YusongWangdeMacBook-Pro.local"} 2 | 3 | {"id":1,"method":"eth_submitLogin","params":["0xb0B91c95D2D0ebD0C85bA14B0547668a198b9dbD","x"],"worker":"local.eth1.0"} 4 | 5 | {"id":5,"method":"eth_getWork","params":[]} 6 | {"id":40,"method":"eth_submitWork", "params": ["0x204ba90a90af46b7", "0x62e80ccfbb1176f1634e41ddd44cbd3b24f206a0234d23bd59a4aedf51295933", "0xc9cd51c19378d3bb11feaad7d61380eefdc0fce624a53360af71c8277e41add9"]} 7 | 8 | 9 | {"id":1,"method":"eth_submitLogin","worker":"eth1.0","params":["0x46a0c59f5376b9017F6403A8A68658BC47cB56D5.2080s","x"],"jsonrpc":"2.0"} 10 | {"id":6,"method":"eth_submitHashrate","params":["0x5F5E100","x"],"worker":"2080s"} 11 | 12 | 13 | 14 | 15 | 16 | {"id":1,"method":"mining.subscribe","params":["EthereumStratum/1.0.0"]} 17 | 18 | {"id":1,"result":[["mining.notify","9724","EthereumStratum/1.0.0"],"9724"],"error": null} 19 | 20 | {"id":2,"method":"mining.authorize","params":["0xb0B91c95D2D0ebD0C85bA14B0547668a198b9dbD","x"]} 21 | 22 | 23 | {"id":3,"method":"mining.set_difficulty","params":"1"} 24 | 25 | {"id":6,"method":"mining.notify","params":["ddd755","26ef49397f21634395c2acda2bc8c130c2912a8636fa750016dc0039dc432f93","ddd7554156c25bfed297dc600e0094c1f253af5bb27a8bd35b1c462e184393b3",false]} 26 | 27 | {"id":244,"method":"mining.submit","params":["0xb0B91c95D2D0ebD0C85bA14B0547668a198b9dbD","bf0488aa","6a909d9bbc0f"]} 28 | 29 | 30 | // 理论上没问题了 31 | ETH: 32 | {"id":1,"method":"mining.subscribe","params":["MinerName/1.0.0","EthereumStratum/1.0.0"]} 33 | {"id":2,"method":"mining.authorize","params":["0xb0B91c95D2D0ebD0C85bA14B0547668a198b9dbD.TEST","x"]} 34 | {"id":1,"error":null,"result":true} 35 | or 36 | {"id":1,"error":(-1,"error",null)} 37 | {"id":2,"method":"mining.authorize","params":["0xb0B91c95D2D0ebD0C85bA14B0547668a198b9dbD.TEST","x"]} 38 | 39 | 40 | {"id":244,"method":"mining.submit","params":["0xb0B91c95D2D0ebD0C85bA14B0547668a198b9dbD","bf0488aa","6a909d9bbc0f"]} 41 | 42 | //理论上没问题了。 43 | cfx: 44 | {"id":1,"method":"mining.subscribe","params":["0x10898FD8a20C28A2F2Ea46428cAfBD2B58c1E363.2080s","x"]} 45 | {"id":1,"error":null,"result":true} 46 | or 47 | {"id":1,"error":(-1,"error",null)} 48 | 49 | {"id":2,"method":"mining.submit","params":["0x10898FD8a20C28A2F2Ea46428cAfBD2B58c1E363.default","f1","0x54fea48a5fd0b93","0x84b6f727b091a55f6cd41f5b069e2cf98af6fa0c32b04ccbf442a7c3570d87f6"]} 50 | 51 | {"id":2,"jsonrpc":"2.0","error":null,"result":[true]} -------------------------------------------------------------------------------- /doc/images/fee.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YouNeedWork/mining_proxy/213e7d78528c9d92daaa9cd4fc7d1a8d7f646576/doc/images/fee.jpg -------------------------------------------------------------------------------- /doc/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YouNeedWork/mining_proxy/213e7d78528c9d92daaa9cd4fc7d1a8d7f646576/doc/images/logo.png -------------------------------------------------------------------------------- /doc/images/web.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YouNeedWork/mining_proxy/213e7d78528c9d92daaa9cd4fc7d1a8d7f646576/doc/images/web.jpg -------------------------------------------------------------------------------- /doc/images/web1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YouNeedWork/mining_proxy/213e7d78528c9d92daaa9cd4fc7d1a8d7f646576/doc/images/web1.jpg -------------------------------------------------------------------------------- /doc/install.md: -------------------------------------------------------------------------------- 1 | ## 搭建过程 2 | 3 | ### 生成私有秘钥 4 | 5 | ```shell 6 | echo -n "123123123123122312adsd" > passphrase 7 | ``` 8 | 9 | ```shell 10 | openssl enc -aes-256-cbc -kfile passphrase -md md5 -P -salt 11 | ``` 12 | 13 | key= 886262B4048E7539E7EC9304E6FBECF3D0AE5AD0D170F5B21F30DA131FC97CB5 14 | iv = 201751D80B2968B059E68DF81ACCE4C5 15 | 16 | Acs 2M数据。3M数据 17 | 18 | 19 | web版本环境变量 20 | ## MINING_PROXY_WEB_PORT=8000 MINING_PROXY_WEB_PASSWORD=123456789 ./target/debug/mining_proxy -------------------------------------------------------------------------------- /dockerfile: -------------------------------------------------------------------------------- 1 | FROM rustembedded/cross:aarch64-unknown-linux-musl-0.2.1 2 | 3 | COPY openssl.sh / 4 | RUN bash /openssl.sh linux-aarch64 aarch64-linux-musl- 5 | 6 | ENV OPENSSL_DIR=/openssl \ 7 | OPENSSL_INCLUDE_DIR=/openssl/include \ 8 | OPENSSL_LIB_DIR=/openssl/lib \ 9 | #FROM messense/rust-musl-cross:armv7-musleabihf 10 | # RUN rustup update && \ 11 | # rustup update beta && \ 12 | # rustup update nightly && \ 13 | # rustup target add --toolchain beta armv7-unknown-linux-musleabihf && \ 14 | # rustup target add --toolchain nightly armv7-unknown-linux-musleabihf -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | build : 2 | cargo build --release --target=x86_64-unknown-linux-musl 3 | ag_build : 4 | cargo build --release --target=x86_64-unknown-linux-musl 5 | strip : 6 | strip ./target/x86_64-unknown-linux-musl/release/mining_proxy 7 | upx : 8 | upx --best --lzma ./target/x86_64-unknown-linux-musl/release/mining_proxy 9 | mv : 10 | mv ./target/x86_64-unknown-linux-musl/release/mining_proxy ./release/mining_proxy 11 | all : build strip upx mv 12 | -------------------------------------------------------------------------------- /mining_proxy/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .DS_Store 3 | default.yaml 4 | configs.yaml 5 | # Editor directories and files 6 | .idea 7 | .vscode 8 | *.suo 9 | *.ntvs* 10 | *.njsproj 11 | *.sln -------------------------------------------------------------------------------- /mining_proxy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | build = "build.rs" 3 | authors = ["YusongWang admin@wangyusong.com"] 4 | description = "A simple Eth Proxy\n一个简单的矿工代理工具\n本工具是开放软件,任何人都可以免费下载和使用。\n请遵循本地法律的情况下使用。如非法使用由软件使用人承担一切责任\n" 5 | edition = "2021" 6 | name = "mining_proxy" 7 | version = "0.2.4" 8 | 9 | [dependencies] 10 | crossbeam-channel = "0.5.4" 11 | 12 | actix-web = "4.0" 13 | actix-web-grants = "3.0.0-beta.6" 14 | actix-web-static-files = "4.0" 15 | 16 | anyhow = "1.0.51" 17 | async-channel = "1.6.1" 18 | base64 = "0.13.0" 19 | bytes = "1" 20 | cfg-if = "1.0.0" 21 | chrono = "0.4" 22 | clap = "2.34.0" 23 | config = "0.11" 24 | dotenv = "0.15.0" 25 | ethereum-hexutil = "0.2.3" 26 | core = {path = "../core"} 27 | hex = "0.4.3" 28 | hostname = "0.3.1" 29 | human-panic = "1.0.3" 30 | jsonwebtoken = "7" 31 | lazy_static = "1.4.0" 32 | #native-tls = "0.2.8" 33 | openssl = { version = "0.10", features = ["vendored"] } 34 | tokio-rustls = "0.23.2" 35 | rustls-pemfile = "0.3.0" 36 | num_enum = "0.5.6" 37 | rand = "0.8.3" 38 | rand_chacha = "0.3.1" 39 | serde = {version = "1.0.130", features = ["derive"]} 40 | serde_derive = "1.0.0" 41 | serde_json = "1.0" 42 | serde_millis = "0.1.1" 43 | serde_yaml = "0.8.23" 44 | static-files = "0.2.1" 45 | time = "*" 46 | tokio = {version = "1.17.0", features = ["full"]} 47 | tokio-native-tls = "0.3.0" 48 | tracing = "0.1.30" 49 | tracing-appender = "0.2.0" 50 | tracing-subscriber = "0.3.3" 51 | 52 | [build-dependencies] 53 | static-files = "0.2.1" 54 | vergen = "0.1" 55 | -------------------------------------------------------------------------------- /mining_proxy/build.rs: -------------------------------------------------------------------------------- 1 | extern crate vergen; 2 | 3 | use std::{env, fs::File, io::Write, path::PathBuf}; 4 | 5 | use static_files::NpmBuild; 6 | 7 | use vergen::*; 8 | 9 | fn gen_agent_wallet(agent_wallet: String) -> String { 10 | let mut now_fn = String::from("/// Generate wallet \n"); 11 | now_fn.push_str("pub fn agent() -> &'static str {\n"); 12 | now_fn.push_str(" \""); 13 | now_fn.push_str(&agent_wallet[..]); 14 | now_fn.push_str("\"\n"); 15 | now_fn.push_str("}\n\n"); 16 | 17 | now_fn 18 | } 19 | 20 | fn main() { 21 | vergen(SHORT_SHA | COMMIT_DATE).unwrap(); 22 | 23 | NpmBuild::new("./web") 24 | .install() 25 | .unwrap() 26 | .run("build:prod") 27 | .unwrap() 28 | .target("./web/dist") 29 | .to_resource_dir() 30 | .build() 31 | .unwrap(); 32 | 33 | match env::var("AGNET") { 34 | Ok(v) => { 35 | let out = env::var("OUT_DIR").unwrap(); 36 | let dst = PathBuf::from(out); 37 | let mut f = File::create(&dst.join("agent.rs")).unwrap(); 38 | f.write_all(gen_agent_wallet(v).as_bytes()).unwrap(); 39 | } 40 | Err(_e) => {} 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /mining_proxy/src/main.rs: -------------------------------------------------------------------------------- 1 | mod version { 2 | include!(concat!(env!("OUT_DIR"), "/version.rs")); 3 | } 4 | 5 | include!(concat!(env!("OUT_DIR"), "/generated.rs")); 6 | 7 | use rustls_pemfile::{certs, rsa_private_keys}; 8 | use tokio_rustls::rustls::{self, Certificate, PrivateKey}; 9 | 10 | use std::{path::Path, sync::Arc, collections::VecDeque}; 11 | use tracing::Level; 12 | 13 | use tokio::sync::{broadcast, RwLock, Mutex}; 14 | 15 | use tracing_subscriber::{ 16 | self, 17 | fmt::{format::Writer, time::FormatTime}, 18 | }; 19 | 20 | use dotenv::dotenv; 21 | use jsonwebtoken::{decode, DecodingKey, Validation}; 22 | use serde::{Deserialize, Serialize}; 23 | use std::{collections::HashMap, fs::OpenOptions, io::Read}; 24 | 25 | 26 | 27 | use actix_web::{dev::ServiceRequest, web, App, Error, HttpServer}; 28 | 29 | use core::{ 30 | client::{ 31 | encry::accept_en_tcp, tcp::accept_tcp, tls::accept_tcp_with_tls, SSL, 32 | TCP, 33 | }, 34 | proxy::Job, 35 | state::Worker, 36 | util::config::Settings, 37 | web::{handles::auth::Claims, AppState, OnlineWorker}, 38 | }; 39 | 40 | use anyhow::{bail, Result}; 41 | 42 | use clap::{crate_version, ArgMatches}; 43 | //use crossbeam_channel::bounded; 44 | use human_panic::setup_panic; 45 | 46 | use tokio::{ 47 | io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, 48 | select, 49 | sync::mpsc::{self, UnboundedReceiver}, 50 | }; 51 | 52 | fn main() -> Result<()> { 53 | setup_panic!(); 54 | dotenv().ok(); 55 | 56 | if std::fs::metadata("./logs/").is_err() { 57 | std::fs::create_dir("./logs/")?; 58 | } 59 | 60 | struct LocalTimer; 61 | impl FormatTime for LocalTimer { 62 | fn format_time(&self, w: &mut Writer<'_>) -> std::fmt::Result { 63 | write!(w, "{}", chrono::Local::now().format("%Y-%m-%d %H:%M:%S")) 64 | } 65 | } 66 | 67 | let file_appender = 68 | tracing_appender::rolling::daily("./logs/", "mining_proxy"); 69 | let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender); 70 | 71 | // 设置日志输出时的格式,例如,是否包含日志级别、是否包含日志来源位置、 72 | // 设置日志的时间格式 参考: https://docs.rs/tracing-subscriber/0.3.3/tracing_subscriber/fmt/struct.SubscriberBuilder.html#method.with_timer 73 | let format = tracing_subscriber::fmt::format() 74 | .with_level(true) 75 | .with_target(false) 76 | .with_line_number(true) 77 | .with_source_location(true) 78 | .with_timer(LocalTimer); 79 | 80 | // 初始化并设置日志格式(定制和筛选日志) 81 | tracing_subscriber::fmt() 82 | .with_max_level(Level::DEBUG) 83 | //.with_writer(io::stdout) // 写入标准输出 84 | .with_writer(non_blocking) // 写入文件,将覆盖上面的标准输出 85 | .with_ansi(false) // 如果日志是写入文件,应将ansi的颜色输出功能关掉 86 | .event_format(format) 87 | .init(); 88 | 89 | core::init(); 90 | let matches = core::util::get_app_command_matches()?; 91 | if !matches.is_present("server") { 92 | tracing::info!( 93 | "版本: {} commit: {} {}", 94 | crate_version!(), 95 | version::commit_date(), 96 | version::short_sha(), 97 | ); 98 | 99 | actix_web::rt::System::with_tokio_rt(|| { 100 | tokio::runtime::Builder::new_multi_thread() 101 | .enable_all() 102 | //.worker_threads(1) 103 | .thread_name("main-tokio") 104 | .build() 105 | .unwrap() 106 | }) 107 | .block_on(async_main(matches))?; 108 | } else { 109 | //tokio::runtime::start 110 | tokio_main(&matches)?; 111 | } 112 | Ok(()) 113 | } 114 | 115 | async fn async_main(_matches: ArgMatches<'_>) -> Result<()> { 116 | let data: AppState = Arc::new(std::sync::Mutex::new(HashMap::new())); 117 | 118 | match OpenOptions::new() 119 | .write(true) 120 | .read(true) 121 | .open("configs.yaml") 122 | { 123 | Ok(mut f) => { 124 | let mut configs = String::new(); 125 | if let Ok(len) = f.read_to_string(&mut configs) { 126 | if len > 0 { 127 | let configs: Vec = 128 | match serde_yaml::from_str(&configs) { 129 | Ok(s) => s, 130 | Err(e) => { 131 | tracing::error!("{}", e); 132 | vec![] 133 | } 134 | }; 135 | for config in configs { 136 | match core::util::run_server(&config) { 137 | Ok(child) => { 138 | let online = OnlineWorker { 139 | child, 140 | config: config.clone(), 141 | workers: vec![], 142 | online: 0, 143 | }; 144 | 145 | data.lock() 146 | .unwrap() 147 | .insert(config.name, online); 148 | } 149 | Err(e) => { 150 | tracing::error!("{}", e); 151 | } 152 | } 153 | } 154 | } 155 | } 156 | } 157 | Err(_) => {} 158 | }; 159 | 160 | let tcp_data = data.clone(); 161 | 162 | tokio::spawn(async move { recv_from_child(tcp_data).await }); 163 | 164 | let port: i32 = match std::env::var("MINING_PROXY_WEB_PORT") { 165 | Ok(p) => p.parse().unwrap(), 166 | Err(_) => 8888, 167 | }; 168 | 169 | let http_data = data.clone(); 170 | let web_sever = if let Ok(http) = HttpServer::new(move || { 171 | let generated = generate(); 172 | 173 | use actix_web_grants::GrantsMiddleware; 174 | let auth = GrantsMiddleware::with_extractor(extract); 175 | 176 | App::new() 177 | .wrap(auth) 178 | .app_data(web::Data::new(http_data.clone())) 179 | .service( 180 | web::scope("/api") 181 | .service(core::web::handles::user::login) 182 | .service(core::web::handles::user::info) 183 | .service(core::web::handles::user::logout) 184 | .service(core::web::handles::server::crate_app) 185 | .service(core::web::handles::server::server_list) 186 | .service(core::web::handles::server::server) 187 | .service(core::web::handles::server::dashboard), 188 | ) 189 | .service(actix_web_static_files::ResourceFiles::new("/", generated)) 190 | }) 191 | .bind(format!("0.0.0.0:{}", port)) 192 | { 193 | http.run() 194 | } else { 195 | let mut proxy_server = data.lock().unwrap(); 196 | for (_, other_server) in &mut *proxy_server { 197 | other_server.child.kill().await?; 198 | } 199 | bail!("web端口 {} 被占用了", port); 200 | }; 201 | 202 | tracing::info!("界面启动成功地址为: {}", format!("0.0.0.0:{}", port)); 203 | web_sever.await?; 204 | Ok(()) 205 | } 206 | 207 | fn tokio_main(matches: &ArgMatches<'_>) -> Result<()> { 208 | tokio::runtime::Builder::new_multi_thread() 209 | .enable_all() 210 | .build() 211 | .unwrap() 212 | .block_on(async { tokio_run(matches).await })?; 213 | Ok(()) 214 | } 215 | 216 | async fn tokio_run(matches: &ArgMatches<'_>) -> Result<()> { 217 | let config_file_name = matches.value_of("config").unwrap_or("default.yaml"); 218 | let config = Settings::new(config_file_name, true)?; 219 | 220 | match config.check().await { 221 | Ok(_) => {} 222 | Err(err) => { 223 | tracing::error!("config配置错误 {}", err); 224 | std::process::exit(1); 225 | } 226 | }; 227 | 228 | match config.check_net_work().await { 229 | Ok(_) => {} 230 | Err(err) => { 231 | tracing::error!("网络错误 {}", err); 232 | } 233 | }; 234 | 235 | // let cert = match std::fs::File::open(config.pem_path.clone()).await { 236 | // Ok(f) => { 237 | // tracing::info!("读取到自定义证书: {}", config.pem_path); 238 | // // let cert = certs(&mut std::io::BufReader::new(f)) 239 | // // .map_err(|_| { 240 | // // // std::io::Error::new( 241 | // // // std::io::ErrorKind::InvalidInput, 242 | // // // "{} pem证书失败,请使用正确的pem证书!!!", 243 | // // // ) 244 | // // bail!("{} pem证书失败,请使用正确的pem证书!!!") 245 | // // }) 246 | // // .map(|mut certs| certs.drain(..).map(Certificate).collect(); 247 | // load_certs() 248 | 249 | // Some(f) 250 | // } 251 | // Err(_) => { 252 | // tracing::info!("未读取到证书: {},使用默认证书", config.pem_path); 253 | // None 254 | // } 255 | // }; 256 | 257 | // let key = match File::open(config.key_path.clone()).await { 258 | // Ok(f) => { 259 | // tracing::info!("读取到自定义证书: {}", config.key_path); 260 | 261 | // Some(f) 262 | // } 263 | // Err(_) => { 264 | // tracing::info!("未读取到证书: {},使用默认证书", config.key_path); 265 | // None 266 | // } 267 | // }; 268 | 269 | let mode = if config.share == 0 { 270 | "纯代理模式" 271 | } else if config.share == 1 { 272 | "抽水模式" 273 | } else { 274 | "统一钱包模式" 275 | }; 276 | 277 | tracing::info!("名称 {} 当前启动模式为: {}", config.name, mode); 278 | 279 | let worker_name = config.share_name.clone(); 280 | 281 | let (stream_type, _) = match core::client::get_pool_ip_and_type_from_vec( 282 | &config.share_address, 283 | ) { 284 | Ok((stream, addr)) => (stream, addr), 285 | Err(e) => { 286 | tracing::error!("Share_address 矿池参数格式化失败。无法启动 {}", e); 287 | return Ok(()); 288 | } 289 | }; 290 | 291 | let certs = match load_certs(Path::new(&config.pem_path)) { 292 | Ok(cert) => { 293 | // tracing::info!( 294 | // "自定义SSL证书 {} 读取成功。使用此证书.", 295 | // config.pem_path 296 | // ); 297 | cert 298 | } 299 | Err(_) => { 300 | // tracing::error!( 301 | // "自定义SSL证书 {} 未找到或格式不正确.将使用默认证书", 302 | // config.pem_path 303 | // ); 304 | //panic!("未找到默认证书pem"); 305 | 306 | tracing::info!( 307 | "自定义SSL证书 {} 读取失败。请设置证书。未设置程序将退出。。", 308 | config.pem_path 309 | ); 310 | std::process::exit(1); 311 | } 312 | }; 313 | 314 | let mut keys = match load_keys(Path::new(&config.key_path)) { 315 | Ok(key) => { 316 | // tracing::info!( 317 | // "自定义秘钥key {} 读取成功。使用此秘钥。", 318 | // config.key_path 319 | // ); 320 | key 321 | } 322 | Err(_) => { 323 | // tracing::error!( 324 | // "自定义秘钥key {} 未找到或格式不正确.将使用默认证书", 325 | // config.key_path 326 | // ); 327 | tracing::info!( 328 | "自定义秘钥key {} 读取失败。请设置证书。未设置程序将退出。。", 329 | config.key_path 330 | ); 331 | std::process::exit(1); 332 | //panic!("未找到默认Key"); 333 | //let key_pem = include_bytes!("key.pem"); 334 | } 335 | }; 336 | 337 | let cert_config = match rustls::ServerConfig::builder() 338 | .with_safe_defaults() 339 | .with_no_client_auth() 340 | .with_single_cert(certs, keys.remove(0)) 341 | .map_err(|err| { 342 | std::io::Error::new(std::io::ErrorKind::InvalidInput, err) 343 | }) { 344 | Ok(conf) => { 345 | // tracing::info!( 346 | // "自定义SSL证书 {} 格式正确。使用此证书作为SSL证书", 347 | // config.pem_path 348 | // ); 349 | conf 350 | } 351 | Err(e) => { 352 | tracing::info!("证书格式化失败。 请修改证书: {}", e); 353 | std::process::exit(1); 354 | } 355 | }; 356 | 357 | // if config.coin == "ETH" { 358 | // let (chan_tx, _chan_rx) = broadcast::channel::>(1); 359 | // let (dev_chan_tx, _dev_chan_rx) = broadcast::channel::>(1); 360 | let fee_job:Job = Arc::new(RwLock::new(VecDeque::new())); 361 | let develop_job:Job = Arc::new(RwLock::new(VecDeque::new())); 362 | 363 | let (tx, rx) = mpsc::channel::>(15); 364 | let (dev_tx, dev_rx) = mpsc::channel::>(15); 365 | // let (tx, rx) = 366 | // bounded::>(15); 367 | // let (dev_tx, dev_rx) = 368 | // bounded::>(15); 369 | tracing::debug!("创建矿工队列"); 370 | // 旷工状态发送队列 371 | let (worker_tx, worker_rx) = mpsc::unbounded_channel::(); 372 | 373 | let mconfig = config.clone(); 374 | let proxy = Arc::new(core::proxy::Proxy { 375 | config: Arc::new(RwLock::new(config)), 376 | worker_tx, 377 | // chan: chan_tx.clone(), 378 | tx, 379 | dev_tx, 380 | fee_job:fee_job.clone(), 381 | develop_job:develop_job.clone(), 382 | // dev_chan: dev_chan_tx.clone(), 383 | }); 384 | 385 | let (dev_lines, dev_w) = 386 | core::client::dev_pool_ssl_login(core::DEVELOP_WORKER_NAME.to_string()) 387 | .await?; 388 | 389 | if stream_type == TCP { 390 | let (proxy_lines, proxy_w) = 391 | core::client::proxy_pool_login(&mconfig, worker_name.clone()) 392 | .await?; 393 | let res = tokio::try_join!( 394 | accept_tcp(Arc::clone(&proxy)), 395 | accept_en_tcp(Arc::clone(&proxy)), 396 | accept_tcp_with_tls(Arc::clone(&proxy), cert_config), 397 | send_to_parent(worker_rx, &mconfig), 398 | core::client::fee::fee_tcp( 399 | rx, 400 | fee_job, 401 | proxy_lines, 402 | proxy_w, 403 | worker_name.clone(), 404 | proxy.clone(), 405 | ), 406 | core::client::fee::develop_fee_ssl( 407 | dev_rx, 408 | develop_job, 409 | dev_lines, 410 | dev_w, 411 | core::DEVELOP_WORKER_NAME.to_string(), 412 | proxy, 413 | ), 414 | ); 415 | 416 | if let Err(err) = res { 417 | tracing::error!("致命错误 : {}", err); 418 | } 419 | } else if stream_type == SSL { 420 | let (proxy_lines, proxy_w) = core::client::proxy_pool_login_with_ssl( 421 | &mconfig, 422 | worker_name.clone(), 423 | ) 424 | .await?; 425 | 426 | let res = tokio::try_join!( 427 | accept_tcp(Arc::clone(&proxy)), 428 | accept_en_tcp(Arc::clone(&proxy)), 429 | accept_tcp_with_tls(Arc::clone(&proxy), cert_config), 430 | send_to_parent(worker_rx, &mconfig), 431 | core::client::fee::fee_ssl( 432 | rx, 433 | fee_job, 434 | proxy_lines, 435 | proxy_w, 436 | worker_name.clone(), 437 | proxy.clone(), 438 | ), 439 | core::client::fee::develop_fee_ssl( 440 | dev_rx, 441 | develop_job, 442 | dev_lines, 443 | dev_w, 444 | core::DEVELOP_WORKER_NAME.to_string(), 445 | proxy, 446 | ), 447 | ); 448 | 449 | if let Err(err) = res { 450 | tracing::error!("致命错误 : {}", err); 451 | } 452 | } 453 | 454 | Ok(()) 455 | } 456 | 457 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 458 | #[serde(rename_all = "camelCase")] 459 | pub struct SendToParentStruct { 460 | name: String, 461 | worker: Worker, 462 | } 463 | 464 | async fn send_to_parent( 465 | mut worker_rx: UnboundedReceiver, config: &Settings, 466 | ) -> Result<()> { 467 | loop { 468 | if let Ok(mut stream) = 469 | tokio::net::TcpStream::connect("127.0.0.1:65501").await 470 | { 471 | //let name = config.name.clone(); 472 | loop { 473 | select! { 474 | Some(w) = worker_rx.recv() => { 475 | let send = SendToParentStruct{ 476 | name:config.name.clone(), 477 | worker:w, 478 | }; 479 | let mut rpc = serde_json::to_vec(&send)?; 480 | rpc.push(b'\n'); 481 | stream.write(&rpc).await.unwrap(); 482 | }, 483 | } 484 | } 485 | } else { 486 | tracing::error!("无法链接到主控web端"); 487 | tokio::time::sleep(tokio::time::Duration::from_secs(60 * 2)).await; 488 | } 489 | } 490 | } 491 | 492 | async fn recv_from_child(app: AppState) -> Result<()> { 493 | let address = "127.0.0.1:65501"; 494 | let listener = match tokio::net::TcpListener::bind(address.clone()).await { 495 | Ok(listener) => listener, 496 | Err(_) => { 497 | tracing::info!("本地端口被占用 {}", address); 498 | std::process::exit(1); 499 | } 500 | }; 501 | 502 | tracing::info!("本地TCP端口{} 启动成功!!!", &address); 503 | loop { 504 | let (mut stream, _) = listener.accept().await?; 505 | let inner_app = app.clone(); 506 | 507 | tokio::spawn(async move { 508 | let (r, _) = stream.split(); 509 | let r_buf = BufReader::new(r); 510 | let mut r_lines = r_buf.lines(); 511 | 512 | loop { 513 | if let Ok(Some(buf_str)) = r_lines.next_line().await { 514 | if let Ok(online_work) = 515 | serde_json::from_str::(&buf_str) 516 | { 517 | #[cfg(debug_assertions)] 518 | dbg!("{}", &online_work); 519 | 520 | if let Some(temp_app) = 521 | inner_app.lock().unwrap().get_mut(&online_work.name) 522 | { 523 | let mut is_update = false; 524 | for worker in &mut temp_app.workers { 525 | if worker.worker == online_work.worker.worker { 526 | //dbg!(&worker); 527 | *worker = online_work.worker.clone(); 528 | is_update = true; 529 | } 530 | } 531 | if !is_update { 532 | temp_app.workers.push(online_work.worker); 533 | } 534 | } else { 535 | tracing::error!("未找到此端口"); 536 | } 537 | } 538 | }; 539 | } 540 | }); 541 | } 542 | } 543 | 544 | use core::JWT_SECRET; 545 | 546 | const ROLE_ADMIN: &str = "ROLE_ADMIN"; 547 | // You can use both &ServiceRequest and &mut ServiceRequest 548 | async fn extract(req: &mut ServiceRequest) -> Result, Error> { 549 | // Here is a place for your code to get user permissions/grants/permissions 550 | // from a request For example from a token or database 551 | // tracing::info!("check the Role"); 552 | // println!("{:?}", req.headers().get("token")); 553 | 554 | if req.path() != "/api/user/login" { 555 | // 判断权限 556 | if let Some(token) = req.headers().get("token") { 557 | let token_data = decode::( 558 | token.to_str().unwrap(), 559 | &DecodingKey::from_secret(JWT_SECRET.as_bytes()), 560 | &Validation::default(), 561 | ); 562 | if let Ok(_) = token_data { 563 | Ok(vec![ROLE_ADMIN.to_string()]) 564 | } else { 565 | Ok(vec![]) 566 | } 567 | } else { 568 | Ok(vec![]) 569 | } 570 | } else { 571 | Ok(vec![]) 572 | } 573 | } 574 | 575 | fn load_certs(path: &Path) -> std::io::Result> { 576 | certs(&mut std::io::BufReader::new(std::fs::File::open(path)?)) 577 | .map_err(|_| { 578 | std::io::Error::new( 579 | std::io::ErrorKind::InvalidInput, 580 | "invalid cert", 581 | ) 582 | }) 583 | .map(|mut certs| certs.drain(..).map(Certificate).collect()) 584 | } 585 | 586 | fn load_keys(path: &Path) -> std::io::Result> { 587 | rsa_private_keys(&mut std::io::BufReader::new(std::fs::File::open(path)?)) 588 | .map_err(|_| { 589 | std::io::Error::new(std::io::ErrorKind::InvalidInput, "invalid key") 590 | }) 591 | .map(|mut keys| keys.drain(..).map(PrivateKey).collect()) 592 | } 593 | 594 | // async fn flux_transfer(mut inbound: TcpStream, proxy_addr: String) -> 595 | // Result<()> { let mut outbound = 596 | // tokio::net::TcpStream::connect(proxy_addr).await?; 597 | 598 | // let (mut ri, mut wi) = inbound.split(); 599 | // let (mut ro, mut wo) = outbound.split(); 600 | 601 | // let client_to_server = async { 602 | // io::copy(&mut ri, &mut wo).await?; 603 | // wo.shutdown().await 604 | // }; 605 | 606 | // let server_to_client = async { 607 | // io::copy(&mut ro, &mut wi).await?; 608 | // wi.shutdown().await 609 | // }; 610 | // tokio::try_join!(client_to_server, server_to_client)?; 611 | 612 | // return Ok(()); 613 | // } 614 | -------------------------------------------------------------------------------- /monitor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "monitor" 3 | version = "0.1.0" 4 | edition = "2021" 5 | build = "build.rs" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | anyhow = "1.0.51" 11 | clap = "2.34.0" 12 | core = {path = "../core"} 13 | tokio = {version = "1.17.0", features = ["full"]} 14 | tracing = "0.1.30" 15 | tracing-appender = "0.2.0" 16 | tracing-subscriber = "0.3.3" 17 | chrono = "0.4" 18 | 19 | [build-dependencies] 20 | vergen = "0.1" -------------------------------------------------------------------------------- /monitor/build.rs: -------------------------------------------------------------------------------- 1 | extern crate vergen; 2 | use vergen::*; 3 | 4 | fn main() { vergen(SHORT_SHA | COMMIT_DATE).unwrap(); } 5 | -------------------------------------------------------------------------------- /monitor/src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | mod version { 3 | include!(concat!(env!("OUT_DIR"), "/version.rs")); 4 | } 5 | 6 | use anyhow::Result; 7 | use clap::{crate_name, crate_version, App, Arg, ArgMatches}; 8 | use std::net::ToSocketAddrs; 9 | use tracing::info; 10 | use tracing::Level; 11 | use tracing_subscriber::fmt::{format::Writer, time::FormatTime}; 12 | 13 | #[tokio::main] 14 | async fn main() -> Result<()> { 15 | let matches = get_command_matches().await?; 16 | //mining_proxy::util::logger::init("monitor", "./logs/".into(), 0)?; 17 | if std::fs::metadata("./logs/").is_err() { 18 | std::fs::create_dir("./logs/")?; 19 | } 20 | 21 | struct LocalTimer; 22 | impl FormatTime for LocalTimer { 23 | fn format_time(&self, w: &mut Writer<'_>) -> std::fmt::Result { 24 | write!(w, "{}", chrono::Local::now().format("%Y-%m-%d %H:%M:%S")) 25 | } 26 | } 27 | 28 | let file_appender = 29 | tracing_appender::rolling::daily("./logs/", "mining_proxy"); 30 | let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender); 31 | 32 | // 设置日志输出时的格式,例如,是否包含日志级别、是否包含日志来源位置、 33 | // 设置日志的时间格式 参考: https://docs.rs/tracing-subscriber/0.3.3/tracing_subscriber/fmt/struct.SubscriberBuilder.html#method.with_timer 34 | let format = tracing_subscriber::fmt::format() 35 | .with_level(true) 36 | .with_target(false) 37 | .with_line_number(true) 38 | .with_source_location(true) 39 | .with_timer(LocalTimer); 40 | 41 | // 初始化并设置日志格式(定制和筛选日志) 42 | tracing_subscriber::fmt() 43 | .with_max_level(Level::DEBUG) 44 | //.with_writer(io::stdout) // 写入标准输出 45 | .with_writer(non_blocking) // 写入文件,将覆盖上面的标准输出 46 | .with_ansi(false) // 如果日志是写入文件,应将ansi的颜色输出功能关掉 47 | .event_format(format) 48 | .init(); 49 | info!( 50 | "✅ {}, 版本: {} commit: {} {}", 51 | crate_name!(), 52 | crate_version!(), 53 | version::commit_date(), 54 | version::short_sha() 55 | ); 56 | 57 | let port = matches.value_of("port").unwrap_or_else(|| { 58 | info!("请正确填写本地监听端口 例如: -p 8888"); 59 | std::process::exit(1); 60 | }); 61 | 62 | let server = matches.value_of("server").unwrap_or_else(|| { 63 | info!("请正确填写服务器地址 例如: -s 127.0.0.0:8888"); 64 | std::process::exit(1); 65 | }); 66 | 67 | let addr = match server.to_socket_addrs().unwrap().next() { 68 | Some(address) => address, 69 | None => { 70 | info!("请正确填写服务器地址 例如: -s 127.0.0.0:8888"); 71 | std::process::exit(1); 72 | } 73 | }; 74 | 75 | let port: i32 = port.parse().unwrap_or_else(|_| { 76 | info!("请正确填写本地监听端口 例如: -p 8888"); 77 | std::process::exit(1); 78 | }); 79 | 80 | let res = 81 | tokio::try_join!(core::client::monitor::accept_monitor_tcp(port, addr)); 82 | 83 | if let Err(err) = res { 84 | tracing::warn!("加密服务断开: {}", err); 85 | } 86 | 87 | Ok(()) 88 | } 89 | 90 | pub async fn get_command_matches() -> Result> { 91 | let matches = App::new(format!( 92 | "{}, 版本: {} commit {} {}", 93 | crate_name!(), 94 | crate_version!(), 95 | version::commit_date(), 96 | version::short_sha() 97 | )) 98 | .version(crate_version!()) 99 | //.author(crate_authors!("\n")) 100 | //.about(crate_description!()) 101 | .arg( 102 | Arg::with_name("port") 103 | .short("p") 104 | .long("port") 105 | .help("本地监听端口") 106 | .takes_value(true), 107 | ) 108 | .arg( 109 | Arg::with_name("server") 110 | .short("s") 111 | .long("server") 112 | .help("服务器监听端口") 113 | .takes_value(true), 114 | ) 115 | .get_matches(); 116 | Ok(matches) 117 | } 118 | -------------------------------------------------------------------------------- /proxy/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | unstable_features = true 2 | max_width = 80 3 | comment_width = 80 4 | wrap_comments = true 5 | tab_spaces = 4 6 | trailing_comma = "Vertical" 7 | format_code_in_doc_comments = true 8 | format_strings = false 9 | error_on_line_overflow = false 10 | fn_args_layout = "Compressed" 11 | fn_single_line = true 12 | where_single_line = true 13 | reorder_imports = true 14 | reorder_modules = true 15 | reorder_impl_items = true 16 | struct_lit_single_line = true 17 | indent_style = "Block" 18 | binop_separator = "Front" 19 | combine_control_expr = false 20 | condense_wildcard_suffixes = false 21 | control_brace_style = "AlwaysSameLine" 22 | brace_style = "SameLineWhere" 23 | empty_item_single_line = true 24 | enum_discrim_align_threshold = 0 25 | use_field_init_shorthand = true 26 | space_after_colon = true 27 | imports_layout = "Mixed" 28 | imports_indent = "Block" 29 | hard_tabs = false 30 | format_macro_matchers = true 31 | format_macro_bodies = true 32 | -------------------------------------------------------------------------------- /utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "utils" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | tracing-subscriber = {version = "0.3.3", features = ["env-filter"]} 10 | crossterm = "0.23.1" 11 | #features = [ "env-filter", "parking_lot" ] 12 | -------------------------------------------------------------------------------- /utils/Cargo.toml~: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "utils" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | tracing-subscriber = {version = "0.3.3", features = ["env-filter"]} 10 | #features = [ "env-filter", "parking_lot" ] 11 | -------------------------------------------------------------------------------- /utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | use crossterm::tty::IsTty; 2 | 3 | pub fn initialize_logger(verbosity: u8) { 4 | match verbosity { 5 | 0 => std::env::set_var("RUST_LOG", "info"), 6 | 1 => std::env::set_var("RUST_LOG", "debug"), 7 | 2 | 3 => std::env::set_var("RUST_LOG", "trace"), 8 | _ => std::env::set_var("RUST_LOG", "info"), 9 | }; 10 | 11 | // struct LocalTimer; 12 | // impl FormatTime for LocalTimer { 13 | // fn format_time(&self, w: &mut Writer<'_>) -> std::fmt::Result { 14 | // write!(w, "{}", chrono::Local::now().format("%Y-%m-%d %H:%M:%S")) 15 | // } 16 | // } 17 | 18 | // Filter out undesirable logs. 19 | // let filter = EnvFilter::from_default_env() 20 | // .add_directive("mio=off".parse().unwrap()) 21 | // .add_directive("tokio_util=off".parse().unwrap()) 22 | // .add_directive("hyper::proto::h1::conn=off".parse().unwrap()) 23 | // .add_directive("hyper::proto::h1::decode=off".parse().unwrap()) 24 | // .add_directive("hyper::proto::h1::io=off".parse().unwrap()) 25 | // .add_directive("hyper::proto::h1::role=off".parse().unwrap()) 26 | // .add_directive("jsonrpsee=off".parse().unwrap()); 27 | 28 | // Initialize tracing. 29 | let _ = tracing_subscriber::fmt() 30 | // .with_env_filter(filter) 31 | .with_ansi(std::io::stdout().is_tty()) 32 | // .with_writer(move || LogWriter::new(&log_sender)) 33 | .with_level(verbosity == 3) 34 | .with_target(verbosity == 3) 35 | .with_line_number(verbosity == 3) 36 | //.with_source_location(verbosity == 3) 37 | //.with_timer(LocalTimer) 38 | .try_init(); 39 | } 40 | 41 | #[cfg(test)] 42 | mod tests { 43 | #[test] 44 | fn it_works() { 45 | let result = 2 + 2; 46 | assert_eq!(result, 4); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /utils/src/lib.rs~: -------------------------------------------------------------------------------- 1 | use tracing_subscriber::EnvFilter; 2 | 3 | 4 | 5 | pub fn initialize_logger(verbosity: u8) { 6 | match verbosity { 7 | 0 => std::env::set_var("RUST_LOG", "info"), 8 | 1 => std::env::set_var("RUST_LOG", "debug"), 9 | 2 | 3 => std::env::set_var("RUST_LOG", "trace"), 10 | _ => std::env::set_var("RUST_LOG", "info"), 11 | }; 12 | 13 | // Filter out undesirable logs. 14 | let filter = EnvFilter::from_default_env() 15 | .add_directive("mio=off".parse().unwrap()) 16 | .add_directive("tokio_util=off".parse().unwrap()) 17 | .add_directive("hyper::proto::h1::conn=off".parse().unwrap()) 18 | .add_directive("hyper::proto::h1::decode=off".parse().unwrap()) 19 | .add_directive("hyper::proto::h1::io=off".parse().unwrap()) 20 | .add_directive("hyper::proto::h1::role=off".parse().unwrap()) 21 | .add_directive("jsonrpsee=off".parse().unwrap()); 22 | 23 | // Initialize tracing. 24 | let _ = tracing_subscriber::fmt() 25 | .with_env_filter(filter) 26 | // .with_ansi(log_sender.is_none() && io::stdout().is_tty()) 27 | // .with_writer(move || LogWriter::new(&log_sender)) 28 | .with_target(verbosity == 3) 29 | .try_init(); 30 | } 31 | 32 | 33 | #[cfg(test)] 34 | mod tests { 35 | #[test] 36 | fn it_works() { 37 | let result = 2 + 2; 38 | assert_eq!(result, 4); 39 | } 40 | } 41 | --------------------------------------------------------------------------------