one commit contains all impl
This commit is contained in:
commit
e871ec2b29
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
1634
Cargo.lock
generated
Normal file
1634
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
7
Cargo.toml
Normal file
7
Cargo.toml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"libvanity",
|
||||||
|
"vanity",
|
||||||
|
"vanityd",
|
||||||
|
"vanity-worker",
|
||||||
|
]
|
13
libvanity/Cargo.toml
Normal file
13
libvanity/Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "libvanity"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tonic = "0.8"
|
||||||
|
prost = "0.11"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tonic-build = "0.8"
|
11
libvanity/build.rs
Normal file
11
libvanity/build.rs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fn main() -> std::io::Result<()> {
|
||||||
|
let descriptor_path =
|
||||||
|
std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()).join("descriptor.bin");
|
||||||
|
tonic_build::configure()
|
||||||
|
.build_client(true)
|
||||||
|
.build_server(true)
|
||||||
|
.file_descriptor_set_path(descriptor_path)
|
||||||
|
.compile(&["vanity.proto", "worker.proto"], &["protos"])?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
15
libvanity/protos/vanity.proto
Normal file
15
libvanity/protos/vanity.proto
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package vanity;
|
||||||
|
|
||||||
|
message VanityRequest {
|
||||||
|
string pattern = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message VanityResponse {
|
||||||
|
string result = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
service VanityService {
|
||||||
|
rpc RequestVanity(VanityRequest) returns (VanityResponse);
|
||||||
|
}
|
26
libvanity/protos/worker.proto
Normal file
26
libvanity/protos/worker.proto
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package worker;
|
||||||
|
|
||||||
|
import "vanity.proto";
|
||||||
|
|
||||||
|
message NewRequest {
|
||||||
|
uint64 id = 1;
|
||||||
|
string pattern = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Request {
|
||||||
|
oneof NewOrDone {
|
||||||
|
NewRequest new = 1;
|
||||||
|
uint64 done = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message Result {
|
||||||
|
uint64 id = 1;
|
||||||
|
string result = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
service Worker {
|
||||||
|
rpc Poll(stream Result) returns (stream Request);
|
||||||
|
}
|
9
libvanity/src/lib.rs
Normal file
9
libvanity/src/lib.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
pub mod vanity {
|
||||||
|
tonic::include_proto!("vanity");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod worker {
|
||||||
|
tonic::include_proto!("worker");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const FILE_DESCRIPTOR_SET: &[u8] = tonic::include_file_descriptor_set!("descriptor");
|
24
vanity-worker/Cargo.toml
Normal file
24
vanity-worker/Cargo.toml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
[package]
|
||||||
|
name = "vanity-worker"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "4", features = ["derive", "env", "cargo"] }
|
||||||
|
|
||||||
|
libvanity = {path = "../libvanity"}
|
||||||
|
|
||||||
|
x25519-dalek = "1.2.0"
|
||||||
|
rand_core = {version = "0.5.1", features = ["getrandom"]}
|
||||||
|
|
||||||
|
tonic = "0.8"
|
||||||
|
tokio = {version = "1", features = ["full"]}
|
||||||
|
tokio-stream = { version = "0.1.11", features = ["net"] }
|
||||||
|
|
||||||
|
base64 = "0.13.0"
|
||||||
|
regex = "1.6.0"
|
||||||
|
tracing-subscriber = { version = "0.3.16", features = ["json"] }
|
||||||
|
tracing = { version = "0.1.37", features = ["log"] }
|
||||||
|
futures = "0.3.25"
|
76
vanity-worker/src/main.rs
Normal file
76
vanity-worker/src/main.rs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use rand_core::OsRng;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
use x25519_dalek::{PublicKey, StaticSecret};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let client =
|
||||||
|
libvanity::worker::worker_client::WorkerClient::connect("grpc://127.0.0.1:8877").await?;
|
||||||
|
|
||||||
|
let (tx, rx) = tokio::sync::mpsc::channel(128);
|
||||||
|
|
||||||
|
let requests = std::sync::Arc::new(RwLock::new(HashMap::new()));
|
||||||
|
|
||||||
|
let mut another = client.clone();
|
||||||
|
let requests_writer = requests.clone();
|
||||||
|
let pull_thread = tokio::spawn(async move {
|
||||||
|
use libvanity::worker::request::NewOrDone::{Done, New};
|
||||||
|
|
||||||
|
let mut tasks = another
|
||||||
|
.poll(tokio_stream::wrappers::ReceiverStream::new(rx))
|
||||||
|
.await
|
||||||
|
.expect("msg")
|
||||||
|
.into_inner();
|
||||||
|
let requests = requests_writer;
|
||||||
|
while let Some(msg) = tasks.message().await.unwrap() {
|
||||||
|
if let Some(new_or_done) = msg.new_or_done {
|
||||||
|
match new_or_done {
|
||||||
|
New(request) => {
|
||||||
|
let mut writer = requests.write().await;
|
||||||
|
writer.insert(request.id, request.pattern);
|
||||||
|
}
|
||||||
|
Done(response) => {
|
||||||
|
let mut writer = requests.write().await;
|
||||||
|
writer.remove(&response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let worker_threads = Vec::from_iter(1..10).into_iter().map(|id| {
|
||||||
|
let requests = requests.clone();
|
||||||
|
let tx = tx.clone();
|
||||||
|
let _id = id.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
loop {
|
||||||
|
let requests = requests.read().await.clone();
|
||||||
|
if requests.is_empty() {
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let private = StaticSecret::new(&mut OsRng);
|
||||||
|
let public = PublicKey::from(&private);
|
||||||
|
|
||||||
|
for (id, pattern) in requests.iter() {
|
||||||
|
let matcher = regex::Regex::new(pattern).unwrap();
|
||||||
|
let matcher = matcher.clone();
|
||||||
|
let public_b64 = base64::encode(public.as_bytes());
|
||||||
|
if matcher.is_match(&public_b64) {
|
||||||
|
let mut result = libvanity::worker::Result::default();
|
||||||
|
result.id = id.clone();
|
||||||
|
result.result = base64::encode(private.to_bytes());
|
||||||
|
tx.send(result).await.expect("no error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let _ = tokio::join!(futures::future::join_all(worker_threads), pull_thread);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
16
vanity/Cargo.toml
Normal file
16
vanity/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "vanity"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "4", features = ["derive", "env", "cargo"] }
|
||||||
|
|
||||||
|
tonic = "0.8"
|
||||||
|
tokio = {version = "1", features = ["full"]}
|
||||||
|
tokio-stream = { version = "0.1.11", features = ["net"] }
|
||||||
|
|
||||||
|
|
||||||
|
libvanity = {path = "../libvanity"}
|
30
vanity/src/main.rs
Normal file
30
vanity/src/main.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use clap::Parser;
|
||||||
|
use libvanity::vanity::VanityRequest;
|
||||||
|
|
||||||
|
#[derive(clap::Parser, Debug)]
|
||||||
|
#[clap(
|
||||||
|
about = "ask some worker to generate a private key where public key matches some regular expression"
|
||||||
|
)]
|
||||||
|
struct Args {
|
||||||
|
#[clap(short = 'c', long = "connect", help = "server address to connect to")]
|
||||||
|
endpoint: String,
|
||||||
|
|
||||||
|
#[clap(help = "pattern of public key")]
|
||||||
|
pattern: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
let mut client =
|
||||||
|
libvanity::vanity::vanity_service_client::VanityServiceClient::connect(args.endpoint)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut request = VanityRequest::default();
|
||||||
|
request.pattern = args.pattern;
|
||||||
|
let response = client.request_vanity(request).await?.into_inner();
|
||||||
|
println!("{}", response.result);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
21
vanityd/Cargo.toml
Normal file
21
vanityd/Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[package]
|
||||||
|
name = "vanityd"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "4", features = ["derive", "env", "cargo"] }
|
||||||
|
|
||||||
|
tonic = "0.8"
|
||||||
|
tokio = {version = "1", features = ["full"]}
|
||||||
|
tokio-stream = { version = "0.1.11", features = ["net"] }
|
||||||
|
|
||||||
|
futures = "0.3.25"
|
||||||
|
|
||||||
|
redis = { version = "0.22", features = ["aio", "tokio-comp"] }
|
||||||
|
|
||||||
|
libvanity = {path = "../libvanity"}
|
||||||
|
tracing-subscriber = { version = "0.3.16", features = ["json"] }
|
||||||
|
tracing = { version = "0.1.37", features = ["log"] }
|
1
vanityd/src/consts.rs
Normal file
1
vanityd/src/consts.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub(crate) const REDIS_PUBSUB_PATTERN_RESULT: &str = "vanity:result:";
|
31
vanityd/src/main.rs
Normal file
31
vanityd/src/main.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
pub(crate) mod consts;
|
||||||
|
pub(crate) mod server;
|
||||||
|
pub(crate) mod worker;
|
||||||
|
|
||||||
|
use std::net::ToSocketAddrs;
|
||||||
|
|
||||||
|
use libvanity::{
|
||||||
|
vanity::vanity_service_server::VanityServiceServer, worker::worker_server::WorkerServer,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub use libvanity;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
tracing_subscriber::FmtSubscriber::builder()
|
||||||
|
.with_max_level(tracing::Level::DEBUG)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
let redis = redis::Client::open("redis://127.0.0.1:6379")?;
|
||||||
|
|
||||||
|
let vanity_server = server::VanityService::new(redis.clone());
|
||||||
|
let worker_server = worker::WorkerService::new(redis.clone());
|
||||||
|
|
||||||
|
tonic::transport::Server::builder()
|
||||||
|
.add_service(VanityServiceServer::new(vanity_server))
|
||||||
|
.add_service(WorkerServer::new(worker_server))
|
||||||
|
.serve("127.0.0.1:8877".to_socket_addrs()?.next().unwrap())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
56
vanityd/src/server.rs
Normal file
56
vanityd/src/server.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
use libvanity::vanity::{vanity_service_server as service_server, VanityRequest, VanityResponse};
|
||||||
|
use tokio_stream::StreamExt;
|
||||||
|
use tonic::async_trait;
|
||||||
|
|
||||||
|
use crate::consts::*;
|
||||||
|
|
||||||
|
use redis::AsyncCommands;
|
||||||
|
|
||||||
|
pub(crate) struct VanityService {
|
||||||
|
counter: std::sync::atomic::AtomicU64,
|
||||||
|
redis: redis::Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VanityService {
|
||||||
|
pub fn new(redis: redis::Client) -> Self {
|
||||||
|
Self {
|
||||||
|
redis,
|
||||||
|
counter: std::sync::atomic::AtomicU64::new(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl service_server::VanityService for VanityService {
|
||||||
|
async fn request_vanity(
|
||||||
|
&self,
|
||||||
|
req: tonic::Request<VanityRequest>,
|
||||||
|
) -> Result<tonic::Response<VanityResponse>, tonic::Status> {
|
||||||
|
let id = self
|
||||||
|
.counter
|
||||||
|
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
let pattern = req.into_inner().pattern;
|
||||||
|
|
||||||
|
let mut redis = self.redis.get_async_connection().await.expect("msg");
|
||||||
|
redis
|
||||||
|
.publish::<&str, String, ()>("vanity:new", format!("{}|{}", id, pattern))
|
||||||
|
.await
|
||||||
|
.expect("should not fail");
|
||||||
|
|
||||||
|
let mut pubsub = redis.into_pubsub();
|
||||||
|
pubsub
|
||||||
|
.subscribe(format!("{}:{}", REDIS_PUBSUB_PATTERN_RESULT, id))
|
||||||
|
.await
|
||||||
|
.expect("msg");
|
||||||
|
|
||||||
|
let mut msgs = pubsub.on_message();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if let Some(msg) = msgs.next().await {
|
||||||
|
let mut response = VanityResponse::default();
|
||||||
|
response.result = String::from_utf8_lossy(msg.get_payload_bytes()).to_string();
|
||||||
|
return Ok(tonic::Response::new(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
146
vanityd/src/worker.rs
Normal file
146
vanityd/src/worker.rs
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
use futures::StreamExt;
|
||||||
|
use tonic::async_trait;
|
||||||
|
|
||||||
|
use redis::AsyncCommands;
|
||||||
|
|
||||||
|
use crate::consts::*;
|
||||||
|
|
||||||
|
pub struct WorkerService {
|
||||||
|
redis: redis::Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WorkerService {
|
||||||
|
pub fn new(r: redis::Client) -> Self {
|
||||||
|
Self { redis: r }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl libvanity::worker::worker_server::Worker for WorkerService {
|
||||||
|
type PollStream =
|
||||||
|
tokio_stream::wrappers::ReceiverStream<tonic::Result<libvanity::worker::Request>>;
|
||||||
|
async fn poll(
|
||||||
|
&self,
|
||||||
|
result: tonic::Request<tonic::Streaming<libvanity::worker::Result>>,
|
||||||
|
) -> Result<tonic::Response<Self::PollStream>, tonic::Status> {
|
||||||
|
tracing::trace!("entered polling method");
|
||||||
|
let (tx, rx) = tokio::sync::mpsc::channel(1);
|
||||||
|
|
||||||
|
tracing::debug!("getting redis connection");
|
||||||
|
let redis_new_request = match self.redis.get_async_connection().await {
|
||||||
|
Ok(conn) => conn,
|
||||||
|
Err(err) => return Err(tonic::Status::unavailable(err.to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
// handle new requests or results
|
||||||
|
let new_tx = tx.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
tracing::debug!("entering puller closure");
|
||||||
|
let tx = new_tx.clone();
|
||||||
|
|
||||||
|
let mut pubsub = redis_new_request.into_pubsub();
|
||||||
|
match pubsub.subscribe("vanity:new").await {
|
||||||
|
Err(err) => {
|
||||||
|
tx.send(Err(tonic::Status::unavailable(err.to_string())))
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
match pubsub
|
||||||
|
.psubscribe(format!("{}*", REDIS_PUBSUB_PATTERN_RESULT))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Err(err) => {
|
||||||
|
tx.send(Err(tonic::Status::unavailable(err.to_string())))
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
let mut msgs = pubsub.on_message();
|
||||||
|
while let Some(msg) = msgs.next().await {
|
||||||
|
let mut request = libvanity::worker::Request::default();
|
||||||
|
if let Ok(pattern) = msg.get_channel::<String>() {
|
||||||
|
// result,
|
||||||
|
tracing::info!("pattern = {}", pattern);
|
||||||
|
if let Some(key) = pattern.strip_prefix(&REDIS_PUBSUB_PATTERN_RESULT) {
|
||||||
|
tracing::info!("key = {}", key);
|
||||||
|
if let Ok(id) = u64::from_str_radix(key.trim_start_matches(":"), 10) {
|
||||||
|
request.new_or_done =
|
||||||
|
Some(libvanity::worker::request::NewOrDone::Done(id));
|
||||||
|
tracing::info!("done -> {}", id.clone());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// new pattern request
|
||||||
|
let payload: String = msg.get_payload().unwrap();
|
||||||
|
if let Some((idstr, pattern)) = payload.rsplit_once('|') {
|
||||||
|
if let Ok(id) = u64::from_str_radix(idstr, 10) {
|
||||||
|
let mut new_request = libvanity::worker::NewRequest::default();
|
||||||
|
new_request.id = id.clone();
|
||||||
|
new_request.pattern = String::from(pattern);
|
||||||
|
request.new_or_done =
|
||||||
|
Some(libvanity::worker::request::NewOrDone::New(
|
||||||
|
// New
|
||||||
|
new_request,
|
||||||
|
));
|
||||||
|
tracing::info!("new -> {}", id.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if request.new_or_done == None {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::info!("sending {:?}", request.new_or_done);
|
||||||
|
if let Err(err) = tx.send(Ok(request)).await {
|
||||||
|
tx.send(Err(tonic::Status::from_error(Box::new(err))))
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut in_stream = result.into_inner();
|
||||||
|
let redis = self.redis.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
tracing::debug!("entering trans closure");
|
||||||
|
let redis = redis.clone();
|
||||||
|
while let Some(result) = in_stream.next().await {
|
||||||
|
match result {
|
||||||
|
Ok(result) => {
|
||||||
|
let mut redis = match redis.get_async_connection().await {
|
||||||
|
Ok(redis) => redis,
|
||||||
|
Err(err) => {
|
||||||
|
tx.send(Err(tonic::Status::unavailable(err.to_string())))
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
tracing::info!("request done: {}", result.id);
|
||||||
|
let _: u64 = redis
|
||||||
|
.publish(
|
||||||
|
format!("{}:{}", REDIS_PUBSUB_PATTERN_RESULT, result.id),
|
||||||
|
result.result,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
match tx.send(Err(err)).await {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(_err) => break, // response was droped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tracing::debug!("returned a response stream.( working in background )");
|
||||||
|
Ok(tonic::Response::new(
|
||||||
|
tokio_stream::wrappers::ReceiverStream::new(rx),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user