2023-07-01 12:53:46 +00:00
|
|
|
use std::{
|
|
|
|
sync::{Arc, RwLock},
|
|
|
|
collections::HashMap,
|
|
|
|
};
|
|
|
|
|
|
|
|
use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto};
|
|
|
|
use schnorr_signatures::SchnorrSignature;
|
|
|
|
|
|
|
|
use serai_primitives::NetworkId;
|
|
|
|
|
|
|
|
use jsonrpsee::{RpcModule, server::ServerBuilder};
|
|
|
|
|
|
|
|
mod messages;
|
|
|
|
use messages::*;
|
|
|
|
|
|
|
|
mod queue;
|
|
|
|
use queue::Queue;
|
|
|
|
|
2023-07-26 01:39:29 +00:00
|
|
|
type Db = serai_db::RocksDB;
|
2023-07-17 04:20:10 +00:00
|
|
|
|
2023-07-01 12:53:46 +00:00
|
|
|
lazy_static::lazy_static! {
|
|
|
|
static ref KEYS: Arc<RwLock<HashMap<Service, <Ristretto as Ciphersuite>::G>>> =
|
|
|
|
Arc::new(RwLock::new(HashMap::new()));
|
2023-07-17 04:20:10 +00:00
|
|
|
static ref QUEUES: Arc<RwLock<HashMap<Service, RwLock<Queue<Db>>>>> =
|
2023-07-01 12:53:46 +00:00
|
|
|
Arc::new(RwLock::new(HashMap::new()));
|
|
|
|
}
|
|
|
|
|
|
|
|
// queue RPC method
|
2023-07-17 00:53:58 +00:00
|
|
|
/*
|
|
|
|
Queues a message to be delivered from a processor to a coordinator, or vice versa.
|
|
|
|
|
|
|
|
Messages are authenticated to be coming from the claimed service. Recipient services SHOULD
|
|
|
|
independently verify signatures.
|
|
|
|
|
|
|
|
The metadata specifies an intent. Only one message, for a specified intent, will be delivered.
|
|
|
|
This allows services to safely send messages multiple times without them being delivered multiple
|
|
|
|
times.
|
|
|
|
|
|
|
|
The message will be ordered by this service, with the order having no guarantees other than
|
|
|
|
successful ordering by the time this call returns.
|
|
|
|
*/
|
2023-07-01 12:53:46 +00:00
|
|
|
fn queue_message(meta: Metadata, msg: Vec<u8>, sig: SchnorrSignature<Ristretto>) {
|
|
|
|
{
|
|
|
|
let from = (*KEYS).read().unwrap()[&meta.from];
|
2023-07-17 21:40:34 +00:00
|
|
|
assert!(
|
|
|
|
sig.verify(from, message_challenge(meta.from, from, meta.to, &meta.intent, &msg, sig.R))
|
|
|
|
);
|
2023-07-01 12:53:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Assert one, and only one of these, is the coordinator
|
|
|
|
assert!(matches!(meta.from, Service::Coordinator) ^ matches!(meta.to, Service::Coordinator));
|
|
|
|
|
2023-07-17 00:53:58 +00:00
|
|
|
// TODO: Verify (from, intent) hasn't been prior seen
|
2023-07-01 12:53:46 +00:00
|
|
|
|
|
|
|
// Queue it
|
2023-07-20 22:53:03 +00:00
|
|
|
let id = (*QUEUES).read().unwrap()[&meta.to].write().unwrap().queue_message(QueuedMessage {
|
2023-07-01 12:53:46 +00:00
|
|
|
from: meta.from,
|
2023-07-17 19:49:15 +00:00
|
|
|
// Temporary value which queue_message will override
|
|
|
|
id: u64::MAX,
|
2023-07-01 12:53:46 +00:00
|
|
|
msg,
|
|
|
|
sig: sig.serialize(),
|
|
|
|
});
|
2023-07-20 22:53:03 +00:00
|
|
|
|
|
|
|
log::info!("Queued message from {:?}. It is {:?} {id}", meta.from, meta.to);
|
2023-07-01 12:53:46 +00:00
|
|
|
}
|
|
|
|
|
2023-07-17 00:53:58 +00:00
|
|
|
// next RPC method
|
|
|
|
/*
|
|
|
|
Gets the next message in queue for this service.
|
|
|
|
|
|
|
|
This is not authenticated due to the fact every nonce would have to be saved to prevent replays,
|
|
|
|
or a challenge-response protocol implemented. Neither are worth doing when there should be no
|
|
|
|
sensitive data on this server.
|
|
|
|
|
|
|
|
The expected index is used to ensure a service didn't fall out of sync with this service. It
|
|
|
|
should always be either the next message's ID or *TODO*.
|
|
|
|
*/
|
2023-07-17 00:38:13 +00:00
|
|
|
fn get_next_message(service: Service, _expected: u64) -> Option<QueuedMessage> {
|
2023-07-01 12:53:46 +00:00
|
|
|
// TODO: Verify the expected next message ID matches
|
|
|
|
|
|
|
|
let queue_outer = (*QUEUES).read().unwrap();
|
|
|
|
let queue = queue_outer[&service].read().unwrap();
|
|
|
|
let next = queue.last_acknowledged().map(|i| i + 1).unwrap_or(0);
|
|
|
|
queue.get_message(next)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ack RPC method
|
2023-07-17 00:53:58 +00:00
|
|
|
/*
|
|
|
|
Acknowledges a message as received and handled, meaning it'll no longer be returned as the next
|
|
|
|
message.
|
|
|
|
*/
|
2023-07-17 21:40:34 +00:00
|
|
|
fn ack_message(service: Service, id: u64, sig: SchnorrSignature<Ristretto>) {
|
|
|
|
{
|
|
|
|
let from = (*KEYS).read().unwrap()[&service];
|
|
|
|
assert!(sig.verify(from, ack_challenge(service, from, id, sig.R)));
|
|
|
|
}
|
2023-07-01 12:53:46 +00:00
|
|
|
|
|
|
|
// Is it:
|
|
|
|
// The acknowledged message should be > last acknowledged OR
|
|
|
|
// The acknowledged message should be >=
|
|
|
|
// It's the first if we save messages as acknowledged before acknowledging them
|
|
|
|
// It's the second if we acknowledge messages before saving them as acknowledged
|
|
|
|
// TODO: Check only a proper message is being acked
|
|
|
|
|
2023-07-20 22:53:03 +00:00
|
|
|
log::info!("{:?} is acknowledging {}", service, id);
|
|
|
|
|
2023-07-01 12:53:46 +00:00
|
|
|
(*QUEUES).read().unwrap()[&service].write().unwrap().ack_message(id)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[tokio::main]
|
|
|
|
async fn main() {
|
2023-07-20 22:53:03 +00:00
|
|
|
if std::env::var("RUST_LOG").is_err() {
|
2023-07-21 18:00:03 +00:00
|
|
|
std::env::set_var("RUST_LOG", serai_env::var("RUST_LOG").unwrap_or_else(|| "info".to_string()));
|
2023-07-20 22:53:03 +00:00
|
|
|
}
|
|
|
|
env_logger::init();
|
|
|
|
|
|
|
|
log::info!("Starting message-queue service...");
|
|
|
|
|
2023-07-01 12:53:46 +00:00
|
|
|
// Open the DB
|
2023-07-26 01:39:29 +00:00
|
|
|
let db = serai_db::new_rocksdb(&serai_env::var("DB_PATH").expect("path to DB wasn't specified"));
|
2023-07-01 12:53:46 +00:00
|
|
|
|
|
|
|
let read_key = |str| {
|
2023-07-17 06:01:31 +00:00
|
|
|
let key = serai_env::var(str)?;
|
2023-07-01 12:53:46 +00:00
|
|
|
|
|
|
|
let mut repr = <<Ristretto as Ciphersuite>::G as GroupEncoding>::Repr::default();
|
|
|
|
repr.as_mut().copy_from_slice(&hex::decode(key).unwrap());
|
|
|
|
Some(<Ristretto as Ciphersuite>::G::from_bytes(&repr).unwrap())
|
|
|
|
};
|
|
|
|
|
|
|
|
let register_service = |service, key| {
|
|
|
|
(*KEYS).write().unwrap().insert(service, key);
|
|
|
|
(*QUEUES).write().unwrap().insert(service, RwLock::new(Queue(db.clone(), service)));
|
|
|
|
};
|
|
|
|
|
|
|
|
// Make queues for each NetworkId, other than Serai
|
|
|
|
for network in [NetworkId::Bitcoin, NetworkId::Ethereum, NetworkId::Monero] {
|
|
|
|
// Use a match so we error if the list of NetworkIds changes
|
|
|
|
let Some(key) = read_key(match network {
|
|
|
|
NetworkId::Serai => unreachable!(),
|
|
|
|
NetworkId::Bitcoin => "BITCOIN_KEY",
|
|
|
|
NetworkId::Ethereum => "ETHEREUM_KEY",
|
|
|
|
NetworkId::Monero => "MONERO_KEY",
|
2023-08-01 04:47:36 +00:00
|
|
|
}) else {
|
|
|
|
continue;
|
|
|
|
};
|
2023-07-01 12:53:46 +00:00
|
|
|
|
|
|
|
register_service(Service::Processor(network), key);
|
|
|
|
}
|
|
|
|
|
|
|
|
// And the coordinator's
|
|
|
|
register_service(Service::Coordinator, read_key("COORDINATOR_KEY").unwrap());
|
|
|
|
|
|
|
|
// Start server
|
|
|
|
let builder = ServerBuilder::new();
|
2023-07-17 19:49:15 +00:00
|
|
|
// TODO: Add middleware to check some key is present in the header, making this an authed
|
|
|
|
// connection
|
2023-07-01 12:53:46 +00:00
|
|
|
// TODO: Set max request/response size
|
2023-07-17 04:20:10 +00:00
|
|
|
// 5132 ^ ((b'M' << 8) | b'Q')
|
|
|
|
let listen_on: &[std::net::SocketAddr] = &["0.0.0.0:2287".parse().unwrap()];
|
2023-07-01 12:53:46 +00:00
|
|
|
let server = builder.build(listen_on).await.unwrap();
|
|
|
|
|
|
|
|
let mut module = RpcModule::new(());
|
|
|
|
module
|
|
|
|
.register_method("queue", |args, _| {
|
|
|
|
let args = args.parse::<(Metadata, Vec<u8>, Vec<u8>)>().unwrap();
|
|
|
|
queue_message(
|
|
|
|
args.0,
|
|
|
|
args.1,
|
|
|
|
SchnorrSignature::<Ristretto>::read(&mut args.2.as_slice()).unwrap(),
|
|
|
|
);
|
2023-07-20 22:53:03 +00:00
|
|
|
Ok(true)
|
2023-07-01 12:53:46 +00:00
|
|
|
})
|
|
|
|
.unwrap();
|
|
|
|
module
|
|
|
|
.register_method("next", |args, _| {
|
2023-07-17 19:49:15 +00:00
|
|
|
let args = args.parse::<(Service, u64)>().unwrap();
|
2023-07-20 22:53:03 +00:00
|
|
|
Ok(get_next_message(args.0, args.1))
|
2023-07-01 12:53:46 +00:00
|
|
|
})
|
|
|
|
.unwrap();
|
|
|
|
module
|
|
|
|
.register_method("ack", |args, _| {
|
|
|
|
let args = args.parse::<(Service, u64, Vec<u8>)>().unwrap();
|
|
|
|
ack_message(
|
|
|
|
args.0,
|
|
|
|
args.1,
|
|
|
|
SchnorrSignature::<Ristretto>::read(&mut args.2.as_slice()).unwrap(),
|
|
|
|
);
|
2023-07-20 22:53:03 +00:00
|
|
|
Ok(true)
|
2023-07-01 12:53:46 +00:00
|
|
|
})
|
|
|
|
.unwrap();
|
2023-07-17 04:20:10 +00:00
|
|
|
|
|
|
|
// Run until stopped, which it never will
|
|
|
|
server.start(module).unwrap().stopped().await;
|
2023-07-01 12:53:46 +00:00
|
|
|
}
|