diff --git a/Cargo.toml b/Cargo.toml index d45d3ab..95c7317 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ heed = { git = "https://github.com/meilisearch/heed", tag = "v0.12.4", version = hex = "0.4.3" http = "0.2.9" jsonrpsee = { version = "0.19.0", features = ["client", "macros"] } +never-type = "0.1.1" quinn = "0.10.1" rayon = "1.7.0" rcgen = "0.11.1" diff --git a/src/archive.rs b/src/archive.rs index 297305b..044bd3a 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -5,18 +5,26 @@ use heed::types::*; use heed::{Database, RoTxn, RwTxn}; use serde::{Deserialize, Serialize}; +type ArchiveBodies = + Database, SerdeBincode>>; + #[derive(Clone)] -pub struct Archive { +pub struct Archive< + A, + CustomTxExtension = DefaultTxExtension, + CustomTxOutput = DefaultCustomTxOutput, +> { // Block height to header. headers: Database, SerdeBincode
>, - bodies: Database, SerdeBincode>>, + bodies: ArchiveBodies, hash_to_height: Database, OwnedType<[u8; 4]>>, } -impl< - A: Serialize + for<'de> Deserialize<'de> + 'static, - C: Clone + Serialize + for<'de> Deserialize<'de> + GetValue + 'static, - > Archive +impl Archive +where + A: Serialize + for<'de> Deserialize<'de> + 'static, + CustomTxExtension: Serialize + for<'de> Deserialize<'de> + 'static, + CustomTxOutput: Clone + Serialize + for<'de> Deserialize<'de> + GetValue + 'static, { pub const NUM_DBS: u32 = 3; @@ -37,7 +45,11 @@ impl< Ok(header) } - pub fn get_body(&self, txn: &RoTxn, height: u32) -> Result>, Error> { + pub fn get_body( + &self, + txn: &RoTxn, + height: u32, + ) -> Result>, Error> { let height = height.to_be_bytes(); let header = self.bodies.get(txn, &height)?; Ok(header) @@ -63,7 +75,7 @@ impl< &self, txn: &mut RwTxn, header: &Header, - body: &Body, + body: &Body, ) -> Result<(), Error> { if header.merkle_root != body.compute_merkle_root() { return Err(Error::InvalidMerkleRoot); diff --git a/src/authorization.rs b/src/authorization.rs index 79ab6ed..c7e9532 100644 --- a/src/authorization.rs +++ b/src/authorization.rs @@ -16,42 +16,13 @@ impl GetAddress for Authorization { } } -impl Deserialize<'de> + Send + Sync> Verify for Authorization { - type Error = Error; - fn verify_transaction(transaction: &AuthorizedTransaction) -> Result<(), Self::Error> - where - Self: Sized, - { - verify_authorized_transaction(transaction)?; - Ok(()) - } - - fn verify_body(body: &Body) -> Result<(), Self::Error> - where - Self: Sized, - { - verify_authorizations(body)?; - Ok(()) - } -} - -pub fn get_address(public_key: &PublicKey) -> Address { - let mut hasher = blake3::Hasher::new(); - let mut reader = hasher.update(&public_key.to_bytes()).finalize_xof(); - let mut output: [u8; 20] = [0; 20]; - reader.fill(&mut output); - Address(output) -} - -struct Package<'a> { - messages: Vec<&'a [u8]>, - signatures: Vec, - public_keys: Vec, -} - -pub fn verify_authorized_transaction( - transaction: &AuthorizedTransaction, -) -> Result<(), Error> { +pub fn verify_authorized_transaction( + transaction: &AuthorizedTransaction, +) -> Result<(), Error> +where + CustomTxExtension: Serialize, + CustomTxOutput: Clone + Serialize + Sync, +{ let serialized_transaction = bincode::serialize(&transaction.transaction)?; let messages: Vec<_> = std::iter::repeat(serialized_transaction.as_slice()) .take(transaction.authorizations.len()) @@ -70,9 +41,13 @@ pub fn verify_authorized_transaction( Ok(()) } -pub fn verify_authorizations( - body: &Body, -) -> Result<(), Error> { +pub fn verify_authorizations( + body: &Body, +) -> Result<(), Error> +where + CustomTxExtension: Serialize + Sync, + CustomTxOutput: Clone + Serialize + Sync, +{ let input_numbers = body .transactions .iter() @@ -134,6 +109,45 @@ pub fn verify_authorizations( Ok(()) } +impl Verify for Authorization +where + CustomTxExtension: Serialize + Send + Sync, + CustomTxOutput: Clone + Serialize + for<'de> Deserialize<'de> + Send + Sync, +{ + type Error = Error; + fn verify_transaction( + transaction: &AuthorizedTransaction, + ) -> Result<(), Self::Error> + where + Self: Sized, + { + verify_authorized_transaction(transaction)?; + Ok(()) + } + + fn verify_body(body: &Body) -> Result<(), Self::Error> + where + Self: Sized, + { + verify_authorizations(body)?; + Ok(()) + } +} + +pub fn get_address(public_key: &PublicKey) -> Address { + let mut hasher = blake3::Hasher::new(); + let mut reader = hasher.update(&public_key.to_bytes()).finalize_xof(); + let mut output: [u8; 20] = [0; 20]; + reader.fill(&mut output); + Address(output) +} + +struct Package<'a> { + messages: Vec<&'a [u8]>, + signatures: Vec, + public_keys: Vec, +} + pub fn sign( keypair: &Keypair, transaction: &Transaction, diff --git a/src/drivechain/mod.rs b/src/drivechain/mod.rs index f7972d1..6961c31 100644 --- a/src/drivechain/mod.rs +++ b/src/drivechain/mod.rs @@ -8,13 +8,17 @@ use std::net::SocketAddr; use std::{collections::HashMap, marker::PhantomData}; #[derive(Clone)] -pub struct Drivechain { +pub struct Drivechain< + CustomTxExtension = DefaultTxExtension, + CustomTxOutput = DefaultCustomTxOutput, +> { pub sidechain_number: u8, pub client: HttpClient, - pub _content: PhantomData, + pub _content: PhantomData, + pub _tx_extension: PhantomData, } -impl Drivechain { +impl Drivechain { pub async fn verify_bmm(&self, header: &Header) -> Result<(), Error> { let prev_main_hash = header.prev_main_hash; let block_hash = self @@ -37,7 +41,7 @@ impl Drivechain { &self, end: bitcoin::BlockHash, start: Option, - ) -> Result, Error> { + ) -> Result, Error> { let (deposits, deposit_block_hash) = self.get_deposit_outputs(end, start).await?; let bundle_statuses = self.get_withdrawal_bundle_statuses().await?; let two_way_peg_data = TwoWayPegData { @@ -65,7 +69,13 @@ impl Drivechain { &self, end: bitcoin::BlockHash, start: Option, - ) -> Result<(HashMap>, Option), Error> { + ) -> Result< + ( + HashMap>, + Option, + ), + Error, + > { let deposits = self .client .listsidechaindepositsbyblock(self.sidechain_number, Some(end), start) @@ -141,6 +151,7 @@ impl Drivechain { sidechain_number, client, _content: PhantomData::default(), + _tx_extension: PhantomData::default(), }) } } diff --git a/src/lib.rs b/src/lib.rs index 6983da7..35e4a57 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,7 +17,7 @@ pub use jsonrpsee; pub fn format_deposit_address(str_dest: &str) -> String { let this_sidechain = 0; let deposit_address: String = format!("s{}_{}_", this_sidechain, str_dest); - let hash = sha256::digest(deposit_address.as_bytes()).to_string(); + let hash = sha256::digest(deposit_address.as_bytes()); let hash: String = hash[..6].into(); format!("{}{}", deposit_address, hash) } diff --git a/src/mempool.rs b/src/mempool.rs index ea1cc2a..a5c5386 100644 --- a/src/mempool.rs +++ b/src/mempool.rs @@ -1,18 +1,30 @@ -use crate::types::{AuthorizedTransaction, OutPoint, Txid}; +use crate::types::{ + AuthorizedTransaction, DefaultCustomTxOutput, DefaultTxExtension, OutPoint, Txid, +}; use heed::types::*; use heed::{Database, RoTxn, RwTxn}; use serde::{Deserialize, Serialize}; +type MemPoolTransactions = Database< + OwnedType<[u8; 32]>, + SerdeBincode>, +>; + #[derive(Clone)] -pub struct MemPool { - pub transactions: Database, SerdeBincode>>, +pub struct MemPool< + A, + CustomTxExtension = DefaultTxExtension, + CustomTxOutput = DefaultCustomTxOutput, +> { + pub transactions: MemPoolTransactions, pub spent_utxos: Database, Unit>, } -impl< - A: Serialize + for<'de> Deserialize<'de> + 'static, - C: Serialize + for<'de> Deserialize<'de> + 'static, - > MemPool +impl MemPool +where + A: Serialize + for<'de> Deserialize<'de> + 'static, + CustomTxExtension: Serialize + for<'de> Deserialize<'de> + 'static, + CustomTxOutput: Serialize + for<'de> Deserialize<'de> + 'static, { pub const NUM_DBS: u32 = 1; @@ -28,7 +40,7 @@ impl< pub fn put( &self, txn: &mut RwTxn, - transaction: &AuthorizedTransaction, + transaction: &AuthorizedTransaction, ) -> Result<(), Error> { println!( "adding transaction {} to mempool", @@ -41,7 +53,7 @@ impl< self.spent_utxos.put(txn, input, &())?; } self.transactions - .put(txn, &transaction.transaction.txid().into(), &transaction)?; + .put(txn, &transaction.transaction.txid().into(), transaction)?; Ok(()) } @@ -54,7 +66,7 @@ impl< &self, txn: &RoTxn, number: usize, - ) -> Result>, Error> { + ) -> Result>, Error> { let mut transactions = vec![]; for item in self.transactions.iter(txn)?.take(number) { let (_, transaction) = item?; @@ -63,7 +75,10 @@ impl< Ok(transactions) } - pub fn take_all(&self, txn: &RoTxn) -> Result>, Error> { + pub fn take_all( + &self, + txn: &RoTxn, + ) -> Result>, Error> { let mut transactions = vec![]; for item in self.transactions.iter(txn)? { let (_, transaction) = item?; diff --git a/src/miner.rs b/src/miner.rs index b80519c..cd283f3 100644 --- a/src/miner.rs +++ b/src/miner.rs @@ -8,13 +8,19 @@ use std::str::FromStr as _; pub use crate::drivechain::MainClient; #[derive(Clone)] -pub struct Miner { - pub drivechain: Drivechain, - block: Option<(Header, Body)>, +pub struct Miner +{ + pub drivechain: Drivechain, + block: Option<(Header, Body)>, sidechain_number: u8, } -impl Miner { +impl Miner +where + A: Clone, + CustomTxExtension: Clone + Serialize, + CustomTxOutput: Clone + GetValue + Serialize, +{ pub fn new(sidechain_number: u8, main_addr: SocketAddr) -> Result { let drivechain = Drivechain::new(sidechain_number, main_addr)?; Ok(Self { @@ -38,7 +44,7 @@ impl Miner { amount: u64, height: u32, header: Header, - body: Body, + body: Body, ) -> Result<(), Error> { let str_hash_prev = header.prev_main_hash.to_string(); let critical_hash: [u8; 32] = header.hash().into(); @@ -62,7 +68,9 @@ impl Miner { Ok(()) } - pub async fn confirm_bmm(&mut self) -> Result)>, Error> { + pub async fn confirm_bmm( + &mut self, + ) -> Result)>, Error> { if let Some((header, body)) = self.block.clone() { self.drivechain.verify_bmm(&header).await?; self.block = None; diff --git a/src/net.rs b/src/net.rs index 189671d..ecd8f91 100644 --- a/src/net.rs +++ b/src/net.rs @@ -1,4 +1,6 @@ -use crate::types::{AuthorizedTransaction, Body, Header}; +use crate::types::{ + AuthorizedTransaction, Body, DefaultCustomTxOutput, DefaultTxExtension, Header, +}; use quinn::{ClientConfig, Connection, Endpoint, ServerConfig}; use serde::{Deserialize, Serialize}; use tokio::sync::RwLock; @@ -35,6 +37,29 @@ pub struct Peer { pub connection: Connection, } +#[derive(Debug, Serialize, Deserialize)] +pub enum Request +{ + GetBlock { + height: u32, + }, + PushTransaction { + transaction: AuthorizedTransaction, + }, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum Response +{ + Block { + header: Header, + body: Body, + }, + NoBlock, + TransactionAccepted, + TransactionRejected, +} + impl Peer { pub fn heart_beat(&self, state: &PeerState) -> Result<(), Error> { let message = bincode::serialize(state)?; @@ -42,52 +67,31 @@ impl Peer { Ok(()) } - pub async fn request< - A: Serialize + for<'de> Deserialize<'de>, - C: Serialize + for<'de> Deserialize<'de>, - >( + pub async fn request( &self, - message: &Request, - ) -> Result, Error> { + message: &Request, + ) -> Result, Error> + where + A: Serialize + for<'de> Deserialize<'de>, + CustomTxExtension: Serialize + for<'de> Deserialize<'de>, + CustomTxOutput: Serialize + for<'de> Deserialize<'de>, + { let (mut send, mut recv) = self.connection.open_bi().await?; let message = bincode::serialize(message)?; send.write_all(&message).await?; send.finish().await?; let response = recv.read_to_end(READ_LIMIT).await?; - let response: Response = bincode::deserialize(&response)?; + let response: Response = + bincode::deserialize(&response)?; Ok(response) } } -#[derive(Debug, Serialize, Deserialize)] -pub enum Request { - GetBlock { - height: u32, - }, - PushTransaction { - transaction: AuthorizedTransaction, - }, -} - -#[derive(Debug, Serialize, Deserialize)] -pub enum Response { - Block { header: Header, body: Body }, - NoBlock, - TransactionAccepted, - TransactionRejected, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, Default)] pub struct PeerState { pub block_height: u32, } -impl Default for PeerState { - fn default() -> Self { - Self { block_height: 0 } - } -} - impl Net { pub fn new(bind_addr: SocketAddr) -> Result { let (server, _) = make_server_endpoint(bind_addr)?; diff --git a/src/node.rs b/src/node.rs index 1c8a6ab..93777f4 100644 --- a/src/node.rs +++ b/src/node.rs @@ -11,61 +11,93 @@ use std::{ }; use tokio::sync::RwLock; +pub trait State: Sized { + type Error: CustomError + Debug + Send + Sync; + const NUM_DBS: u32; + const THIS_SIDECHAIN: u8; + fn new(env: &heed::Env) -> Result; + fn validate_filled_transaction( + &self, + txn: &RoTxn, + height: u32, + state: &crate::state::State, + transaction: &FilledTransaction, + ) -> Result<(), Self::Error>; + fn validate_body( + &self, + txn: &RoTxn, + height: u32, + state: &crate::state::State, + body: &Body, + ) -> Result<(), Self::Error>; + fn connect_body( + &self, + txn: &mut RwTxn, + height: u32, + state: &crate::state::State, + body: &Body, + ) -> Result<(), Self::Error>; +} + #[derive(Clone)] -pub struct Node { +pub struct Node { net: crate::net::Net, - state: crate::state::State, + state: crate::state::State, custom_state: S, - archive: crate::archive::Archive, - mempool: crate::mempool::MemPool, - drivechain: crate::drivechain::Drivechain, + archive: crate::archive::Archive, + mempool: crate::mempool::MemPool, + drivechain: crate::drivechain::Drivechain, env: heed::Env, } -impl< - A: Verify - + GetAddress - + Clone - + Debug - + Sync - + Send - + Serialize - + for<'de> Deserialize<'de> - + 'static, - C: Clone - + Debug - + Eq - + Serialize - + for<'de> Deserialize<'de> - + Sync - + Send - + GetValue - + 'static, - S: Clone + State + Send + Sync + 'static, - > Node +impl Node +where + A: Verify + + GetAddress + + Clone + + Debug + + Sync + + Send + + Serialize + + for<'de> Deserialize<'de> + + 'static, + CustomTxExtension: + Clone + Debug + for<'de> Deserialize<'de> + Send + Serialize + Sync + 'static, + CustomTxOutput: Clone + + Debug + + Eq + + Serialize + + for<'de> Deserialize<'de> + + Sync + + Send + + GetValue + + 'static, + S: Clone + State + Send + Sync + 'static, { pub fn new( datadir: &Path, bind_addr: SocketAddr, main_addr: SocketAddr, - ) -> Result>::Error>> { + ) -> Result>::Error>> { let env_path = datadir.join("data.mdb"); // let _ = std::fs::remove_dir_all(&env_path); std::fs::create_dir_all(&env_path)?; let env = heed::EnvOpenOptions::new() .map_size(10 * 1024 * 1024) // 10MB .max_dbs( - crate::state::State::::NUM_DBS + crate::state::State::::NUM_DBS + S::NUM_DBS - + crate::archive::Archive::::NUM_DBS - + crate::mempool::MemPool::::NUM_DBS, + + crate::archive::Archive::::NUM_DBS + + crate::mempool::MemPool::::NUM_DBS, ) .open(env_path)?; let state = crate::state::State::new(&env)?; let archive = crate::archive::Archive::new(&env)?; let mempool = crate::mempool::MemPool::new(&env)?; - let drivechain = - crate::drivechain::Drivechain::new(>::THIS_SIDECHAIN, main_addr)?; + let drivechain = crate::drivechain::Drivechain::new( + >::THIS_SIDECHAIN, + main_addr, + )?; let net = crate::net::Net::new(bind_addr)?; let custom_state = State::new(&env)?; Ok(Self { @@ -79,14 +111,19 @@ impl< }) } - pub fn get_height(&self) -> Result>::Error>> { + pub fn get_height( + &self, + ) -> Result>::Error>> { let txn = self.env.read_txn()?; Ok(self.archive.get_height(&txn)?) } pub fn get_best_hash( &self, - ) -> Result>::Error>> { + ) -> Result< + crate::types::BlockHash, + Error<>::Error>, + > { let txn = self.env.read_txn()?; Ok(self.archive.get_best_hash(&txn)?) } @@ -94,8 +131,8 @@ impl< pub fn validate_transaction( &self, txn: &RoTxn, - transaction: &AuthorizedTransaction, - ) -> Result>::Error>> { + transaction: &AuthorizedTransaction, + ) -> Result>::Error>> { let filled_transaction = self.state.fill_transaction(txn, &transaction.transaction)?; for (authorization, spent_utxo) in transaction .authorizations @@ -109,7 +146,7 @@ impl< if A::verify_transaction(transaction).is_err() { return Err(crate::state::Error::AuthorizationError.into()); } - let height = self.archive.get_height(&txn)?; + let height = self.archive.get_height(txn)?; self.custom_state.validate_filled_transaction( txn, height, @@ -124,12 +161,12 @@ impl< pub async fn submit_transaction( &self, - transaction: &AuthorizedTransaction, - ) -> Result<(), Error<>::Error>> { + transaction: &AuthorizedTransaction, + ) -> Result<(), Error<>::Error>> { { let mut txn = self.env.write_txn()?; - self.validate_transaction(&txn, &transaction)?; - self.mempool.put(&mut txn, &transaction)?; + self.validate_transaction(&txn, transaction)?; + self.mempool.put(&mut txn, transaction)?; txn.commit()?; } for peer in self.net.peers.read().await.values() { @@ -144,7 +181,8 @@ impl< pub fn get_spent_utxos( &self, outpoints: &[OutPoint], - ) -> Result, Error<>::Error>> { + ) -> Result, Error<>::Error>> + { let txn = self.env.read_txn()?; let mut spent = vec![]; for outpoint in outpoints { @@ -158,7 +196,10 @@ impl< pub fn get_utxos_by_addresses( &self, addresses: &HashSet
, - ) -> Result>, Error<>::Error>> { + ) -> Result< + HashMap>, + Error<>::Error>, + > { let txn = self.env.read_txn()?; let utxos = self.state.get_utxos_by_addresses(&txn, addresses)?; Ok(utxos) @@ -166,7 +207,10 @@ impl< pub fn get_all_transactions( &self, - ) -> Result>, Error<>::Error>> { + ) -> Result< + Vec>, + Error<>::Error>, + > { let txn = self.env.read_txn()?; let transactions = self.mempool.take_all(&txn)?; Ok(transactions) @@ -175,7 +219,13 @@ impl< pub fn get_transactions( &self, number: usize, - ) -> Result<(Vec>, u64), Error<>::Error>> { + ) -> Result< + ( + Vec>, + u64, + ), + Error<>::Error>, + > { let mut txn = self.env.write_txn()?; let transactions = self.mempool.take(&txn, number)?; let mut fee: u64 = 0; @@ -218,7 +268,10 @@ impl< pub fn get_pending_withdrawal_bundle( &self, - ) -> Result>, Error<>::Error>> { + ) -> Result< + Option>, + Error<>::Error>, + > { let txn = self.env.read_txn()?; Ok(self.state.get_pending_withdrawal_bundle(&txn)?) } @@ -226,8 +279,8 @@ impl< pub async fn submit_block( &self, header: &Header, - body: &Body, - ) -> Result<(), Error<>::Error>> { + body: &Body, + ) -> Result<(), Error<>::Error>> { let last_deposit_block_hash = { let txn = self.env.read_txn()?; self.state.get_last_deposit_block_hash(&txn)? @@ -238,18 +291,18 @@ impl< .get_two_way_peg_data(header.prev_main_hash, last_deposit_block_hash) .await?; let mut txn = self.env.write_txn()?; - self.state.validate_body(&txn, &body)?; + self.state.validate_body(&txn, body)?; let height = self.archive.get_height(&txn)?; self.custom_state - .validate_body(&txn, height, &self.state, &body)?; - self.state.connect_body(&mut txn, &body)?; + .validate_body(&txn, height, &self.state, body)?; + self.state.connect_body(&mut txn, body)?; self.custom_state - .connect_body(&mut txn, height, &self.state, &body)?; + .connect_body(&mut txn, height, &self.state, body)?; self.state .connect_two_way_peg_data(&mut txn, &two_way_peg_data, height)?; let bundle = self.state.get_pending_withdrawal_bundle(&txn)?; - self.archive.append_header(&mut txn, &header)?; - self.archive.put_body(&mut txn, &header, &body)?; + self.archive.append_header(&mut txn, header)?; + self.archive.put_body(&mut txn, header, body)?; for transaction in &body.transactions { self.mempool.delete(&mut txn, &transaction.txid())?; } @@ -265,7 +318,10 @@ impl< Ok(()) } - pub async fn connect(&self, addr: SocketAddr) -> Result<(), Error<>::Error>> { + pub async fn connect( + &self, + addr: SocketAddr, + ) -> Result<(), Error<>::Error>> { let peer = self.net.connect(addr).await?; let peer0 = peer.clone(); let node0 = self.clone(); @@ -280,7 +336,7 @@ impl< } } }); - let peer0 = peer.clone(); + let peer0 = peer; let node0 = self.clone(); tokio::spawn(async move { loop { @@ -299,7 +355,7 @@ impl< pub async fn heart_beat_listen( &self, peer: &crate::net::Peer, - ) -> Result<(), Error<>::Error>> { + ) -> Result<(), Error<>::Error>> { let message = match peer.connection.read_datagram().await { Ok(message) => message, Err(err) => { @@ -321,7 +377,7 @@ impl< pub async fn peer_listen( &self, peer: &crate::net::Peer, - ) -> Result<(), Error<>::Error>> { + ) -> Result<(), Error<>::Error>> { let (mut send, mut recv) = peer .connection .accept_bi() @@ -331,7 +387,7 @@ impl< .read_to_end(crate::net::READ_LIMIT) .await .map_err(crate::net::Error::from)?; - let message: Request = bincode::deserialize(&data)?; + let message: Request = bincode::deserialize(&data)?; match message { Request::GetBlock { height } => { let (header, body) = { @@ -358,12 +414,13 @@ impl< }; match valid { Err(err) => { - let response = Response::::TransactionRejected; + let response = + Response::::TransactionRejected; let response = bincode::serialize(&response)?; send.write_all(&response) .await .map_err(crate::net::Error::from)?; - return Err(err.into()); + return Err(err); } Ok(_) => { { @@ -377,12 +434,13 @@ impl< continue; } peer0 - .request(&Request::::PushTransaction { + .request(&Request::::PushTransaction { transaction: transaction.clone(), }) .await?; } - let response = Response::::TransactionAccepted; + let response = + Response::::TransactionAccepted; let response = bincode::serialize(&response)?; send.write_all(&response) .await @@ -395,7 +453,9 @@ impl< Ok(()) } - pub fn run(&mut self) -> Result<(), Error<>::Error>> { + pub fn run( + &mut self, + ) -> Result<(), Error<>::Error>> { // Listening to connections. let node = self.clone(); tokio::spawn(async move { @@ -533,31 +593,3 @@ pub enum Error { #[error("custom error")] Custom(#[from] E), } - -pub trait State: Sized { - type Error: CustomError + Debug + Send + Sync; - const NUM_DBS: u32; - const THIS_SIDECHAIN: u8; - fn new(env: &heed::Env) -> Result; - fn validate_filled_transaction( - &self, - txn: &RoTxn, - height: u32, - state: &crate::state::State, - transaction: &FilledTransaction, - ) -> Result<(), Self::Error>; - fn validate_body( - &self, - txn: &RoTxn, - height: u32, - state: &crate::state::State, - body: &Body, - ) -> Result<(), Self::Error>; - fn connect_body( - &self, - txn: &mut RwTxn, - height: u32, - state: &crate::state::State, - body: &Body, - ) -> Result<(), Self::Error>; -} diff --git a/src/state.rs b/src/state.rs index 61b0209..fcfa3c4 100644 --- a/src/state.rs +++ b/src/state.rs @@ -9,18 +9,22 @@ use std::fmt::Debug; use std::marker::PhantomData; #[derive(Clone)] -pub struct State { - pub utxos: Database, SerdeBincode>>, - pub pending_withdrawal_bundle: Database, SerdeBincode>>, +pub struct State +{ + pub utxos: Database, SerdeBincode>>, + pub pending_withdrawal_bundle: + Database, SerdeBincode>>, pub last_withdrawal_bundle_failure_height: Database, OwnedType>, pub last_deposit_block: Database, SerdeBincode>, pub _body: PhantomData, + pub _tx_extension: PhantomData, } -impl< - A: crate::types::Verify + GetAddress, - C: GetValue + Debug + Clone + Eq + Serialize + for<'de> Deserialize<'de> + 'static, - > State +impl State +where + A: crate::types::Verify + GetAddress, + CustomTxExtension: Clone + Serialize, + CustomTxOutput: GetValue + Debug + Clone + Eq + Serialize + for<'de> Deserialize<'de> + 'static, { pub const NUM_DBS: u32 = 5; pub const WITHDRAWAL_BUNDLE_FAILURE_GAP: u32 = 4; @@ -38,10 +42,14 @@ impl< last_withdrawal_bundle_failure_height, last_deposit_block, _body: PhantomData::default(), + _tx_extension: PhantomData::default(), }) } - pub fn get_utxos(&self, txn: &RoTxn) -> Result>, Error> { + pub fn get_utxos( + &self, + txn: &RoTxn, + ) -> Result>, Error> { let mut utxos = HashMap::new(); for item in self.utxos.iter(txn)? { let (outpoint, output) = item?; @@ -54,7 +62,7 @@ impl< &self, txn: &RoTxn, addresses: &HashSet
, - ) -> Result>, Error> { + ) -> Result>, Error> { let mut utxos = HashMap::new(); for item in self.utxos.iter(txn)? { let (outpoint, output) = item?; @@ -68,8 +76,8 @@ impl< pub fn fill_transaction( &self, txn: &RoTxn, - transaction: &Transaction, - ) -> Result, Error> { + transaction: &Transaction, + ) -> Result, Error> { let mut spent_utxos = vec![]; for input in &transaction.inputs { let utxo = self @@ -88,7 +96,7 @@ impl< &self, txn: &RoTxn, block_height: u32, - ) -> Result>, Error> { + ) -> Result>, Error> { use bitcoin::blockdata::{opcodes, script}; // Weight of a bundle with 0 outputs. const BUNDLE_0_WEIGHT: u64 = 504; @@ -103,7 +111,7 @@ impl< // destination -> (value, mainchain fee, spent_utxos) let mut address_to_aggregated_withdrawal = HashMap::< bitcoin::Address, - AggregatedWithdrawal, + AggregatedWithdrawal, >::new(); for item in self.utxos.iter(txn)? { let (outpoint, output) = item?; @@ -137,7 +145,7 @@ impl< address_to_aggregated_withdrawal.into_values().collect(); aggregated_withdrawals.sort_by_key(|a| std::cmp::Reverse(a.clone())); let mut fee = 0; - let mut spent_utxos = HashMap::>::new(); + let mut spent_utxos = HashMap::>::new(); let mut bundle_outputs = vec![]; for aggregated in &aggregated_withdrawals { if bundle_outputs.len() > MAX_BUNDLE_OUTPUTS { @@ -191,7 +199,7 @@ impl< let commitment = crate::types::hash(&inputs); let script = script::Builder::new() .push_opcode(opcodes::all::OP_RETURN) - .push_slice(&commitment) + .push_slice(commitment) .into_script(); let inputs_commitment_txout = bitcoin::TxOut { value: 0, @@ -226,13 +234,13 @@ impl< pub fn get_pending_withdrawal_bundle( &self, txn: &RoTxn, - ) -> Result>, Error> { + ) -> Result>, Error> { Ok(self.pending_withdrawal_bundle.get(txn, &0)?) } pub fn validate_filled_transaction( &self, - transaction: &FilledTransaction, + transaction: &FilledTransaction, ) -> Result { let mut value_in: u64 = 0; let mut value_out: u64 = 0; @@ -248,7 +256,11 @@ impl< Ok(value_in - value_out) } - pub fn validate_body(&self, txn: &RoTxn, body: &Body) -> Result { + pub fn validate_body( + &self, + txn: &RoTxn, + body: &Body, + ) -> Result { let mut coinbase_value: u64 = 0; for output in &body.coinbase { coinbase_value += output.get_value(); @@ -290,13 +302,13 @@ impl< &self, txn: &RoTxn, ) -> Result, Error> { - Ok(self.last_deposit_block.get(&txn, &0)?) + Ok(self.last_deposit_block.get(txn, &0)?) } pub fn connect_two_way_peg_data( &self, txn: &mut RwTxn, - two_way_peg_data: &TwoWayPegData, + two_way_peg_data: &TwoWayPegData, block_height: u32, ) -> Result<(), Error> { // Handle deposits. @@ -349,7 +361,11 @@ impl< Ok(()) } - pub fn connect_body(&self, txn: &mut RwTxn, body: &Body) -> Result<(), Error> { + pub fn connect_body( + &self, + txn: &mut RwTxn, + body: &Body, + ) -> Result<(), Error> { let merkle_root = body.compute_merkle_root(); for (vout, output) in body.coinbase.iter().enumerate() { let outpoint = OutPoint::Coinbase { diff --git a/src/types/mod.rs b/src/types/mod.rs index 5a24430..12b429a 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -5,12 +5,11 @@ mod address; mod hashes; mod types; -pub use types::*; pub use bitcoin; +pub use blake3; pub use bs58; pub use serde; -pub use blake3; - +pub use types::*; /* // Replace () with a type (usually an enum) for output data specific for your sidechain. @@ -41,14 +40,14 @@ pub enum WithdrawalBundleStatus { } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct WithdrawalBundle { - pub spent_utxos: HashMap>, +pub struct WithdrawalBundle { + pub spent_utxos: HashMap>, pub transaction: bitcoin::Transaction, } #[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct TwoWayPegData { - pub deposits: HashMap>, +pub struct TwoWayPegData { + pub deposits: HashMap>, pub deposit_block_hash: Option, pub bundle_statuses: HashMap, } @@ -66,14 +65,17 @@ pub struct DisconnectData { */ #[derive(Eq, PartialEq, Clone, Debug)] -pub struct AggregatedWithdrawal { - pub spent_utxos: HashMap>, +pub struct AggregatedWithdrawal { + pub spent_utxos: HashMap>, pub main_address: bitcoin::Address, pub value: u64, pub main_fee: u64, } -impl Ord for AggregatedWithdrawal { +impl Ord for AggregatedWithdrawal +where + CustomTxOutput: std::cmp::Eq, +{ fn cmp(&self, other: &Self) -> Ordering { if self == other { Ordering::Equal @@ -88,7 +90,10 @@ impl Ord for AggregatedWithdrawal { } } -impl PartialOrd for AggregatedWithdrawal { +impl PartialOrd for AggregatedWithdrawal +where + CustomTxOutput: std::cmp::Eq, +{ fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } diff --git a/src/types/types.rs b/src/types/types.rs index ffeb6e7..56de7a4 100644 --- a/src/types/types.rs +++ b/src/types/types.rs @@ -3,6 +3,10 @@ pub use crate::types::hashes::*; use serde::{Deserialize, Serialize}; use std::collections::HashMap; +// remove once ! is stabilized +// see tracking issue: https://github.com/rust-lang/rust/issues/35121 +use never_type::Never; + #[derive(Hash, Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum OutPoint { // Created by transactions. @@ -23,15 +27,24 @@ impl std::fmt::Display for OutPoint { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct Output { - pub address: Address, - pub content: Content, +/// The default custom tx output. +/// The `Never` type is used to express that +/// the custom output variant is not used by default. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DefaultCustomTxOutput(pub Never); + +impl Serialize for DefaultCustomTxOutput { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_unit_struct("DefaultCustomTxOutput") + } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum Content { - Custom(C), +pub enum Content { + Custom(CustomTxOutput), Value(u64), Withdrawal { value: u64, @@ -40,6 +53,12 @@ pub enum Content { }, } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Output { + pub address: Address, + pub content: Content, +} + impl Content { pub fn is_custom(&self) -> bool { matches!(self, Self::Custom(_)) @@ -77,25 +96,44 @@ impl GetValue for Content { } } +/// The default tx extension +pub(crate) type DefaultTxExtension = (); + +/// `CustomTxExtension` is used to add custom data to a transaction. +/// `CustomTxOutput` is used to add support for custom output kinds. #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Transaction { +pub struct Transaction< + CustomTxExtension = DefaultTxExtension, + CustomTxOutput = DefaultCustomTxOutput, +> { pub inputs: Vec, - pub outputs: Vec>, + pub outputs: Vec>, + pub extension: CustomTxExtension, } -impl Transaction { +impl Transaction +where + CustomTxExtension: Serialize, + CustomTxOutput: Serialize, +{ pub fn txid(&self) -> Txid { hash(self).into() } } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct FilledTransaction { - pub transaction: Transaction, - pub spent_utxos: Vec>, +pub struct FilledTransaction< + CustomTxExtension = DefaultTxExtension, + CustomTxOutput = DefaultCustomTxOutput, +> { + pub transaction: Transaction, + pub spent_utxos: Vec>, } -impl FilledTransaction { +impl FilledTransaction +where + CustomTxOutput: GetValue, +{ pub fn get_value_in(&self) -> u64 { self.spent_utxos.iter().map(GetValue::get_value).sum() } @@ -120,23 +158,31 @@ impl FilledTransaction { } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct AuthorizedTransaction { - pub transaction: Transaction, +pub struct AuthorizedTransaction< + A, + CustomTxExtension = DefaultTxExtension, + CustomTxOutput = DefaultCustomTxOutput, +> { + pub transaction: Transaction, /// Authorization is called witness in Bitcoin. pub authorizations: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Body { - pub coinbase: Vec>, - pub transactions: Vec>, +pub struct Body { + pub coinbase: Vec>, + pub transactions: Vec>, pub authorizations: Vec, } -impl Body { +impl Body +where + CustomTxExtension: Serialize, + CustomTxOutput: Clone + GetValue + Serialize, +{ pub fn new( - authorized_transactions: Vec>, - coinbase: Vec>, + authorized_transactions: Vec>, + coinbase: Vec>, ) -> Self { let mut authorizations = Vec::with_capacity( authorized_transactions @@ -169,7 +215,7 @@ impl Body { .collect() } - pub fn get_outputs(&self) -> HashMap> { + pub fn get_outputs(&self) -> HashMap> { let mut outputs = HashMap::new(); let merkle_root = self.compute_merkle_root(); for (vout, output) in self.coinbase.iter().enumerate() { @@ -207,12 +253,20 @@ impl GetValue for () { } } -pub trait Verify { +impl GetValue for Never { + fn get_value(&self) -> u64 { + 0 + } +} + +pub trait Verify { type Error; - fn verify_transaction(transaction: &AuthorizedTransaction) -> Result<(), Self::Error> + fn verify_transaction( + transaction: &AuthorizedTransaction, + ) -> Result<(), Self::Error> where Self: Sized; - fn verify_body(body: &Body) -> Result<(), Self::Error> + fn verify_body(body: &Body) -> Result<(), Self::Error> where Self: Sized; } diff --git a/src/wallet.rs b/src/wallet.rs index 2caaf31..32d9c82 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -1,6 +1,7 @@ pub use crate::authorization::{get_address, Authorization}; use crate::types::{ - bitcoin, Address, AuthorizedTransaction, GetValue, OutPoint, Output, Transaction, + bitcoin, Address, AuthorizedTransaction, DefaultCustomTxOutput, GetValue, OutPoint, Output, + Transaction, }; use byteorder::{BigEndian, ByteOrder}; use ed25519_dalek_bip32::*; @@ -11,16 +12,19 @@ use std::collections::{HashMap, HashSet}; use std::path::Path; #[derive(Clone)] -pub struct Wallet { +pub struct Wallet { env: heed::Env, // FIXME: Don't store the seed in ddktext. seed: Database, OwnedType<[u8; 64]>>, pub address_to_index: Database, OwnedType<[u8; 4]>>, pub index_to_address: Database, SerdeBincode
>, - pub utxos: Database, SerdeBincode>>, + pub utxos: Database, SerdeBincode>>, } -impl Deserialize<'de> + 'static> Wallet { +impl Wallet +where + CustomTxOutput: GetValue + Clone + Serialize + for<'de> Deserialize<'de> + 'static, +{ pub const NUM_DBS: u32 = 5; pub fn new(path: &Path) -> Result { @@ -44,7 +48,7 @@ impl Deserialize<'de> + 'static> Wall pub fn set_seed(&self, seed: &[u8; 64]) -> Result<(), Error> { let mut txn = self.env.write_txn()?; - self.seed.put(&mut txn, &0, &seed)?; + self.seed.put(&mut txn, &0, seed)?; self.address_to_index.clear(&mut txn)?; self.index_to_address.clear(&mut txn)?; self.utxos.clear(&mut txn)?; @@ -57,13 +61,14 @@ impl Deserialize<'de> + 'static> Wall Ok(self.seed.get(&txn, &0)?.is_some()) } - pub fn create_withdrawal( + pub fn create_withdrawal( &self, main_address: bitcoin::Address, value: u64, main_fee: u64, fee: u64, - ) -> Result, Error> { + extension: CustomTxExtension, + ) -> Result, Error> { let (total, coins) = self.select_coins(value + fee + main_fee)?; let change = total - value - fee; let inputs = coins.into_keys().collect(); @@ -81,15 +86,20 @@ impl Deserialize<'de> + 'static> Wall content: crate::types::Content::Value(change), }, ]; - Ok(Transaction { inputs, outputs }) + Ok(Transaction { + inputs, + outputs, + extension, + }) } - pub fn create_transaction( + pub fn create_transaction( &self, address: Address, value: u64, fee: u64, - ) -> Result, Error> { + extension: CustomTxExtension, + ) -> Result, Error> { let (total, coins) = self.select_coins(value + fee)?; let change = total - value - fee; let inputs = coins.into_keys().collect(); @@ -103,10 +113,17 @@ impl Deserialize<'de> + 'static> Wall content: crate::types::Content::Value(change), }, ]; - Ok(Transaction { inputs, outputs }) + Ok(Transaction { + inputs, + outputs, + extension, + }) } - pub fn select_coins(&self, value: u64) -> Result<(u64, HashMap>), Error> { + pub fn select_coins( + &self, + value: u64, + ) -> Result<(u64, HashMap>), Error> { let txn = self.env.read_txn()?; let mut utxos = vec![]; for item in self.utxos.iter(&txn)? { @@ -129,7 +146,7 @@ impl Deserialize<'de> + 'static> Wall if total < value { return Err(Error::NotEnoughFunds); } - return Ok((total, selected)); + Ok((total, selected)) } pub fn delete_utxos(&self, outpoints: &[OutPoint]) -> Result<(), Error> { @@ -141,7 +158,10 @@ impl Deserialize<'de> + 'static> Wall Ok(()) } - pub fn put_utxos(&self, utxos: &HashMap>) -> Result<(), Error> { + pub fn put_utxos( + &self, + utxos: &HashMap>, + ) -> Result<(), Error> { let mut txn = self.env.write_txn()?; for (outpoint, output) in utxos { self.utxos.put(&mut txn, outpoint, output)?; @@ -160,7 +180,7 @@ impl Deserialize<'de> + 'static> Wall Ok(balance) } - pub fn get_utxos(&self) -> Result>, Error> { + pub fn get_utxos(&self) -> Result>, Error> { let txn = self.env.read_txn()?; let mut utxos = HashMap::new(); for item in self.utxos.iter(&txn)? { @@ -182,8 +202,8 @@ impl Deserialize<'de> + 'static> Wall pub fn authorize( &self, - transaction: Transaction, - ) -> Result, Error> { + transaction: Transaction, + ) -> Result, Error> { let txn = self.env.read_txn()?; let mut authorizations = vec![]; for input in &transaction.inputs {