migrated keys to the solana sdk
This commit is contained in:
parent
248c2f694c
commit
01b889d273
2286
Cargo.lock
generated
2286
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -4,16 +4,17 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
actix-web = "4.9.0"
|
||||||
async-stream = "0.3.5"
|
async-stream = "0.3.5"
|
||||||
dashmap = "6.0.1"
|
dashmap = "6.0.1"
|
||||||
ed25519-dalek = { version = "2.1.1", features = ["rand_core", "serde"] }
|
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
prost = "0.13.1"
|
prost = "0.13.1"
|
||||||
prost-types = "0.13.1"
|
prost-types = "0.13.1"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
salvo = { version = "0.70.0", features = ["affix"] }
|
serde = { version = "1.0.210", features = ["derive"] }
|
||||||
|
solana-sdk = "2.0.9"
|
||||||
tabled = "0.16.0"
|
tabled = "0.16.0"
|
||||||
tokio = { version = "1.39.2", features = ["macros"] }
|
tokio = { version = "1.39.2", features = ["fs", "macros", "rt-multi-thread"] }
|
||||||
tokio-stream = { version = "0.1.15", features = ["sync"] }
|
tokio-stream = { version = "0.1.15", features = ["sync"] }
|
||||||
tonic = "0.12.1"
|
tonic = "0.12.1"
|
||||||
|
|
||||||
|
3
rustfmt.toml
Normal file
3
rustfmt.toml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
reorder_impl_items = true
|
||||||
|
use_small_heuristics = "Max"
|
||||||
|
merge_imports = true
|
220
src/datastore.rs
220
src/datastore.rs
@ -1,16 +1,18 @@
|
|||||||
use crate::grpc::challenge::NodeUpdate;
|
use crate::{grpc::challenge::NodeUpdate, persistence::FileManager};
|
||||||
use crate::persistence::FileManager;
|
|
||||||
use dashmap::{DashMap, DashSet};
|
use dashmap::{DashMap, DashSet};
|
||||||
use ed25519_dalek::{Signer, SigningKey, VerifyingKey, PUBLIC_KEY_LENGTH};
|
use solana_sdk::{
|
||||||
use rand::rngs::OsRng;
|
pubkey::{ParsePubkeyError, Pubkey},
|
||||||
use std::time::Duration;
|
signature::{keypair::Keypair, Signer},
|
||||||
use std::time::SystemTime;
|
};
|
||||||
use std::time::UNIX_EPOCH;
|
use std::{
|
||||||
|
str::FromStr,
|
||||||
|
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||||
|
};
|
||||||
use tabled::{Table, Tabled};
|
use tabled::{Table, Tabled};
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub struct NodeInfo {
|
pub struct NodeInfo {
|
||||||
pub pubkey: VerifyingKey,
|
pub pubkey: Pubkey,
|
||||||
pub updated_at: SystemTime,
|
pub updated_at: SystemTime,
|
||||||
pub public: bool,
|
pub public: bool,
|
||||||
}
|
}
|
||||||
@ -19,9 +21,10 @@ pub struct NodeInfo {
|
|||||||
pub struct Store {
|
pub struct Store {
|
||||||
nodes: DashMap<IP, NodeInfo>,
|
nodes: DashMap<IP, NodeInfo>,
|
||||||
conns: DashSet<IP>,
|
conns: DashSet<IP>,
|
||||||
keys: DashMap<VerifyingKey, SigningKey>,
|
keys: DashMap<Pubkey, Keypair>,
|
||||||
persistence: FileManager,
|
persistence: FileManager,
|
||||||
}
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum SigningError {
|
pub enum SigningError {
|
||||||
CorruptedKey,
|
CorruptedKey,
|
||||||
KeyNotFound,
|
KeyNotFound,
|
||||||
@ -33,8 +36,8 @@ impl From<hex::FromHexError> for SigningError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ed25519_dalek::ed25519::Error> for SigningError {
|
impl From<ParsePubkeyError> for SigningError {
|
||||||
fn from(_: ed25519_dalek::ed25519::Error) -> Self {
|
fn from(_: ParsePubkeyError) -> Self {
|
||||||
Self::CorruptedKey
|
Self::CorruptedKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,23 +94,14 @@ impl Store {
|
|||||||
public: bool,
|
public: bool,
|
||||||
}
|
}
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
for (ip, node_info) in self
|
for (ip, node_info) in self.nodes.iter().map(|n| (n.key().clone(), n.value().clone())) {
|
||||||
.nodes
|
let pubkey = node_info.pubkey.to_string();
|
||||||
.iter()
|
|
||||||
.map(|n| (n.key().clone(), n.value().clone()))
|
|
||||||
{
|
|
||||||
let pubkey = hex::encode(node_info.pubkey.as_bytes());
|
|
||||||
let age = SystemTime::now()
|
let age = SystemTime::now()
|
||||||
.duration_since(node_info.updated_at)
|
.duration_since(node_info.updated_at)
|
||||||
.unwrap_or(Duration::ZERO)
|
.unwrap_or(Duration::ZERO)
|
||||||
.as_secs();
|
.as_secs();
|
||||||
let public = node_info.public;
|
let public = node_info.public;
|
||||||
output.push(OutputRow {
|
output.push(OutputRow { ip, pubkey, age, public });
|
||||||
ip,
|
|
||||||
pubkey,
|
|
||||||
age,
|
|
||||||
public,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
Table::new(output).to_string()
|
Table::new(output).to_string()
|
||||||
}
|
}
|
||||||
@ -122,32 +116,18 @@ impl Store {
|
|||||||
timestamp: String,
|
timestamp: String,
|
||||||
}
|
}
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
for (ip, keypair, timestamp) in self
|
for (ip, keypair, timestamp) in
|
||||||
.persistence
|
self.persistence.get_page_of_20(offset).await.unwrap().iter().map(|n| {
|
||||||
.get_page_of_20(offset)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.iter()
|
|
||||||
.map(|n| {
|
|
||||||
(
|
(
|
||||||
n.ip.to_string(),
|
n.ip.to_string(),
|
||||||
n.keypair.clone(),
|
Keypair::from_bytes(&n.keypair.to_bytes()).unwrap(),
|
||||||
n.joined_at
|
n.joined_at.duration_since(UNIX_EPOCH).unwrap().as_secs().to_string(),
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.unwrap()
|
|
||||||
.as_secs()
|
|
||||||
.to_string(),
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
let id = offset;
|
let id = offset;
|
||||||
let pubkey = hex::encode(keypair.verifying_key().as_bytes());
|
let pubkey = keypair.pubkey().to_string();
|
||||||
output.push(OutputRow {
|
output.push(OutputRow { id, ip, pubkey, timestamp });
|
||||||
id,
|
|
||||||
ip,
|
|
||||||
pubkey,
|
|
||||||
timestamp,
|
|
||||||
});
|
|
||||||
offset += 1;
|
offset += 1;
|
||||||
}
|
}
|
||||||
Table::new(output).to_string()
|
Table::new(output).to_string()
|
||||||
@ -160,11 +140,8 @@ impl Store {
|
|||||||
) -> Result<String, SigningError> {
|
) -> Result<String, SigningError> {
|
||||||
let crate::persistence::Node { keypair, .. } =
|
let crate::persistence::Node { keypair, .. } =
|
||||||
self.persistence.get_node_by_id(key_id).await?;
|
self.persistence.get_node_by_id(key_id).await?;
|
||||||
|
let signature = keypair.sign_message(message.as_bytes());
|
||||||
// let signature = format!("{:?}", signing_key.sign(message.as_bytes()));
|
Ok(signature.to_string())
|
||||||
let signature = hex::encode(keypair.sign(message.as_bytes()).to_bytes());
|
|
||||||
|
|
||||||
Ok(signature)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn sign_message_with_key(
|
pub async fn sign_message_with_key(
|
||||||
@ -172,31 +149,26 @@ impl Store {
|
|||||||
message: &str,
|
message: &str,
|
||||||
pubkey: &str,
|
pubkey: &str,
|
||||||
) -> Result<String, SigningError> {
|
) -> Result<String, SigningError> {
|
||||||
let mut pubkey_bytes = [0u8; PUBLIC_KEY_LENGTH];
|
let pubkey = Pubkey::from_str(&pubkey)?;
|
||||||
hex::decode_to_slice(pubkey, &mut pubkey_bytes)?;
|
|
||||||
let pubkey = VerifyingKey::from_bytes(&pubkey_bytes)?;
|
|
||||||
|
|
||||||
let signing_key = match self.get_privkey(&pubkey).await {
|
let keypair = match self.get_keypair(&pubkey).await {
|
||||||
Some(k) => k,
|
Some(k) => k,
|
||||||
None => return Err(SigningError::KeyNotFound),
|
None => return Err(SigningError::KeyNotFound),
|
||||||
};
|
};
|
||||||
|
let signature = keypair.sign_message(message.as_bytes());
|
||||||
// let signature = format!("{:?}", signing_key.sign(message.as_bytes()));
|
Ok(signature.to_string())
|
||||||
let signature = hex::encode(signing_key.sign(message.as_bytes()).to_bytes());
|
|
||||||
|
|
||||||
Ok(signature)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add_key(&self, pubkey: VerifyingKey, privkey: SigningKey) {
|
pub async fn add_key(&self, pubkey: Pubkey, keypair: Keypair) {
|
||||||
self.keys.insert(pubkey, privkey);
|
self.keys.insert(pubkey, keypair);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn remove_key(&self, pubkey: &VerifyingKey) {
|
pub async fn remove_key(&self, pubkey: &Pubkey) {
|
||||||
self.keys.remove(pubkey);
|
self.keys.remove(pubkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_privkey(&self, pubkey: &VerifyingKey) -> Option<SigningKey> {
|
async fn get_keypair(&self, pubkey: &Pubkey) -> Option<Keypair> {
|
||||||
self.keys.get(pubkey).map(|k| k.value().clone())
|
self.keys.get(pubkey).map(|k| Keypair::from_bytes(&k.to_bytes()).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This returns true if NodeInfo got modified.
|
/// This returns true if NodeInfo got modified.
|
||||||
@ -204,15 +176,14 @@ impl Store {
|
|||||||
/// On a side note, there are two types of people in this world:
|
/// On a side note, there are two types of people in this world:
|
||||||
/// 1. Those that can extrapolate... WAT?
|
/// 1. Those that can extrapolate... WAT?
|
||||||
pub async fn process_node_update(&self, node: NodeUpdate) -> bool {
|
pub async fn process_node_update(&self, node: NodeUpdate) -> bool {
|
||||||
let key_bytes = match hex::decode(node.keypair.clone()) {
|
// solana-sdk is great; it panics if the base58 string is corrupted
|
||||||
|
// we wrap this in catch_unwind() to make sure it doesn't crash the app
|
||||||
|
let keypair =
|
||||||
|
match std::panic::catch_unwind(|| Keypair::from_base58_string(&node.keypair.clone())) {
|
||||||
Ok(k) => k,
|
Ok(k) => k,
|
||||||
Err(_) => return false,
|
Err(_) => return false,
|
||||||
};
|
};
|
||||||
let privkey = SigningKey::from_bytes(match &key_bytes.as_slice().try_into() {
|
let pubkey = keypair.pubkey();
|
||||||
Ok(p) => p,
|
|
||||||
Err(_) => return false,
|
|
||||||
});
|
|
||||||
let pubkey = privkey.verifying_key();
|
|
||||||
|
|
||||||
// TODO: check this suggestion
|
// TODO: check this suggestion
|
||||||
// let updated_at_std = node
|
// let updated_at_std = node
|
||||||
@ -223,19 +194,13 @@ impl Store {
|
|||||||
let updated_at_std: SystemTime = match node.updated_at {
|
let updated_at_std: SystemTime = match node.updated_at {
|
||||||
Some(ts) => {
|
Some(ts) => {
|
||||||
let duration = Duration::new(ts.seconds as u64, ts.nanos as u32);
|
let duration = Duration::new(ts.seconds as u64, ts.nanos as u32);
|
||||||
UNIX_EPOCH
|
UNIX_EPOCH.checked_add(duration).unwrap_or(SystemTime::now())
|
||||||
.checked_add(duration)
|
|
||||||
.unwrap_or(SystemTime::now())
|
|
||||||
}
|
}
|
||||||
None => SystemTime::now(),
|
None => SystemTime::now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.add_key(pubkey, privkey.clone()).await;
|
self.add_key(pubkey, Keypair::from_bytes(&keypair.to_bytes()).unwrap()).await;
|
||||||
let node_info = NodeInfo {
|
let node_info = NodeInfo { pubkey, updated_at: updated_at_std, public: node.public };
|
||||||
pubkey,
|
|
||||||
updated_at: updated_at_std,
|
|
||||||
public: node.public,
|
|
||||||
};
|
|
||||||
if let Some(mut old_node_info) = self.update_node(node.ip.clone(), node_info.clone()).await
|
if let Some(mut old_node_info) = self.update_node(node.ip.clone(), node_info.clone()).await
|
||||||
{
|
{
|
||||||
if !node_info.public {
|
if !node_info.public {
|
||||||
@ -249,7 +214,7 @@ impl Store {
|
|||||||
false => false,
|
false => false,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let Ok(persistence_node) = (node.ip.as_str(), privkey, updated_at_std).try_into() {
|
if let Ok(persistence_node) = (node.ip.as_str(), keypair, updated_at_std).try_into() {
|
||||||
if let Err(e) = self.persistence.append_node(persistence_node).await {
|
if let Err(e) = self.persistence.append_node(persistence_node).await {
|
||||||
println!("Could not save data to disk: {e}.");
|
println!("Could not save data to disk: {e}.");
|
||||||
}
|
}
|
||||||
@ -271,10 +236,8 @@ impl Store {
|
|||||||
pub async fn remove_inactive_nodes(&self) {
|
pub async fn remove_inactive_nodes(&self) {
|
||||||
let mut dangling_pubkeys = Vec::new();
|
let mut dangling_pubkeys = Vec::new();
|
||||||
self.nodes.retain(|_, v| {
|
self.nodes.retain(|_, v| {
|
||||||
let age = SystemTime::now()
|
let age =
|
||||||
.duration_since(v.updated_at)
|
SystemTime::now().duration_since(v.updated_at).unwrap_or(Duration::ZERO).as_secs();
|
||||||
.unwrap_or(Duration::ZERO)
|
|
||||||
.as_secs();
|
|
||||||
if age > 120 {
|
if age > 120 {
|
||||||
dangling_pubkeys.push(v.pubkey.clone());
|
dangling_pubkeys.push(v.pubkey.clone());
|
||||||
false
|
false
|
||||||
@ -290,10 +253,10 @@ impl Store {
|
|||||||
pub async fn get_localhost(&self) -> NodeUpdate {
|
pub async fn get_localhost(&self) -> NodeUpdate {
|
||||||
// TODO trigger reset_localhost_keys on error instead of expects
|
// TODO trigger reset_localhost_keys on error instead of expects
|
||||||
let node = self.nodes.get("localhost").expect("no localhost node");
|
let node = self.nodes.get("localhost").expect("no localhost node");
|
||||||
let key = self.keys.get(&node.pubkey).expect("no localhost key");
|
let keypair = self.keys.get(&node.pubkey).expect("no localhost key");
|
||||||
NodeUpdate {
|
NodeUpdate {
|
||||||
ip: "localhost".to_string(),
|
ip: "localhost".to_string(),
|
||||||
keypair: hex::encode(key.value().as_bytes()),
|
keypair: keypair.to_base58_string(),
|
||||||
updated_at: Some(prost_types::Timestamp::from(node.value().updated_at)),
|
updated_at: Some(prost_types::Timestamp::from(node.value().updated_at)),
|
||||||
public: false,
|
public: false,
|
||||||
}
|
}
|
||||||
@ -301,45 +264,29 @@ impl Store {
|
|||||||
|
|
||||||
/// refreshes the keys of the node and returns a protobuf for the network
|
/// refreshes the keys of the node and returns a protobuf for the network
|
||||||
pub async fn reset_localhost_keys(&self) -> NodeUpdate {
|
pub async fn reset_localhost_keys(&self) -> NodeUpdate {
|
||||||
let mut csprng = OsRng;
|
let keypair_raw = Keypair::new();
|
||||||
let keypair_raw = SigningKey::generate(&mut csprng);
|
let keypair = keypair_raw.to_base58_string();
|
||||||
let keypair = hex::encode(keypair_raw.as_bytes());
|
let pubkey = keypair_raw.pubkey();
|
||||||
let pubkey = keypair_raw.verifying_key();
|
|
||||||
let ip = "localhost".to_string();
|
let ip = "localhost".to_string();
|
||||||
let updated_at = SystemTime::now();
|
let updated_at = SystemTime::now();
|
||||||
let public = false;
|
let public = false;
|
||||||
self.add_key(pubkey, keypair_raw.clone()).await;
|
self.add_key(pubkey, keypair_raw).await;
|
||||||
if let Some(old_data) = self
|
if let Some(old_data) =
|
||||||
.update_node(
|
self.update_node(ip.clone(), NodeInfo { pubkey, updated_at, public }).await
|
||||||
ip.clone(),
|
|
||||||
NodeInfo {
|
|
||||||
pubkey,
|
|
||||||
updated_at,
|
|
||||||
public,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
{
|
||||||
self.remove_key(&old_data.pubkey).await;
|
self.remove_key(&old_data.pubkey).await;
|
||||||
};
|
};
|
||||||
let updated_at = Some(prost_types::Timestamp::from(updated_at));
|
let updated_at = Some(prost_types::Timestamp::from(updated_at));
|
||||||
NodeUpdate {
|
NodeUpdate { ip, keypair, updated_at, public }
|
||||||
ip,
|
|
||||||
keypair,
|
|
||||||
updated_at,
|
|
||||||
public,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_full_node_list(&self) -> Vec<NodeUpdate> {
|
pub async fn get_full_node_list(&self) -> Vec<NodeUpdate> {
|
||||||
self.nodes
|
self.nodes
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|node| {
|
.filter_map(|node| {
|
||||||
self.keys
|
self.keys.get(&node.value().pubkey).map(|keypair| NodeUpdate {
|
||||||
.get(&node.value().pubkey)
|
|
||||||
.map(|signing_key| NodeUpdate {
|
|
||||||
ip: node.key().to_string(),
|
ip: node.key().to_string(),
|
||||||
keypair: hex::encode(signing_key.as_bytes()),
|
keypair: keypair.to_base58_string(),
|
||||||
updated_at: Some(prost_types::Timestamp::from(node.value().updated_at)),
|
updated_at: Some(prost_types::Timestamp::from(node.value().updated_at)),
|
||||||
public: node.value().public,
|
public: node.value().public,
|
||||||
})
|
})
|
||||||
@ -349,8 +296,7 @@ impl Store {
|
|||||||
|
|
||||||
// returns a random node that does not have an active connection
|
// returns a random node that does not have an active connection
|
||||||
pub async fn get_random_node(&self) -> Option<String> {
|
pub async fn get_random_node(&self) -> Option<String> {
|
||||||
use rand::rngs::OsRng;
|
use rand::{rngs::OsRng, RngCore};
|
||||||
use rand::RngCore;
|
|
||||||
let len = self.nodes.len();
|
let len = self.nodes.len();
|
||||||
if len == 0 {
|
if len == 0 {
|
||||||
return None;
|
return None;
|
||||||
@ -367,13 +313,12 @@ impl Store {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
|
||||||
use dashmap::DashMap;
|
|
||||||
use ed25519_dalek::SigningKey;
|
|
||||||
use rand::rngs::OsRng;
|
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use tokio::fs::File;
|
|
||||||
use tokio::io::AsyncWriteExt;
|
use dashmap::DashMap;
|
||||||
|
use tokio::{fs::File, io::AsyncWriteExt};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
async fn setup_file_manager(function: &str) -> std::io::Result<FileManager> {
|
async fn setup_file_manager(function: &str) -> std::io::Result<FileManager> {
|
||||||
let _ = tokio::fs::create_dir_all(".tmp").await;
|
let _ = tokio::fs::create_dir_all(".tmp").await;
|
||||||
@ -386,14 +331,11 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn node_info_creation() {
|
fn node_info_creation() {
|
||||||
let keypair = SigningKey::generate(&mut OsRng);
|
let keypair = Keypair::new();
|
||||||
let node_info = NodeInfo {
|
let node_info =
|
||||||
pubkey: keypair.verifying_key(),
|
NodeInfo { pubkey: keypair.pubkey(), updated_at: SystemTime::now(), public: true };
|
||||||
updated_at: SystemTime::now(),
|
|
||||||
public: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(node_info.pubkey, keypair.verifying_key());
|
assert_eq!(node_info.pubkey, keypair.pubkey());
|
||||||
assert!(node_info.updated_at >= UNIX_EPOCH);
|
assert!(node_info.updated_at >= UNIX_EPOCH);
|
||||||
assert!(node_info.public);
|
assert!(node_info.public);
|
||||||
}
|
}
|
||||||
@ -426,18 +368,18 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn sign_message_with_key() {
|
async fn sign_message_with_key() {
|
||||||
let keypair = SigningKey::generate(&mut OsRng);
|
let keypair = Keypair::new();
|
||||||
let pubkey_hex = hex::encode(keypair.verifying_key().as_bytes());
|
let pubkey_string = keypair.pubkey().to_string();
|
||||||
let store = Store {
|
let store = Store {
|
||||||
nodes: DashMap::new(),
|
nodes: DashMap::new(),
|
||||||
conns: DashSet::new(),
|
conns: DashSet::new(),
|
||||||
keys: DashMap::new(),
|
keys: DashMap::new(),
|
||||||
persistence: setup_file_manager("sign_message_with_key").await.unwrap(),
|
persistence: setup_file_manager("sign_message_with_key").await.unwrap(),
|
||||||
};
|
};
|
||||||
store.keys.insert(keypair.verifying_key(), keypair);
|
store.keys.insert(keypair.pubkey(), keypair);
|
||||||
|
|
||||||
let message = "Test message";
|
let message = "Test message";
|
||||||
let result = store.sign_message_with_key(message, &pubkey_hex).await;
|
let result = store.sign_message_with_key(message, &pubkey_string).await;
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
if let Ok(signature) = result {
|
if let Ok(signature) = result {
|
||||||
@ -447,15 +389,12 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn process_node_update() {
|
async fn process_node_update() {
|
||||||
let keypair = SigningKey::generate(&mut OsRng);
|
let keypair = Keypair::new();
|
||||||
let node_update = NodeUpdate {
|
let node_update = NodeUpdate {
|
||||||
ip: "127.0.0.1".to_string(),
|
ip: "127.0.0.1".to_string(),
|
||||||
keypair: hex::encode(keypair.as_bytes()),
|
keypair: keypair.to_base58_string(),
|
||||||
updated_at: Some(prost_types::Timestamp {
|
updated_at: Some(prost_types::Timestamp {
|
||||||
seconds: SystemTime::now()
|
seconds: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64,
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.unwrap()
|
|
||||||
.as_secs() as i64,
|
|
||||||
nanos: 0,
|
nanos: 0,
|
||||||
}),
|
}),
|
||||||
public: true,
|
public: true,
|
||||||
@ -476,12 +415,9 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn get_full_node_list() {
|
async fn get_full_node_list() {
|
||||||
let keypair = SigningKey::generate(&mut OsRng);
|
let keypair = Keypair::new();
|
||||||
let node_info = NodeInfo {
|
let node_info =
|
||||||
pubkey: keypair.verifying_key(),
|
NodeInfo { pubkey: keypair.pubkey(), updated_at: SystemTime::now(), public: true };
|
||||||
updated_at: SystemTime::now(),
|
|
||||||
public: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
let store = Store {
|
let store = Store {
|
||||||
nodes: DashMap::new(),
|
nodes: DashMap::new(),
|
||||||
@ -491,13 +427,13 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
store.nodes.insert("127.0.0.1".to_string(), node_info);
|
store.nodes.insert("127.0.0.1".to_string(), node_info);
|
||||||
store.keys.insert(keypair.verifying_key(), keypair.clone());
|
store.keys.insert(keypair.pubkey(), Keypair::from_bytes(&keypair.to_bytes()).unwrap());
|
||||||
|
|
||||||
let node_list = store.get_full_node_list().await;
|
let node_list = store.get_full_node_list().await;
|
||||||
|
|
||||||
assert_eq!(node_list.len(), 1);
|
assert_eq!(node_list.len(), 1);
|
||||||
assert_eq!(node_list[0].ip, "127.0.0.1");
|
assert_eq!(node_list[0].ip, "127.0.0.1");
|
||||||
assert_eq!(node_list[0].keypair, hex::encode(keypair.as_bytes()));
|
assert_eq!(node_list[0].keypair, keypair.to_base58_string());
|
||||||
assert!(node_list[0].public);
|
assert!(node_list[0].public);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
use crate::datastore::{SigningError, Store};
|
use crate::datastore::{SigningError, Store};
|
||||||
|
use actix_web::error::ResponseError;
|
||||||
|
use actix_web::http::StatusCode;
|
||||||
|
use actix_web::{web, App, HttpResponse, HttpServer, Responder, Result};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::fmt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use salvo::affix;
|
|
||||||
use salvo::prelude::*;
|
|
||||||
|
|
||||||
const HOMEPAGE: &str = r#"Welcome, beloved hacker!
|
const HOMEPAGE: &str = r#"Welcome, beloved hacker!
|
||||||
|
|
||||||
I am a node that is part of the DeTEE Hacker Challenge network.
|
I am a node that is part of the DeTEE Hacker Challenge network.
|
||||||
@ -33,6 +35,7 @@ If you manage to steal a key, contact us at https://detee.cloud
|
|||||||
Good luck!
|
Good luck!
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
enum HTTPError {
|
enum HTTPError {
|
||||||
NoKeyID,
|
NoKeyID,
|
||||||
NoPubkey,
|
NoPubkey,
|
||||||
@ -40,42 +43,53 @@ enum HTTPError {
|
|||||||
Store(SigningError),
|
Store(SigningError),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
impl fmt::Display for HTTPError {
|
||||||
impl Writer for HTTPError {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
async fn write(self, _req: &mut Request, _depot: &mut Depot, res: &mut Response) {
|
match *self {
|
||||||
res.status_code(StatusCode::BAD_REQUEST);
|
HTTPError::NoKeyID => write!(f, "Key ID must be specified as a query param"),
|
||||||
match self {
|
HTTPError::NoPubkey => write!(f, "Pubkey must be specified as a query param"),
|
||||||
HTTPError::NoKeyID => res.render("key ID must be specified as a get param"),
|
HTTPError::NoMessage => write!(f, "Something must be specified as a query param"),
|
||||||
HTTPError::NoPubkey => res.render("pubkey must be specified as GET param"),
|
HTTPError::Store(ref err) => write!(f, "{}", err),
|
||||||
HTTPError::NoMessage => res.render("something must be specified as GET param"),
|
}
|
||||||
HTTPError::Store(e) => res.render(format!("{e}")),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[handler]
|
impl ResponseError for HTTPError {
|
||||||
async fn homepage() -> String {
|
fn status_code(&self) -> StatusCode {
|
||||||
HOMEPAGE.to_string()
|
StatusCode::BAD_REQUEST
|
||||||
}
|
}
|
||||||
|
|
||||||
#[handler]
|
fn error_response(&self) -> HttpResponse {
|
||||||
async fn memory_list(depot: &mut Depot) -> String {
|
HttpResponse::BadRequest().body(self.to_string())
|
||||||
let ds = depot.obtain::<Arc<Store>>().unwrap();
|
}
|
||||||
ds.tabled_memory_list().await // TODO: make this paginated
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[handler]
|
#[derive(Deserialize)]
|
||||||
async fn memory_sign(req: &mut Request, depot: &mut Depot) -> Result<String, HTTPError> {
|
struct SignQuery {
|
||||||
let ds = depot.obtain::<Arc<Store>>().unwrap();
|
pubkey: Option<String>,
|
||||||
let pubkey = match req.query::<String>("pubkey") {
|
something: Option<String>,
|
||||||
Some(k) => k,
|
key: Option<u64>,
|
||||||
None => return Err(HTTPError::NoPubkey),
|
page: Option<u64>,
|
||||||
};
|
}
|
||||||
|
|
||||||
let something = match req.query::<String>("something") {
|
async fn homepage() -> impl Responder {
|
||||||
Some(k) => k,
|
HttpResponse::Ok().body(HOMEPAGE)
|
||||||
None => return Err(HTTPError::NoMessage),
|
}
|
||||||
};
|
|
||||||
|
async fn memory_list(store: web::Data<Arc<Store>>) -> impl Responder {
|
||||||
|
let ds = store.get_ref();
|
||||||
|
let list = ds.tabled_memory_list().await; // TODO: make paginated
|
||||||
|
HttpResponse::Ok().body(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn memory_sign(
|
||||||
|
store: web::Data<Arc<Store>>,
|
||||||
|
req: web::Query<SignQuery>,
|
||||||
|
) -> Result<String, HTTPError> {
|
||||||
|
let ds = store.get_ref();
|
||||||
|
|
||||||
|
let pubkey = req.pubkey.clone().ok_or(HTTPError::NoPubkey)?;
|
||||||
|
let something = req.something.clone().ok_or(HTTPError::NoMessage)?;
|
||||||
|
|
||||||
match ds.sign_message_with_key(&something, &pubkey).await {
|
match ds.sign_message_with_key(&something, &pubkey).await {
|
||||||
Ok(s) => Ok(s),
|
Ok(s) => Ok(s),
|
||||||
@ -83,29 +97,21 @@ async fn memory_sign(req: &mut Request, depot: &mut Depot) -> Result<String, HTT
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[handler]
|
async fn disk_list(store: web::Data<Arc<Store>>, req: web::Query<SignQuery>) -> impl Responder {
|
||||||
async fn disk_list(req: &mut Request, depot: &mut Depot) -> Result<String, HTTPError> {
|
let ds = store.get_ref();
|
||||||
let ds = depot.obtain::<Arc<Store>>().unwrap();
|
let page = req.page.unwrap_or(0);
|
||||||
let page = match req.query::<u64>("page") {
|
let list = ds.tabled_disk_list(page).await;
|
||||||
Some(n) => n,
|
HttpResponse::Ok().body(list)
|
||||||
None => 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(ds.tabled_disk_list(page).await)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[handler]
|
async fn disk_sign(
|
||||||
async fn disk_sign(req: &mut Request, depot: &mut Depot) -> Result<String, HTTPError> {
|
store: web::Data<Arc<Store>>,
|
||||||
let ds = depot.obtain::<Arc<Store>>().unwrap();
|
req: web::Query<SignQuery>,
|
||||||
let key = match req.query::<u64>("key") {
|
) -> Result<String, HTTPError> {
|
||||||
Some(k) => k,
|
let ds = store.get_ref();
|
||||||
None => return Err(HTTPError::NoKeyID),
|
|
||||||
};
|
|
||||||
|
|
||||||
let something = match req.query::<String>("something") {
|
let key = req.key.ok_or(HTTPError::NoKeyID)?;
|
||||||
Some(k) => k,
|
let something = req.something.clone().ok_or(HTTPError::NoMessage)?;
|
||||||
None => return Err(HTTPError::NoMessage),
|
|
||||||
};
|
|
||||||
|
|
||||||
match ds.disk_sign_message_with_key(&something, key).await {
|
match ds.disk_sign_message_with_key(&something, key).await {
|
||||||
Ok(s) => Ok(s),
|
Ok(s) => Ok(s),
|
||||||
@ -114,20 +120,24 @@ async fn disk_sign(req: &mut Request, depot: &mut Depot) -> Result<String, HTTPE
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn init(ds: Arc<Store>) {
|
pub async fn init(ds: Arc<Store>) {
|
||||||
let acceptor = TcpListener::new("0.0.0.0:31372").bind().await;
|
HttpServer::new(move || {
|
||||||
let router = Router::new()
|
App::new()
|
||||||
.hoop(affix::inject(ds))
|
.app_data(web::Data::new(ds.clone()))
|
||||||
.get(homepage)
|
.route("/", web::get().to(homepage))
|
||||||
.push(
|
.service(
|
||||||
Router::with_path("memory")
|
web::scope("/memory")
|
||||||
.get(memory_list)
|
.route("", web::get().to(memory_list))
|
||||||
.push(Router::with_path("sign").get(memory_sign)),
|
.route("/sign", web::get().to(memory_sign)),
|
||||||
)
|
)
|
||||||
.push(
|
.service(
|
||||||
Router::with_path("disk")
|
web::scope("/disk")
|
||||||
.get(disk_list)
|
.route("", web::get().to(disk_list))
|
||||||
.push(Router::with_path("sign").get(disk_sign)),
|
.route("/sign", web::get().to(disk_sign)),
|
||||||
);
|
)
|
||||||
println!("{:?}", router);
|
})
|
||||||
Server::new(acceptor).serve(router).await;
|
.bind("0.0.0.0:31372")
|
||||||
|
.unwrap()
|
||||||
|
.run()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
use ed25519_dalek::SigningKey;
|
use solana_sdk::signer::keypair::Keypair;
|
||||||
use ed25519_dalek::KEYPAIR_LENGTH;
|
use std::{
|
||||||
use std::net::AddrParseError;
|
net::{AddrParseError, Ipv4Addr},
|
||||||
use std::net::Ipv4Addr;
|
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
};
|
||||||
use tokio::fs::File;
|
use tokio::{
|
||||||
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt, SeekFrom};
|
fs::File,
|
||||||
use tokio::sync::Mutex;
|
io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt, SeekFrom},
|
||||||
|
sync::Mutex,
|
||||||
|
};
|
||||||
|
|
||||||
const DATA_SIZE: usize = 76;
|
const DATA_SIZE: usize = 76;
|
||||||
|
const KEYPAIR_LENGTH: usize = 64;
|
||||||
|
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
CorruptedIP,
|
CorruptedIP,
|
||||||
@ -20,21 +23,25 @@ impl From<AddrParseError> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Node {
|
pub struct Node {
|
||||||
pub ip: Ipv4Addr,
|
pub ip: Ipv4Addr,
|
||||||
pub keypair: SigningKey,
|
pub keypair: Keypair,
|
||||||
pub joined_at: SystemTime,
|
pub joined_at: SystemTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<(&str, SigningKey, SystemTime)> for Node {
|
impl Clone for Node {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
let cloned_keypair = Keypair::from_bytes(&self.keypair.to_bytes()).unwrap();
|
||||||
|
|
||||||
|
Node { ip: self.ip, keypair: cloned_keypair, joined_at: self.joined_at }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<(&str, Keypair, SystemTime)> for Node {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
fn try_from(value: (&str, SigningKey, SystemTime)) -> Result<Self, Self::Error> {
|
|
||||||
Ok(Self {
|
fn try_from(value: (&str, Keypair, SystemTime)) -> Result<Self, Self::Error> {
|
||||||
ip: value.0.parse()?,
|
Ok(Self { ip: value.0.parse()?, keypair: value.1, joined_at: value.2 })
|
||||||
keypair: value.1,
|
|
||||||
joined_at: value.2,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,21 +50,16 @@ impl Node {
|
|||||||
self.ip.to_string()
|
self.ip.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signing_key(&self) -> SigningKey {
|
fn signing_key(&self) -> Keypair {
|
||||||
self.keypair.clone()
|
Keypair::from_bytes(&self.keypair.to_bytes()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_bytes(self) -> [u8; DATA_SIZE] {
|
fn to_bytes(self) -> [u8; DATA_SIZE] {
|
||||||
let mut result = [0; DATA_SIZE];
|
let mut result = [0; DATA_SIZE];
|
||||||
result[0..4].copy_from_slice(&self.ip.octets());
|
result[0..4].copy_from_slice(&self.ip.octets());
|
||||||
result[4..68].copy_from_slice(&self.keypair.to_keypair_bytes());
|
result[4..68].copy_from_slice(&self.keypair.to_bytes());
|
||||||
result[68..DATA_SIZE].copy_from_slice(
|
result[68..DATA_SIZE].copy_from_slice(
|
||||||
&self
|
&self.joined_at.duration_since(UNIX_EPOCH).unwrap().as_secs().to_le_bytes(),
|
||||||
.joined_at
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.unwrap()
|
|
||||||
.as_secs()
|
|
||||||
.to_le_bytes(),
|
|
||||||
);
|
);
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
@ -66,15 +68,11 @@ impl Node {
|
|||||||
let ip: [u8; 4] = bytes[0..4].try_into().unwrap();
|
let ip: [u8; 4] = bytes[0..4].try_into().unwrap();
|
||||||
let ip: Ipv4Addr = ip.into();
|
let ip: Ipv4Addr = ip.into();
|
||||||
let keypair: [u8; KEYPAIR_LENGTH] = bytes[4..68].try_into().unwrap();
|
let keypair: [u8; KEYPAIR_LENGTH] = bytes[4..68].try_into().unwrap();
|
||||||
let keypair: SigningKey = SigningKey::from_keypair_bytes(&keypair).unwrap();
|
let keypair: Keypair = Keypair::from_bytes(&keypair).unwrap();
|
||||||
let joined_at: [u8; 8] = bytes[68..DATA_SIZE].try_into().unwrap();
|
let joined_at: [u8; 8] = bytes[68..DATA_SIZE].try_into().unwrap();
|
||||||
let joined_at: u64 = u64::from_le_bytes(joined_at);
|
let joined_at: u64 = u64::from_le_bytes(joined_at);
|
||||||
let joined_at = SystemTime::UNIX_EPOCH + Duration::from_secs(joined_at);
|
let joined_at = SystemTime::UNIX_EPOCH + Duration::from_secs(joined_at);
|
||||||
Self {
|
Self { ip, keypair, joined_at }
|
||||||
ip,
|
|
||||||
keypair,
|
|
||||||
joined_at,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,9 +83,7 @@ pub struct FileManager {
|
|||||||
impl FileManager {
|
impl FileManager {
|
||||||
pub async fn init(path: &str) -> std::io::Result<Self> {
|
pub async fn init(path: &str) -> std::io::Result<Self> {
|
||||||
let file = File::options().read(true).append(true).open(path).await?;
|
let file = File::options().read(true).append(true).open(path).await?;
|
||||||
Ok(Self {
|
Ok(Self { file: Mutex::new(file) })
|
||||||
file: Mutex::new(file),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn append_node(&self, node: Node) -> std::io::Result<()> {
|
pub async fn append_node(&self, node: Node) -> std::io::Result<()> {
|
||||||
@ -100,10 +96,7 @@ impl FileManager {
|
|||||||
|
|
||||||
pub async fn get_node_by_id(&self, id: u64) -> std::io::Result<Node> {
|
pub async fn get_node_by_id(&self, id: u64) -> std::io::Result<Node> {
|
||||||
let mut file = self.file.lock().await;
|
let mut file = self.file.lock().await;
|
||||||
file.seek(SeekFrom::Start(
|
file.seek(SeekFrom::Start(id.wrapping_mul(DATA_SIZE.try_into().unwrap_or(0)))).await?;
|
||||||
id.wrapping_mul(DATA_SIZE.try_into().unwrap_or(0)),
|
|
||||||
))
|
|
||||||
.await?;
|
|
||||||
let mut node_bytes = [0; DATA_SIZE];
|
let mut node_bytes = [0; DATA_SIZE];
|
||||||
file.read_exact(&mut node_bytes).await?;
|
file.read_exact(&mut node_bytes).await?;
|
||||||
Ok(Node::from_bytes(node_bytes))
|
Ok(Node::from_bytes(node_bytes))
|
||||||
@ -113,11 +106,7 @@ impl FileManager {
|
|||||||
/// Specify offset (the number of nodes to skip).
|
/// Specify offset (the number of nodes to skip).
|
||||||
pub async fn get_page_of_20(&self, offset: u64) -> std::io::Result<Vec<Node>> {
|
pub async fn get_page_of_20(&self, offset: u64) -> std::io::Result<Vec<Node>> {
|
||||||
let mut file = self.file.lock().await;
|
let mut file = self.file.lock().await;
|
||||||
file.seek(SeekFrom::Start(
|
file.seek(SeekFrom::Start(offset.wrapping_mul(DATA_SIZE.try_into().unwrap_or(0)))).await?;
|
||||||
offset
|
|
||||||
.wrapping_mul(DATA_SIZE.try_into().unwrap_or(0)),
|
|
||||||
))
|
|
||||||
.await?;
|
|
||||||
let mut nodes = Vec::new();
|
let mut nodes = Vec::new();
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
loop {
|
loop {
|
||||||
@ -139,12 +128,9 @@ impl FileManager {
|
|||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use ed25519_dalek::SigningKey;
|
|
||||||
use rand::rngs::OsRng;
|
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use std::io::Result;
|
use std::io::Result;
|
||||||
use tokio::fs::remove_file;
|
use tokio::{fs::remove_file, io::AsyncWriteExt};
|
||||||
use tokio::io::AsyncWriteExt;
|
|
||||||
|
|
||||||
const TEST_FILE_PREFIX: &str = ".tmp/test_";
|
const TEST_FILE_PREFIX: &str = ".tmp/test_";
|
||||||
fn get_test_file_name(function: &str) -> String {
|
fn get_test_file_name(function: &str) -> String {
|
||||||
@ -162,11 +148,11 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn node_round_trip() {
|
fn node_round_trip() {
|
||||||
let keypair = SigningKey::generate(&mut OsRng);
|
let keypair = Keypair::new();
|
||||||
|
|
||||||
let original_node = Node {
|
let original_node = Node {
|
||||||
ip: "192.168.1.1".parse().unwrap(),
|
ip: "192.168.1.1".parse().unwrap(),
|
||||||
keypair: keypair.clone(),
|
keypair: Keypair::from_bytes(&keypair.to_bytes()).unwrap(),
|
||||||
joined_at: SystemTime::now(),
|
joined_at: SystemTime::now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -174,21 +160,10 @@ mod tests {
|
|||||||
let restored_node = Node::from_bytes(node_bytes);
|
let restored_node = Node::from_bytes(node_bytes);
|
||||||
|
|
||||||
assert_eq!(original_node.ip_as_string(), restored_node.ip_as_string());
|
assert_eq!(original_node.ip_as_string(), restored_node.ip_as_string());
|
||||||
|
assert_eq!(original_node.keypair.to_bytes(), restored_node.keypair.to_bytes());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
original_node.keypair.to_keypair_bytes(),
|
original_node.joined_at.duration_since(UNIX_EPOCH).unwrap().as_secs(),
|
||||||
restored_node.keypair.to_keypair_bytes()
|
restored_node.joined_at.duration_since(UNIX_EPOCH).unwrap().as_secs()
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
original_node
|
|
||||||
.joined_at
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.unwrap()
|
|
||||||
.as_secs(),
|
|
||||||
restored_node
|
|
||||||
.joined_at
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.unwrap()
|
|
||||||
.as_secs()
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,12 +179,12 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_random_node() -> Node {
|
fn get_random_node() -> Node {
|
||||||
let keypair = SigningKey::generate(&mut OsRng);
|
let keypair = Keypair::new();
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let ipv4 = Ipv4Addr::new(rng.gen(), rng.gen(), rng.gen(), rng.gen());
|
let ipv4 = Ipv4Addr::new(rng.gen(), rng.gen(), rng.gen(), rng.gen());
|
||||||
Node {
|
Node {
|
||||||
ip: ipv4,
|
ip: ipv4,
|
||||||
keypair: keypair.clone(),
|
keypair: Keypair::from_bytes(&keypair.to_bytes()).unwrap(),
|
||||||
joined_at: SystemTime::now(),
|
joined_at: SystemTime::now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,11 +200,7 @@ mod tests {
|
|||||||
assert_eq!(node.keypair, retrieved_node.keypair);
|
assert_eq!(node.keypair, retrieved_node.keypair);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
node.joined_at.duration_since(UNIX_EPOCH).unwrap().as_secs(),
|
node.joined_at.duration_since(UNIX_EPOCH).unwrap().as_secs(),
|
||||||
retrieved_node
|
retrieved_node.joined_at.duration_since(UNIX_EPOCH).unwrap().as_secs()
|
||||||
.joined_at
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.unwrap()
|
|
||||||
.as_secs()
|
|
||||||
);
|
);
|
||||||
remove_file(get_test_file_name(function_name)).await?;
|
remove_file(get_test_file_name(function_name)).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -248,10 +219,7 @@ mod tests {
|
|||||||
manager.append_node(node3.clone()).await.unwrap();
|
manager.append_node(node3.clone()).await.unwrap();
|
||||||
let retrieved_node2 = manager.get_node_by_id(1).await?;
|
let retrieved_node2 = manager.get_node_by_id(1).await?;
|
||||||
assert_eq!(node1.ip_as_string(), retrieved_node1.ip_as_string());
|
assert_eq!(node1.ip_as_string(), retrieved_node1.ip_as_string());
|
||||||
assert_eq!(
|
assert_eq!(node1.keypair.to_bytes(), retrieved_node1.keypair.to_bytes());
|
||||||
node1.keypair.to_keypair_bytes(),
|
|
||||||
retrieved_node1.keypair.to_keypair_bytes()
|
|
||||||
);
|
|
||||||
assert_eq!(node2.ip_as_string(), retrieved_node2.ip_as_string());
|
assert_eq!(node2.ip_as_string(), retrieved_node2.ip_as_string());
|
||||||
assert_eq!(node2.keypair, retrieved_node2.keypair);
|
assert_eq!(node2.keypair, retrieved_node2.keypair);
|
||||||
let retrieved_node3 = manager.get_node_by_id(2).await?;
|
let retrieved_node3 = manager.get_node_by_id(2).await?;
|
||||||
|
Loading…
Reference in New Issue
Block a user