Splitting side-node crate into lib/bin for integration tests
This commit is contained in:
@@ -7,20 +7,20 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
#[add_crdt_fields]
|
#[add_crdt_fields]
|
||||||
#[derive(Clone, CrdtNode, Serialize, Deserialize)]
|
#[derive(Clone, CrdtNode, Serialize, Deserialize)]
|
||||||
pub(crate) struct TransactionList {
|
pub struct TransactionList {
|
||||||
pub(crate) list: ListCrdt<Transaction>,
|
pub list: ListCrdt<Transaction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TransactionList {
|
impl TransactionList {
|
||||||
pub(crate) fn view_sha(&self) -> String {
|
pub fn view_sha(&self) -> String {
|
||||||
sha256::digest(serde_json::to_string(&self.list.view()).unwrap().as_bytes()).to_string()
|
sha256::digest(serde_json::to_string(&self.list.view()).unwrap().as_bytes()).to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A fake Transaction struct we can use as a simulated payload
|
/// A fake Transaction struct we can use as a simulated payload
|
||||||
#[add_crdt_fields]
|
#[add_crdt_fields]
|
||||||
#[derive(Clone, CrdtNode, Serialize, Deserialize)]
|
#[derive(Clone, CrdtNode, Serialize, Deserialize, PartialEq)]
|
||||||
pub(crate) struct Transaction {
|
pub struct Transaction {
|
||||||
from: String,
|
from: String,
|
||||||
to: String,
|
to: String,
|
||||||
amount: f64,
|
amount: f64,
|
||||||
|
|||||||
56
side-node/src/lib.rs
Normal file
56
side-node/src/lib.rs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
use bft_json_crdt::json_crdt::{BaseCrdt, SignedOp};
|
||||||
|
use cli::{parse_args, Commands};
|
||||||
|
use crdt::TransactionList;
|
||||||
|
use node::SideNode;
|
||||||
|
use tokio::{sync::mpsc, task};
|
||||||
|
use websocket::WebSocketClient;
|
||||||
|
|
||||||
|
pub(crate) mod cli;
|
||||||
|
pub mod crdt;
|
||||||
|
pub(crate) mod init;
|
||||||
|
pub(crate) mod keys;
|
||||||
|
pub(crate) mod node;
|
||||||
|
pub(crate) mod stdin;
|
||||||
|
pub mod utils;
|
||||||
|
pub(crate) mod websocket;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
pub async fn run() {
|
||||||
|
let args = parse_args();
|
||||||
|
|
||||||
|
match &args.command {
|
||||||
|
Some(Commands::Init { name }) => {
|
||||||
|
let config = init::config::SideNodeConfig {
|
||||||
|
name: name.to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = init::init(utils::home(name), config);
|
||||||
|
}
|
||||||
|
Some(Commands::Run { name }) => {
|
||||||
|
let mut node = setup(name).await;
|
||||||
|
node.start().await;
|
||||||
|
}
|
||||||
|
None => println!("No command provided. Exiting. See --help for more information."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wire everything up outside the application so we can test more easily later
|
||||||
|
async fn setup(name: &String) -> SideNode {
|
||||||
|
// First, load up the keys and create a bft-crdt
|
||||||
|
let side_dir = utils::home(name);
|
||||||
|
let keys = keys::load_from_file(side_dir);
|
||||||
|
let crdt = BaseCrdt::<TransactionList>::new(&keys);
|
||||||
|
|
||||||
|
// Channels for internal communication, and a tokio task for stdin input
|
||||||
|
let (incoming_sender, incoming_receiver) = mpsc::channel::<SignedOp>(32);
|
||||||
|
let (stdin_sender, stdin_receiver) = std::sync::mpsc::channel();
|
||||||
|
task::spawn(async move {
|
||||||
|
stdin::input(stdin_sender);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Finally, create the node and return it
|
||||||
|
let handle = WebSocketClient::new(incoming_sender).await;
|
||||||
|
let node = SideNode::new(crdt, keys, incoming_receiver, stdin_receiver, handle);
|
||||||
|
println!("Node setup complete.");
|
||||||
|
node
|
||||||
|
}
|
||||||
@@ -1,56 +1,5 @@
|
|||||||
use bft_json_crdt::json_crdt::{BaseCrdt, SignedOp};
|
use side_node;
|
||||||
use cli::{parse_args, Commands};
|
|
||||||
use crdt::TransactionList;
|
|
||||||
use node::SideNode;
|
|
||||||
use tokio::{sync::mpsc, task};
|
|
||||||
use websocket::WebSocketClient;
|
|
||||||
|
|
||||||
pub(crate) mod cli;
|
fn main() {
|
||||||
pub(crate) mod crdt;
|
side_node::run();
|
||||||
pub(crate) mod init;
|
|
||||||
pub(crate) mod keys;
|
|
||||||
pub(crate) mod node;
|
|
||||||
pub(crate) mod stdin;
|
|
||||||
pub(crate) mod utils;
|
|
||||||
pub(crate) mod websocket;
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() {
|
|
||||||
let args = parse_args();
|
|
||||||
|
|
||||||
match &args.command {
|
|
||||||
Some(Commands::Init { name }) => {
|
|
||||||
let config = init::config::SideNodeConfig {
|
|
||||||
name: name.to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let _ = init::init(utils::home(name), config);
|
|
||||||
}
|
|
||||||
Some(Commands::Run { name }) => {
|
|
||||||
let mut node = setup(name).await;
|
|
||||||
node.start().await;
|
|
||||||
}
|
|
||||||
None => println!("No command provided. Exiting. See --help for more information."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wire everything up outside the application so we can test more easily later
|
|
||||||
async fn setup(name: &String) -> SideNode {
|
|
||||||
// First, load up the keys and create a bft-crdt
|
|
||||||
let side_dir = utils::home(name);
|
|
||||||
let keys = keys::load_from_file(side_dir);
|
|
||||||
let crdt = BaseCrdt::<TransactionList>::new(&keys);
|
|
||||||
|
|
||||||
// Channels for internal communication, and a tokio task for stdin input
|
|
||||||
let (incoming_sender, incoming_receiver) = mpsc::channel::<SignedOp>(32);
|
|
||||||
let (stdin_sender, stdin_receiver) = std::sync::mpsc::channel();
|
|
||||||
task::spawn(async move {
|
|
||||||
stdin::input(stdin_sender);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Finally, create the node and return it
|
|
||||||
let handle = WebSocketClient::new(incoming_sender).await;
|
|
||||||
let node = SideNode::new(crdt, keys, incoming_receiver, stdin_receiver, handle);
|
|
||||||
println!("Node setup complete.");
|
|
||||||
node
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ impl SideNode {
|
|||||||
loop {
|
loop {
|
||||||
match self.stdin_receiver.try_recv() {
|
match self.stdin_receiver.try_recv() {
|
||||||
Ok(stdin) => {
|
Ok(stdin) => {
|
||||||
let transaction = utils::fake_transaction(stdin);
|
let transaction = utils::fake_transaction_json(stdin);
|
||||||
let json = serde_json::to_value(transaction).unwrap();
|
let json = serde_json::to_value(transaction).unwrap();
|
||||||
let signed_op = self.add_transaction_local(json);
|
let signed_op = self.add_transaction_local(json);
|
||||||
self.send_to_network(signed_op).await;
|
self.send_to_network(signed_op).await;
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ pub(crate) fn home(name: &String) -> std::path::PathBuf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a fake transaction with customizable from_pubkey String
|
/// Generate a fake transaction with customizable from_pubkey String
|
||||||
pub(crate) fn fake_transaction(from_pubkey: String) -> Value {
|
pub fn fake_transaction_json(from: String) -> Value {
|
||||||
json!({
|
json!({
|
||||||
"from": from_pubkey,
|
"from": from,
|
||||||
"to": "Bob",
|
"to": "Bob",
|
||||||
"amount": 1
|
"amount": 1
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
use bft_crdt_derive::add_crdt_fields;
|
use bft_crdt_derive::add_crdt_fields;
|
||||||
use bft_json_crdt::json_crdt::{BaseCrdt, CrdtNode, IntoCrdtNode};
|
use bft_json_crdt::json_crdt::{BaseCrdt, CrdtNode, IntoCrdtNode, SignedOp};
|
||||||
use bft_json_crdt::op::ROOT_ID;
|
use bft_json_crdt::op::ROOT_ID;
|
||||||
use bft_json_crdt::{keypair::make_keypair, list_crdt::ListCrdt};
|
use bft_json_crdt::{keypair::make_keypair, list_crdt::ListCrdt};
|
||||||
|
use serde_json::Value;
|
||||||
#[add_crdt_fields]
|
use side_node::crdt::{Transaction, TransactionList};
|
||||||
#[derive(Clone, CrdtNode)]
|
|
||||||
struct ListExample {
|
|
||||||
list: ListCrdt<char>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 1 - send valid updates
|
// case 1 - send valid updates
|
||||||
#[test]
|
#[test]
|
||||||
@@ -15,15 +11,35 @@ fn test_valid_updates() {
|
|||||||
// Insert to crdt.doc on local node, test applying the same operation to a remote node
|
// Insert to crdt.doc on local node, test applying the same operation to a remote node
|
||||||
// and check that the view is the same
|
// and check that the view is the same
|
||||||
let keypair1 = make_keypair();
|
let keypair1 = make_keypair();
|
||||||
let mut crdt1 = BaseCrdt::<ListExample>::new(&keypair1);
|
let mut crdt1 = BaseCrdt::<TransactionList>::new(&keypair1);
|
||||||
let _a = crdt1.doc.list.insert(ROOT_ID, 'a').sign(&keypair1);
|
|
||||||
let _b = crdt1.doc.list.insert(_a.id(), 'b').sign(&keypair1);
|
|
||||||
let _c = crdt1.doc.list.insert(_b.id(), 'c').sign(&keypair1);
|
|
||||||
|
|
||||||
assert_eq!(crdt1.doc.list.view(), vec!['a', 'b', 'c']);
|
let val_a = side_node::utils::fake_transaction_json(String::from("a"));
|
||||||
|
let val_b = side_node::utils::fake_transaction_json(String::from("b"));
|
||||||
|
let val_c = side_node::utils::fake_transaction_json(String::from("c"));
|
||||||
|
|
||||||
|
let _a = crdt1
|
||||||
|
.doc
|
||||||
|
.list
|
||||||
|
.insert(ROOT_ID, val_a.clone())
|
||||||
|
.sign(&keypair1);
|
||||||
|
let _b = crdt1
|
||||||
|
.doc
|
||||||
|
.list
|
||||||
|
.insert(_a.id(), val_b.clone())
|
||||||
|
.sign(&keypair1);
|
||||||
|
let _c = crdt1
|
||||||
|
.doc
|
||||||
|
.list
|
||||||
|
.insert(_b.id(), val_c.clone())
|
||||||
|
.sign(&keypair1);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
crdt1.doc.list.view(),
|
||||||
|
vec![to_tx(_a.clone()), to_tx(_b.clone()), to_tx(_c.clone())]
|
||||||
|
);
|
||||||
|
|
||||||
let keypair2 = make_keypair();
|
let keypair2 = make_keypair();
|
||||||
let mut crdt2 = BaseCrdt::<ListExample>::new(&keypair2);
|
let mut crdt2 = BaseCrdt::<TransactionList>::new(&keypair2);
|
||||||
crdt2.apply(_a.clone());
|
crdt2.apply(_a.clone());
|
||||||
crdt2.apply(_b);
|
crdt2.apply(_b);
|
||||||
crdt2.apply(_c.clone());
|
crdt2.apply(_c.clone());
|
||||||
@@ -43,3 +59,20 @@ fn test_valid_updates() {
|
|||||||
"views are still equal after repeated applies"
|
"views are still equal after repeated applies"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn to_tx(op: SignedOp) -> Transaction {
|
||||||
|
let val = op.inner.content;
|
||||||
|
serde_json::from_value(val.into()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn iter(&self) -> impl Iterator<Item = &Transaction> {
|
||||||
|
// self.ops
|
||||||
|
// .iter()
|
||||||
|
// .filter(|op| !op.is_deleted && op.content.is_some())
|
||||||
|
// .map(|op| op.content.as_ref().unwrap())
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /// Convenience function to get a vector of visible list elements
|
||||||
|
// pub fn view(&self) -> Vec<T> {
|
||||||
|
// self.iter().map(|i| i.to_owned()).collect()
|
||||||
|
// }
|
||||||
|
|||||||
Reference in New Issue
Block a user