├── db ├── initial.trustnote.sqlite └── initial.trustnote-light.sqlite ├── src ├── network │ ├── mod.rs │ ├── wallet.rs │ └── network.rs ├── utils │ ├── mod.rs │ ├── fifo_cache.rs │ ├── atomic_lock.rs │ ├── event.rs │ └── map_lock.rs ├── time.rs ├── error.rs ├── my_witness.rs ├── signature.rs ├── lib.rs ├── light_wallet.rs ├── config.rs ├── mc_outputs.rs ├── definition.rs ├── db.rs ├── object_hash.rs ├── headers_commission.rs ├── parent_composer.rs ├── paid_witnessing.rs ├── wallet.rs ├── witness_proof.rs ├── graph.rs ├── joint_storage.rs └── spec.rs ├── .travis.yml ├── .gitignore ├── hub ├── Cargo.toml ├── settings.json └── src │ ├── timer.rs │ └── main.rs ├── wallet_base ├── Cargo.toml └── src │ └── lib.rs ├── ttt ├── Cargo.toml └── src │ ├── config.rs │ ├── ttt.yml │ └── main.rs ├── settings.json ├── Cargo.toml ├── LICENSE ├── RaspberryPi.md ├── WIP_INIT.md └── README.md /db/initial.trustnote.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trustnote-org/rust-trustnote/HEAD/db/initial.trustnote.sqlite -------------------------------------------------------------------------------- /db/initial.trustnote-light.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trustnote-org/rust-trustnote/HEAD/db/initial.trustnote-light.sqlite -------------------------------------------------------------------------------- /src/network/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod hub; 2 | mod network; 3 | pub mod wallet; 4 | 5 | pub use self::network::{WsConnection, WsServer}; 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: rust 3 | cache: cargo 4 | 5 | os: 6 | - linux 7 | 8 | rust: 9 | - nightly 10 | 11 | evn: 12 | - RUST_BACKTRACE=1 13 | 14 | script: 15 | - cargo test --all 16 | -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod atomic_lock; 2 | #[macro_use] 3 | pub mod event; 4 | pub mod fifo_cache; 5 | pub mod map_lock; 6 | 7 | pub use self::atomic_lock::{AtomicLock, AtomicLockGuard}; 8 | pub use self::fifo_cache::FifoCache; 9 | pub use self::map_lock::{MapLock, MapLockGuard}; 10 | -------------------------------------------------------------------------------- /src/time.rs: -------------------------------------------------------------------------------- 1 | use std::time::{SystemTime, UNIX_EPOCH}; 2 | 3 | /// return milliseconds since unix epoch 4 | pub fn now() -> u64 { 5 | let dur = SystemTime::now() 6 | .duration_since(UNIX_EPOCH) 7 | .expect("Time went backwards"); 8 | 9 | dur.as_secs() * 1000 + u64::from(dur.subsec_nanos()) / 1_000_000 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | /.vscode/ 5 | /rls/ 6 | 7 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 8 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 9 | Cargo.lock 10 | 11 | # These are backup files generated by rustfmt 12 | **/*.rs.bk 13 | 14 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use failure::Error; 2 | 3 | #[derive(Debug, Fail)] 4 | pub enum TrustnoteError { 5 | // TODO: need to define own error 6 | #[fail(display = "catchup prepare already current")] 7 | CatchupAlreadyCurrent, 8 | #[fail(display = "some witnesses have references in their addresses")] 9 | WitnessChanged, 10 | } 11 | 12 | pub type Result = ::std::result::Result; 13 | -------------------------------------------------------------------------------- /hub/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | description = "trustnote hub rust implmentation" 3 | name = "trustnote_hub" 4 | version = "0.2.0" 5 | authors = ["Xudong Huang "] 6 | license = "MIT" 7 | 8 | [dependencies] 9 | trustnote = { path = ".."} 10 | 11 | may = "0.3" 12 | log = "0.4" 13 | fern = "0.5" 14 | chrono = "0.4" 15 | serde_json = "1" 16 | may_signal = {git = "https://github.com/Xudong-Huang/may_signal.git"} 17 | -------------------------------------------------------------------------------- /wallet_base/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | description = "trustnote wallet basic rust implmentation" 3 | name = "trustnote_wallet_base" 4 | version = "0.1.0" 5 | authors = ["Xudong Huang "] 6 | license = "MIT" 7 | 8 | [dependencies] 9 | rand = "0.5" 10 | sha2 = "0.7" 11 | base64 = "0.9" 12 | bitcoin = "0.13" 13 | failure = "0.1" 14 | secp256k1 = "0.9" 15 | serde_json = "1" 16 | lazy_static = "1" 17 | trustnote = { path = ".."} 18 | wallet = { git = "https://github.com/rust-bitcoin/rust-wallet.git" } 19 | 20 | [dev-dependencies] 21 | 22 | -------------------------------------------------------------------------------- /ttt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | description = "trustnote wallet cli rust implmentation" 3 | name = "ttt" 4 | version = "0.1.0" 5 | authors = ["Xudong Huang "] 6 | license = "MIT" 7 | 8 | [dependencies] 9 | trustnote = { path = ".."} 10 | trustnote_wallet_base = { path = "../wallet_base" } 11 | clap = {version = "2", features = ["yaml"]} 12 | 13 | may = "0.3" 14 | log = "0.4" 15 | fern = "0.5" 16 | chrono = "0.4" 17 | failure = "0.1" 18 | 19 | serde = "1" 20 | serde_json = "1" 21 | serde_derive = "1" 22 | 23 | [dependencies.rusqlite] 24 | version = "0.13" 25 | features = ["bundled"] 26 | -------------------------------------------------------------------------------- /settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "genesis_unit": "V/NuDxzT7VFa/AqfBsAZ8suG4uj3u+l0kXOLE+nP+dU=", 3 | "witnesses": [ 4 | "6LDM27ELDDAJBTNTVVQQYW7MWOK3F6WD", 5 | "BP2NYKORMOB5SEUTFSVPF2CMSQSVEZOS", 6 | "C6D4XKXDO4JAUT3BR27RM3UHKYGILR3X", 7 | "CGCU5BBDWY2ZU3XKUXNGDTXDY7VXXJNJ", 8 | "E45DPZHBPI7YX3CDG7HWTWBWRNGBV6C3", 9 | "EPG47NW4DDKIBUFZBDVQU3KHYCCMXTDN", 10 | "FF6X4KX3OOAAZUYWXDAHQJIJ5HDZLSXL", 11 | "JVFHPXAA7FJEJU3TSTR5ETYVOXHOBR4H", 12 | "MWJTSFCRBCV2CVT3SCDYZW2F2N3JKPIP", 13 | "NJSDFSIRZT5I5YQONDNEMKXSFNJPSO6A", 14 | "OALYXCMDI6ODRWMY6YO6WUPL6Q5ZBAO5", 15 | "UABSDF77S6SU4FDAXWTYIODVODCAA22A" 16 | ] 17 | } -------------------------------------------------------------------------------- /hub/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "remote_hub": [ 3 | "119.28.86.54:6616" 4 | ], 5 | "hub_server_port": 6615, 6 | "genesis_unit": "V/NuDxzT7VFa/AqfBsAZ8suG4uj3u+l0kXOLE+nP+dU=", 7 | "witnesses": [ 8 | "6LDM27ELDDAJBTNTVVQQYW7MWOK3F6WD", 9 | "BP2NYKORMOB5SEUTFSVPF2CMSQSVEZOS", 10 | "C6D4XKXDO4JAUT3BR27RM3UHKYGILR3X", 11 | "CGCU5BBDWY2ZU3XKUXNGDTXDY7VXXJNJ", 12 | "E45DPZHBPI7YX3CDG7HWTWBWRNGBV6C3", 13 | "EPG47NW4DDKIBUFZBDVQU3KHYCCMXTDN", 14 | "FF6X4KX3OOAAZUYWXDAHQJIJ5HDZLSXL", 15 | "JVFHPXAA7FJEJU3TSTR5ETYVOXHOBR4H", 16 | "MWJTSFCRBCV2CVT3SCDYZW2F2N3JKPIP", 17 | "NJSDFSIRZT5I5YQONDNEMKXSFNJPSO6A", 18 | "OALYXCMDI6ODRWMY6YO6WUPL6Q5ZBAO5", 19 | "UABSDF77S6SU4FDAXWTYIODVODCAA22A" 20 | ] 21 | } -------------------------------------------------------------------------------- /src/my_witness.rs: -------------------------------------------------------------------------------- 1 | use config; 2 | use db; 3 | use error::Result; 4 | 5 | lazy_static! { 6 | pub static ref MY_WITNESSES: Vec = read_my_witnesses().unwrap(); 7 | } 8 | 9 | fn read_my_witnesses() -> Result> { 10 | // read from database 11 | let db = db::DB_POOL.get_connection(); 12 | let witnesses = db.get_my_witnesses()?; 13 | 14 | // if the data base is empty we should wait until 15 | if witnesses.is_empty() { 16 | let config_witnesses = config::get_witnesses(); 17 | db.insert_witnesses(&config_witnesses)?; 18 | Ok(config_witnesses.to_vec()) 19 | } else { 20 | assert_eq!(witnesses.len(), config::COUNT_WITNESSES); 21 | Ok(witnesses) 22 | } 23 | } 24 | 25 | #[cfg(test)] 26 | mod tests { 27 | use super::*; 28 | 29 | #[test] 30 | fn get_witnesses() { 31 | assert_eq!(MY_WITNESSES.len(), config::COUNT_WITNESSES); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | description = "trustnote rust implmentation" 3 | name = "trustnote" 4 | version = "0.2.0" 5 | authors = ["Xudong Huang "] 6 | license = "MIT" 7 | 8 | [dependencies] 9 | may = "0.3" 10 | log = "0.4" 11 | url = "1.7" 12 | rand = "0.5" 13 | chrono = "0.4" 14 | config = "0.9" 15 | failure = "0.1" 16 | app_dirs = "1.2" 17 | indexmap = "1.0" 18 | num_cpus = "1" 19 | crossbeam = "0.4" 20 | may_waiter = "0.1" 21 | lazy_static = "1" 22 | tungstenite = "0.6" 23 | 24 | serde = "1" 25 | serde_json = "1" 26 | serde_derive = "1" 27 | 28 | base64 = "0.9" 29 | base32 = "0.3" 30 | secp256k1 = "0.9" 31 | sha1 = "0.6" 32 | sha2 = "0.7" 33 | ripemd160 = "0.7" 34 | bit-vec = "0.5" 35 | 36 | rust-embed = { git = "https://github.com/Xudong-Huang/rust-embed.git" } 37 | 38 | [dependencies.rusqlite] 39 | version = "0.13" 40 | features = ["bundled"] 41 | 42 | 43 | [profile.release] 44 | lto = true 45 | # panic = "abort" 46 | 47 | [workspace] 48 | members = [ 49 | "hub", 50 | "ttt", 51 | "wallet_base", 52 | ] 53 | -------------------------------------------------------------------------------- /src/utils/fifo_cache.rs: -------------------------------------------------------------------------------- 1 | extern crate indexmap; 2 | 3 | use may::sync::RwLock; 4 | use std::hash::Hash; 5 | 6 | pub struct FifoCache { 7 | inner: RwLock>, 8 | capacity: usize, 9 | } 10 | 11 | impl FifoCache { 12 | pub fn with_capacity(capacity: usize) -> FifoCache { 13 | FifoCache { 14 | inner: RwLock::new(indexmap::IndexMap::with_capacity(capacity)), 15 | capacity, 16 | } 17 | } 18 | 19 | #[inline] 20 | pub fn get(&self, k: &K) -> Option { 21 | self.inner.read().unwrap().get(k).cloned() 22 | } 23 | 24 | #[inline] 25 | pub fn insert(&self, k: K, v: V) -> Option { 26 | let mut map = self.inner.write().unwrap(); 27 | while self.capacity - 1 < map.len() { 28 | map.pop(); 29 | } 30 | map.insert(k, v) 31 | } 32 | 33 | #[inline] 34 | pub fn remove(&self, k: &K) -> Option { 35 | self.inner.write().unwrap().remove(k) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 TrustNote Foundation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/utils/atomic_lock.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicBool, Ordering}; 2 | 3 | #[derive(Debug, Default)] 4 | pub struct AtomicLock { 5 | is_locked: AtomicBool, 6 | } 7 | 8 | #[derive(Debug)] 9 | pub struct AtomicLockGuard<'a> { 10 | owner: &'a AtomicLock, 11 | } 12 | 13 | impl<'a> Drop for AtomicLockGuard<'a> { 14 | fn drop(&mut self) { 15 | self.owner.is_locked.store(false, Ordering::Release); 16 | } 17 | } 18 | 19 | impl AtomicLock { 20 | pub fn new() -> Self { 21 | AtomicLock { 22 | is_locked: AtomicBool::new(false), 23 | } 24 | } 25 | 26 | pub fn try_lock(&self) -> Option { 27 | if self 28 | .is_locked 29 | .compare_and_swap(false, true, Ordering::SeqCst) 30 | { 31 | return None; 32 | } 33 | 34 | Some(AtomicLockGuard { owner: self }) 35 | } 36 | 37 | pub fn is_locked(&self) -> bool { 38 | self.is_locked.load(Ordering::Acquire) 39 | } 40 | } 41 | 42 | #[test] 43 | fn test_atomic_lock() { 44 | let lock = AtomicLock::new(); 45 | assert_eq!(lock.is_locked(), false); 46 | let g = lock.try_lock().unwrap(); 47 | assert_eq!(lock.is_locked(), true); 48 | drop(g); 49 | assert_eq!(lock.is_locked(), false); 50 | } 51 | -------------------------------------------------------------------------------- /RaspberryPi.md: -------------------------------------------------------------------------------- 1 | # Building TrustNote Rust SDK From Source for Raspberry Pi 2 | 3 | *Note: The following procedures were tested to work for both raspberry pi 2b and 2b+.* 4 | 5 | ## Build for the host (Ubuntu) 6 | 7 | ### Install Rust 8 | ``` 9 | curl https://sh.rustup.rs -sSf | sh 10 | ``` 11 | 12 | ### Config your current shell 13 | ``` 14 | source $HOME/.cargo/env 15 | ``` 16 | 17 | ### Clone TrustNote Rust SDK 18 | ``` 19 | git clone https://github.com/trustnote/rust-trustnote.git 20 | ``` 21 | 22 | ### Build the project 23 | ``` 24 | cargo build 25 | ``` 26 | 27 | *Note: You may need to run ```sudo apt install libssl-dev``` if you see error messages like this:* 28 | ``` 29 | error: failed to run custom build command for `openssl-sys v0.9.35` 30 | ``` 31 | 32 | ## Cross Compile 33 | 34 | ### Install the environment 35 | ``` 36 | sudo apt-get install make git-core ncurses-dev gcc-arm* 37 | ``` 38 | 39 | ### Configure cargo for cross compilation 40 | ``` 41 | cd ttt 42 | nano .cargo/config 43 | ``` 44 | 45 | ### Edit the contents: 46 | ``` 47 | [target.arm-unknown-linux-gnueabi] 48 | linker = "arm-linux-gnueabi-gcc" 49 | ar = "arm-linux-gnueabi-gcc" 50 | ``` 51 | 52 | ### Install the cross compiled standard crates 53 | ``` 54 | rustup target add armv7-unknown-linux-gnueabihf 55 | ``` 56 | 57 | ### Build the project for the target 58 | ``` 59 | cargo build --target arm-unknown-linux-gnueabi 60 | ``` 61 | 62 | If successful, you will find the executable ttt from ```target/arm-unknown-linux-gnueabi/debug/``` where you can deploy the binary to the target. 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /hub/src/timer.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use may::coroutine; 4 | use trustnote::db; 5 | use trustnote::joint_storage; 6 | use trustnote::network::hub; 7 | 8 | pub fn start_global_timers() { 9 | // find and handle ready joints 10 | go!(move || loop { 11 | info!("find_and_handle_joints_that_are_ready"); 12 | let mut db = db::DB_POOL.get_connection(); 13 | t!(hub::find_and_handle_joints_that_are_ready(&mut db, None)); 14 | coroutine::sleep(Duration::from_secs(5)); 15 | }); 16 | 17 | // request needed joints that were not received during the previous session 18 | go!(move || loop { 19 | let db = db::DB_POOL.get_connection(); 20 | info!("re_requeset_lost_joints"); 21 | t!(hub::re_requeset_lost_joints(&db)); 22 | coroutine::sleep(Duration::from_secs(8)); 23 | }); 24 | 25 | // remove those junk joints 26 | go!(move || loop { 27 | coroutine::sleep(Duration::from_secs(30 * 60)); 28 | let db = db::DB_POOL.get_connection(); 29 | info!("purge_junk_unhandled_joints"); 30 | t!(hub::purge_junk_unhandled_joints(&db)); 31 | }); 32 | 33 | // purge uncovered nonserial joints 34 | go!(move || loop { 35 | coroutine::sleep(Duration::from_secs(60)); 36 | info!("purge_uncovered_nonserial_joints_under_lock"); 37 | t!(joint_storage::purge_uncovered_nonserial_joints_under_lock()); 38 | }); 39 | 40 | // auto connection if peers count is under threshold 41 | go!(move || loop { 42 | coroutine::sleep(Duration::from_secs(30)); 43 | info!("auto conntect to other peers"); 44 | t!(hub::auto_connection()); 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /WIP_INIT.md: -------------------------------------------------------------------------------- 1 | Trustnote rust init project 2 | ## Goal 3 | * to pass simple test cases for DAG based block chain 4 | * to supply a basic dev framework for future rust development 5 | 6 | ## Supported and Not supported 7 | * nodes discovery is not included(each node would have a fixed peer list) 8 | * only payment unit is supported, which means other messages and functions are not supported in this version 9 | 10 | ## Methodology 11 | * rewrite subset of JS based Trustnote, no algorithm changed, just language level translation 12 | 13 | ## Components 14 | all the following components are implemented by RUST. 15 | * network - wss based interfaces 16 | * database - sqlite based storage 17 | * specs - json data serialization/de-serialization for unit 18 | * consensus - DAG algorithm (this would be a big project, need to learning a lot about the current implementation) 19 | * crypto /hash 20 | 21 | ## Functions need to develop 22 | * catchup DAG (both from database and network **Big work**) 23 | * create a unit 24 | * validate a unit 25 | * save a unit 26 | * broadcast a unit 27 | * receive a unit 28 | * stable a unit (commits unit) 29 | 30 | ## Scenario 31 | the node act as a HUB, receive unit from headless wallet, validate and save it, and then broadcast to a normal JS version Hub and verify it works. 32 | 33 | How to see that it works? By using the Trustnote explorer to verify if the unit is successfully saved on the main chain. 34 | 35 | ## Challenges 36 | * not fully understand every aspect of the Trustnote 37 | * lack of qualified rust developers 38 | * hard to absorb current JS implementation 39 | * need Trustnote experts to participant in the project, from discussion to implementation and testing 40 | 41 | 42 | ## Time estimation of the project (total 25~35MD) 43 | * project overall design 2 MD 44 | * component break and interface design 5 MD 45 | * component implementation - 10~20 MD 46 | * unit test and integration test - 3 MD 47 | * debug and fix errors need unexpected time - 5+ MD -------------------------------------------------------------------------------- /ttt/src/config.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | 3 | use serde_json; 4 | use trustnote::Result; 5 | use trustnote_wallet_base::*; 6 | 7 | const SETTINGS_FILE: &str = "settings.json"; 8 | 9 | #[derive(Debug, Serialize, Deserialize)] 10 | pub struct Settings { 11 | pub hub_url: Vec, 12 | pub mnemonic: String, 13 | } 14 | 15 | impl Default for Settings { 16 | fn default() -> Self { 17 | let hub_url; 18 | if cfg!(debug_assertions) { 19 | hub_url = vec![String::from("119.28.86.54:6616")]; 20 | } else { 21 | hub_url = vec![String::from("raytest.trustnote.org:80")]; 22 | } 23 | 24 | Settings { 25 | hub_url, 26 | mnemonic: mnemonic("") 27 | .expect("failed to generate mnemonic") 28 | .to_string(), 29 | } 30 | } 31 | } 32 | 33 | impl Settings { 34 | pub fn show_config(&self) { 35 | use std::io::stdout; 36 | println!("settings:"); 37 | serde_json::to_writer_pretty(stdout(), self).unwrap(); 38 | println!("\n"); 39 | } 40 | } 41 | 42 | fn open_settings() -> Result { 43 | let mut settings_path = ::std::env::current_dir()?; 44 | settings_path.push(SETTINGS_FILE); 45 | let file = File::open(settings_path)?; 46 | let settings = serde_json::from_reader(file)?; 47 | Ok(settings) 48 | } 49 | 50 | fn save_settings(settings: &Settings) -> Result<()> { 51 | let mut settings_path = ::std::env::current_dir()?; 52 | settings_path.push(SETTINGS_FILE); 53 | 54 | let file = File::create(settings_path)?; 55 | 56 | serde_json::to_writer_pretty(file, settings)?; 57 | Ok(()) 58 | } 59 | 60 | pub fn update_mnemonic(mnemonic: &str) -> Result<()> { 61 | let mnemonic = Mnemonic::from(mnemonic)?.to_string(); 62 | let mut settings = get_settings(); 63 | if settings.mnemonic != mnemonic { 64 | println!("will update mnemonic to: {}", mnemonic); 65 | settings.mnemonic = mnemonic; 66 | } 67 | save_settings(&settings) 68 | } 69 | 70 | pub fn get_settings() -> Settings { 71 | match open_settings() { 72 | Ok(s) => s, 73 | Err(_) => { 74 | warn!("can't open settings.json, will use default settings"); 75 | let settings = Settings::default(); 76 | save_settings(&settings).expect("failed to save settings"); 77 | settings 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/signature.rs: -------------------------------------------------------------------------------- 1 | use base64; 2 | use error::Result; 3 | use failure::ResultExt; 4 | use secp256k1::{key, Message, Secp256k1, Signature}; 5 | 6 | lazy_static! { 7 | // initialize consume too much memory, init it in thread context 8 | static ref SECP256K1: Secp256k1 = ::std::thread::spawn(|| Secp256k1::new()).join().unwrap(); 9 | } 10 | 11 | pub trait Signer { 12 | fn sign(&self, hash: &[u8], address: &str) -> Result; 13 | } 14 | 15 | /// return a bas64 string for the encrypted hash with the priv_key 16 | pub fn sign(hash: &[u8], priv_key: &[u8]) -> Result { 17 | let msg = Message::from_slice(hash)?; 18 | let priv_key = key::SecretKey::from_slice(&SECP256K1, priv_key)?; 19 | 20 | //Sign it with the secret key 21 | let recoverable = SECP256K1 22 | .sign_recoverable(&msg, &priv_key) 23 | .context("SECP256K1 sign failed")?; 24 | let (_, sig) = recoverable.serialize_compact(&SECP256K1); 25 | Ok(base64::encode(&sig[..])) 26 | } 27 | 28 | /// verify the bas64 string signiture with the hash and pub key (a bas64 string) 29 | pub fn verify(hash: &[u8], b64_sig: &str, b64_pub_key: &str) -> Result<()> { 30 | let msg = Message::from_slice(hash)?; 31 | let sig = &base64::decode(b64_sig)?; 32 | let pub_key = key::PublicKey::from_slice(&SECP256K1, &base64::decode(b64_pub_key)?)?; 33 | 34 | // verify the signature 35 | let signature = 36 | Signature::from_compact(&SECP256K1, sig).context("invalid SECP256K1 signature")?; 37 | SECP256K1 38 | .verify(&msg, &signature, &pub_key) 39 | .context("SECP256K1 verify failed")?; 40 | Ok(()) 41 | } 42 | 43 | #[test] 44 | fn test_signature() -> Result<()> { 45 | let hash = "KLop9582tzXZJbytWjiWLcnpEdvJI7mUymbnUPXweOM="; 46 | let priv_key = "jQGnkLnZlX2DjBUd8JKgHgw23zSdRL/Azx3foi/WqvE="; 47 | let sig = 48 | "YCdh5Q6jOiKQy2R9mQwKJ6tBnq31VFZX2dkb7Ypr+/5z6jj4GLEFT9RtryC4+mSILtKKLeN9YnBmYI4Xa+4tDw=="; 49 | 50 | assert_eq!( 51 | sign(&base64::decode(hash)?, &base64::decode(priv_key)?)?, 52 | sig 53 | ); 54 | 55 | let hash = "uPQs4TwLtDGRAdH8sbIJ1ZyWpEmwHWRAhXpamODZ7Kk="; 56 | let pub_key = "A0qTjB3ZjHf2yT1EIvLrkVAWY8MPSueNcB4GTlKGo/o6"; 57 | let sig = 58 | "up+2Fjhnu4OjJeesBPCgoZE+6ReqQDdnqcjhbq2iaulHjlwKYLcwRrD3udSWdHS57ceQeZ+LVPWYBMWBloAgpA=="; 59 | 60 | assert_eq!(verify(&base64::decode(hash)?, sig, pub_key)?, ()); 61 | 62 | Ok(()) 63 | } 64 | -------------------------------------------------------------------------------- /ttt/src/ttt.yml: -------------------------------------------------------------------------------- 1 | name: ttt 2 | version: "0.1" 3 | about: ttt wallet command line tool 4 | 5 | # AppSettings can be defined as a list and are **not** ascii case sensitive 6 | settings: 7 | - ArgRequiredElseHelp 8 | 9 | args: 10 | - verbose: 11 | short: v 12 | multiple: true 13 | help: Sets the level of verbosity 14 | 15 | # All subcommands must be listed in the 'subcommand:' object, where the key to 16 | # the list is the name of the subcommand, and all settings for that command are 17 | # are part of a Hash object 18 | subcommands: 19 | # The name of this subcommand will be 'subcmd' which can be accessed in your 20 | # Rust code later 21 | - init: 22 | about: Create a ttt wallet 23 | args: 24 | - MNEMONIC: 25 | help: init the wallet with the mnemonic 26 | takes_value: true 27 | required: false 28 | - send: 29 | about: Pay TTT to an address 30 | args: 31 | - pay: 32 | help: pay TTT to
33 | short: p 34 | long: pay 35 | multiple: true 36 | value_names: 37 | - ADDRESS 38 | - AMOUNT 39 | takes_value: true 40 | required: false 41 | - text: 42 | help: encode a text message in the unit to send 43 | short: t 44 | long: text 45 | takes_value: true 46 | required: false 47 | 48 | - log: 49 | about: Show the history of this wallet account 50 | args: 51 | - v: 52 | help: show details of specified history transaction 53 | short: v 54 | takes_value: true 55 | required: false 56 | value_name: INDEX 57 | - n: 58 | help: the maximum of transactions to be shown 59 | short: n 60 | takes_value: true 61 | required: false 62 | default_value: "20" 63 | value_name: NUM 64 | - info: 65 | about: Show the wallet info 66 | - balance: 67 | about: Show the wallet balance 68 | args: 69 | - s: 70 | help: show stable balance 71 | short: s 72 | takes_value: false 73 | required: false 74 | - p: 75 | help: show pending balance 76 | short: p 77 | takes_value: false 78 | required: false 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | #[macro_use] 4 | extern crate may; 5 | extern crate may_waiter; 6 | extern crate num_cpus; 7 | extern crate rusqlite; 8 | extern crate serde; 9 | #[macro_use] 10 | extern crate serde_json; 11 | #[macro_use] 12 | extern crate serde_derive; 13 | extern crate tungstenite; 14 | extern crate url; 15 | 16 | #[macro_use] 17 | extern crate failure; 18 | #[macro_use] 19 | extern crate rust_embed; 20 | #[macro_use] 21 | extern crate lazy_static; 22 | extern crate app_dirs; 23 | extern crate base32; 24 | extern crate base64; 25 | extern crate bit_vec; 26 | extern crate crossbeam; 27 | extern crate rand; 28 | extern crate ripemd160; 29 | extern crate secp256k1; 30 | extern crate sha1; 31 | extern crate sha2; 32 | 33 | macro_rules! some_if { 34 | ($condition:expr, $some:expr) => {{ 35 | if $condition { 36 | Some($some) 37 | } else { 38 | None 39 | } 40 | }}; 41 | } 42 | 43 | macro_rules! some_if_option { 44 | ($condition:expr, $some:expr) => {{ 45 | if $condition { 46 | $some 47 | } else { 48 | None 49 | } 50 | }}; 51 | } 52 | 53 | #[macro_export] 54 | macro_rules! t { 55 | ($e:expr) => { 56 | match $e { 57 | Ok(val) => val, 58 | Err(err) => { 59 | error!("call = {:?}\nerr = {:?}", stringify!($e), err); 60 | } 61 | } 62 | }; 63 | } 64 | 65 | // this is a special go macro that can return Result and print the error and backtrace 66 | #[macro_export] 67 | macro_rules! try_go { 68 | ($func:expr) => {{ 69 | fn _go_check(f: F) -> F 70 | where 71 | F: FnOnce() -> ::std::result::Result<(), E> + Send + 'static, 72 | E: Send + 'static, 73 | { 74 | f 75 | } 76 | let f = _go_check($func); 77 | go!(move || if let Err(e) = f() { 78 | error!("coroutine error: {}", e); 79 | error!("back_trace={}", e.backtrace()); 80 | }) 81 | }}; 82 | } 83 | 84 | #[macro_use] 85 | pub mod utils; 86 | 87 | pub mod config; 88 | pub mod db; 89 | pub mod error; 90 | pub mod graph; 91 | pub mod headers_commission; 92 | pub mod mc_outputs; 93 | pub mod my_witness; 94 | pub mod network; 95 | pub mod paid_witnessing; 96 | pub mod spec; 97 | 98 | pub mod catchup; 99 | pub mod composer; 100 | mod definition; 101 | pub mod joint; 102 | pub mod joint_storage; 103 | pub mod light; 104 | pub mod light_wallet; 105 | pub mod main_chain; 106 | mod obj_ser; 107 | pub mod object_hash; 108 | pub mod parent_composer; 109 | pub mod signature; 110 | pub mod storage; 111 | pub mod time; 112 | pub mod validation; 113 | pub mod wallet; 114 | pub mod witness_proof; 115 | pub use error::{Result, TrustnoteError}; 116 | -------------------------------------------------------------------------------- /src/light_wallet.rs: -------------------------------------------------------------------------------- 1 | //use trustnote::network; 2 | 3 | use failure::ResultExt; 4 | 5 | use error::Result; 6 | use light::HistoryRequest; 7 | use my_witness; 8 | use rusqlite::Connection; 9 | 10 | pub fn get_history(db: &Connection) -> Result { 11 | let witnesses = my_witness::MY_WITNESSES.clone(); 12 | if witnesses.is_empty() { 13 | bail!("witnesses not found"); 14 | } 15 | 16 | let addresses = 17 | read_my_addresses(db).context("prepare_request_for_history read_my_addresses failed")?; 18 | let mut requested_joints = read_list_of_unstable_units(db) 19 | .context("prepare_request_for_history read_list_of_unstable_units failed")?; 20 | if requested_joints.is_empty() { 21 | // here we can't give an empty vec, just make up one 22 | requested_joints.push("v|NuDxzT7VFa/AqfBsAZ8suG4uj3u+l0kXOLE+nP+dU=".to_string()); 23 | } 24 | 25 | let mut req_history = HistoryRequest { 26 | witnesses, 27 | addresses, 28 | requested_joints, 29 | // here we can't give an empty vec, just make up one 30 | known_stable_units: vec!["v|NuDxzT7VFa/AqfBsAZ8suG4uj3u+l0kXOLE+nP+dU=".to_string()], 31 | }; 32 | 33 | let addresses_list = req_history 34 | .addresses 35 | .iter() 36 | .map(|s| format!("'{}'", s)) 37 | .collect::>() 38 | .join(", "); 39 | let sql = format!( 40 | "SELECT unit FROM unit_authors JOIN units USING(unit) WHERE is_stable=1 AND address IN({}) \ 41 | UNION \ 42 | SELECT unit FROM outputs JOIN units USING(unit) WHERE is_stable=1 AND address IN({})", 43 | addresses_list, addresses_list); 44 | let mut stmt = db.prepare_cached(&sql)?; 45 | let known_stable_units = stmt 46 | .query_map(&[], |row| row.get(0))? 47 | .collect::<::std::result::Result, _>>()?; 48 | 49 | if !known_stable_units.is_empty() { 50 | req_history.known_stable_units = known_stable_units; 51 | } 52 | 53 | Ok(req_history) 54 | } 55 | 56 | fn read_my_addresses(db: &Connection) -> Result> { 57 | let mut stmt = db.prepare_cached( 58 | "SELECT address FROM my_addresses \ 59 | UNION \ 60 | SELECT shared_address AS address FROM shared_addresses", 61 | )?; 62 | let addresses = stmt 63 | .query_map(&[], |row| row.get(0))? 64 | .collect::<::std::result::Result, _>>()?; 65 | if addresses.is_empty() { 66 | bail!("addresses not found"); 67 | } 68 | Ok(addresses) 69 | } 70 | 71 | fn read_list_of_unstable_units(db: &Connection) -> Result> { 72 | let mut stmt = db.prepare_cached("SELECT unit FROM units WHERE is_stable=0")?; 73 | let units = stmt 74 | .query_map(&[], |row| row.get(0))? 75 | .collect::<::std::result::Result, _>>()?; 76 | if units.is_empty() { 77 | info!("unstable_units not found"); 78 | } 79 | Ok(units) 80 | } 81 | -------------------------------------------------------------------------------- /src/utils/event.rs: -------------------------------------------------------------------------------- 1 | use may::sync::RwLock; 2 | 3 | trait FnOps: Send + Sync { 4 | fn call_box(self: &Self, data: &T) -> (); 5 | } 6 | 7 | impl FnOps for F 8 | where 9 | F: Fn(&T) -> () + Send + Sync, 10 | { 11 | fn call_box(self: &Self, data: &T) -> () { 12 | (*self)(data) 13 | } 14 | } 15 | 16 | /// event handlers for a given `Event` type 17 | pub struct EventHandlers { 18 | ops: RwLock>>>, 19 | } 20 | 21 | impl Default for EventHandlers { 22 | fn default() -> Self { 23 | EventHandlers { 24 | ops: RwLock::new(Vec::new()), 25 | } 26 | } 27 | } 28 | 29 | impl EventHandlers { 30 | fn add_op(&self, f: F) 31 | where 32 | F: Fn(&T) -> () + Send + Sync + 'static, 33 | { 34 | self.ops.write().unwrap().push(Box::new(f)); 35 | } 36 | 37 | fn run(&'static self, data: T) { 38 | let g = self.ops.read().unwrap(); 39 | if !g.is_empty() { 40 | go!(move || for op in g.iter() { 41 | op.call_box(&data); 42 | }); 43 | } 44 | } 45 | } 46 | 47 | /// Event trait 48 | pub trait Event: Sized + Send + 'static { 49 | fn get_event_handlers() -> &'static EventHandlers; 50 | 51 | /// trigger an event, if any hanlders for the event type was registered 52 | /// the event handlers would be executed asynchronously 53 | fn trigger(self) { 54 | Self::get_event_handlers().run(self); 55 | } 56 | 57 | /// globally register an event handler for the event 58 | /// you can add any number of event handlers, 59 | /// each handler take a ref of the event data as parameter 60 | fn add_handler(f: F) 61 | where 62 | F: Fn(&Self) -> () + Send + Sync + 'static, 63 | { 64 | Self::get_event_handlers().add_op(f); 65 | } 66 | } 67 | 68 | /// macro used to implement `Event` trait for a type 69 | /// any tpyes that impl `Send` and `Sync` can be an event type 70 | #[macro_export] 71 | #[doc(hidden)] 72 | macro_rules! impl_event { 73 | ($T:ty) => { 74 | impl $crate::utils::event::Event for $T { 75 | fn get_event_handlers() -> &'static $crate::utils::event::EventHandlers { 76 | lazy_static! { 77 | static ref HANDLERS: $crate::utils::event::EventHandlers<$T> = 78 | $crate::utils::event::EventHandlers::default(); 79 | } 80 | &*HANDLERS 81 | } 82 | } 83 | }; 84 | } 85 | 86 | /// emit an event, if any hanlders for the event type was registered 87 | /// the event handlers would be executed asynchronously 88 | pub fn emit_event(event: T) { 89 | event.trigger(); 90 | } 91 | 92 | #[cfg(test)] 93 | mod test { 94 | use super::*; 95 | 96 | #[test] 97 | fn test_event() { 98 | struct MyEvent { 99 | data: u32, 100 | } 101 | impl_event!(MyEvent); 102 | let s = MyEvent { data: 42 }; 103 | MyEvent::add_handler(|s| assert_eq!(s.data % 2, 0)); 104 | MyEvent::add_handler(|s| assert_eq!(s.data, 42)); 105 | s.trigger(); 106 | } 107 | 108 | #[test] 109 | fn test_emit_event() { 110 | impl_event!(u32); 111 | ::add_handler(|v| assert_eq!(*v, 64)); 112 | emit_event(64); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | extern crate config; 2 | 3 | use self::config::*; 4 | use may::sync::RwLock; 5 | 6 | pub const HASH_LENGTH: usize = 44; 7 | pub const PUBKEY_LENGTH: usize = 44; 8 | pub const SIG_LENGTH: usize = 88; 9 | pub const MAX_COMPLEXITY: usize = 100; 10 | pub const COUNT_WITNESSES: usize = 12; 11 | pub const TOTAL_WHITEBYTES: i64 = 500_000_000_000_000; 12 | pub const MAX_WITNESS_LIST_MUTATIONS: usize = 1; 13 | pub const MAJORITY_OF_WITNESSES: usize = 7; 14 | pub const VERSION: &str = "1.0"; 15 | pub const ALT: &str = "1"; 16 | pub const LIBRARY: &str = "rust-trustnote"; 17 | // TODO: how to read version from Cargo.toml? 18 | pub const LIBRARY_VERSION: &str = "0.1.0"; 19 | pub const STALLED_TIMEOUT: usize = 10; 20 | pub const MAX_MESSAGES_PER_UNIT: usize = 128; 21 | pub const MAX_PARENT_PER_UNIT: usize = 16; 22 | pub const MAX_AUTHORS_PER_UNIT: usize = 16; 23 | pub const MAX_SPEND_PROOFS_PER_MESSAGE: usize = 128; 24 | pub const MAX_INPUTS_PER_PAYMENT_MESSAGE: usize = 128; 25 | pub const MAX_OUTPUTS_PER_PAYMENT_MESSAGE: usize = 128; 26 | pub const MAX_AUTHENTIFIER_LENGTH: usize = 4096; 27 | pub const COUNT_MC_BALLS_FOR_PAID_WITNESSING: u32 = 100; 28 | pub const MAX_DATA_FEED_NAME_LENGTH: usize = 64; 29 | pub const MAX_DATA_FEED_VALUE_LENGTH: usize = 64; 30 | pub const MAX_ITEMS_IN_CACHE: usize = 1000; 31 | pub const MAX_OUTBOUND_CONNECTIONS: usize = 5; 32 | pub const TRANSFER_INPUT_SIZE: u32 = 60; 33 | pub const ADDRESS_SIZE: u32 = 32; 34 | pub const HEADERS_COMMISSION_INPUT_SIZE: u32 = 18; 35 | pub const WITNESSING_INPUT_SIZE: u32 = 26; 36 | 37 | lazy_static! { 38 | static ref CONFIG: RwLock = RwLock::new({ 39 | let mut settings = Config::default(); 40 | settings 41 | .merge(File::with_name("settings.json")) 42 | .expect("failed to load config"); 43 | settings 44 | }); 45 | } 46 | 47 | pub fn show_config() { 48 | println!("\nconfig:"); 49 | println!("\tremote_hub = {:?}", get_remote_hub_url()); 50 | println!("\thub_server_port = {}", get_hub_server_port()); 51 | println!("\tdatabase_path = {:?}", get_database_path(false)); 52 | println!("\n"); 53 | } 54 | 55 | pub fn get_witnesses() -> [String; 12] { 56 | let cfg = CONFIG.read().unwrap(); 57 | cfg.get::<[String; 12]>("witnesses") 58 | .expect("failed to read witnesses") 59 | } 60 | 61 | pub fn get_genesis_unit() -> String { 62 | let cfg = CONFIG.read().unwrap(); 63 | cfg.get::("genesis_unit").unwrap_or_else(|e| { 64 | error!("can't read genesis unit, will use default value, err={}", e); 65 | String::from("V/NuDxzT7VFa/AqfBsAZ8suG4uj3u+l0kXOLE+nP+dU=") 66 | }) 67 | } 68 | 69 | pub fn get_remote_hub_url() -> Vec { 70 | let cfg = CONFIG.read().unwrap(); 71 | cfg.get::>("remote_hub") 72 | .unwrap_or_else(|_| vec!["127.0.0.1:6655".to_string()]) 73 | } 74 | 75 | pub fn get_hub_server_port() -> u16 { 76 | let cfg = CONFIG.read().unwrap(); 77 | cfg.get::("hub_server_port").unwrap_or(6615) 78 | } 79 | 80 | pub fn get_database_path(is_wallet: bool) -> ::std::path::PathBuf { 81 | use app_dirs::*; 82 | 83 | // wallet use current working directory 84 | if is_wallet { 85 | let mut db_path = ::std::env::current_dir().expect("call current_dir failed"); 86 | db_path.push("trustnote_light.sqlite"); 87 | db_path 88 | } else { 89 | const APP_INFO: AppInfo = AppInfo { 90 | name: "rust-trustnote", 91 | author: "trustnote-hub", 92 | }; 93 | 94 | let mut db_path = get_app_root(AppDataType::UserData, &APP_INFO) 95 | .unwrap_or_else(|e| panic!("failed to get app dir, err={}", e)); 96 | if !db_path.exists() { 97 | ::std::fs::create_dir_all(&db_path).unwrap_or_else(|e| { 98 | panic!("failed to create database dir: {:?}, err={}", db_path, e) 99 | }); 100 | } 101 | db_path.push("trustnote.sqlite"); 102 | db_path 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /hub/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | extern crate chrono; 4 | extern crate fern; 5 | #[macro_use] 6 | extern crate trustnote; 7 | #[macro_use] 8 | extern crate may; 9 | extern crate may_signal; 10 | extern crate serde_json; 11 | 12 | mod timer; 13 | use trustnote::*; 14 | 15 | fn log_init() { 16 | // TODO: need to implement async logs 17 | let log_lvl = if cfg!(debug_assertions) { 18 | log::LevelFilter::Debug 19 | } else { 20 | log::LevelFilter::Warn 21 | }; 22 | 23 | fern::Dispatch::new() 24 | .format(|out, message, record| { 25 | out.finish(format_args!( 26 | "{}[{}][{}] {}", 27 | chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S%.3f]"), 28 | record.level(), 29 | record.target(), 30 | message 31 | )) 32 | }).level(log_lvl) 33 | .chain(std::io::stdout()) 34 | .apply() 35 | .unwrap(); 36 | 37 | info!("log init done!"); 38 | } 39 | 40 | fn start_ws_server() -> Result<::may::coroutine::JoinHandle<()>> { 41 | use network::hub::WSS; 42 | use network::WsServer; 43 | 44 | let port = config::get_hub_server_port(); 45 | 46 | let server = WsServer::start(("0.0.0.0", port), |c| { 47 | WSS.add_inbound(c); 48 | }); 49 | println!("Websocket server running on ws://0.0.0.0:{}", port); 50 | 51 | Ok(server) 52 | } 53 | 54 | fn connect_to_remote() -> Result<()> { 55 | let peers = config::get_remote_hub_url(); 56 | 57 | for peer in peers { 58 | if let Err(e) = network::hub::create_outbound_conn(&peer) { 59 | error!(" fail to connected: {}, err={}", peer, e); 60 | } 61 | } 62 | 63 | go!(move || if let Err(e) = network::hub::start_catchup() { 64 | error!("catchup error: {}", e); 65 | error!("back_trace={}", e.backtrace()); 66 | ::std::process::abort(); 67 | }); 68 | Ok(()) 69 | } 70 | 71 | fn network_cleanup() { 72 | network::hub::WSS.close_all(); 73 | } 74 | 75 | // register golbal event handlers 76 | fn register_event_handlers() { 77 | use main_chain::MciStableEvent; 78 | use utils::event::Event; 79 | 80 | MciStableEvent::add_handler(|v| t!(network::hub::notify_watchers_about_stable_joints(v.mci))); 81 | } 82 | 83 | // the hub server logic that run in coroutine context 84 | fn run_hub_server() -> Result<()> { 85 | register_event_handlers(); 86 | let _server = start_ws_server(); 87 | connect_to_remote()?; 88 | timer::start_global_timers(); 89 | Ok(()) 90 | } 91 | 92 | #[allow(dead_code)] 93 | fn test_read_joint() -> Result<()> { 94 | fn pause() { 95 | use std::io::{self, Read}; 96 | io::stdin().read(&mut [0]).ok(); 97 | } 98 | 99 | fn print_joint(unit: &str) -> Result<()> { 100 | let db = db::DB_POOL.get_connection(); 101 | let joint = storage::read_joint_directly(&db, &unit.to_string())?; 102 | println!("joint = {}", serde_json::to_string_pretty(&joint)?); 103 | Ok(()) 104 | } 105 | 106 | print_joint("V/NuDxzT7VFa/AqfBsAZ8suG4uj3u+l0kXOLE+nP+dU=")?; 107 | print_joint("g9HQWWTdz8n9+KRYFxOyHNEH7kp7N4j1vU7F1VIpEC8=")?; 108 | pause(); 109 | Ok(()) 110 | } 111 | 112 | fn main() -> Result<()> { 113 | // init default coroutine settings 114 | let stack_size = if cfg!(debug_assertions) { 115 | 0x4000 116 | } else { 117 | 0x2000 118 | }; 119 | may::config() 120 | .set_stack_size(stack_size) 121 | .set_io_workers(0) 122 | .set_workers(1); 123 | 124 | log_init(); 125 | config::show_config(); 126 | 127 | // uncomment it to test read joint from db 128 | // test_read_joint()?; 129 | 130 | go!(|| run_hub_server().unwrap()).join().unwrap(); 131 | 132 | // wait user input a ctrl_c to exit 133 | may_signal::ctrl_c().recv().unwrap(); 134 | 135 | // close all the connections 136 | network_cleanup(); 137 | info!("bye from main!\n\n"); 138 | Ok(()) 139 | } 140 | -------------------------------------------------------------------------------- /src/mc_outputs.rs: -------------------------------------------------------------------------------- 1 | use error::Result; 2 | use rusqlite::Connection; 3 | 4 | pub fn read_next_spendable_mc_index( 5 | db: &Connection, 6 | kind: &str, 7 | address: &str, 8 | conflict_units: &[String], 9 | ) -> Result { 10 | let sql = if !conflict_units.is_empty() { 11 | let conflict_units_list = conflict_units 12 | .iter() 13 | .map(|s| format!("'{}'", s)) 14 | .collect::>() 15 | .join(", "); 16 | format!( 17 | "SELECT to_main_chain_index FROM inputs CROSS JOIN units USING(unit) \ 18 | WHERE type={} AND address={} AND sequence='good' \ 19 | AND unit NOT IN({}) \ 20 | ORDER BY to_main_chain_index DESC LIMIT 1", 21 | kind, address, conflict_units_list 22 | ) 23 | } else { 24 | format!( 25 | "SELECT to_main_chain_index FROM inputs CROSS JOIN units USING(unit) \ 26 | WHERE type={} AND address={} AND sequence='good' \ 27 | ORDER BY to_main_chain_index DESC LIMIT 1", 28 | kind, address 29 | ) 30 | }; 31 | 32 | let mut stmt = db.prepare(&sql)?; 33 | let mut rows = stmt.query_map(&[], |row| row.get::<_, u32>(0))?; 34 | let row = rows.next(); 35 | if row.is_none() { 36 | Ok(0) 37 | } else { 38 | Ok(row.unwrap()? + 1) 39 | } 40 | } 41 | 42 | pub fn read_max_spendable_mc_index(db: &Connection, kind: &str) -> Result { 43 | let sql = format!( 44 | "SELECT MAX(main_chain_index) AS max_mc_index FROM {}_outputs", 45 | kind 46 | ); 47 | 48 | let mut stmt = db.prepare_cached(&sql)?; 49 | let max_mc_index = stmt.query_row(&[], |row| row.get::<_, u32>(0)).unwrap_or(0); 50 | 51 | Ok(max_mc_index) 52 | } 53 | 54 | pub struct McIndexInterval { 55 | pub from_mci: u32, 56 | pub to_mci: u32, 57 | pub accumulated: i64, 58 | pub has_sufficient: bool, 59 | } 60 | 61 | pub fn find_mc_index_interval_to_target_amount( 62 | db: &Connection, 63 | kind: &str, 64 | address: &String, 65 | max_mci: u32, 66 | target_amount: u64, 67 | ) -> Result> { 68 | let from_mci = read_next_spendable_mc_index(db, kind, address, &[])?; 69 | 70 | if from_mci > max_mci { 71 | return Ok(None); 72 | } 73 | 74 | let mut max_spendable_mci = read_max_spendable_mc_index(db, kind)?; 75 | if max_spendable_mci == 0 { 76 | return Ok(None); 77 | } 78 | 79 | if max_spendable_mci > max_mci { 80 | max_spendable_mci = max_mci; 81 | } 82 | 83 | //Original js checks whether there is a overflow 84 | // if (target_amount === Infinity) 85 | // target_amount = 1e15; 86 | 87 | //Original js has another implementation for mysql 88 | let min_mc_output = if kind == "witnessing" { 11.0 } else { 344.0 }; 89 | let max_count_outputs = (target_amount as f64 / min_mc_output).ceil() as i64; 90 | 91 | let sql = format!( 92 | "SELECT main_chain_index, amount \ 93 | FROM {}_outputs \ 94 | WHERE is_spent=0 AND address=? AND main_chain_index>=? AND main_chain_index<=? \ 95 | ORDER BY main_chain_index LIMIT ?", 96 | kind 97 | ); 98 | 99 | struct Row { 100 | main_chain_index: u32, 101 | amount: i64, 102 | } 103 | 104 | let mut stmt = db.prepare_cached(&sql)?; 105 | let rows = stmt.query_map( 106 | &[address, &from_mci, &max_spendable_mci, &max_count_outputs], 107 | |row| Row { 108 | main_chain_index: row.get(0), 109 | amount: row.get(1), 110 | }, 111 | )?; 112 | 113 | let mut outputs = Vec::new(); 114 | for row in rows { 115 | outputs.push(row?); 116 | } 117 | 118 | if outputs.is_empty() { 119 | return Ok(None); 120 | } 121 | 122 | let mut accumulated = 0; 123 | let mut to_mci = 0; 124 | let mut has_sufficient = false; 125 | for output in outputs { 126 | accumulated += output.amount; 127 | to_mci = output.main_chain_index; 128 | if accumulated as u64 > target_amount { 129 | has_sufficient = true; 130 | break; 131 | } 132 | } 133 | 134 | Ok(Some(McIndexInterval { 135 | from_mci, 136 | to_mci, 137 | accumulated, 138 | has_sufficient, 139 | })) 140 | } 141 | 142 | pub fn calc_earnings( 143 | db: &Connection, 144 | kind: &str, 145 | from_main_chain_index: u32, 146 | to_main_chain_index: u32, 147 | address: &String, 148 | ) -> Result { 149 | let sql = format!( 150 | "SELECT SUM(amount) AS total FROM {}_outputs \ 151 | WHERE main_chain_index>=? AND main_chain_index<=? \ 152 | AND address=?", 153 | kind 154 | ); 155 | 156 | let mut stmt = db.prepare_cached(&sql)?; 157 | let total = stmt 158 | .query_row( 159 | &[&from_main_chain_index, &to_main_chain_index, address], 160 | |row| row.get::<_, u32>(0), 161 | ).unwrap_or(0); 162 | 163 | Ok(total) 164 | } 165 | -------------------------------------------------------------------------------- /src/definition.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use config; 4 | use error::Result; 5 | use failure::ResultExt; 6 | use rusqlite::Connection; 7 | use serde::Deserialize; 8 | use serde_json::Value; 9 | use signature; 10 | use spec::*; 11 | use validation::ValidationState; 12 | 13 | struct Definition<'a> { 14 | op: &'a str, 15 | args: &'a Value, 16 | } 17 | 18 | impl<'a> Definition<'a> { 19 | fn from_value(value: &'a Value) -> Result { 20 | if !value.is_array() { 21 | println!("definition={:?}", value); 22 | bail!("definition must be array"); 23 | } 24 | 25 | let arr = value.as_array().unwrap(); 26 | if arr.len() != 2 { 27 | bail!("expression must be 2-element array"); 28 | } 29 | let op = arr[0] 30 | .as_str() 31 | .ok_or_else(|| format_err!("op is not a string"))?; 32 | let args = &arr[1]; 33 | 34 | Ok(Definition { op, args }) 35 | } 36 | } 37 | 38 | #[derive(Deserialize)] 39 | #[serde(deny_unknown_fields)] 40 | struct SigValue<'a> { 41 | algo: Option<&'a str>, 42 | pubkey: &'a str, 43 | } 44 | 45 | pub fn validate_definition(definition: &Value, is_asset: bool) -> Result<()> { 46 | fn evaluate( 47 | definition: &Value, 48 | is_in_negation: bool, 49 | is_asset: bool, 50 | complexity: &mut usize, 51 | ) -> Result { 52 | *complexity += 1; 53 | if *complexity > config::MAX_COMPLEXITY { 54 | bail!("complexity exceeded"); 55 | } 56 | 57 | let definition = Definition::from_value(definition)?; 58 | 59 | match definition.op { 60 | "sig" => { 61 | if is_in_negation { 62 | bail!("sig cannot be negated"); 63 | } 64 | if is_asset { 65 | bail!("asset condition cannot have sig"); 66 | } 67 | 68 | let sig_value = 69 | SigValue::deserialize(definition.args).context("can't convert to SigValue")?; 70 | 71 | if let Some(algo) = sig_value.algo { 72 | ensure!(algo == "secp256k1", "unsupported sig algo"); 73 | } 74 | 75 | ensure!( 76 | sig_value.pubkey.len() == config::HASH_LENGTH, 77 | "wrong pubkey length" 78 | ); 79 | } 80 | op => unimplemented!("unsupported op: {}", op), 81 | } 82 | Ok(true) 83 | } 84 | 85 | let mut complexity = 0; 86 | let has_sig = evaluate(definition, false, is_asset, &mut complexity)?; 87 | 88 | if !has_sig && !is_asset { 89 | bail!("each branch must have a signature"); 90 | } 91 | 92 | Ok(()) 93 | } 94 | 95 | pub fn validate_authentifiers( 96 | _db: &Connection, 97 | _address: &str, 98 | asset: &Value, 99 | definition: &Value, 100 | _unit: &Unit, 101 | validate_state: &mut ValidationState, 102 | authentifiers: &HashMap, 103 | ) -> Result<()> { 104 | let evaluate = |definition: &Value, path: &str, used_path: &mut Vec| -> Result<()> { 105 | let definition = Definition::from_value(definition)?; 106 | match definition.op { 107 | "sig" => { 108 | let sig = authentifiers 109 | .get(path) 110 | .ok_or_else(|| format_err!("authentifier path not found: {}", path))?; 111 | used_path.push(path.to_owned()); 112 | 113 | if validate_state.unsigned && sig.starts_with('-') { 114 | return Ok(()); 115 | } 116 | 117 | let sig_value = 118 | SigValue::deserialize(definition.args).context("can't conver to SigValue")?; 119 | let unit_hash = validate_state 120 | .unit_hash_to_sign 121 | .as_ref() 122 | .expect("no unit hash to sign found"); 123 | 124 | signature::verify(unit_hash, sig, sig_value.pubkey) 125 | .context(format!("bad signature at path: {:?}", path))?; 126 | } 127 | op => unimplemented!("unsupported op: {}", op), 128 | } 129 | Ok(()) 130 | }; 131 | 132 | let is_asset = authentifiers.is_empty(); 133 | if is_asset && !asset.is_null() { 134 | bail!("incompatible params"); 135 | } 136 | validate_definition(definition, is_asset)?; 137 | let mut used_path = Vec::new(); 138 | evaluate(definition, "r", &mut used_path)?; 139 | if !is_asset && used_path.len() != authentifiers.len() { 140 | bail!( 141 | "some authentifiers are not used, used={:?}, passed={:?}", 142 | used_path, 143 | authentifiers 144 | ); 145 | } 146 | Ok(()) 147 | } 148 | 149 | pub fn has_references(definition: &Value) -> Result { 150 | let definition = Definition::from_value(definition).context("has_references")?; 151 | 152 | match definition.op { 153 | "sig" | " hash" | "cosigned by" => Ok(false), 154 | op => unimplemented!("unkonw op: {}", op), 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/db.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::ops::{Deref, DerefMut}; 3 | use std::path::PathBuf; 4 | use std::sync::atomic::{AtomicBool, Ordering}; 5 | 6 | use config; 7 | use error::Result; 8 | use may; 9 | use may::sync::mpmc::{self, Receiver, Sender}; 10 | use num_cpus; 11 | use rusqlite::{Connection, OpenFlags}; 12 | 13 | #[derive(RustEmbed)] 14 | #[folder = "db/"] 15 | struct InitDatabase; 16 | 17 | lazy_static! { 18 | static ref IS_WALLET: AtomicBool = AtomicBool::new(false); 19 | pub static ref DB_POOL: DatabasePool = DatabasePool::new(); 20 | } 21 | 22 | pub fn use_wallet_db() { 23 | IS_WALLET.store(true, Ordering::Relaxed); 24 | } 25 | 26 | fn create_database_if_necessary() -> Result { 27 | let is_wallet = IS_WALLET.load(Ordering::Relaxed); 28 | let db_path = config::get_database_path(is_wallet); 29 | if !db_path.exists() { 30 | let init_db = if is_wallet { 31 | InitDatabase::get("initial.trustnote-light.sqlite") 32 | } else { 33 | InitDatabase::get("initial.trustnote.sqlite") 34 | }.expect("failed to find init db"); 35 | info!("create_database_if_necessary done: db_path: {:?}", db_path); 36 | ::std::fs::write(&db_path, init_db)?; 37 | } 38 | 39 | Ok(db_path) 40 | } 41 | 42 | pub struct DatabasePool { 43 | db_rx: Receiver, 44 | db_tx: Sender, 45 | } 46 | 47 | impl Default for DatabasePool { 48 | fn default() -> Self { 49 | Self::new() 50 | } 51 | } 52 | 53 | impl DatabasePool { 54 | pub fn new() -> Self { 55 | // database path 56 | let db_path = create_database_if_necessary().expect("create database error"); 57 | // create the connection pool 58 | let (db_tx, db_rx) = mpmc::channel(); 59 | 60 | may::coroutine::scope(|s| { 61 | for _ in 0..(num_cpus::get() * 4) { 62 | go!(s, || { 63 | let conn = match Connection::open_with_flags( 64 | &db_path, 65 | OpenFlags::SQLITE_OPEN_READ_WRITE 66 | // | OpenFlags::SQLITE_OPEN_SHARED_CACHE 67 | // | OpenFlags::SQLITE_OPEN_NO_MUTEX, 68 | ) { 69 | Ok(conn) => conn, 70 | Err(e) => { 71 | error!("{}", e.to_string()); 72 | ::std::process::abort(); 73 | } 74 | }; 75 | 76 | db_tx.send(conn).unwrap(); 77 | }); 78 | } 79 | }); 80 | 81 | info!("open database connections done"); 82 | DatabasePool { db_rx, db_tx } 83 | } 84 | 85 | pub fn get_connection(&self) -> Database { 86 | Database { 87 | db: Some(self.db_rx.recv().unwrap()), 88 | tx: self.db_tx.clone(), 89 | } 90 | } 91 | } 92 | 93 | pub struct Database { 94 | db: Option, 95 | tx: Sender, 96 | } 97 | 98 | impl Deref for Database { 99 | type Target = Connection; 100 | 101 | #[inline] 102 | fn deref(&self) -> &Connection { 103 | self.db.as_ref().unwrap() 104 | } 105 | } 106 | 107 | impl DerefMut for Database { 108 | #[inline] 109 | fn deref_mut(&mut self) -> &mut Connection { 110 | self.db.as_mut().unwrap() 111 | } 112 | } 113 | 114 | impl Drop for Database { 115 | fn drop(&mut self) { 116 | let db = self.db.take().unwrap(); 117 | self.tx.send(db).unwrap(); 118 | } 119 | } 120 | 121 | impl Database { 122 | pub fn get_my_witnesses(&self) -> Result> { 123 | let mut stmt = self.prepare_cached("SELECT address FROM my_witnesses")?; 124 | let rows = stmt.query_map(&[], |row| row.get(0))?; 125 | 126 | let mut names = Vec::new(); 127 | for name_result in rows { 128 | names.push(name_result?); 129 | } 130 | Ok(names) 131 | } 132 | 133 | pub fn insert_witnesses(&self, witnesses: &[String]) -> Result<()> { 134 | let witnesses_str = witnesses 135 | .iter() 136 | .map(|s| format!("('{}')", s)) 137 | .collect::>() 138 | .join(","); 139 | let sql = format!( 140 | "INSERT INTO my_witnesses (address) VALUES {}", 141 | witnesses_str 142 | ); 143 | 144 | let mut stmt = self.prepare_cached(&sql)?; 145 | stmt.execute(&[])?; 146 | Ok(()) 147 | } 148 | } 149 | 150 | pub trait FnQuery { 151 | fn call_box(self: Box, &Connection) -> Result<()>; 152 | } 153 | 154 | impl Result<()>> FnQuery for F { 155 | fn call_box(self: Box, db: &Connection) -> Result<()> { 156 | (*self)(db) 157 | } 158 | } 159 | 160 | #[derive(Default)] 161 | pub struct DbQueries { 162 | queries: Vec>, 163 | } 164 | 165 | impl fmt::Debug for DbQueries { 166 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 167 | //TODO: to add some real debug info 168 | write!(f, "DbQueries") 169 | } 170 | } 171 | 172 | impl DbQueries { 173 | pub fn new() -> Self { 174 | DbQueries { 175 | queries: Vec::new(), 176 | } 177 | } 178 | 179 | #[inline] 180 | pub fn add_query(&mut self, f: F) 181 | where 182 | F: FnOnce(&Connection) -> Result<()> + 'static, 183 | { 184 | self.queries.push(Box::new(f)); 185 | } 186 | 187 | // execute all queries and ignore the error 188 | pub fn execute_all(self, db: &Connection) { 189 | for query in self.queries { 190 | t!(query.call_box(db)); 191 | } 192 | } 193 | 194 | // execute queries and return earlier if any failed 195 | pub fn execute(self, db: &Connection) -> Result<()> { 196 | for query in self.queries { 197 | query.call_box(db)?; 198 | } 199 | Ok(()) 200 | } 201 | } 202 | 203 | #[test] 204 | fn test_db() -> Result<()> { 205 | let db = DB_POOL.get_connection(); 206 | 207 | let names = db.get_my_witnesses()?; 208 | 209 | for name in names { 210 | println!("name = {}", name); 211 | } 212 | 213 | Ok(()) 214 | } 215 | -------------------------------------------------------------------------------- /src/object_hash.rs: -------------------------------------------------------------------------------- 1 | use base32; 2 | use base64; 3 | use bit_vec::BitVec; 4 | use error::Result; 5 | use obj_ser::to_string; 6 | use rand::{self, Rng}; 7 | use ripemd160::Ripemd160; 8 | use serde::ser::Serialize; 9 | use sha2::{Digest, Sha256}; 10 | use std::collections::HashSet; 11 | 12 | pub fn get_base64_hash(object: &T) -> Result 13 | where 14 | T: Serialize, 15 | { 16 | Ok(base64::encode(&Sha256::digest( 17 | to_string(object)?.as_bytes(), 18 | ))) 19 | } 20 | 21 | pub fn get_chash(object: &T) -> Result 22 | where 23 | T: Serialize, 24 | { 25 | let hash = Ripemd160::digest(&to_string(object)?.as_bytes()); 26 | let truncate_hash = &hash[4..]; 27 | 28 | let mut chash = BitVec::from_elem(160, false); 29 | let clean_data = BitVec::from_bytes(&truncate_hash); 30 | let checksum = get_checksum(&truncate_hash); 31 | 32 | let mut clean_data_index = 0; 33 | let mut checksum_index = 0; 34 | let mut chash_index = 0; 35 | 36 | while chash_index < chash.len() { 37 | if CHECKSUM_OFFSETS.contains(&chash_index) { 38 | chash.set(chash_index, checksum[checksum_index]); 39 | checksum_index += 1; 40 | } else { 41 | chash.set(chash_index, clean_data[clean_data_index]); 42 | clean_data_index += 1; 43 | } 44 | chash_index += 1; 45 | } 46 | 47 | Ok(base32::encode( 48 | base32::Alphabet::RFC4648 { padding: true }, 49 | &chash.to_bytes(), 50 | )) 51 | } 52 | 53 | //A constant HashSet to store the offsets to insert the checksum into clean data 54 | //When mix or separate data, it can be used to check whether the bit should be a checksum 55 | //The original array pi is the fractional part from PI as a array. 56 | //See the original chash.js for more details. 57 | lazy_static! { 58 | static ref CHECKSUM_OFFSETS: HashSet = { 59 | let pi = [ 60 | 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9, 3, 2, 3, 8, 4, 6, 2, 6, 4, 3, 3, 8, 3, 2, 7, 61 | 9, 5, 0, 2, 8, 8, 4, 1, 9, 7, 1, 6, 9, 3, 9, 9, 3, 7, 5, 1, 0, 62 | ]; 63 | 64 | let mut offset = 0; 65 | let mut set = HashSet::new(); 66 | for i in pi.iter() { 67 | if *i > 0 { 68 | offset += i; 69 | set.insert(offset); 70 | } 71 | } 72 | 73 | set 74 | }; 75 | } 76 | 77 | fn get_checksum(data: &[u8]) -> BitVec { 78 | let sha256 = Sha256::digest(data); 79 | let checksum = [sha256[5], sha256[13], sha256[21], sha256[29]]; 80 | BitVec::from_bytes(&checksum) 81 | } 82 | 83 | pub fn is_chash_valid(encoded: &str) -> bool { 84 | let chash = base32::decode(base32::Alphabet::RFC4648 { padding: true }, &encoded) 85 | .expect("base32 decode return None"); 86 | 87 | let chash = BitVec::from_bytes(&chash); 88 | let mut checksum = BitVec::new(); 89 | let mut clean_data = BitVec::new(); 90 | 91 | //let mut chash_index = 0; 92 | for (chash_index, bit) in chash.iter().enumerate() { 93 | if CHECKSUM_OFFSETS.contains(&chash_index) { 94 | checksum.push(bit); 95 | } else { 96 | clean_data.push(bit); 97 | } 98 | } 99 | 100 | get_checksum(&clean_data.to_bytes()) == checksum 101 | } 102 | 103 | pub fn get_ball_hash( 104 | unit: &str, 105 | parent_balls: &[String], 106 | skiplist_balls: &[String], 107 | is_nonserial: bool, 108 | ) -> String { 109 | #[inline] 110 | fn is_empty(arr: &[T]) -> bool { 111 | arr.is_empty() 112 | } 113 | 114 | #[derive(Serialize)] 115 | struct BallHashObj<'a> { 116 | unit: &'a str, 117 | #[serde(skip_serializing_if = "is_empty")] 118 | parent_balls: &'a [String], 119 | #[serde(skip_serializing_if = "is_empty")] 120 | skiplist_balls: &'a [String], 121 | #[serde(skip_serializing_if = "Option::is_none")] 122 | is_nonserial: Option, 123 | } 124 | 125 | let is_nonserial = if is_nonserial { Some(true) } else { None }; 126 | let ball = BallHashObj { 127 | unit, 128 | parent_balls, 129 | skiplist_balls, 130 | is_nonserial, 131 | }; 132 | 133 | get_base64_hash(&ball).expect("failed to calc ball hash") 134 | } 135 | 136 | #[inline] 137 | pub fn gen_random_string(len: usize) -> String { 138 | use rand::distributions::Standard; 139 | 140 | let bytes: Vec = rand::thread_rng() 141 | .sample_iter(&Standard) 142 | .take(len) 143 | .collect(); 144 | base64::encode(&bytes) 145 | } 146 | 147 | //////////////////////////////////////////////////////////////////////////////// 148 | 149 | #[test] 150 | fn test_payload() { 151 | use serde_json; 152 | use spec; 153 | 154 | //Copied from the Unit json string 155 | let json = r#"{ 156 | "outputs": [ 157 | { 158 | "address": "7JXBJQPQC3466UPK7C6ABA6VVU6YFYAI", 159 | "amount": 10000 160 | }, 161 | { 162 | "address": "JERTY5XNENMHYQW7NVBXUB5CU3IDODA3", 163 | "amount": 99989412 164 | } 165 | ], 166 | "inputs": [ 167 | { 168 | "unit": "lQCxxsMslXLzQKybX2KArOGho8XuNf1Lpds2abdf8O4=", 169 | "message_index": 0, 170 | "output_index": 1 171 | } 172 | ] 173 | }"#; 174 | let payload: spec::Payload = serde_json::from_str(json).unwrap(); 175 | let expected = "5CYeTTa4VQxgF4b1Tn33NBlKilJadddwBMLvtp1HIus="; 176 | 177 | //println!("{:?}", to_base64_hash(&payload)); 178 | assert_eq!(get_base64_hash(&payload).unwrap(), expected); 179 | } 180 | 181 | #[test] 182 | fn test_chash160() { 183 | let data = "A0mQdZvy+bGpIu/yBSNt7eB4mTZUQiM173bIQTOQRz3U"; 184 | let expected = "YFAR4AK2RSRTAWZ3ILRFZOMN7M7QJTJ2"; 185 | 186 | assert_eq!(get_chash(&data).unwrap(), expected); 187 | } 188 | 189 | #[test] 190 | fn test_chash160_validation() { 191 | let valid = "YFAR4AK2RSRTAWZ3ILRFZOMN7M7QJTJ2"; 192 | let invalid = "NFAR4AK2RSRTAWZ3ILRFZOMN7M7QJTJ2"; 193 | 194 | assert_eq!(is_chash_valid(valid), true); 195 | assert_eq!(is_chash_valid(invalid), false); 196 | } 197 | -------------------------------------------------------------------------------- /src/network/wallet.rs: -------------------------------------------------------------------------------- 1 | use std::net::ToSocketAddrs; 2 | use std::sync::Arc; 3 | use std::time::Duration; 4 | 5 | use super::network::{Sender, Server, WsConnection}; 6 | use config; 7 | use error::Result; 8 | use failure::ResultExt; 9 | use joint::Joint; 10 | use light; 11 | use light::LastStableBallAndParentUnitsAndWitnessListUnit; 12 | use light_wallet; 13 | use may::coroutine; 14 | use may::net::TcpStream; 15 | use my_witness; 16 | use rusqlite::Connection; 17 | use serde_json::{self, Value}; 18 | use tungstenite::client::client; 19 | use tungstenite::handshake::client::Request; 20 | use tungstenite::protocol::Role; 21 | use url::Url; 22 | 23 | #[derive(Default)] 24 | pub struct WalletData {} 25 | 26 | pub type WalletConn = WsConnection; 27 | 28 | fn init_connection(ws: &Arc) -> Result<()> { 29 | use rand::{thread_rng, Rng}; 30 | 31 | ws.send_version()?; 32 | 33 | let mut rng = thread_rng(); 34 | let n: u64 = rng.gen_range(0, 1000); 35 | let ws_c = Arc::downgrade(ws); 36 | 37 | // start the heartbeat timer for each connection 38 | go!(move || loop { 39 | coroutine::sleep(Duration::from_millis(3000 + n)); 40 | let ws = match ws_c.upgrade() { 41 | Some(ws) => ws, 42 | None => return, 43 | }; 44 | if ws.get_last_recv_tm().elapsed() < Duration::from_secs(5) { 45 | continue; 46 | } 47 | // heartbeat failed so just close the connnection 48 | let rsp = ws.send_heartbeat(); 49 | if rsp.is_err() { 50 | error!("heartbeat err= {}", rsp.unwrap_err()); 51 | } 52 | }); 53 | 54 | Ok(()) 55 | } 56 | 57 | pub fn create_outbound_conn(address: A) -> Result> { 58 | let stream = TcpStream::connect(address)?; 59 | let peer = match stream.peer_addr() { 60 | Ok(addr) => addr.to_string(), 61 | Err(_) => "unknown peer".to_owned(), 62 | }; 63 | let url = Url::parse("wss://localhost/")?; 64 | let req = Request::from(url); 65 | let (conn, _) = client(req, stream)?; 66 | 67 | let ws = WsConnection::new(conn, WalletData::default(), peer, Role::Client)?; 68 | 69 | init_connection(&ws)?; 70 | Ok(ws) 71 | } 72 | 73 | impl WalletConn { 74 | fn send_version(&self) -> Result<()> { 75 | self.send_just_saying( 76 | "version", 77 | json!({ 78 | "protocol_version": config::VERSION, 79 | "alt": config::ALT, 80 | "library": config::LIBRARY, 81 | "library_version": config::LIBRARY_VERSION, 82 | "program": "rust-trustnote-ttt", 83 | "program_version": "0.1.0" 84 | }), 85 | ) 86 | } 87 | 88 | fn send_heartbeat(&self) -> Result<()> { 89 | self.send_request("heartbeat", &Value::Null)?; 90 | Ok(()) 91 | } 92 | 93 | pub fn post_joint(&self, joint: &Joint) -> Result<()> { 94 | self.send_request("post_joint", &serde_json::to_value(joint)?)?; 95 | Ok(()) 96 | } 97 | 98 | pub fn get_parents_and_last_ball_and_witness_list_unit( 99 | &self, 100 | ) -> Result { 101 | let resp = self.send_request( 102 | "light/get_parents_and_last_ball_and_witness_list_unit", 103 | &json!({"witnesses": &*my_witness::MY_WITNESSES}), 104 | )?; 105 | 106 | Ok(serde_json::from_value(resp)?) 107 | } 108 | 109 | pub fn refresh_history(&self, db: &Connection) -> Result<()> { 110 | let req_get_history = 111 | light_wallet::get_history(db).context("prepare_request_for_history failed")?; 112 | 113 | let response_history = self 114 | .send_request("light/get_history", &serde_json::to_value(req_get_history)?) 115 | .context("send request get_history failed")?; 116 | 117 | let mut response_history_s: light::HistoryResponse = 118 | serde_json::from_value(response_history)?; 119 | 120 | light::process_history(&db, &mut response_history_s) 121 | } 122 | 123 | pub fn get_witnesses(&self) -> Result> { 124 | let witnesses = self 125 | .send_request("get_witnesses", &Value::Null) 126 | .context("failed to get witnesses")?; 127 | let witnesses: Vec = 128 | serde_json::from_value(witnesses).context("failed to parse witnesses")?; 129 | if witnesses.len() != config::COUNT_WITNESSES { 130 | bail!( 131 | "witnesses must contains {} addresses, but we got {}", 132 | config::COUNT_WITNESSES, 133 | witnesses.len() 134 | ); 135 | } 136 | Ok(witnesses) 137 | } 138 | } 139 | 140 | // the server side impl 141 | impl WalletConn { 142 | fn on_version(&self, version: Value) -> Result<()> { 143 | if version["protocol_version"].as_str() != Some(config::VERSION) { 144 | error!("Incompatible versions, mine {}", config::VERSION); 145 | } 146 | 147 | if version["alt"].as_str() != Some(config::ALT) { 148 | error!("Incompatible alt, mine {}", config::ALT); 149 | } 150 | 151 | info!("got peer version: {}", version); 152 | Ok(()) 153 | } 154 | 155 | fn on_hub_challenge(&self, param: Value) -> Result<()> { 156 | // TODO: add special wallet logic here 157 | // this is hub, we do nothing here 158 | // only wallet would save the challenge and save the challenge 159 | // for next login and match 160 | info!("peer is a hub, challenge = {}", param); 161 | Ok(()) 162 | } 163 | 164 | fn on_heartbeat(&self, _: Value) -> Result { 165 | Ok(Value::Null) 166 | } 167 | 168 | fn on_subscribe(&self, _param: Value) -> Result { 169 | bail!("I'm light, cannot subscribe you to updates"); 170 | } 171 | } 172 | 173 | impl Server for WalletData { 174 | fn on_message(ws: Arc, subject: String, body: Value) -> Result<()> { 175 | match subject.as_str() { 176 | "version" => ws.on_version(body)?, 177 | "hub/challenge" => ws.on_hub_challenge(body)?, 178 | subject => error!("on_message unknown subject: {}", subject), 179 | } 180 | Ok(()) 181 | } 182 | 183 | fn on_request(ws: Arc, command: String, params: Value) -> Result { 184 | let response = match command.as_str() { 185 | "heartbeat" => ws.on_heartbeat(params)?, 186 | "subscribe" => ws.on_subscribe(params)?, 187 | command => bail!("on_request unknown command: {}", command), 188 | }; 189 | Ok(response) 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TrustNote Rust SDK 2 | 3 | [![Build Status](https://travis-ci.org/trustnote/rust-trustnote.svg?branch=master)](https://travis-ci.org/trustnote/rust-trustnote) 4 | 5 | Re-implementation of TrustNote using Rust, currently it connects to the TrustNote testnet only. 6 | 7 | # Getting Started 8 | 9 | ## Prerequistes 10 | 11 | * Ubuntu Linux - The SDK can be downloaded from https://github.com/trustnote/rust-trustnote/releases/download/0.3.0/ubuntu_ttt.zip 12 | * Or Windows - The SDK can be downloaded from https://github.com/trustnote/rust-trustnote/releases/download/0.3.0/windows_ttt.zip 13 | 14 | *Note: You can also compile the SDK for other target platforms. If you want to do so, the source code is available [here](https://github.com/trustnote/rust-trustnote/archive/0.3.0.zip):* 15 | 16 | ## Get the Release (Ubuntu Linux) 17 | 18 | 1. Download the package and unzip it 19 | 20 | ``` 21 | wget https://github.com/trustnote/rust-trustnote/releases/download/0.3.0/ubuntu_ttt.zip 22 | unzip ubuntu_ttt.zip 23 | ``` 24 | 25 | 2. Set the permission and make it executable 26 | 27 | ``` 28 | chmod +x ttt 29 | ``` 30 | 31 | ## Initialize a New Wallet 32 | 33 | ``` 34 | ./ttt init 35 | ``` 36 | 37 | Because you are initializing a new wallet from scratch, the system will generate a default mnemonic for you. 38 | 39 | 40 | ## Import an Existing Wallet from Mnemonic 41 | 42 | ``` 43 | ./ttt init "select initial pet jazz alone stamp copper vault private slight rocket stock" 44 | ``` 45 | 46 | ## Configuration (Optional) 47 | 48 | Whether you are initiating a new wallet, or importing an existing wallet, in both cases, mnemonics will be stored in a file called settings.json in the same directory after the wallet is initialized. 49 | 50 | Here is an example when you opens the file: 51 | 52 | ``` 53 | { 54 | "hub_url": [ 55 | "dev.trustnote.org:6616" 56 | ], 57 | "mnemonic": "select initial pet jazz alone stamp copper vault private slight rocket stock" 58 | } 59 | ``` 60 | 61 | *Note hub_url is the address of the hub in use, and dev.trustnote.org:6616 is the hub that used by the TrustNote TestNet, if you want to switch your wallet to connect to the mainnet, you can change hub_url as bob.trustnote.org:6616.* 62 | 63 | The 12 words after “mnemonic” are your personal mnemonics. You can also change the mnemonic over here, but please note the mnemonic must be generated by a TrustNote wallet otherwise it simply won’t work. 64 | 65 | Every time is you make any changes to ```settings.json```, you will need to manually delete ```trustnote_light.sqlite```, the database file in the same folder and run the following command (Check the information of your wallet) to apply your changes and check if your changes take effect. 66 | 67 | ## Check the Information of Your Wallet 68 | 69 | After wallet being initialized or imported, or after the configuration being manually updated, you will be able to review the information of your wallet by running this command: 70 | 71 | ``` 72 | ./ttt info 73 | ``` 74 | 75 | Then it will show your wallet’s information like this: 76 | 77 | ``` 78 | current wallet info: 79 | 80 | device_address: 0IKB7F3DAVIB3YHKJYWWVSBC6CSK76IE7 81 | wallet_public_key: xpub6D9Xmp2Y9XTpZYZ5xk4cNxSQoBufvQ5SWLATBwyaSh38G6aiCrUzUGuEtMoRMPy3a3wKJ8B6obtpUvu89sBbadqah9iXLWohTZi9FWj7JML 82 | └──wallet_id(0): UYwIQm+PIzvY5lceD6uX+yAc86LfaC3RFobSdxGfHmk= 83 | └──address(0/0): HU475BN5CEEPYL3WPLK5KA3FKXXN5NAD 84 | ├── path: /m/44'/0'/0'/0/0 85 | ├── pubkey: A3OTtemVlcteNZafJyXoCbE0UJ5SL74UI0cIjyJC4bCe 86 | └── balance: 299.999MN 87 | ``` 88 | 89 | In this example,``` HU475BN5CEEPYL3WPLK5KA3FKXXN5NAD``` is the wallet’s address, and the wallet’s balance is 299.999 MN of TTT. 90 | 91 | ## Get some TTT Test Notes for free 92 | 93 | By default, this tutorial runs on TrustNote testnet instead of mainnet, and for testing purpose, we do provide some TTT Test Notes for free: 94 | 95 | Open http://dev.trustnote.org/getTTT from your web browser, simple input your wallet address and press the “get TTT” button. 96 | 97 | Usually the TTT Test Notes will be transferred to your wallet in about a few seconds, sometimes it may take up to 1 or 2 minutes. You can check the new balance of your wallet by running the ```./TTT``` info command again. 98 | 99 | *Note: TTT Test Notes can be used to issue tokens and pay for the transaction fees on the testnet, however it doesn’t work on TrustNote’s mainnet, and it can’t be traded on those crypto-currency markets where TTT is currently being listed. 100 | ref* 101 | 102 | ## Transfer the Money (TTT Test Notes) 103 | 104 | Before we move to this vital step, please make sure you have a seperate wallet addresses for testing. In this tutorial, we will use address ```OKLGMIWBCFITVWKZF3JASA23OMZLICSH``` as a sample. 105 | 106 | If you don’t have a separate address can be used for testing, don’t worry, we provided you an easy way to generate address and its mnemonic for your testing purpose. 107 | 108 | Simply visit http://developers.trustnote.org/fancy-address/index.html and get your testing wallet address including its mnemonic. 109 | Now let’s transferring the TTT Test Notes from your new wallet to the testing address by running the following command: 110 | 111 | ``` 112 | ./ttt send -p OKLGMIWBCFITVWKZF3JASA23OMZLICSH 9.99 -t hello 113 | ``` 114 | 115 | From the return message below, you will see the *from* address and *to* address, and the amount being transferred. 116 | 117 | *Note ```-t``` specifies any texts for your own record, it is an optional field but please don’t forget that texts will be saved on a blockchain!* 118 | 119 | ``` 120 | refresh history done 121 | 122 | FROM : HU475BN5CEEPYL3WPLK5KA3FKXXN5NAD 123 | TO : 124 | address : OKLGMIWBCFITVWKZF3JASA23OMZLICSH, amount : 9.99 125 | UNIT : ly1DYqhjP/QngSCHP6t1NqumabOZQdR5bSiqzDDt7bc= 126 | TEXT : hello 127 | DATE : 2018-08-15 15:59:27.973 128 | ``` 129 | 130 | ## Check the Balance 131 | 132 | Again, we have several options here: 133 | 134 | 1. Check the balance including both confirmed and unconfirmed: 135 | 136 | ``` 137 | ./ttt balance 138 | ``` 139 | 140 | 2. Check the balance (confirmed only): 141 | 142 | ``` 143 | ./ttt balance -s 144 | ``` 145 | 146 | 3. Check the balance (unconfirmed only): 147 | 148 | ``` 149 | ./ttt balance -p 150 | ``` 151 | 152 | *Tip: As TrustNote uses UTXO model, sometime you will see strange transactions such as: When Bob transfers 100 TTT to Alice, you may see Bob transferred 150 TTT to Alice, and then 50 TTT are transferred from Alice back to Bob, that’s why sometime you may notice there is 50 TTT pending transaction (unconfirmed) during the process. But after being confirmed, everything should come to normal again.* 153 | 154 | ## What else I can do with the Rust SDK? 155 | 156 | Running SDK in a different location (file directory) will generate ```settings.json``` file in different places. This means that you can use Python, PHP, or any other scripting language to build a multi-user wallet system. 157 | 158 | For example, you can put the Rust SDK on the server and provide your web-based multi-account wallet service running on TrustNote network. 159 | 160 | This is still an early version of the release, more features will be added in futures releases. I hope you like it! 161 | To help us make it better, you are welcomed to report any bugs on TrustNote Github bug portal, or ask any questions you might have on the TrustNote subreddit. 162 | 163 | 164 | See [WIP_INIT.md](WIP_INIT.md) for information about the Rust Implementation of the TrustNote protocol. 165 | 166 | See [RaspberryPi.md](RaspberryPi.md) for how to build the TrustNote Rust SDK from Source for Raspberry Pi. 167 | -------------------------------------------------------------------------------- /src/utils/map_lock.rs: -------------------------------------------------------------------------------- 1 | use may::sync::{Blocker, Mutex}; 2 | 3 | use std::collections::HashSet; 4 | use std::collections::LinkedList; 5 | use std::fmt::{self, Debug}; 6 | use std::hash::Hash; 7 | use std::sync::Arc; 8 | 9 | struct Task { 10 | keys: Vec, 11 | blocker: Arc, 12 | } 13 | 14 | struct Inner { 15 | // curent keys in the 16 | keys: HashSet, 17 | // queue for blockers 18 | tasks: LinkedList>, 19 | } 20 | 21 | impl Inner { 22 | // detect if key is in use 23 | fn keys_is_locked(&self, keys: &[T]) -> bool { 24 | for key in keys { 25 | if self.keys.contains(key) { 26 | return true; 27 | } 28 | } 29 | false 30 | } 31 | 32 | fn keys_unlock(&mut self, keys: &[T]) { 33 | self.keys.retain(|key| !keys.contains(key)); 34 | } 35 | 36 | fn keys_lock(&mut self, keys: &[T]) { 37 | for k in keys { 38 | let ret = self.keys.insert(k.clone()); 39 | assert_eq!(ret, true); 40 | } 41 | } 42 | 43 | // find out next suitable blocker for wakeup 44 | fn task_dequeue(&mut self) -> Option> { 45 | let mut idx = 0; 46 | let mut is_found = false; 47 | for task in &self.tasks { 48 | // find the first task that is ready to go 49 | if !self.keys_is_locked(&task.keys) { 50 | is_found = true; 51 | break; 52 | } 53 | idx += 1; 54 | } 55 | 56 | if !is_found { 57 | return None; 58 | } 59 | 60 | // remove the task in the queue 61 | let mut list = self.tasks.split_off(idx); 62 | let task = list.pop_front().unwrap(); 63 | self.tasks.append(&mut list); 64 | 65 | Some(task) 66 | } 67 | 68 | // put the entry into the waiting queue 69 | fn task_enqueue(&mut self, entry: Task) { 70 | self.tasks.push_back(entry); 71 | } 72 | } 73 | 74 | // protect the struct by a RwLock 75 | pub struct MapLock(Mutex>); 76 | 77 | impl Debug for MapLock { 78 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 79 | write!(f, "MapLock{{ ... }}") 80 | } 81 | } 82 | 83 | impl Default for MapLock { 84 | fn default() -> Self { 85 | MapLock::new() 86 | } 87 | } 88 | 89 | impl MapLock { 90 | pub fn new() -> Self { 91 | MapLock(Mutex::new(Inner { 92 | keys: HashSet::new(), 93 | tasks: LinkedList::new(), 94 | })) 95 | } 96 | 97 | // return how many tasks waiting on the MapLock 98 | pub fn get_waiter_num(&self) -> usize { 99 | let g = self.0.lock().unwrap(); 100 | g.tasks.len() 101 | } 102 | 103 | // used internally 104 | fn release_keys(&self, keys: &[T]) { 105 | let mut g = self.0.lock().unwrap(); 106 | // remove keys from the map lock 107 | g.keys_unlock(keys); 108 | 109 | // release all the tasks that is qualified 110 | while let Some(task) = g.task_dequeue() { 111 | g.keys_lock(&task.keys); 112 | task.blocker.unpark(); 113 | } 114 | } 115 | 116 | // we must keep the strict order for fairness 117 | pub fn try_lock(&self, keys: Vec) -> Option> { 118 | let mut g = self.0.lock().unwrap(); 119 | 120 | // first check if there are other pending task is ok for wakeup 121 | // we don't need to do this because: 122 | // 1. the mutex already have a task queue for fairness 123 | // 2. the drop of guard should wakeup all the tasks 124 | // while let Some(task) = g.task_dequeue() { 125 | // g.keys_lock(&task.keys); 126 | // task.blocker.unpark(); 127 | // } 128 | 129 | // check our keys at last 130 | if g.keys_is_locked(&keys) { 131 | return None; 132 | } 133 | 134 | // ok, the keys are not in use 135 | // mark the keys as in use 136 | g.keys_lock(&keys); 137 | 138 | // return the guard 139 | Some(MapLockGuard { owner: self, keys }) 140 | } 141 | 142 | pub fn lock(&self, keys: Vec) -> MapLockGuard { 143 | use may::coroutine::{self, ParkError}; 144 | 145 | let mut g = self.0.lock().unwrap(); 146 | 147 | // first check if there are other pending task is ok for wakeup 148 | // we don't need to do this because: 149 | // 1. the mutex already have a task queue for fairness 150 | // 2. the drop of guard should wakeup all the tasks 151 | // while let Some(task) = g.task_dequeue() { 152 | // g.keys_lock(&task.keys); 153 | // task.blocker.unpark(); 154 | // } 155 | 156 | if !g.keys_is_locked(&keys) { 157 | // ok, the keys are not in use 158 | // mark the keys as in use 159 | g.keys_lock(&keys); 160 | return MapLockGuard { owner: self, keys }; 161 | } 162 | 163 | // put the blocker in the waiting queue 164 | let blocker = Blocker::current(); 165 | let task = Task { 166 | keys: keys.clone(), 167 | blocker: blocker.clone(), 168 | }; 169 | g.task_enqueue(task); 170 | drop(g); 171 | 172 | // wait until unparked 173 | match blocker.park(None) { 174 | Ok(_) => {} 175 | Err(ParkError::Timeout) => unreachable!(), 176 | Err(ParkError::Canceled) => { 177 | coroutine::trigger_cancel_panic(); 178 | } 179 | } 180 | 181 | // we comeback, it's safe to say that our keys are locked 182 | MapLockGuard { owner: self, keys } 183 | } 184 | } 185 | 186 | #[derive(Debug)] 187 | pub struct MapLockGuard<'a, T: Clone + Hash + Eq + 'a> { 188 | owner: &'a MapLock, 189 | keys: Vec, 190 | } 191 | 192 | impl<'a, T: Clone + Hash + Eq> Drop for MapLockGuard<'a, T> { 193 | fn drop(&mut self) { 194 | // remove the entry 195 | self.owner.release_keys(&self.keys); 196 | } 197 | } 198 | 199 | #[cfg(test)] 200 | mod tests { 201 | use super::*; 202 | 203 | #[test] 204 | fn test_map_lock() { 205 | let lock = Arc::new(MapLock::new()); 206 | let g = lock.lock(vec!["test"]); 207 | let g1 = lock.try_lock(vec!["test", "test1"]); 208 | assert_eq!(g1.is_some(), false); 209 | drop(g); 210 | let g2 = lock.try_lock(vec!["test", "test1"]); 211 | assert_eq!(g2.is_some(), true); 212 | 213 | let lock_1 = lock.clone(); 214 | let j = go!(move || { 215 | let _g = lock_1.lock(vec!["test"]); 216 | println!("comeback in coroutine"); 217 | }); 218 | 219 | drop(g2); 220 | j.join().unwrap(); 221 | } 222 | 223 | #[test] 224 | fn test_map_try_lock() { 225 | let lock = MapLock::new(); 226 | let g = lock.try_lock(vec!["test"]); 227 | assert_eq!(g.is_some(), true); 228 | let g1 = lock.try_lock(vec!["test"]); 229 | assert_eq!(g1.is_some(), false); 230 | let g2 = lock.try_lock(vec!["test1"]); 231 | assert_eq!(g2.is_some(), true); 232 | drop(g); 233 | let g1 = lock.try_lock(vec!["test"]); 234 | assert_eq!(g1.is_some(), true); 235 | } 236 | 237 | #[test] 238 | fn test_map_unlock() { 239 | let lock = Arc::new(MapLock::new()); 240 | let g = lock.lock(vec!["test1", "test2"]); 241 | 242 | let lock_1 = lock.clone(); 243 | let j1 = go!(move || { 244 | let _g = lock_1.lock(vec!["test1"]); 245 | println!("comeback in coroutine1"); 246 | }); 247 | 248 | let lock_2 = lock.clone(); 249 | let j2 = go!(move || { 250 | let _g = lock_2.lock(vec!["test2"]); 251 | println!("comeback in coroutine2"); 252 | }); 253 | 254 | drop(g); // this will release both coroutine 255 | j1.join().unwrap(); 256 | j2.join().unwrap(); 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/headers_commission.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use error::Result; 4 | use may::sync::Mutex; 5 | use rusqlite::Connection; 6 | 7 | struct ChildInfo { 8 | child_unit: String, 9 | next_mc_unit: String, 10 | } 11 | 12 | lazy_static! { 13 | static ref MAX_SPENDABLE_MCI: Mutex> = Mutex::new(None); 14 | } 15 | 16 | fn get_max_spendable_mci(db: &Connection) -> Result { 17 | let mut stmt = db.prepare_cached( 18 | "SELECT MAX(main_chain_index) AS max_spendable_mci FROM headers_commission_outputs", 19 | )?; 20 | let mci = stmt 21 | .query_row(&[], |row| row.get::<_, Option>(0)) 22 | .unwrap_or(None) 23 | .unwrap_or(0); 24 | Ok(mci) 25 | } 26 | 27 | fn get_winner_info<'a>(children: &'a mut Vec) -> Result<&'a ChildInfo> { 28 | if children.len() == 1 { 29 | return Ok(&children[0]); 30 | } 31 | 32 | use sha1::Sha1; 33 | children.sort_by_key(|child| { 34 | let mut m = Sha1::new(); 35 | m.update(child.child_unit.as_bytes()); 36 | m.update(child.next_mc_unit.as_bytes()); 37 | m.digest().to_string() 38 | }); 39 | 40 | Ok(&children[0]) 41 | } 42 | 43 | pub fn calc_headers_commissions(db: &Connection) -> Result<()> { 44 | // here for multi-thread we need a mutex to protect db without dup insertions 45 | let mut max_spendable_mci = MAX_SPENDABLE_MCI.lock().unwrap(); 46 | if max_spendable_mci.is_none() { 47 | *max_spendable_mci = Some(get_max_spendable_mci(db)?); 48 | } 49 | let since_mc_index = max_spendable_mci.unwrap(); 50 | 51 | // chunits is any child unit and contender for headers commission, punits is hc-payer unit 52 | let sql = 53 | "SELECT chunits.unit AS child_unit, punits.headers_commission, next_mc_units.unit AS next_mc_unit, punits.unit AS payer_unit \ 54 | FROM units AS chunits \ 55 | JOIN parenthoods ON chunits.unit=parenthoods.child_unit \ 56 | JOIN units AS punits ON parenthoods.parent_unit=punits.unit \ 57 | JOIN units AS next_mc_units ON next_mc_units.is_on_main_chain=1 AND next_mc_units.main_chain_index=punits.main_chain_index+1 \ 58 | WHERE chunits.is_stable=1 \ 59 | AND +chunits.sequence='good' \ 60 | AND punits.main_chain_index>? \ 61 | AND +punits.sequence='good' \ 62 | AND punits.is_stable=1 \ 63 | AND chunits.main_chain_index-punits.main_chain_index<=1 \ 64 | AND next_mc_units.is_stable=1"; 65 | 66 | struct Row { 67 | child_unit: String, 68 | headers_commission: u32, 69 | next_mc_unit: String, 70 | payer_unit: String, 71 | } 72 | 73 | let mut stmt = db.prepare_cached(&sql)?; 74 | let rows = stmt.query_map(&[&since_mc_index], |row| Row { 75 | child_unit: row.get(0), 76 | headers_commission: row.get(1), 77 | next_mc_unit: row.get(2), 78 | payer_unit: row.get(3), 79 | })?; 80 | 81 | struct ChildrenInfo { 82 | headers_commission: u32, 83 | children: Vec, 84 | } 85 | 86 | let mut assoc_children_infos = HashMap::::new(); 87 | for row in rows { 88 | let row = row?; 89 | let payer_unit = row.payer_unit; 90 | let child_unit = row.child_unit; 91 | 92 | let info = assoc_children_infos 93 | .entry(payer_unit) 94 | .or_insert(ChildrenInfo { 95 | headers_commission: row.headers_commission, 96 | children: Vec::new(), 97 | }); 98 | 99 | ensure!( 100 | info.headers_commission == row.headers_commission, 101 | "different headers_commission" 102 | ); 103 | 104 | info.children.push(ChildInfo { 105 | child_unit, 106 | next_mc_unit: row.next_mc_unit, 107 | }) 108 | } 109 | 110 | //Create a nested HashMap, first key by child_unit then key by payer_unit 111 | let mut assoc_won_amounts = HashMap::new(); 112 | for (payer_unit, children_info) in &mut assoc_children_infos { 113 | let headers_commission = children_info.headers_commission; 114 | let winner_child_info = get_winner_info(&mut children_info.children); 115 | let child_unit = &winner_child_info?.child_unit; 116 | 117 | let amount_map = assoc_won_amounts 118 | .entry(child_unit) 119 | .or_insert_with(HashMap::::new); 120 | amount_map.insert(payer_unit.to_string(), headers_commission); 121 | } 122 | 123 | if assoc_won_amounts.keys().len() > 0 { 124 | let winner_units_list = assoc_won_amounts 125 | .keys() 126 | .map(|s| format!("'{}'", s)) 127 | .collect::>() 128 | .join(", "); 129 | 130 | let sql = 131 | format!( 132 | "SELECT \ 133 | unit_authors.unit, \ 134 | unit_authors.address, \ 135 | 100 AS earned_headers_commission_share \ 136 | FROM unit_authors \ 137 | LEFT JOIN earned_headers_commission_recipients USING(unit) \ 138 | WHERE unit_authors.unit IN({}) AND earned_headers_commission_recipients.unit IS NULL \ 139 | UNION ALL \ 140 | SELECT \ 141 | unit, \ 142 | address, \ 143 | earned_headers_commission_share \ 144 | FROM earned_headers_commission_recipients \ 145 | WHERE unit IN({})", winner_units_list, winner_units_list); 146 | 147 | let mut stmt = db.prepare(&sql)?; 148 | 149 | struct Row { 150 | unit: String, 151 | address: String, 152 | earned_headers_commission_share: u32, 153 | } 154 | 155 | let rows = stmt.query_map(&[], |row| Row { 156 | unit: row.get(0), 157 | address: row.get(1), 158 | earned_headers_commission_share: row.get(2), 159 | })?; 160 | 161 | let mut values = Vec::new(); 162 | for row in rows { 163 | let row = row?; 164 | let child_unit = row.unit; 165 | 166 | let entry = assoc_won_amounts.get(&child_unit); 167 | ensure!(entry.is_some(), "no amount for child unit {}", child_unit); 168 | let entry = entry.unwrap(); 169 | 170 | for payer_unit in entry.keys() { 171 | let full_amount = entry.get(payer_unit); 172 | ensure!( 173 | full_amount.is_some(), 174 | "no amount for child unit {} and payer unit {}", 175 | child_unit, 176 | payer_unit 177 | ); 178 | let full_amount = *full_amount.unwrap(); 179 | 180 | let amount = if row.earned_headers_commission_share == 100 { 181 | full_amount 182 | } else { 183 | (f64::from(full_amount) * f64::from(row.earned_headers_commission_share) 184 | / 100.0) 185 | .round() as u32 186 | }; 187 | 188 | let value = format!("('{}','{}',{})", payer_unit, row.address, amount); 189 | values.push(value); 190 | } 191 | } 192 | 193 | let value_list = values.join(", "); 194 | 195 | let sql = format!( 196 | "INSERT INTO headers_commission_contributions (unit, address, amount) VALUES {}", 197 | value_list 198 | ); 199 | 200 | let mut stmt = db.prepare(&sql)?; 201 | stmt.execute(&[])?; 202 | } 203 | 204 | let mut stmt = db.prepare_cached( 205 | "INSERT INTO headers_commission_outputs (main_chain_index, address, amount) \ 206 | SELECT main_chain_index, address, SUM(amount) FROM headers_commission_contributions JOIN units USING(unit) \ 207 | WHERE main_chain_index>? \ 208 | GROUP BY main_chain_index, address")?; 209 | stmt.execute(&[&since_mc_index])?; 210 | 211 | *max_spendable_mci = Some(get_max_spendable_mci(db)?); 212 | Ok(()) 213 | } 214 | 215 | pub fn get_max_spendable_mci_for_last_ball_mci(last_ball_mci: u32) -> u32 { 216 | last_ball_mci - 1 217 | } 218 | -------------------------------------------------------------------------------- /src/parent_composer.rs: -------------------------------------------------------------------------------- 1 | use error::Result; 2 | use failure::ResultExt; 3 | use rusqlite::Connection; 4 | 5 | use config; 6 | use main_chain; 7 | use spec::Unit; 8 | use storage; 9 | 10 | #[derive(Debug, Clone)] 11 | pub struct LastStableBallAndParentUnits { 12 | pub parent_units: Vec, 13 | pub last_stable_mc_ball: Option, 14 | pub last_stable_mc_ball_mci: u32, 15 | pub last_stable_mc_ball_unit: Option, 16 | } 17 | 18 | pub fn pick_parent_units_and_last_ball( 19 | db: &Connection, 20 | witnesses: &Vec, 21 | ) -> Result { 22 | let witnesses_list = witnesses 23 | .iter() 24 | .map(|s| format!("'{}'", s)) 25 | .collect::>() 26 | .join(", "); 27 | 28 | let parent_units = pick_parent_units(db, &witnesses_list).context("parent units not found")?; 29 | 30 | let last_stable_mc_ball_unit = find_last_stable_mc_ball(db, &witnesses_list) 31 | .context("failed to find_last_stable_mc_ball")?; 32 | 33 | let LastStableBallAndParentUnits { 34 | last_stable_mc_ball, 35 | last_stable_mc_ball_mci, 36 | last_stable_mc_ball_unit, 37 | parent_units, 38 | } = adjust_last_stable_mc_ball_and_parents( 39 | db, 40 | last_stable_mc_ball_unit.unwrap(), 41 | parent_units, 42 | &witnesses_list, 43 | ).context("failed to adjust_last_stable_mc_ball_and_parents")?; 44 | 45 | let witness_list_unit = 46 | storage::find_witness_list_unit(db, witnesses, last_stable_mc_ball_mci)?; 47 | 48 | let mut tmp_unit = Unit::default(); 49 | tmp_unit.parent_units = parent_units; 50 | tmp_unit.witness_list_unit = witness_list_unit; 51 | 52 | storage::determine_if_has_witness_list_mutations_along_mc( 53 | db, 54 | &tmp_unit, 55 | &last_stable_mc_ball_unit.clone().unwrap(), 56 | witnesses, 57 | ).context("failed to determine_if_has_witness_list_mutations_along_mc.")?; 58 | 59 | Ok(LastStableBallAndParentUnits { 60 | last_stable_mc_ball_unit, 61 | last_stable_mc_ball, 62 | last_stable_mc_ball_mci, 63 | parent_units: tmp_unit.parent_units, 64 | }) 65 | } 66 | 67 | fn pick_parent_units(db: &Connection, witnesses_list: &str) -> Result> { 68 | struct TempUnit { 69 | unit: String, 70 | version: String, 71 | alt: String, 72 | count_matching_witnesses: u32, 73 | } 74 | 75 | let sql = format!( 76 | "SELECT unit, version, alt, ( \ 77 | SELECT COUNT(*) \ 78 | FROM unit_witnesses \ 79 | WHERE unit_witnesses.unit IN(units.unit, units.witness_list_unit) AND address IN({}) \ 80 | ) AS count_matching_witnesses \ 81 | FROM units INDEXED BY byFree \ 82 | LEFT JOIN archived_joints USING(unit) \ 83 | WHERE + sequence = 'good' AND is_free=1 AND archived_joints.unit IS NULL ORDER BY unit LIMIT {}", 84 | witnesses_list, config::MAX_PARENT_PER_UNIT); 85 | 86 | let mut stmt = db.prepare_cached(&sql)?; 87 | let rows = stmt 88 | .query_map(&[], |row| TempUnit { 89 | unit: row.get(0), 90 | version: row.get(1), 91 | alt: row.get(2), 92 | count_matching_witnesses: row.get(3), 93 | })?.collect::<::std::result::Result, _>>()?; 94 | 95 | if rows 96 | .iter() 97 | .any(|row| row.version != config::VERSION || row.alt != config::ALT) 98 | { 99 | bail!("wrong network"); 100 | } 101 | let count_required_matches = config::COUNT_WITNESSES - config::MAX_WITNESS_LIST_MUTATIONS; 102 | let tmp_units = rows 103 | .into_iter() 104 | .filter(|row| row.count_matching_witnesses >= count_required_matches as u32) 105 | .collect::>(); 106 | 107 | if tmp_units.is_empty() { 108 | let parent_units = pick_deep_parent_units(db, witnesses_list) 109 | .context("failed to pick deep parent units")?; 110 | Ok(parent_units) 111 | } else { 112 | Ok(tmp_units.into_iter().map(|x| x.unit).collect::>()) 113 | } 114 | } 115 | 116 | fn pick_deep_parent_units(db: &Connection, witnesses_list: &str) -> Result> { 117 | let sql = format!( 118 | "SELECT unit \ 119 | FROM units \ 120 | WHERE +sequence='good' \ 121 | AND ( \ 122 | SELECT COUNT(*) \ 123 | FROM unit_witnesses \ 124 | WHERE unit_witnesses.unit IN(units.unit, units.witness_list_unit) AND address IN({}) \ 125 | )>={} \ 126 | ORDER BY main_chain_index DESC LIMIT 1", 127 | witnesses_list, 128 | config::COUNT_WITNESSES - config::MAX_WITNESS_LIST_MUTATIONS 129 | ); 130 | 131 | let mut stmt = db.prepare_cached(&sql)?; 132 | let rows = stmt 133 | .query_map(&[], |row| row.get(0))? 134 | .collect::<::std::result::Result, _>>()?; 135 | if rows.is_empty() { 136 | bail!("failed to find compatible parents: no deep units"); 137 | } 138 | 139 | Ok(rows) 140 | } 141 | 142 | fn find_last_stable_mc_ball(db: &Connection, witnesses_list: &str) -> Result> { 143 | let sql = format!( 144 | "SELECT ball, unit, main_chain_index FROM units JOIN balls USING(unit) \ 145 | WHERE is_on_main_chain=1 AND is_stable=1 AND +sequence='good' AND ( \ 146 | SELECT COUNT(*) \ 147 | FROM unit_witnesses \ 148 | WHERE unit_witnesses.unit IN(units.unit, units.witness_list_unit) AND address IN({}) \ 149 | )>={} \ 150 | ORDER BY main_chain_index DESC LIMIT 1", 151 | witnesses_list, 152 | config::COUNT_WITNESSES - config::MAX_WITNESS_LIST_MUTATIONS, 153 | ); 154 | 155 | let mut stmt = db.prepare_cached(&sql)?; 156 | let rows = stmt 157 | .query_map(&[], |row| row.get(1))? 158 | .collect::<::std::result::Result, _>>()?; 159 | if rows.is_empty() { 160 | bail!("failed to find last stable ball"); 161 | } 162 | Ok(rows.into_iter().nth(0)) 163 | } 164 | 165 | fn adjust_last_stable_mc_ball_and_parents( 166 | db: &Connection, 167 | mut last_stable_mc_ball_unit: String, 168 | mut parent_units: Vec, 169 | witnesses_list: &str, 170 | ) -> Result { 171 | loop { 172 | let is_stable = main_chain::determin_if_stable_in_laster_units( 173 | db, 174 | &last_stable_mc_ball_unit, 175 | &parent_units, 176 | )?; 177 | if is_stable { 178 | struct TempBallMci { 179 | ball: String, 180 | main_chain_index: u32, 181 | } 182 | 183 | let mut stmt = db.prepare( 184 | "SELECT ball, main_chain_index FROM units JOIN balls USING(unit) WHERE unit=?", 185 | )?; 186 | let rows = stmt 187 | .query_map(&[&last_stable_mc_ball_unit], |row| TempBallMci { 188 | ball: row.get(0), 189 | main_chain_index: row.get(1), 190 | })?.collect::<::std::result::Result, _>>()?; 191 | if rows.len() != 1 { 192 | bail!("not 1 ball by unit {}", last_stable_mc_ball_unit); 193 | } 194 | 195 | let row = rows.into_iter().nth(0).unwrap(); 196 | return Ok(LastStableBallAndParentUnits { 197 | last_stable_mc_ball: Some(row.ball), 198 | last_stable_mc_ball_unit: Some(last_stable_mc_ball_unit), 199 | last_stable_mc_ball_mci: row.main_chain_index, 200 | parent_units: parent_units, 201 | }); 202 | } 203 | 204 | info!( 205 | "will adjust last stable ball because {} is not stable in view of parents {:?}", 206 | &last_stable_mc_ball_unit, parent_units 207 | ); 208 | 209 | if parent_units.len() > 1 { 210 | parent_units = pick_deep_parent_units(db, witnesses_list) 211 | .context("pick_deep_parent_units in adjust failed: ")?; 212 | continue; 213 | } 214 | 215 | let obj_unit_props = storage::read_static_unit_property(db, &last_stable_mc_ball_unit)?; 216 | if let Some(unit) = obj_unit_props.best_parent_unit { 217 | last_stable_mc_ball_unit = unit; 218 | } else { 219 | bail!("no best parent of {}", last_stable_mc_ball_unit); 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /wallet_base/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate failure; 3 | #[macro_use] 4 | extern crate serde_json; 5 | #[macro_use] 6 | extern crate lazy_static; 7 | 8 | extern crate base64; 9 | extern crate bitcoin; 10 | extern crate rand; 11 | extern crate secp256k1; 12 | extern crate sha2; 13 | extern crate trustnote; 14 | extern crate wallet; 15 | 16 | use bitcoin::network::constants::Network; 17 | use bitcoin::util::bip32::ChildNumber; 18 | use rand::{OsRng, RngCore}; 19 | use trustnote::object_hash; 20 | use wallet::keyfactory::{KeyFactory, Seed}; 21 | 22 | pub use bitcoin::util::bip32::{ExtendedPrivKey, ExtendedPubKey}; 23 | pub use wallet::mnemonic::Mnemonic; 24 | 25 | pub type Result = ::std::result::Result; 26 | 27 | lazy_static! { 28 | // initialize consume too much memory, init it in thread context 29 | static ref KEY_FACTORY: KeyFactory = 30 | ::std::thread::spawn(|| KeyFactory::new()).join().unwrap(); 31 | 32 | static ref SECP256K1: secp256k1::Secp256k1 = 33 | ::std::thread::spawn(|| secp256k1::Secp256k1::new()).join().unwrap(); 34 | } 35 | 36 | pub trait Base64KeyExt: Sized { 37 | fn to_base64_key(&self) -> String; 38 | } 39 | 40 | impl Base64KeyExt for ExtendedPubKey { 41 | fn to_base64_key(&self) -> String { 42 | base64::encode(&self.public_key.serialize()[..]) 43 | } 44 | } 45 | 46 | /// generate random mnemonic 47 | pub fn mnemonic(passphrase: &str) -> Result { 48 | let mut encrypted = vec![0u8; 16]; 49 | if let Ok(mut rng) = OsRng::new() { 50 | rng.fill_bytes(encrypted.as_mut_slice()); 51 | let mnemonic = Mnemonic::new(&encrypted, passphrase)?; 52 | return Ok(mnemonic); 53 | } 54 | bail!("can not obtain random source"); 55 | } 56 | 57 | /// generator master private key from mnemonic 58 | pub fn master_private_key(mnemonic: &Mnemonic, salt: &str) -> Result { 59 | let seed = Seed::new(&mnemonic, salt); 60 | Ok(KEY_FACTORY.master_private_key(Network::Bitcoin, &seed)?) 61 | } 62 | 63 | /// get extended public key for a known private key 64 | pub fn extended_public_from_private(extended_private_key: &ExtendedPrivKey) -> ExtendedPubKey { 65 | KEY_FACTORY.extended_public_from_private(extended_private_key) 66 | } 67 | 68 | /// get wallet pubkey for a index 69 | pub fn wallet_pubkey(master_prvk: &ExtendedPrivKey, wallet: u32) -> Result { 70 | let prvk = KEY_FACTORY.private_child(master_prvk, ChildNumber::Hardened(44))?; 71 | let prvk = KEY_FACTORY.private_child(&prvk, ChildNumber::Hardened(0))?; 72 | let prvk = KEY_FACTORY.private_child(&prvk, ChildNumber::Hardened(wallet))?; 73 | Ok(KEY_FACTORY.extended_public_from_private(&prvk)) 74 | } 75 | 76 | /// get wallet prvkey for an address 77 | pub fn wallet_address_prvkey( 78 | master_prvk: &ExtendedPrivKey, 79 | wallet: u32, 80 | is_change: bool, 81 | index: u32, 82 | ) -> Result { 83 | let prvk = KEY_FACTORY.private_child(master_prvk, ChildNumber::Hardened(44))?; 84 | let prvk = KEY_FACTORY.private_child(&prvk, ChildNumber::Hardened(0))?; 85 | let prvk = KEY_FACTORY.private_child(&prvk, ChildNumber::Hardened(wallet))?; 86 | let prvk = KEY_FACTORY.private_child(&prvk, ChildNumber::Normal(is_change as u32))?; 87 | Ok(KEY_FACTORY.private_child(&prvk, ChildNumber::Normal(index))?) 88 | } 89 | 90 | /// get wallet pubkey for an address 91 | /// the wallet_pubk should be the return value of `wallet_pubkey` 92 | pub fn wallet_address_pubkey( 93 | wallet_pubk: &ExtendedPubKey, 94 | is_change: bool, 95 | index: u32, 96 | ) -> Result { 97 | let pubk = KEY_FACTORY.public_child(wallet_pubk, ChildNumber::Normal(is_change as u32))?; 98 | Ok(KEY_FACTORY.public_child(&pubk, ChildNumber::Normal(index))?) 99 | } 100 | 101 | /// get device address 102 | pub fn device_address(master_prvk: &ExtendedPrivKey) -> Result { 103 | use secp256k1::key::PublicKey; 104 | let prvk = KEY_FACTORY.private_child(master_prvk, ChildNumber::Hardened(1))?; 105 | let pubk = PublicKey::from_secret_key(&SECP256K1, &prvk.secret_key)?; 106 | let pub_b64 = base64::encode(&pubk.serialize()[..]); 107 | let mut device_address = object_hash::get_chash(&pub_b64)?; 108 | device_address.insert(0, '0'); 109 | Ok(device_address) 110 | } 111 | 112 | /// get wallet address 113 | /// the wallet_pubk should be the return value of `wallet_pubkey` 114 | pub fn wallet_address(wallet_pubk: &ExtendedPubKey, is_change: bool, index: u32) -> Result { 115 | let pubk = wallet_address_pubkey(wallet_pubk, is_change, index)?; 116 | let pub_b64 = base64::encode(&pubk.public_key.serialize()[..]); 117 | let json = json!(["sig", { "pubkey": pub_b64 }]); 118 | Ok(object_hash::get_chash(&json)?) 119 | } 120 | 121 | /// get wallet address 122 | /// the wallet_pubk should be the return value of `wallet_pubkey` 123 | pub fn wallet_id(wallet_pubk: &ExtendedPubKey) -> String { 124 | use sha2::Digest; 125 | base64::encode(&sha2::Sha256::digest(wallet_pubk.to_string().as_bytes())) 126 | } 127 | 128 | /// sign for hash, return base64 string 129 | pub fn sign(hash: &[u8], prvk: &ExtendedPrivKey) -> Result { 130 | // let hash = base64::decode(hash)?; 131 | //Sign it with the secret key 132 | let msg = secp256k1::Message::from_slice(hash)?; 133 | let recoverable = SECP256K1.sign_recoverable(&msg, &prvk.secret_key)?; 134 | let (_, sig) = recoverable.serialize_compact(&SECP256K1); 135 | Ok(base64::encode(&sig[..])) 136 | } 137 | 138 | /// verify the bas64 string signiture with the hash and pub key (a bas64 string) 139 | pub fn verify(hash: &str, b64_sig: &str, b64_pub_key: &str) -> Result<()> { 140 | let hash = base64::decode(hash)?; 141 | let msg = secp256k1::Message::from_slice(&hash)?; 142 | let sig = base64::decode(b64_sig)?; 143 | let pub_key = secp256k1::key::PublicKey::from_slice(&SECP256K1, &base64::decode(b64_pub_key)?)?; 144 | 145 | // verify the signature 146 | let signature = secp256k1::Signature::from_compact(&SECP256K1, &sig)?; 147 | SECP256K1.verify(&msg, &signature, &pub_key)?; 148 | Ok(()) 149 | } 150 | 151 | #[test] 152 | fn test_mnemonic() -> Result<()> { 153 | let mnemonic = mnemonic("")?; 154 | println!("mnemonic = {}", mnemonic.to_string()); 155 | Ok(()) 156 | } 157 | 158 | #[test] 159 | fn test_master_private_key() -> Result<()> { 160 | let mnemonic = mnemonic("")?; 161 | let prvk = master_private_key(&mnemonic, "")?; 162 | println!("master_private_key = {}", prvk.to_string()); 163 | Ok(()) 164 | } 165 | 166 | #[test] 167 | fn test_extended_public_from_private() -> Result<()> { 168 | let mnemonic = mnemonic("")?; 169 | let prvk = master_private_key(&mnemonic, "")?; 170 | let pubk = extended_public_from_private(&prvk); 171 | println!("master_private_key = {}", pubk.to_string()); 172 | Ok(()) 173 | } 174 | 175 | #[test] 176 | fn test_wallet_pubkey() -> Result<()> { 177 | let mnemonic = mnemonic("")?; 178 | let prvk = master_private_key(&mnemonic, "")?; 179 | let index = 0; 180 | let wallet_pubk = wallet_pubkey(&prvk, index)?; 181 | println!("wallet_public_key_{} = {}", index, wallet_pubk.to_string()); 182 | Ok(()) 183 | } 184 | 185 | #[test] 186 | fn test_sign_and_verify() -> Result<()> { 187 | // data must be a valid sha256 hash 188 | let hash = "KLop9582tzXZJbytWjiWLcnpEdvJI7mUymbnUPXweOM="; 189 | let wallet = 0; 190 | let is_change = false; 191 | let index = 0; 192 | 193 | let mnemonic = mnemonic("")?; 194 | let master_prvk = master_private_key(&mnemonic, "")?; 195 | 196 | let prvk = wallet_address_prvkey(&master_prvk, wallet, is_change, index)?; 197 | let wallet_pubk = wallet_pubkey(&master_prvk, wallet)?; 198 | let pubk = wallet_address_pubkey(&wallet_pubk, is_change, index)?; 199 | 200 | let sig = sign(&base64::decode(hash)?, &prvk)?; 201 | verify(hash, &sig, &pubk.to_base64_key()) 202 | } 203 | 204 | #[test] 205 | fn test_device_address() -> Result<()> { 206 | let mnemonic = mnemonic("")?; 207 | let prvk = master_private_key(&mnemonic, "")?; 208 | let wallet = 0; 209 | 210 | println!("mnemonic = {}", mnemonic.to_string()); 211 | println!("wallet_private_key = {}", prvk.to_string()); 212 | 213 | let wallet_pubk = wallet_pubkey(&prvk, wallet)?; 214 | println!("wallet_public_key = {}", wallet_pubk.to_string()); 215 | 216 | let wallet_id = wallet_id(&wallet_pubk); 217 | println!("wallet_id= {}", wallet_id); 218 | 219 | let wallet_address = wallet_address(&wallet_pubk, false, 0)?; 220 | println!("wallet_0/0_address = {}", wallet_address); 221 | 222 | let device_address = device_address(&prvk)?; 223 | println!("device_address = {}", device_address); 224 | assert_eq!(object_hash::is_chash_valid(&device_address[1..]), true); 225 | Ok(()) 226 | } 227 | -------------------------------------------------------------------------------- /src/paid_witnessing.rs: -------------------------------------------------------------------------------- 1 | use config; 2 | use error::Result; 3 | use graph; 4 | use mc_outputs; 5 | use rusqlite::Connection; 6 | use storage; 7 | 8 | pub fn calc_witness_earnings( 9 | db: &Connection, 10 | kind: &String, 11 | from_main_chain_index: u32, 12 | to_main_chain_index: u32, 13 | address: &String, 14 | ) -> Result { 15 | let mut stmt = db.prepare_cached( 16 | "SELECT COUNT(*) AS count FROM units \ 17 | WHERE is_on_main_chain=1 AND is_stable=1 \ 18 | AND main_chain_index>=? AND main_chain_index<=?", 19 | )?; 20 | 21 | let count = stmt 22 | .query_row( 23 | &[ 24 | &to_main_chain_index, 25 | &(to_main_chain_index + config::COUNT_MC_BALLS_FOR_PAID_WITNESSING + 1), 26 | ], 27 | |row| row.get(0), 28 | ).unwrap_or(0); 29 | 30 | ensure!( 31 | count == config::COUNT_MC_BALLS_FOR_PAID_WITNESSING + 2, 32 | "not enough stable MC units after to_main_chain_index" 33 | ); 34 | 35 | mc_outputs::calc_earnings( 36 | db, 37 | kind, 38 | from_main_chain_index, 39 | to_main_chain_index, 40 | address, 41 | ) 42 | } 43 | 44 | pub fn get_max_spendable_mci_for_last_ball_mci(last_ball_mci: u32) -> Option { 45 | last_ball_mci.checked_sub(1 + config::COUNT_MC_BALLS_FOR_PAID_WITNESSING) 46 | } 47 | 48 | fn read_mc_unit_witnesses(db: &Connection, main_chain_index: u32) -> Result> { 49 | let mut stmt = db.prepare_cached( 50 | "SELECT witness_list_unit, unit FROM units \ 51 | WHERE main_chain_index=? AND is_on_main_chain=1", 52 | )?; 53 | 54 | struct Row { 55 | witness_list_unit: Option, 56 | unit: String, 57 | } 58 | 59 | let rows = stmt.query_map(&[&main_chain_index], |row| Row { 60 | witness_list_unit: row.get(0), 61 | unit: row.get(1), 62 | })?; 63 | 64 | let mut units = Vec::new(); 65 | for row in rows { 66 | units.push(row?); 67 | } 68 | 69 | ensure!(units.len() == 1, "not 1 row on MC {}", main_chain_index); 70 | 71 | let unit = if units[0].witness_list_unit.is_some() { 72 | units[0].witness_list_unit.as_ref().unwrap().clone() 73 | } else { 74 | units[0].unit.clone() 75 | }; 76 | 77 | storage::read_witness_list(db, &unit) 78 | } 79 | 80 | fn build_paid_witnesses( 81 | db: &Connection, 82 | unit_prop: graph::UnitProps, 83 | witnesses: &[String], 84 | ) -> Result<()> { 85 | let main_chain_index = unit_prop.main_chain_index.unwrap_or(0); 86 | let to_main_chain_index = main_chain_index + config::COUNT_MC_BALLS_FOR_PAID_WITNESSING; 87 | 88 | let units = graph::read_descendant_units_by_authors_before_mc_index( 89 | db, 90 | &unit_prop, 91 | &witnesses, 92 | to_main_chain_index, 93 | )?; 94 | 95 | let unit = unit_prop.unit; 96 | let unit_list = units 97 | .iter() 98 | .map(|s| format!("'{}'", s)) 99 | .collect::>() 100 | .join(", "); 101 | 102 | let witness_list = witnesses 103 | .iter() 104 | .map(|s| format!("'{}'", s)) 105 | .collect::>() 106 | .join(", "); 107 | 108 | let mut paid_witnesses = Vec::new(); 109 | let value_list; 110 | 111 | if !units.is_empty() { 112 | let sql = format!( 113 | "SELECT address, MIN(main_chain_index-{}) AS delay FROM units \ 114 | LEFT JOIN unit_authors USING(unit) \ 115 | WHERE unit IN({}) AND address IN({}) AND +sequence='good' \ 116 | GROUP BY address", 117 | main_chain_index, unit_list, witness_list 118 | ); 119 | 120 | struct UnitProps { 121 | address: String, 122 | delay: u32, 123 | } 124 | 125 | let mut stmt = db.prepare(&sql)?; 126 | let rows = stmt.query_map(&[], |row| UnitProps { 127 | address: row.get(0), 128 | delay: row.get(1), 129 | })?; 130 | 131 | for row in rows { 132 | paid_witnesses.push(row?); 133 | } 134 | } 135 | 136 | let mut count_paid_witnesses = paid_witnesses.len() as u32; 137 | 138 | //If the query result is empty or no query at all 139 | if count_paid_witnesses == 0 { 140 | count_paid_witnesses = witnesses.len() as u32; 141 | value_list = witnesses 142 | .iter() 143 | .map(|s| format!("('{}', '{}', NULL)", unit, s)) 144 | .collect::>() 145 | .join(", "); 146 | } else { 147 | value_list = paid_witnesses 148 | .iter() 149 | .map(|s| format!("('{}', '{}', {})", unit, s.address, s.delay)) 150 | .collect::>() 151 | .join(", "); 152 | } 153 | 154 | let sql = format!( 155 | "INSERT INTO paid_witness_events_tmp (unit, address, delay) VALUES {}", 156 | value_list 157 | ); 158 | let mut stmt = db.prepare(&sql)?; 159 | stmt.execute(&[])?; 160 | 161 | //update count paid witnesses 162 | let mut stmt = db.prepare_cached("UPDATE balls SET count_paid_witnesses=? WHERE unit=?")?; 163 | stmt.execute(&[&count_paid_witnesses, &unit])?; 164 | 165 | Ok(()) 166 | } 167 | 168 | fn build_paid_witnesses_for_main_chain_index(db: &Connection, main_chain_index: u32) -> Result<()> { 169 | info!("updating paid witnesses mci {}", main_chain_index); 170 | let mut stmt = db.prepare_cached( 171 | "SELECT COUNT(*) AS count, \ 172 | SUM(CASE WHEN is_stable=1 THEN 1 ELSE 0 END) AS count_on_stable_mc \ 173 | FROM units WHERE is_on_main_chain=1 \ 174 | AND main_chain_index>=? AND main_chain_index<=?", 175 | )?; 176 | 177 | let (count, count_on_stable_mc) = stmt.query_row( 178 | &[ 179 | &main_chain_index, 180 | &(main_chain_index + config::COUNT_MC_BALLS_FOR_PAID_WITNESSING + 1), 181 | ], 182 | |row| (row.get::<_, u32>(0), row.get::<_, u32>(1)), 183 | )?; 184 | 185 | ensure!( 186 | count == config::COUNT_MC_BALLS_FOR_PAID_WITNESSING + 2, 187 | "main chain is not long enough yet for MC index {}", 188 | main_chain_index 189 | ); 190 | 191 | ensure!( 192 | count_on_stable_mc == count, 193 | "not enough stable MC units yet after MC index {}: count_on_stable_mc={}, count={}", 194 | main_chain_index, 195 | count_on_stable_mc, 196 | count 197 | ); 198 | 199 | let witnesses = read_mc_unit_witnesses(db, main_chain_index)?; 200 | 201 | let mut stmt = db.prepare_cached( 202 | "CREATE TEMPORARY TABLE paid_witness_events_tmp ( \ 203 | unit CHAR(44) NOT NULL, \ 204 | address CHAR(32) NOT NULL, \ 205 | delay TINYINT NULL)", 206 | )?; 207 | stmt.execute(&[])?; 208 | 209 | struct TablePaidWitnessEventsTmp<'a> { 210 | db: &'a Connection, 211 | } 212 | impl<'a> Drop for TablePaidWitnessEventsTmp<'a> { 213 | fn drop(&mut self) { 214 | let _ = self 215 | .db 216 | .prepare_cached("DROP TABLE IF EXISTS paid_witness_events_tmp") 217 | .and_then(|mut stmt| stmt.execute(&[])); 218 | } 219 | } 220 | let _tmp_table = TablePaidWitnessEventsTmp { db }; 221 | 222 | //In build_paid_witnesses(), graph::UnitProp only use some of the columns, no need to select * and parse them all 223 | let mut stmt = db.prepare_cached( 224 | "SELECT unit, level, latest_included_mc_index, main_chain_index, is_on_main_chain, is_free \ 225 | FROM units WHERE main_chain_index=?", 226 | )?; 227 | let rows = stmt.query_map(&[&main_chain_index], |row| graph::UnitProps { 228 | unit: row.get(0), 229 | level: row.get(1), 230 | latest_included_mc_index: row.get(2), 231 | main_chain_index: row.get(3), 232 | is_on_main_chain: row.get(4), 233 | is_free: row.get(5), 234 | })?; 235 | 236 | for row in rows { 237 | let unit_prop = row?; 238 | 239 | build_paid_witnesses(db, unit_prop, &witnesses)?; 240 | } 241 | 242 | let mut stmt = db.prepare_cached( 243 | "INSERT INTO witnessing_outputs (main_chain_index, address, amount) \ 244 | SELECT main_chain_index, address, \ 245 | SUM(CASE WHEN sequence='good' THEN ROUND(1.0*payload_commission/count_paid_witnesses) ELSE 0 END) \ 246 | FROM balls \ 247 | JOIN units USING(unit) \ 248 | JOIN paid_witness_events_tmp USING(unit) \ 249 | WHERE main_chain_index=? \ 250 | GROUP BY address" 251 | )?; 252 | stmt.execute(&[&main_chain_index])?; 253 | 254 | Ok(()) 255 | } 256 | 257 | fn build_paid_witnesses_till_main_chain_index( 258 | db: &Connection, 259 | to_main_chain_index: u32, 260 | ) -> Result<()> { 261 | let mut stmt = db.prepare_cached( 262 | "SELECT MIN(main_chain_index) AS min_main_chain_index FROM balls \ 263 | CROSS JOIN units USING(unit) \ 264 | WHERE count_paid_witnesses IS NULL", 265 | )?; 266 | 267 | let mut main_chain_index = stmt.query_row(&[], |row| row.get(0))?; 268 | while main_chain_index <= to_main_chain_index { 269 | build_paid_witnesses_for_main_chain_index(db, main_chain_index)?; 270 | main_chain_index += 1; 271 | } 272 | 273 | Ok(()) 274 | } 275 | 276 | pub fn update_paid_witnesses(db: &Connection) -> Result<()> { 277 | info!("updating paid witnesses"); 278 | let last_stable_mci = storage::read_last_stable_mc_index(db)?; 279 | let max_spendable_mc_index = get_max_spendable_mci_for_last_ball_mci(last_stable_mci); 280 | 281 | if max_spendable_mc_index.is_some() { 282 | build_paid_witnesses_till_main_chain_index(db, max_spendable_mc_index.unwrap())?; 283 | } 284 | 285 | Ok(()) 286 | } 287 | -------------------------------------------------------------------------------- /src/wallet.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::sync::Arc; 3 | 4 | use composer::{self, ComposeInfo}; 5 | use error::Result; 6 | use network::wallet::WalletConn; 7 | use rusqlite::Connection; 8 | use serde_json; 9 | use spec::Output; 10 | 11 | pub fn update_wallet_address( 12 | db: &Connection, 13 | device_address: &String, 14 | wallet_id: &String, 15 | address: &String, 16 | address_pubk: &String, 17 | ) -> Result<()> { 18 | let pubk_at_device = format!("$pubkey@{}", device_address); 19 | let definition_template = serde_json::to_string(&json!(["sig", { "pubkey": pubk_at_device }]))?; 20 | let mut stmt = db.prepare_cached( 21 | "INSERT OR IGNORE INTO wallets ('wallet', 'account', 'definition_template') \ 22 | VALUES (?, 0, ?)", 23 | )?; 24 | stmt.execute(&[wallet_id, &definition_template])?; 25 | 26 | let mut stmt = db.prepare_cached( 27 | "INSERT OR IGNORE INTO wallet_signing_paths ('wallet', 'signing_path', 'device_address') \ 28 | VALUES (?, 'r', ?)", 29 | )?; 30 | stmt.execute(&[wallet_id, device_address])?; 31 | 32 | let definition = serde_json::to_string(&json!(["sig", { "pubkey": address_pubk }]))?; 33 | let mut stmt = db.prepare_cached( 34 | "INSERT OR IGNORE INTO my_addresses ('address', 'wallet', 'is_change', 'address_index', 'definition') \ 35 | VALUES (?, ?, 0, 0, ?)")?; 36 | stmt.execute(&[address, wallet_id, &definition])?; 37 | 38 | Ok(()) 39 | } 40 | 41 | #[derive(Clone, Debug, Serialize, Deserialize)] 42 | pub struct TransactionHistory { 43 | pub amount: i64, 44 | pub address_to: String, 45 | pub address_from: String, 46 | pub confirmations: bool, 47 | pub fee: u32, 48 | pub unit: String, 49 | pub timestamp: i64, 50 | pub level: Option, 51 | pub mci: Option, 52 | } 53 | 54 | pub fn read_transaction_history(db: &Connection, address: &str) -> Result> { 55 | let mut history_transactions = Vec::new(); 56 | 57 | let mut stmt = db.prepare_cached( 58 | "SELECT unit, level, is_stable, sequence, address, \ 59 | strftime('%s', units.creation_date) AS ts, headers_commission+payload_commission AS fee, \ 60 | SUM(amount) AS amount, address AS to_address, NULL AS from_address, main_chain_index AS mci \ 61 | FROM units JOIN outputs USING(unit) \ 62 | WHERE address=? AND asset is NULL \ 63 | GROUP BY unit, address \ 64 | UNION \ 65 | SELECT unit, level, is_stable, sequence, address, \ 66 | strftime('%s', units.creation_date) AS ts, headers_commission+payload_commission AS fee, \ 67 | NULL AS amount, NULL AS to_address, address AS from_address, main_chain_index AS mci \ 68 | FROM units JOIN inputs USING(unit) \ 69 | WHERE address=? AND asset is NULL \ 70 | ORDER BY ts DESC", 71 | )?; 72 | 73 | #[derive(Debug)] 74 | struct TempRow { 75 | unit: String, 76 | level: Option, 77 | is_stable: u32, 78 | sequence: String, 79 | address: String, 80 | timestamp: i64, 81 | fee: u32, 82 | amount: Option, 83 | to_address: Option, 84 | from_address: Option, 85 | mci: Option, 86 | }; 87 | 88 | let rows = stmt 89 | .query_map(&[&address, &address], |row| TempRow { 90 | unit: row.get(0), 91 | level: row.get(1), 92 | is_stable: row.get(2), 93 | sequence: row.get(3), 94 | address: row.get(4), 95 | timestamp: row.get::<_, String>(5).parse::().unwrap() * 1000, 96 | fee: row.get(6), 97 | amount: row.get(7), 98 | to_address: row.get(8), 99 | from_address: row.get(9), 100 | mci: row.get(10), 101 | })?.collect::<::std::result::Result, _>>()?; 102 | 103 | let mut movements = HashMap::new(); 104 | for row in rows { 105 | //debug!("{:?}", row); 106 | struct Movement { 107 | plus: i64, 108 | has_minus: bool, 109 | timestamp: i64, 110 | level: Option, 111 | is_stable: bool, 112 | sequence: String, 113 | fee: u32, 114 | mci: Option, 115 | from_address: Option, 116 | to_address: Option, 117 | amount: Option, 118 | }; 119 | 120 | let has_to_address = row.to_address.is_some(); 121 | let has_from_address = row.from_address.is_some(); 122 | 123 | let mut movement = movements.entry(row.unit).or_insert(Movement { 124 | plus: 0, 125 | has_minus: false, 126 | timestamp: row.timestamp, 127 | level: row.level, 128 | is_stable: row.is_stable > 0, 129 | sequence: row.sequence, 130 | fee: row.fee, 131 | mci: row.mci, 132 | from_address: row.from_address, 133 | to_address: row.to_address, 134 | amount: row.amount, 135 | }); 136 | 137 | if has_to_address { 138 | movement.plus = movement.plus + row.amount.unwrap_or(0); 139 | } 140 | 141 | if has_from_address { 142 | movement.has_minus = true; 143 | } 144 | } 145 | 146 | for (unit, movement) in movements.into_iter() { 147 | //TODO: handle invalid case 148 | let _sequence = movement.sequence; 149 | 150 | //Receive 151 | if movement.plus > 0 && !movement.has_minus { 152 | let mut stmt = db.prepare_cached( 153 | "SELECT DISTINCT address FROM inputs \ 154 | WHERE unit=? AND asset is NULL ORDER BY address", 155 | )?; 156 | 157 | let addresses = stmt 158 | .query_map(&[&unit], |row| row.get(0))? 159 | .collect::<::std::result::Result, _>>()?; 160 | 161 | for address in addresses { 162 | let transaction = TransactionHistory { 163 | amount: movement.amount.unwrap_or(0), 164 | address_to: movement 165 | .to_address 166 | .as_ref() 167 | .cloned() 168 | .unwrap_or(String::new()), 169 | address_from: address, 170 | confirmations: movement.is_stable, 171 | fee: movement.fee, 172 | unit: unit.clone(), 173 | timestamp: movement.timestamp, 174 | level: movement.level, 175 | mci: movement.mci, 176 | }; 177 | 178 | history_transactions.push(transaction); 179 | } 180 | } else if movement.has_minus { 181 | //The amount is none when sending out 182 | let mut stmt = db.prepare_cached( 183 | "SELECT address, SUM(amount) AS amount, (address!=?) AS is_external \ 184 | FROM outputs \ 185 | WHERE unit=? AND asset is NULL \ 186 | GROUP BY address", 187 | )?; 188 | 189 | #[derive(Debug)] 190 | struct PayeeRows { 191 | address: Option, 192 | amount: Option, 193 | is_external: bool, 194 | } 195 | 196 | let payee_rows = stmt 197 | .query_map(&[&address, &unit], |row| PayeeRows { 198 | address: row.get(0), 199 | amount: row.get(1), 200 | is_external: row.get(2), 201 | })?.collect::<::std::result::Result, _>>()?; 202 | 203 | for payee_row in payee_rows { 204 | //debug!("{:?}", payee_row); 205 | 206 | if !payee_row.is_external { 207 | continue; 208 | } 209 | 210 | let transaction = TransactionHistory { 211 | amount: -payee_row.amount.unwrap_or(0), 212 | address_to: payee_row.address.unwrap_or(String::new()), 213 | address_from: movement 214 | .from_address 215 | .as_ref() 216 | .cloned() 217 | .unwrap_or(String::new()), 218 | confirmations: movement.is_stable, 219 | fee: movement.fee, 220 | unit: unit.clone(), 221 | timestamp: movement.timestamp, 222 | level: movement.level, 223 | mci: movement.mci, 224 | }; 225 | 226 | history_transactions.push(transaction); 227 | } 228 | } 229 | } 230 | 231 | //Should sort by level and time, but level is None in light wallet 232 | history_transactions.sort_by(|a, b| b.timestamp.cmp(&a.timestamp)); 233 | 234 | Ok(history_transactions) 235 | } 236 | 237 | // return values: first is unstable balance, second is stable balance. 238 | pub fn get_balance(db: &Connection, address: &str) -> Result<(i64, i64)> { 239 | let mut stmt = db.prepare_cached( 240 | "SELECT asset, is_stable, SUM(amount) AS balance \ 241 | FROM outputs JOIN units USING(unit) \ 242 | WHERE is_spent=0 AND address=? AND sequence='good' AND asset IS NULL \ 243 | GROUP BY is_stable", 244 | )?; 245 | 246 | let rows = stmt 247 | .query_map(&[&address], |row| row.get(2))? 248 | .collect::<::std::result::Result, _>>()?; 249 | 250 | match rows.len() { 251 | 2 => return Ok((rows[0], rows[1])), 252 | 1 => return Ok((0, rows[0])), 253 | _ => return Ok((0, 0)), 254 | } 255 | } 256 | 257 | pub fn prepare_payment( 258 | ws: &Arc, 259 | address_amount: &Vec<(&str, f64)>, 260 | text: Option<&str>, 261 | wallet_info_address: &str, 262 | ) -> Result { 263 | let mut outputs = Vec::new(); 264 | for (address, amount) in address_amount.into_iter() { 265 | outputs.push(Output { 266 | address: address.to_string(), 267 | amount: (amount * 1_000_000.0).round() as i64, 268 | }); 269 | } 270 | let amounts = outputs.iter().fold(0, |acc, x| acc + x.amount); 271 | outputs.push(Output { 272 | address: wallet_info_address.to_string(), 273 | amount: 0, 274 | }); 275 | 276 | let light_props = match ws.get_parents_and_last_ball_and_witness_list_unit() { 277 | Ok(res) => { 278 | if res.parent_units.is_empty() 279 | || res.last_stable_mc_ball.is_none() 280 | || res.last_stable_mc_ball_unit.is_none() 281 | { 282 | bail!("invalid parents or last stable mc ball"); 283 | } 284 | res 285 | } 286 | Err(e) => bail!( 287 | "err : get_parents_and_last_ball_and_witness_list_unit err:{:?}", 288 | e 289 | ), 290 | }; 291 | 292 | let messages = if text.is_some() { 293 | vec![composer::create_text_message(&text.unwrap().to_string())?] 294 | } else { 295 | vec![] 296 | }; 297 | 298 | Ok(ComposeInfo { 299 | paying_addresses: vec![wallet_info_address.to_string()], 300 | input_amount: amounts as u64, 301 | signing_addresses: Vec::new(), 302 | outputs: outputs, 303 | messages, 304 | light_props: light_props, 305 | earned_headers_commission_recipients: Vec::new(), 306 | witnesses: Vec::new(), 307 | inputs: Vec::new(), 308 | send_all: false, // FIXME: now send_all is always false 309 | }) 310 | } 311 | -------------------------------------------------------------------------------- /ttt/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | #[macro_use] 4 | extern crate clap; 5 | #[macro_use] 6 | extern crate failure; 7 | #[macro_use] 8 | extern crate serde_derive; 9 | 10 | extern crate chrono; 11 | extern crate fern; 12 | extern crate may; 13 | extern crate rusqlite; 14 | extern crate serde; 15 | extern crate serde_json; 16 | extern crate trustnote; 17 | extern crate trustnote_wallet_base; 18 | 19 | mod config; 20 | 21 | use std::sync::Arc; 22 | 23 | use chrono::{Local, TimeZone}; 24 | use clap::App; 25 | use composer; 26 | use failure::ResultExt; 27 | use rusqlite::Connection; 28 | use trustnote::network::wallet::WalletConn; 29 | use trustnote::*; 30 | use trustnote_wallet_base::{Base64KeyExt, ExtendedPrivKey, ExtendedPubKey, Mnemonic}; 31 | 32 | use trustnote::signature::Signer; 33 | 34 | struct WalletInfo { 35 | #[allow(dead_code)] 36 | master_prvk: ExtendedPrivKey, 37 | wallet_pubk: ExtendedPubKey, 38 | device_address: String, 39 | wallet_0_id: String, 40 | _00_address: String, 41 | _00_address_pubk: ExtendedPubKey, 42 | _00_address_prvk: ExtendedPrivKey, 43 | } 44 | 45 | impl WalletInfo { 46 | fn from_mnemonic(mnemonic: &str) -> Result { 47 | let wallet = 0; 48 | let mnemonic = Mnemonic::from(&mnemonic)?; 49 | let master_prvk = trustnote_wallet_base::master_private_key(&mnemonic, "")?; 50 | let device_address = trustnote_wallet_base::device_address(&master_prvk)?; 51 | let wallet_pubk = trustnote_wallet_base::wallet_pubkey(&master_prvk, wallet)?; 52 | let wallet_0_id = trustnote_wallet_base::wallet_id(&wallet_pubk); 53 | let _00_address = trustnote_wallet_base::wallet_address(&wallet_pubk, false, 0)?; 54 | let _00_address_prvk = 55 | trustnote_wallet_base::wallet_address_prvkey(&master_prvk, 0, false, 0)?; 56 | let _00_address_pubk = 57 | trustnote_wallet_base::wallet_address_pubkey(&wallet_pubk, false, 0)?; 58 | 59 | Ok(WalletInfo { 60 | master_prvk, 61 | wallet_pubk, 62 | device_address, 63 | wallet_0_id, 64 | _00_address, 65 | _00_address_pubk, 66 | _00_address_prvk, 67 | }) 68 | } 69 | } 70 | 71 | impl Signer for WalletInfo { 72 | fn sign(&self, hash: &[u8], address: &str) -> Result { 73 | if address != self._00_address { 74 | bail!("invalid address for wallet to sign"); 75 | } 76 | 77 | trustnote_wallet_base::sign(hash, &self._00_address_prvk) 78 | } 79 | } 80 | 81 | fn init_log(verbosity: u64) { 82 | let log_lvl = match verbosity { 83 | 0 => log::LevelFilter::Off, 84 | 1 => log::LevelFilter::Error, 85 | 2 => log::LevelFilter::Info, 86 | _ => log::LevelFilter::Debug, 87 | }; 88 | 89 | fern::Dispatch::new() 90 | .format(|out, message, record| { 91 | out.finish(format_args!( 92 | "{}[{}][{}] {}", 93 | chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S%.3f]"), 94 | record.level(), 95 | record.target(), 96 | message 97 | )) 98 | }).level(log_lvl) 99 | .chain(std::io::stdout()) 100 | .apply() 101 | .unwrap(); 102 | 103 | debug!("log init done!"); 104 | } 105 | 106 | fn init(verbosity: u64) -> Result<()> { 107 | // init default coroutine settings 108 | let stack_size = if cfg!(debug_assertions) { 109 | 0x4000 110 | } else { 111 | 0x2000 112 | }; 113 | may::config().set_stack_size(stack_size); 114 | 115 | init_log(verbosity); 116 | db::use_wallet_db(); 117 | 118 | Ok(()) 119 | } 120 | 121 | fn connect_to_remote(peers: &[String]) -> Result> { 122 | for peer in peers { 123 | match network::wallet::create_outbound_conn(&peer) { 124 | Err(e) => { 125 | error!(" fail to connected: {}, err={}", peer, e); 126 | continue; 127 | } 128 | Ok(c) => return Ok(c), 129 | } 130 | } 131 | bail!("failed to connect remote hub"); 132 | } 133 | 134 | fn info(db: &Connection, wallet_info: &WalletInfo) -> Result<()> { 135 | let address_pubk = wallet_info._00_address_pubk.to_base64_key(); 136 | let (unstable_balance, stable_balance) = wallet::get_balance(&db, &wallet_info._00_address)?; 137 | let total = (unstable_balance + stable_balance) as f64 / 1000_000.0; 138 | let stable = stable_balance as f64 / 1000_000.0; 139 | let pending = unstable_balance as f64 / 1000_000.0; 140 | 141 | println!("\ncurrent wallet info:\n"); 142 | println!("device_address: {}", wallet_info.device_address); 143 | println!("wallet_public_key: {}", wallet_info.wallet_pubk.to_string()); 144 | println!("└──wallet_id(0): {}", wallet_info.wallet_0_id); 145 | println!(" └──address(0/0): {}", wallet_info._00_address); 146 | println!(" ├── path: /m/44'/0'/0'/0/0"); 147 | println!(" ├── pubkey: {}", address_pubk); 148 | println!(" └── balance: {:.6}", total); 149 | println!(" ├── stable: {:.6}", stable); 150 | println!(" └── pending: {:.6}", pending); 151 | 152 | Ok(()) 153 | } 154 | 155 | // save wallet address in database 156 | fn update_wallet_address(db: &Connection, wallet_info: &WalletInfo) -> Result<()> { 157 | use trustnote_wallet_base::Base64KeyExt; 158 | 159 | wallet::update_wallet_address( 160 | db, 161 | &wallet_info.device_address, 162 | &wallet_info.wallet_0_id, 163 | &wallet_info._00_address, 164 | &wallet_info._00_address_pubk.to_base64_key(), 165 | )?; 166 | Ok(()) 167 | } 168 | 169 | // we need to sync witnesses from hub if necessary 170 | fn check_witnesses(ws: &WalletConn, db: &db::Database) -> Result<()> { 171 | let witnesses = db.get_my_witnesses()?; 172 | 173 | // if the data base is empty we should wait until 174 | if witnesses.is_empty() { 175 | let witnesses = ws.get_witnesses()?; 176 | db.insert_witnesses(&witnesses)?; 177 | } else { 178 | assert_eq!(witnesses.len(), trustnote::config::COUNT_WITNESSES); 179 | } 180 | Ok(()) 181 | } 182 | 183 | fn sync(ws: &WalletConn, db: &db::Database, wallet_info: &WalletInfo) -> Result<()> { 184 | update_wallet_address(db, wallet_info)?; 185 | check_witnesses(ws, db)?; 186 | match ws.refresh_history(db) { 187 | Ok(_) => info!("refresh history done"), 188 | Err(e) => bail!("refresh history failed, err={:?}", e), 189 | } 190 | Ok(()) 191 | } 192 | 193 | fn history_log( 194 | db: &Connection, 195 | wallet_info: &WalletInfo, 196 | index: Option, 197 | max: usize, 198 | ) -> Result<()> { 199 | let histories = wallet::read_transaction_history(db, &wallet_info._00_address)?; 200 | 201 | if let Some(index) = index { 202 | if index == 0 || index > histories.len() { 203 | bail!("invalid transaction index"); 204 | } 205 | 206 | let history = &histories[index - 1]; 207 | if history.amount > 0 { 208 | println!("FROM : {}", history.address_from); 209 | } else { 210 | println!("TO : {}", history.address_to); 211 | } 212 | println!("UNIT : {}", history.unit); 213 | println!("AMOUNT : {:.6} MN", history.amount as f64 / 1_000_000.0); 214 | println!( 215 | "DATE : {}", 216 | Local.timestamp_millis(history.timestamp).naive_local() 217 | ); 218 | println!("CONFIRMED: {}", history.confirmations); 219 | } else { 220 | for (id, history) in histories.iter().enumerate() { 221 | if id > max - 1 { 222 | break; 223 | } 224 | println!( 225 | "#{:<4} {:>10.6} MN \t{}", 226 | id + 1, 227 | history.amount as f64 / 1_000_000.0, 228 | Local.timestamp_millis(history.timestamp).naive_local() 229 | ); 230 | } 231 | } 232 | 233 | Ok(()) 234 | } 235 | 236 | fn send_payment( 237 | ws: &Arc, 238 | db: &Connection, 239 | text: Option<&str>, 240 | address_amount: &Vec<(&str, f64)>, 241 | wallet_info: &WalletInfo, 242 | ) -> Result<()> { 243 | let payment = wallet::prepare_payment(ws, address_amount, text, &wallet_info._00_address)?; 244 | let joint = composer::compose_joint(db, payment, wallet_info)?; 245 | ws.post_joint(&joint)?; 246 | 247 | println!("FROM : {}", wallet_info._00_address); 248 | println!("TO : "); 249 | for (address, amount) in address_amount { 250 | println!(" address : {}, amount : {}", address, amount); 251 | } 252 | println!("UNIT : {}", joint.unit.unit.unwrap()); 253 | println!("TEXT : {}", text.unwrap_or("")); 254 | println!( 255 | "DATE : {}", 256 | Local.timestamp_millis(time::now() as i64).naive_local() 257 | ); 258 | Ok(()) 259 | } 260 | 261 | fn main() -> Result<()> { 262 | let yml = load_yaml!("ttt.yml"); 263 | let m = App::from_yaml(yml).get_matches(); 264 | 265 | let verbosity = m.occurrences_of("verbose"); 266 | init(verbosity)?; 267 | 268 | // init command 269 | if let Some(init_arg) = m.subcommand_matches("init") { 270 | if let Some(mnemonic) = init_arg.value_of("MNEMONIC") { 271 | config::update_mnemonic(mnemonic)?; 272 | } 273 | // create settings 274 | let settings = config::get_settings(); 275 | settings.show_config(); 276 | // every init would remove the local database 277 | ::std::fs::remove_file(trustnote::config::get_database_path(true)).ok(); 278 | return Ok(()); 279 | } 280 | 281 | let settings = config::get_settings(); 282 | let wallet_info = WalletInfo::from_mnemonic(&settings.mnemonic)?; 283 | let db = db::DB_POOL.get_connection(); 284 | let ws = connect_to_remote(&settings.hub_url)?; 285 | // other commad would just sync data first 286 | sync(&ws, &db, &wallet_info)?; 287 | 288 | //Info 289 | if let Some(_info) = m.subcommand_matches("info") { 290 | return info(&db, &wallet_info); 291 | } 292 | 293 | //Log 294 | if let Some(log) = m.subcommand_matches("log") { 295 | let n = value_t!(log.value_of("n"), usize)?; 296 | 297 | let v = value_t!(log.value_of("v"), usize); 298 | match v { 299 | Ok(v) => { 300 | return history_log(&db, &wallet_info, Some(v), n); 301 | } 302 | Err(clap::Error { 303 | kind: clap::ErrorKind::ArgumentNotFound, 304 | .. 305 | }) => { 306 | return history_log(&db, &wallet_info, None, n); 307 | } 308 | Err(e) => e.exit(), 309 | } 310 | } 311 | 312 | //Send 313 | if let Some(send) = m.subcommand_matches("send") { 314 | let mut address_amount = Vec::new(); 315 | if let Some(pay) = send.values_of("pay") { 316 | let v = pay.collect::>(); 317 | for arg in v.chunks(2) { 318 | if !::object_hash::is_chash_valid(arg[0]) { 319 | bail!("invalid address, please check"); 320 | } 321 | let amount = arg[1].parse::().context("invalid amount arg")?; 322 | if amount > std::u64::MAX as f64 || amount < 0.000001 { 323 | bail!("invalid amount, please check"); 324 | } 325 | address_amount.push((arg[0], amount)); 326 | } 327 | } 328 | 329 | let text = send.value_of("text"); 330 | return send_payment(&ws, &db, text, &address_amount, &wallet_info); 331 | } 332 | 333 | if let Some(balance) = m.subcommand_matches("balance") { 334 | let (unstable_balance, stable_balance) = 335 | wallet::get_balance(&db, &wallet_info._00_address)?; 336 | 337 | if let Some(_s) = balance.values_of("s") { 338 | println!("{:.6}", stable_balance as f64 / 1000_000.0); 339 | return Ok(()); 340 | } 341 | 342 | if let Some(_p) = balance.values_of("p") { 343 | println!("{:.6}", unstable_balance as f64 / 1000_000.0); 344 | return Ok(()); 345 | } 346 | 347 | println!( 348 | "{:.6}", 349 | (stable_balance + unstable_balance) as f64 / 1000_000.0 350 | ); 351 | return Ok(()); 352 | } 353 | 354 | Ok(()) 355 | } 356 | -------------------------------------------------------------------------------- /src/network/network.rs: -------------------------------------------------------------------------------- 1 | // use std::io::Read; 2 | use std::marker::PhantomData; 3 | use std::net::ToSocketAddrs; 4 | use std::sync::atomic::{AtomicUsize, Ordering}; 5 | use std::sync::Arc; 6 | use std::time::{Duration, Instant}; 7 | 8 | use error::Result; 9 | use may::coroutine::JoinHandle; 10 | use may::net::{TcpListener, TcpStream}; 11 | use may::sync::{AtomicOption, RwLock}; 12 | use may_waiter::WaiterMap; 13 | use serde_json::{self, Value}; 14 | use tungstenite::protocol::Role; 15 | use tungstenite::server::accept; 16 | use tungstenite::{Message, WebSocket}; 17 | 18 | macro_rules! t_c { 19 | ($e:expr) => { 20 | match $e { 21 | Ok(val) => val, 22 | Err(err) => { 23 | error!("call = {:?}\nerr = {:?}", stringify!($e), err); 24 | continue; 25 | } 26 | } 27 | }; 28 | } 29 | 30 | // the server part trait 31 | pub trait Server { 32 | fn on_message(ws: Arc>, subject: String, body: Value) -> Result<()>; 33 | fn on_request(ws: Arc>, command: String, params: Value) -> Result; 34 | } 35 | 36 | pub trait Sender { 37 | fn send_json(&self, value: Value) -> Result<()>; 38 | 39 | fn send_message(&self, kind: &str, content: Value) -> Result<()> { 40 | self.send_json(json!([kind, &content])) 41 | } 42 | 43 | fn send_just_saying(&self, subject: &str, body: Value) -> Result<()> { 44 | self.send_message("justsaying", json!({ "subject": subject, "body": body })) 45 | } 46 | 47 | fn send_error(&self, error: Value) -> Result<()> { 48 | self.send_just_saying("error", error) 49 | } 50 | 51 | fn send_info(&self, info: Value) -> Result<()> { 52 | self.send_just_saying("info", info) 53 | } 54 | 55 | fn send_result(&self, result: Value) -> Result<()> { 56 | self.send_just_saying("result", result) 57 | } 58 | 59 | fn send_error_result(&self, unit: &str, error: &str) -> Result<()> { 60 | self.send_result(json!({ "unit": unit, "result": "error", "error": error })) 61 | } 62 | 63 | fn send_response(&self, tag: &str, response: Value) -> Result<()> { 64 | if response.is_null() { 65 | return self.send_message("response", json!({ "tag": tag })); 66 | } 67 | self.send_message("response", json!({ "tag": tag, "response": response })) 68 | } 69 | 70 | fn send_error_response(&self, tag: &str, error: Value) -> Result<()> { 71 | self.send_response(tag, json!({ "error": error })) 72 | } 73 | } 74 | 75 | struct WsInner { 76 | ws: WebSocket, 77 | last_recv: Instant, 78 | } 79 | 80 | pub struct WsConnection { 81 | // lock proected inner 82 | ws: RwLock, 83 | // peer name is never changed once init 84 | peer: String, 85 | // the waiting request 86 | req_map: Arc>, 87 | // the listening coroutine 88 | listener: AtomicOption>, 89 | // the actual state data 90 | data: T, 91 | // for request unique id generation 92 | id: AtomicUsize, 93 | } 94 | 95 | impl Sender for WsConnection { 96 | fn send_json(&self, value: Value) -> Result<()> { 97 | let msg = serde_json::to_string(&value)?; 98 | if msg.len() < 1000 { 99 | debug!("SENDING to {}: {}", self.peer, msg); 100 | } else { 101 | debug!("SENDING to {}: huge message", self.peer); 102 | } 103 | 104 | let mut g = self.ws.write().unwrap(); 105 | g.ws.write_message(Message::Text(msg))?; 106 | Ok(()) 107 | } 108 | } 109 | 110 | impl WsConnection { 111 | // use &String instead of &str for db 112 | pub fn get_peer(&self) -> &String { 113 | &self.peer 114 | } 115 | 116 | pub fn get_last_recv_tm(&self) -> Instant { 117 | let g = self.ws.read().unwrap(); 118 | g.last_recv 119 | } 120 | 121 | pub fn set_last_recv_tm(&self, time: Instant) { 122 | let mut g = self.ws.write().unwrap(); 123 | g.last_recv = time; 124 | } 125 | 126 | pub fn get_data(&self) -> &T { 127 | &self.data 128 | } 129 | } 130 | 131 | impl Drop for WsConnection { 132 | fn drop(&mut self) { 133 | if ::std::thread::panicking() { 134 | return; 135 | } 136 | if let Some(h) = self.listener.take(Ordering::Relaxed) { 137 | // close the connection first 138 | self.ws.write().unwrap().ws.close(None).ok(); 139 | unsafe { h.coroutine().cancel() }; 140 | h.join().ok(); 141 | } 142 | } 143 | } 144 | 145 | impl WsConnection { 146 | /// create a client from stream socket 147 | pub fn new(ws: WebSocket, data: T, peer: String, role: Role) -> Result> 148 | where 149 | T: Server + Send + Sync + 'static, 150 | { 151 | let req_map = Arc::new(WaiterMap::new()); 152 | 153 | let req_map_1 = req_map.clone(); 154 | let mut reader = WebSocket::from_raw_socket(ws.get_ref().try_clone()?, role, None); 155 | let ws = Arc::new(WsConnection { 156 | ws: RwLock::new(WsInner { 157 | ws, 158 | last_recv: Instant::now(), 159 | }), 160 | peer, 161 | req_map, 162 | listener: AtomicOption::none(), 163 | data, 164 | id: AtomicUsize::new(0), 165 | }); 166 | 167 | // we can't have a strong ref in the driver coroutine! 168 | // or it will never got dropped 169 | let ws_1 = Arc::downgrade(&ws); 170 | 171 | let listener = go!(move || { 172 | loop { 173 | let msg = match reader.read_message() { 174 | Ok(msg) => msg, 175 | Err(e) => { 176 | error!("{}", e.to_string()); 177 | break; 178 | } 179 | }; 180 | 181 | let msg = match msg { 182 | Message::Text(s) => s, 183 | _ => { 184 | error!("only text ws packet are supported"); 185 | continue; 186 | } 187 | }; 188 | 189 | let mut value: Value = t_c!(serde_json::from_str(&msg)); 190 | let msg_type = value[0].take(); 191 | let msg_type = t_c!(msg_type.as_str().ok_or("no msg type")); 192 | 193 | // we use weak ref here, need to upgrade to check if dropped 194 | let ws = match ws_1.upgrade() { 195 | Some(c) => c, 196 | None => return, 197 | }; 198 | if msg.len() < 1000 { 199 | debug!("RECV from {}: {}", ws.peer, msg); 200 | } else { 201 | debug!("RECV from {}: huge message!", ws.peer); 202 | } 203 | 204 | ws.set_last_recv_tm(Instant::now()); 205 | 206 | match msg_type { 207 | "justsaying" => { 208 | #[derive(Deserialize)] 209 | struct JustSaying { 210 | subject: String, 211 | #[serde(default)] 212 | body: Value, 213 | }; 214 | let JustSaying { subject, body } = 215 | t_c!(serde_json::from_value(value[1].take())); 216 | go!(move || if let Err(e) = T::on_message(ws, subject, body) { 217 | error!("{}", e); 218 | }); 219 | } 220 | "request" => { 221 | #[derive(Deserialize)] 222 | struct Request { 223 | command: String, 224 | tag: String, 225 | #[serde(default)] 226 | params: Value, 227 | }; 228 | let Request { 229 | command, 230 | tag, 231 | params, 232 | } = t_c!(serde_json::from_value(value[1].take())); 233 | go!(move || { 234 | // need to get and set the tag!! 235 | match T::on_request(ws.clone(), command, params) { 236 | Ok(rsp) => { 237 | // send the response 238 | t!(ws.send_response(&tag, rsp)); 239 | } 240 | Err(e) => { 241 | error!("{:?}", e); 242 | let error = json!(e.to_string()); 243 | t!(ws.send_error_response(&tag, error)); 244 | } 245 | } 246 | }); 247 | } 248 | "response" => { 249 | // set the wait req 250 | let tag = match value[1]["tag"].as_str() { 251 | Some(t) => t 252 | .parse() 253 | .unwrap_or_else(|e| panic!("tag {:?} is not u64! err={}", t, e)), 254 | None => { 255 | error!("tag is not found for response"); 256 | continue; 257 | } 258 | }; 259 | debug!("got response for tag={}", tag); 260 | req_map_1.set_rsp(&tag, value).ok(); 261 | } 262 | s => { 263 | error!("unkonw msg type: {}", s); 264 | continue; 265 | } 266 | } 267 | } 268 | }); 269 | 270 | ws.listener.swap(listener, Ordering::Relaxed); 271 | Ok(ws) 272 | } 273 | 274 | pub fn send_request(&self, command: &str, param: &Value) -> Result { 275 | let mut request = match param { 276 | Value::Null => json!({ "command": command }), 277 | _ => json!({"command": command, "params": param}), 278 | }; 279 | let tag = self.id.fetch_add(1, Ordering::Relaxed); 280 | request["tag"] = json!(tag.to_string()); 281 | 282 | let blocker = self.req_map.new_waiter(tag); 283 | self.send_message("request", request)?; 284 | 285 | let timeout = Some(Duration::from_secs(::config::STALLED_TIMEOUT as u64)); 286 | #[derive(Deserialize)] 287 | struct Response { 288 | #[allow(dead_code)] 289 | tag: String, 290 | #[serde(default)] 291 | response: Value, 292 | }; 293 | 294 | let rsp: Response = serde_json::from_value(blocker.wait_rsp(timeout)?[1].take())?; 295 | if !rsp.response["error"].is_null() { 296 | bail!("{} err: {}", command, rsp.response["error"]); 297 | } 298 | Ok(rsp.response) 299 | } 300 | 301 | #[inline] 302 | pub fn conn_eq(&self, other: &WsConnection) -> bool { 303 | ::std::ptr::eq(self, other) 304 | } 305 | } 306 | 307 | // helper struct for easy use 308 | pub struct WsServer(PhantomData); 309 | 310 | impl WsServer { 311 | // f is used to save the connection globally 312 | pub fn start(address: A, f: F) -> JoinHandle<()> 313 | where 314 | A: ToSocketAddrs, 315 | F: Fn(Arc>) + Send + 'static, 316 | T: Server + Default + Send + Sync + 'static, 317 | { 318 | let address = address 319 | .to_socket_addrs() 320 | .expect("invalid address") 321 | .next() 322 | .expect("can't resolve address"); 323 | 324 | go!(move || { 325 | let listener = TcpListener::bind(address).unwrap(); 326 | // for stream in listener.incoming() { 327 | while let Ok((stream, _)) = listener.accept() { 328 | let peer = match stream.peer_addr() { 329 | Ok(addr) => addr.to_string(), 330 | Err(_) => "unknown peer".to_owned(), 331 | }; 332 | let ws = t_c!(accept(stream)); 333 | let ws = t_c!(WsConnection::new(ws, T::default(), peer, Role::Server)); 334 | f(ws); 335 | } 336 | }) 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /src/witness_proof.rs: -------------------------------------------------------------------------------- 1 | use error::{Result, TrustnoteError}; 2 | use joint::Joint; 3 | use my_witness::MY_WITNESSES; 4 | use object_hash; 5 | use rusqlite::Connection; 6 | use serde_json::Value; 7 | use spec::*; 8 | use std::collections::HashMap; 9 | use storage; 10 | use validation; 11 | 12 | pub struct PrepareWitnessProof { 13 | pub unstable_mc_joints: Vec, 14 | pub witness_change_and_definition: Vec, 15 | pub last_ball_unit: String, 16 | pub last_ball_mci: u32, 17 | } 18 | 19 | pub fn prepare_witness_proof( 20 | db: &Connection, 21 | witnesses: &[String], 22 | last_stable_mci: u32, 23 | ) -> Result { 24 | let mut witness_change_and_definition = Vec::new(); 25 | let mut unstable_mc_joints = Vec::new(); 26 | let mut last_ball_units = Vec::new(); 27 | let last_ball_mci; 28 | let last_ball_unit; 29 | 30 | if storage::determine_if_witness_and_address_definition_have_refs(db, witnesses)? { 31 | return Err(TrustnoteError::WitnessChanged.into()); 32 | } 33 | 34 | // collect all unstable MC units 35 | let mut found_witnesses = Vec::new(); 36 | let mut stmt = db.prepare_cached( 37 | "SELECT unit FROM units \ 38 | WHERE is_on_main_chain=1 AND is_stable=0 \ 39 | ORDER BY main_chain_index DESC", 40 | )?; 41 | 42 | let units = stmt.query_map(&[], |row| row.get::<_, String>(0))?; 43 | 44 | for unit in units { 45 | if unit.is_err() { 46 | error!("failed to get unit, err={:?}", unit); 47 | continue; 48 | } 49 | let unit = unit.unwrap(); 50 | // let unit = unit?; 51 | let mut joint = storage::read_joint_with_ball(db, &unit)?; 52 | // FIXME: WTF of this?! the unit might get stabilized while we were reading other units 53 | joint.ball = None; 54 | for author in &joint.unit.authors { 55 | let address = &author.address; 56 | if witnesses.contains(address) && !found_witnesses.contains(address) { 57 | found_witnesses.push(address.clone()); 58 | } 59 | 60 | if joint.unit.last_ball_unit.is_some() 61 | && found_witnesses.len() >= ::config::MAJORITY_OF_WITNESSES 62 | { 63 | last_ball_units.push(joint.unit.last_ball_unit.as_ref().unwrap().clone()) 64 | } 65 | } 66 | unstable_mc_joints.push(joint); 67 | } 68 | 69 | // select the newest last ball unit 70 | if last_ball_units.is_empty() { 71 | bail!("your witness list might be too much off, too few witness authored units"); 72 | } 73 | 74 | let last_ball_units_set = last_ball_units 75 | .iter() 76 | .map(|s| format!("'{}'", s)) 77 | .collect::>() 78 | .join(", "); 79 | let sql = format!( 80 | "SELECT unit, main_chain_index FROM units \ 81 | WHERE unit IN({}) \ 82 | ORDER BY main_chain_index DESC LIMIT 1", 83 | last_ball_units_set 84 | ); 85 | let row = db.query_row(&sql, &[], |row| (row.get(0), row.get(1)))?; 86 | last_ball_unit = row.0; 87 | last_ball_mci = row.1; 88 | if last_stable_mci >= last_ball_mci { 89 | return Err(TrustnoteError::CatchupAlreadyCurrent.into()); 90 | } 91 | 92 | // add definition changes and new definitions of witnesses 93 | let after_last_stable_mci_cond = if last_stable_mci > 0 { 94 | format!("latest_included_mc_index>={}", last_stable_mci) 95 | } else { 96 | "1".to_owned() 97 | }; 98 | 99 | let witness_set = witnesses 100 | .iter() 101 | .map(|s| format!("'{}'", s)) 102 | .collect::>() 103 | .join(", "); 104 | 105 | let sql = format!( 106 | "SELECT unit, `level` \ 107 | FROM unit_authors INDEXED BY unitAuthorsIndexByAddressDefinitionChash \ 108 | CROSS JOIN units USING(unit) \ 109 | WHERE address IN({}) AND definition_chash IS NOT NULL AND {} AND is_stable=1 AND sequence='good' \ 110 | UNION \ 111 | SELECT unit, `level` \ 112 | FROM address_definition_changes \ 113 | CROSS JOIN units USING(unit) \ 114 | WHERE address_definition_changes.address IN({}) AND {} AND is_stable=1 AND sequence='good' \ 115 | ORDER BY `level`", witness_set, after_last_stable_mci_cond, witness_set, after_last_stable_mci_cond); 116 | 117 | let mut stmt = db.prepare(&sql)?; 118 | let units = stmt.query_map(&[], |row| row.get(0))?; 119 | for unit in units { 120 | let unit = unit?; 121 | let joint = storage::read_joint_directly(db, &unit)?; 122 | witness_change_and_definition.push(joint); 123 | } 124 | 125 | Ok(PrepareWitnessProof { 126 | unstable_mc_joints, 127 | witness_change_and_definition, 128 | last_ball_unit, 129 | last_ball_mci, 130 | }) 131 | } 132 | 133 | #[derive(Debug)] 134 | pub struct ProcessWitnessProof { 135 | pub last_ball_units: Vec, 136 | pub assoc_last_ball_by_last_ball_unit: HashMap, 137 | } 138 | 139 | pub fn process_witness_proof( 140 | db: &Connection, 141 | unstable_mc_joints: &[Joint], 142 | witness_change_and_definition: &[Joint], 143 | from_current: bool, 144 | ) -> Result { 145 | let mut parent_units = Vec::new(); 146 | let mut found_witnesses = Vec::new(); 147 | let mut last_ball_units = Vec::new(); 148 | let mut assoc_last_ball_by_last_ball_unit = HashMap::::new(); 149 | let mut witness_joints = Vec::new(); 150 | 151 | for joint in unstable_mc_joints { 152 | let unit = &joint.unit; 153 | let unit_hash = joint.get_unit_hash(); 154 | ensure!(joint.ball.is_none(), "unstable mc but has ball"); 155 | ensure!(joint.has_valid_hashes(), "invalid hash"); 156 | if !parent_units.is_empty() { 157 | ensure!(parent_units.contains(unit_hash), "not in parents"); 158 | } 159 | 160 | let mut added_joint = false; 161 | for author in &unit.authors { 162 | let address = &author.address; 163 | if MY_WITNESSES.contains(address) { 164 | if !found_witnesses.contains(address) { 165 | found_witnesses.push(address.clone()); 166 | } 167 | if !added_joint { 168 | witness_joints.push(joint); 169 | } 170 | added_joint = true; 171 | } 172 | } 173 | parent_units = unit.parent_units.clone(); 174 | if unit.last_ball_unit.is_some() && found_witnesses.len() >= ::config::MAJORITY_OF_WITNESSES 175 | { 176 | let last_ball_unit = unit.last_ball_unit.as_ref().unwrap().clone(); 177 | let last_ball = unit.last_ball.as_ref().unwrap().clone(); 178 | last_ball_units.push(last_ball_unit.clone()); 179 | assoc_last_ball_by_last_ball_unit.insert(last_ball_unit, last_ball); 180 | } 181 | } 182 | 183 | ensure!( 184 | found_witnesses.len() >= ::config::MAJORITY_OF_WITNESSES, 185 | "not enough witnesses" 186 | ); 187 | ensure!( 188 | !last_ball_units.is_empty(), 189 | "processWitnessProof: no last ball units" 190 | ); 191 | 192 | // changes and definitions of witnesses 193 | for joint in witness_change_and_definition { 194 | ensure!( 195 | joint.ball.is_some(), 196 | "witness_change_and_definition_joints: joint without ball" 197 | ); 198 | ensure!( 199 | joint.has_valid_hashes(), 200 | "witness_change_and_definition_joints: invalid hash" 201 | ); 202 | let unit = &joint.unit; 203 | 204 | let mut author_by_witness = false; 205 | for author in &unit.authors { 206 | let address = &author.address; 207 | if MY_WITNESSES.contains(address) { 208 | author_by_witness = true; 209 | break; 210 | } 211 | } 212 | ensure!(author_by_witness, "not authored by my witness"); 213 | } 214 | 215 | let mut assoc_definitions = HashMap::::new(); 216 | let mut assoc_definition_chashes = HashMap::::new(); 217 | 218 | if !from_current { 219 | for address in MY_WITNESSES.iter() { 220 | assoc_definition_chashes.insert(address.clone(), address.clone()); 221 | } 222 | } 223 | 224 | for address in MY_WITNESSES.iter() { 225 | match storage::read_definition_by_address(db, address, None)? { 226 | // if found 227 | Ok(definition) => { 228 | let definition_chash = object_hash::get_chash(&definition)?; 229 | assoc_definitions.insert(definition_chash.clone(), definition); 230 | assoc_definition_chashes.insert(address.clone(), definition_chash); 231 | } 232 | // if NotFound 233 | Err(chash) => { 234 | assoc_definition_chashes.insert(address.clone(), chash); 235 | } 236 | } 237 | } 238 | 239 | let mut validate_unit = |unit: &Unit, require_definition_or_change: bool| -> Result<()> { 240 | let mut b_found = false; 241 | for author in &unit.authors { 242 | let address = &author.address; 243 | if !MY_WITNESSES.contains(address) { 244 | // not a witness - skip it 245 | continue; 246 | } 247 | 248 | let definition_chash = { 249 | let chash = assoc_definition_chashes.get(address); 250 | ensure!( 251 | chash.is_some(), 252 | "definition chash not known for address {}", 253 | address 254 | ); 255 | chash.unwrap().clone() 256 | }; 257 | 258 | if !author.definition.is_null() { 259 | let chash = object_hash::get_chash(&author.definition)?; 260 | ensure!( 261 | chash == *definition_chash, 262 | "definition doesn't hash to the expected value" 263 | ); 264 | assoc_definitions.insert(definition_chash.clone(), author.definition.clone()); 265 | b_found = true; 266 | } 267 | 268 | if assoc_definitions.get(&definition_chash).is_none() { 269 | let definition = storage::read_definition(db, &definition_chash)?; 270 | assoc_definitions.insert(definition_chash.clone(), definition); 271 | } 272 | 273 | // handle author 274 | validation::validate_author_signature_without_ref( 275 | db, 276 | author, 277 | unit, 278 | assoc_definitions.get(&definition_chash).unwrap_or_else(|| { 279 | panic!( 280 | "failed to find definition, definition_chash={}", 281 | definition_chash 282 | ) 283 | }), 284 | )?; 285 | for message in &unit.messages { 286 | let payment = match message.payload.as_ref() { 287 | Some(Payload::Payment(ref p)) => Some(p), 288 | _ => None, 289 | }; 290 | let payload_address = payment.and_then(|p| p.address.as_ref()); 291 | if message.app == "address_definition_change" 292 | && (payload_address == Some(address) 293 | || (unit.authors.len() == 1 && &unit.authors[0].address == address)) 294 | { 295 | let payment = payment.unwrap(); 296 | let chash = payment 297 | .definition_chash 298 | .as_ref() 299 | .expect("no chash in payload") 300 | .clone(); 301 | assoc_definition_chashes.insert(address.clone(), chash); 302 | b_found = true; 303 | } 304 | } 305 | } 306 | 307 | if require_definition_or_change && !b_found { 308 | bail!("neither definition nor change"); 309 | } 310 | Ok(()) 311 | }; 312 | 313 | for joint in witness_change_and_definition { 314 | let unit_hash = joint.get_unit_hash(); 315 | if from_current { 316 | let mut stmt = db.prepare_cached("SELECT 1 FROM units WHERE unit=? AND is_stable=1")?; 317 | let rows = stmt.query_map(&[unit_hash], |row| row.get::<_, u32>(0))?; 318 | if rows.count() > 0 { 319 | continue; 320 | } 321 | } 322 | validate_unit(&joint.unit, true)?; 323 | } 324 | 325 | for joint in witness_joints { 326 | validate_unit(&joint.unit, false)?; 327 | } 328 | 329 | Ok(ProcessWitnessProof { 330 | last_ball_units, 331 | assoc_last_ball_by_last_ball_unit, 332 | }) 333 | } 334 | -------------------------------------------------------------------------------- /src/graph.rs: -------------------------------------------------------------------------------- 1 | use error::Result; 2 | use rusqlite::Connection; 3 | use storage; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct UnitProps { 7 | pub unit: String, 8 | pub level: u32, 9 | pub latest_included_mc_index: Option, 10 | pub main_chain_index: Option, 11 | pub is_on_main_chain: Option, 12 | pub is_free: u32, 13 | } 14 | 15 | pub fn compare_units(db: &Connection, unit1: &str, unit2: &str) -> Result> { 16 | if unit1 == unit2 { 17 | return Ok(Some(0)); 18 | } 19 | 20 | let units = [unit1, unit2]; 21 | let unit_list = units 22 | .iter() 23 | .map(|s| format!("'{}'", s)) 24 | .collect::>() 25 | .join(", "); 26 | 27 | let sql = format!( 28 | "SELECT unit, level, latest_included_mc_index, main_chain_index, is_on_main_chain, is_free \ 29 | FROM units WHERE unit IN({})", 30 | unit_list 31 | ); 32 | 33 | let mut stmt = db.prepare(&sql)?; 34 | let rows = stmt.query_map(&[], |row| UnitProps { 35 | unit: row.get(0), 36 | level: row.get(1), 37 | latest_included_mc_index: row.get(2), 38 | main_chain_index: row.get(3), 39 | is_on_main_chain: row.get(4), 40 | is_free: row.get(5), 41 | })?; 42 | 43 | let mut unit_props = Vec::new(); 44 | for row in rows { 45 | unit_props.push(row?); 46 | } 47 | 48 | ensure!( 49 | unit_props.len() == 2, 50 | "Not 2 rows for {} and {}", 51 | unit1, 52 | unit2 53 | ); 54 | 55 | let (unit_props1, unit_props2) = if unit_props[0].unit == unit1 { 56 | (&unit_props[0], &unit_props[1]) 57 | } else { 58 | (&unit_props[1], &unit_props[0]) 59 | }; 60 | 61 | compare_unit_props(db, unit_props1, unit_props2) 62 | } 63 | 64 | pub fn compare_unit_props( 65 | db: &Connection, 66 | unit_props1: &UnitProps, 67 | unit_props2: &UnitProps, 68 | ) -> Result> { 69 | if unit_props1.unit == unit_props2.unit { 70 | return Ok(Some(0)); 71 | } 72 | if unit_props1.level == unit_props2.level { 73 | return Ok(None); 74 | } 75 | if unit_props1.is_free == 1 && unit_props2.is_free == 1 { 76 | // free units 77 | return Ok(None); 78 | } 79 | 80 | // genesis 81 | if unit_props1.latest_included_mc_index == None { 82 | return Ok(Some(-1)); 83 | } 84 | if unit_props2.latest_included_mc_index == None { 85 | return Ok(Some(1)); 86 | } 87 | 88 | if unit_props1.latest_included_mc_index >= unit_props2.main_chain_index 89 | && unit_props2.main_chain_index != None 90 | { 91 | return Ok(Some(1)); 92 | } 93 | if unit_props2.latest_included_mc_index >= unit_props1.main_chain_index 94 | && unit_props1.main_chain_index != None 95 | { 96 | return Ok(Some(-1)); 97 | } 98 | 99 | if unit_props1.level <= unit_props2.level 100 | && unit_props1.latest_included_mc_index <= unit_props2.latest_included_mc_index 101 | && (unit_props1.main_chain_index == None 102 | || unit_props2.main_chain_index == None 103 | || unit_props1.main_chain_index <= unit_props2.main_chain_index) 104 | || unit_props1.level >= unit_props2.level 105 | && unit_props1.latest_included_mc_index >= unit_props2.latest_included_mc_index 106 | && (unit_props1.main_chain_index == None 107 | || unit_props2.main_chain_index == None 108 | || unit_props1.main_chain_index >= unit_props2.main_chain_index) 109 | { 110 | // still can be comparable 111 | } else { 112 | return Ok(None); 113 | } 114 | 115 | let (earlier_unit, later_unit, result_if_found) = if unit_props1.level < unit_props2.level { 116 | (unit_props1, unit_props2, -1) 117 | } else { 118 | (unit_props2, unit_props1, 1) 119 | }; 120 | 121 | // can be negative if main_chain_index == None but that doesn't matter 122 | let earlier_unit_delta = earlier_unit.main_chain_index.unwrap_or(0) 123 | - earlier_unit.latest_included_mc_index.unwrap_or(0); 124 | let later_unit_delta = 125 | later_unit.main_chain_index.unwrap_or(0) - later_unit.latest_included_mc_index.unwrap_or(0); 126 | 127 | let mut start_units = Vec::new(); 128 | if later_unit_delta > earlier_unit_delta { 129 | //GoUp() 130 | start_units.push(later_unit.unit.clone()); 131 | 132 | loop { 133 | let start_unit_list = start_units 134 | .iter() 135 | .map(|s| format!("'{}'", s)) 136 | .collect::>() 137 | .join(", "); 138 | 139 | let sql = format!( 140 | "SELECT unit, level, latest_included_mc_index, main_chain_index, is_on_main_chain \ 141 | FROM parenthoods JOIN units ON parent_unit=unit \ 142 | WHERE child_unit IN({})", 143 | start_unit_list 144 | ); 145 | 146 | let mut stmt = db.prepare(&sql)?; 147 | let rows = stmt.query_map(&[], |row| UnitProps { 148 | unit: row.get(0), 149 | level: row.get(1), 150 | latest_included_mc_index: row.get(2), 151 | main_chain_index: row.get(3), 152 | is_on_main_chain: row.get(4), 153 | is_free: 0, //is_free is not queried 154 | })?; 155 | 156 | let mut new_start_units = Vec::new(); 157 | for row in rows { 158 | let unit = row?; 159 | if unit.unit == earlier_unit.unit { 160 | return Ok(Some(result_if_found)); 161 | } 162 | 163 | if unit.is_on_main_chain == Some(0) && unit.level > earlier_unit.level { 164 | new_start_units.push(unit.unit.clone()); 165 | } 166 | } 167 | 168 | if !new_start_units.is_empty() { 169 | start_units = new_start_units; 170 | } else { 171 | return Ok(None);; 172 | } 173 | } 174 | } else { 175 | // GoDown 176 | start_units.push(earlier_unit.unit.clone()); 177 | 178 | loop { 179 | let start_unit_list = start_units 180 | .iter() 181 | .map(|s| format!("'{}'", s)) 182 | .collect::>() 183 | .join(", "); 184 | 185 | let sql = format!( 186 | "SELECT unit, level, latest_included_mc_index, main_chain_index, is_on_main_chain \ 187 | FROM parenthoods JOIN units ON child_unit=unit \ 188 | WHERE parent_unit IN({})", 189 | start_unit_list 190 | ); 191 | 192 | let mut stmt = db.prepare(&sql)?; 193 | let rows = stmt.query_map(&[], |row| UnitProps { 194 | unit: row.get(0), 195 | level: row.get(1), 196 | latest_included_mc_index: row.get(2), 197 | main_chain_index: row.get(3), 198 | is_on_main_chain: row.get(4), 199 | is_free: 0, //is_free is not queried 200 | })?; 201 | 202 | let mut new_start_units = Vec::new(); 203 | for row in rows { 204 | let unit = row?; 205 | if unit.unit == earlier_unit.unit { 206 | return Ok(Some(result_if_found)); 207 | } 208 | 209 | if unit.is_on_main_chain == Some(0) && unit.level < later_unit.level { 210 | new_start_units.push(unit.unit.clone()); 211 | } 212 | } 213 | 214 | if !new_start_units.is_empty() { 215 | start_units = new_start_units; 216 | } else { 217 | return Ok(None); 218 | } 219 | } 220 | } 221 | } 222 | 223 | pub fn determine_if_included( 224 | db: &Connection, 225 | earlier_unit: &String, 226 | later_units: &[String], 227 | ) -> Result { 228 | if ::spec::is_genesis_unit(&earlier_unit) { 229 | return Ok(true); 230 | } 231 | 232 | let (earlier_unit_props, later_units_props) = 233 | storage::read_props_of_units(db, &earlier_unit, later_units)?; 234 | 235 | if earlier_unit_props.is_free == 1 { 236 | return Ok(false); 237 | } 238 | 239 | ensure!( 240 | !later_units_props.is_empty(), 241 | "no later unit props were read" 242 | ); 243 | 244 | //spec::UnitProps.latest_included_mc_index and spec::UnitProps.main_chain_index is not Option 245 | let max_later_limci = later_units_props 246 | .iter() 247 | .max_by_key(|props| props.latest_included_mc_index) 248 | .unwrap() 249 | .latest_included_mc_index; 250 | if 251 | /*earlier_unit_props.main_chain_index.is_some() 252 | &&*/ 253 | max_later_limci >= earlier_unit_props.main_chain_index { 254 | return Ok(true); 255 | } 256 | 257 | let max_later_level = later_units_props 258 | .iter() 259 | .max_by_key(|props| props.level) 260 | .unwrap() 261 | .level; 262 | if max_later_level < earlier_unit_props.level { 263 | return Ok(false); 264 | } 265 | 266 | let mut start_units = later_units.to_vec(); 267 | 268 | loop { 269 | let start_unit_list = start_units 270 | .iter() 271 | .map(|s| format!("'{}'", s)) 272 | .collect::>() 273 | .join(", "); 274 | 275 | let sql = format!( 276 | "SELECT unit, level, latest_included_mc_index, main_chain_index, is_on_main_chain \ 277 | FROM parenthoods JOIN units ON parent_unit=unit \ 278 | WHERE child_unit IN({})", 279 | start_unit_list 280 | ); 281 | 282 | let mut stmt = db.prepare(&sql)?; 283 | let rows = stmt.query_map(&[], |row| UnitProps { 284 | unit: row.get(0), 285 | level: row.get(1), 286 | latest_included_mc_index: row.get(2), 287 | main_chain_index: row.get(3), 288 | is_on_main_chain: row.get(4), 289 | is_free: 0, //is_free is not queried 290 | })?; 291 | 292 | let mut new_start_units = Vec::new(); 293 | for row in rows { 294 | let unit = row?; 295 | if unit.unit == *earlier_unit { 296 | return Ok(true); 297 | } 298 | 299 | if unit.is_on_main_chain == Some(0) && unit.level > earlier_unit_props.level { 300 | new_start_units.push(unit.unit.clone()); 301 | } 302 | } 303 | 304 | if !new_start_units.is_empty() { 305 | new_start_units.sort(); 306 | new_start_units.dedup(); 307 | start_units = new_start_units; 308 | } else { 309 | return Ok(false); 310 | } 311 | } 312 | } 313 | 314 | pub fn determine_if_included_or_equal( 315 | db: &Connection, 316 | earlier_unit: &String, 317 | later_units: &[String], 318 | ) -> Result { 319 | if later_units.contains(earlier_unit) { 320 | return Ok(true); 321 | } 322 | 323 | determine_if_included(db, earlier_unit, later_units) 324 | } 325 | 326 | pub fn read_descendant_units_by_authors_before_mc_index( 327 | db: &Connection, 328 | earlier_unit: &UnitProps, 329 | author_addresses: &[String], 330 | to_main_chain_index: u32, 331 | ) -> Result> { 332 | let mut units = Vec::new(); 333 | 334 | let author_address_list = author_addresses 335 | .iter() 336 | .map(|s| format!("'{}'", s)) 337 | .collect::>() 338 | .join(", "); 339 | 340 | ensure!( 341 | earlier_unit.main_chain_index.is_some(), 342 | "earlier unit has no main chain index" 343 | ); 344 | let earlier_unit_mci = earlier_unit.main_chain_index.unwrap(); 345 | 346 | //Missing db.forceIndex("byMcIndex") from original js 347 | let sql = format!( 348 | "SELECT unit FROM units \ 349 | LEFT JOIN unit_authors USING(unit) \ 350 | WHERE latest_included_mc_index>={} AND main_chain_index>{} \ 351 | AND main_chain_index<={} AND latest_included_mc_index<{} \ 352 | AND address IN({})", 353 | earlier_unit_mci, 354 | earlier_unit_mci, 355 | to_main_chain_index, 356 | to_main_chain_index, 357 | author_address_list 358 | ); 359 | 360 | let mut stmt = db.prepare(&sql)?; 361 | let rows = stmt.query_map(&[], |row| row.get::<_, String>(0))?; 362 | 363 | for row in rows { 364 | units.push(row?) 365 | } 366 | 367 | let mut start_units = Vec::new(); 368 | start_units.push(earlier_unit.unit.clone()); 369 | 370 | loop { 371 | let start_unit_list = start_units 372 | .iter() 373 | .map(|s| format!("'{}'", s)) 374 | .collect::>() 375 | .join(", "); 376 | 377 | let sql = format!( 378 | "SELECT units.unit, unit_authors.address AS author_in_list \ 379 | FROM parenthoods \ 380 | JOIN units ON child_unit=units.unit \ 381 | LEFT JOIN unit_authors ON unit_authors.unit=units.unit \ 382 | AND address IN({}) \ 383 | WHERE parent_unit IN({}) \ 384 | AND latest_included_mc_index<{} \ 385 | AND main_chain_index<={}", 386 | author_address_list, start_unit_list, earlier_unit_mci, to_main_chain_index 387 | ); 388 | 389 | //The query fields have different structures 390 | struct UnitProps { 391 | unit: String, 392 | author_in_list: Option, 393 | } 394 | 395 | let mut stmt = db.prepare(&sql)?; 396 | let rows = stmt.query_map(&[], |row| UnitProps { 397 | unit: row.get(0), 398 | author_in_list: row.get(1), 399 | })?; 400 | 401 | let mut new_start_units = Vec::new(); 402 | 403 | for row in rows { 404 | let unit = row?; 405 | 406 | new_start_units.push(unit.unit.clone()); 407 | 408 | if unit.author_in_list.is_some() { 409 | units.push(unit.unit.clone()); 410 | } 411 | } 412 | 413 | if !new_start_units.is_empty() { 414 | start_units = new_start_units; 415 | } else { 416 | return Ok(units); 417 | } 418 | } 419 | } 420 | -------------------------------------------------------------------------------- /src/joint_storage.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | use std::rc::Rc; 3 | 4 | use db; 5 | use error::Result; 6 | use joint::Joint; 7 | use may::sync::Mutex; 8 | use rusqlite::Connection; 9 | use serde_json; 10 | use storage; 11 | 12 | #[derive(Debug)] 13 | pub enum CheckNewResult { 14 | Known, 15 | KnownUnverified, 16 | KnownBad, 17 | New, 18 | } 19 | 20 | pub fn check_new_unit(db: &Connection, unit: &String) -> Result { 21 | if storage::is_known_unit(unit) { 22 | return Ok(CheckNewResult::Known); 23 | } 24 | 25 | let mut stmt = db.prepare_cached("SELECT 1 FROM units WHERE unit=?")?; 26 | if stmt.exists(&[unit])? { 27 | storage::set_unit_is_known(unit); 28 | return Ok(CheckNewResult::Known); 29 | } 30 | 31 | let mut stmt = db.prepare_cached("SELECT 1 FROM unhandled_joints WHERE unit=?")?; 32 | if stmt.exists(&[unit])? { 33 | return Ok(CheckNewResult::KnownUnverified); 34 | } 35 | 36 | let mut stmt = db.prepare_cached("SELECT error FROM known_bad_joints WHERE unit=?")?; 37 | let mut rows = stmt.query(&[unit])?; 38 | if let Some(row) = rows.next() { 39 | let error: String = row?.get_checked(0)?; 40 | warn!("detect knownbad unit {}, err: {}", unit, error); 41 | return Ok(CheckNewResult::KnownBad); 42 | } 43 | 44 | Ok(CheckNewResult::New) 45 | } 46 | 47 | pub fn check_new_joint(db: &Connection, joint: &Joint) -> Result { 48 | let unit = joint.get_unit_hash(); 49 | let ret = check_new_unit(db, unit)?; 50 | if let CheckNewResult::New = ret { 51 | let mut stmt = db.prepare_cached("SELECT error FROM known_bad_joints WHERE joint=?")?; 52 | let joint_hash = joint.get_joint_hash(); 53 | let mut rows = stmt.query(&[&joint_hash])?; 54 | if let Some(row) = rows.next() { 55 | let error: String = row?.get_checked(0)?; 56 | warn!("detect knownbad joint {}, err: {}", joint_hash, error); 57 | return Ok(CheckNewResult::KnownBad); 58 | } 59 | } 60 | Ok(ret) 61 | } 62 | 63 | pub fn remove_unhandled_joint_and_dependencies(db: &mut Connection, unit: &String) -> Result<()> { 64 | let tx = db.transaction()?; 65 | { 66 | let mut stmt = tx.prepare_cached("DELETE FROM unhandled_joints WHERE unit=?")?; 67 | stmt.execute(&[unit])?; 68 | 69 | let mut stmt = tx.prepare_cached("DELETE FROM dependencies WHERE unit=?")?; 70 | stmt.execute(&[unit])?; 71 | } 72 | tx.commit()?; 73 | Ok(()) 74 | } 75 | 76 | pub fn save_unhandled_joint_and_dependencies( 77 | db: &mut Connection, 78 | joint: &Joint, 79 | missing_parent_units: &[String], 80 | peer: &String, 81 | ) -> Result<()> { 82 | let unit = joint.get_unit_hash(); 83 | let tx = db.transaction()?; 84 | { 85 | let mut stmt = 86 | tx.prepare_cached("INSERT INTO unhandled_joints (unit, json, peer) VALUES (?, ?, ?)")?; 87 | stmt.execute(&[unit, &serde_json::to_string(joint)?, peer])?; 88 | let missing_units = missing_parent_units 89 | .iter() 90 | .map(|parent| format!("('{}', '{}')", unit, parent)) 91 | .collect::>() 92 | .join(", "); 93 | let sql = format!( 94 | "INSERT OR IGNORE INTO dependencies (unit, depends_on_unit) VALUES {}", 95 | missing_units 96 | ); 97 | let mut stmt = tx.prepare(&sql)?; 98 | stmt.execute(&[])?; 99 | } 100 | tx.commit()?; 101 | Ok(()) 102 | } 103 | 104 | pub fn find_lost_joints(db: &Connection) -> Result> { 105 | let mut stmt = db.prepare_cached( 106 | "SELECT DISTINCT depends_on_unit \ 107 | FROM dependencies \ 108 | LEFT JOIN unhandled_joints ON depends_on_unit=unhandled_joints.unit \ 109 | LEFT JOIN units ON depends_on_unit=units.unit \ 110 | WHERE unhandled_joints.unit IS NULL AND units.unit IS NULL AND dependencies.creation_date < \'NOW() + INTERVAL -8 SECOND\'" 111 | )?; 112 | 113 | let rows = stmt.query_map(&[], |row| row.get(0))?; 114 | 115 | let mut names = Vec::new(); 116 | for depend_result in rows { 117 | names.push(depend_result?); 118 | } 119 | 120 | Ok(names) 121 | } 122 | 123 | pub fn read_joints_since_mci(db: &Connection, mci: u32) -> Result> { 124 | let mut stmt = db.prepare_cached( 125 | "SELECT units.unit FROM units LEFT JOIN archived_joints USING(unit) \ 126 | WHERE (is_stable=0 AND main_chain_index>=? OR main_chain_index IS NULL OR is_free=1) AND archived_joints.unit IS NULL \ 127 | ORDER BY +level")?; 128 | 129 | let ret = stmt 130 | .query_map(&[&mci], |row| row.get(0))? 131 | .collect::<::std::result::Result, _>>()?; 132 | 133 | let mut joints = Vec::new(); 134 | for unit in ret { 135 | match storage::read_joint(db, &unit) { 136 | Ok(j) => joints.push(j), 137 | Err(e) => error!("read_joint err={}", e), 138 | } 139 | } 140 | 141 | Ok(joints) 142 | } 143 | 144 | #[derive(Debug)] 145 | pub struct ReadyJoint { 146 | pub joint: Joint, 147 | pub create_ts: usize, 148 | pub peer: String, 149 | } 150 | 151 | pub fn read_dependent_joints_that_are_ready( 152 | db: &Connection, 153 | unit: Option<&String>, 154 | ) -> Result<(Vec)> { 155 | let (from, where_clause) = if unit.is_some() { 156 | ( 157 | "FROM dependencies AS src_deps JOIN dependencies USING(unit)", 158 | format!("WHERE src_deps.depends_on_unit='{}'", unit.unwrap()), 159 | ) 160 | } else { 161 | ("FROM dependencies", String::new()) 162 | }; 163 | 164 | let sql = format!( 165 | "SELECT dependencies.unit, unhandled_joints.unit AS unit_for_json, \ 166 | SUM(CASE WHEN units.unit IS NULL THEN 1 ELSE 0 END) AS count_missing_parents \ 167 | {} \ 168 | JOIN unhandled_joints ON dependencies.unit=unhandled_joints.unit \ 169 | LEFT JOIN units ON dependencies.depends_on_unit=units.unit \ 170 | {} \ 171 | GROUP BY dependencies.unit \ 172 | HAVING count_missing_parents=0 \ 173 | ORDER BY NULL", 174 | from, where_clause 175 | ); 176 | 177 | let mut ret = Vec::new(); 178 | let mut stmt = db.prepare(&sql)?; 179 | let rows = stmt 180 | .query_map(&[], |row| row.get(1))? 181 | .collect::<::std::result::Result, _>>()?; 182 | 183 | for row in rows { 184 | let unit: String = row; 185 | let mut stmt = db.prepare_cached( 186 | "SELECT json, peer, strftime('%s', creation_date) AS creation_ts FROM unhandled_joints WHERE unit=?")?; 187 | 188 | let mut rows_inner = stmt 189 | .query_map(&[&unit], |row_inner| ReadyJoint { 190 | joint: serde_json::from_str(&row_inner.get::<_, String>(0)) 191 | .expect("failed to parse json"), 192 | create_ts: row_inner.get::<_, String>(2).parse::().unwrap() * 1000, 193 | peer: row_inner.get(1), 194 | })?.collect::<::std::result::Result, _>>()?; 195 | 196 | ret.append(rows_inner.as_mut()); 197 | } 198 | 199 | Ok(ret) 200 | } 201 | 202 | pub fn purge_joint_and_dependencies( 203 | db: &mut Connection, 204 | joint: &Joint, 205 | err: &str, 206 | f: F, 207 | ) -> Result<()> 208 | where 209 | F: Fn(&str, &str, &str) + 'static, 210 | { 211 | let unit = joint.get_unit_hash(); 212 | let rc_unit = Rc::new(unit.clone()); 213 | 214 | let tx = db.transaction()?; 215 | { 216 | let mut stmt = 217 | tx.prepare_cached("INSERT INTO known_bad_joints (unit, json, error) VALUES (?, ?, ?)")?; 218 | stmt.execute(&[unit, &serde_json::to_string(joint)?, &err])?; 219 | 220 | let mut stmt = tx.prepare_cached("DELETE FROM unhandled_joints WHERE unit=?")?; 221 | stmt.execute(&[unit])?; 222 | 223 | let mut stmt = tx.prepare_cached("DELETE FROM dependencies WHERE unit=?")?; 224 | stmt.execute(&[unit])?; 225 | } 226 | 227 | let mut queries = db::DbQueries::new(); 228 | 229 | collet_queries_to_purge_dependent_joints(&tx, rc_unit, &mut queries, err, f)?; 230 | 231 | queries.execute(&tx)?; 232 | tx.commit()?; 233 | 234 | Ok(()) 235 | } 236 | 237 | fn collet_queries_to_purge_dependent_joints( 238 | db: &Connection, 239 | unit: Rc, 240 | queries: &mut db::DbQueries, 241 | err: &str, 242 | f: F, 243 | ) -> Result<()> 244 | where 245 | F: Fn(&str, &str, &str) + 'static, 246 | { 247 | struct TempUnitProp { 248 | unit: String, 249 | peer: String, 250 | } 251 | 252 | let mut deque = VecDeque::new(); 253 | deque.push_back(TempUnitProp { 254 | unit: unit.to_string(), 255 | peer: String::from("unknow"), 256 | }); 257 | 258 | while let Some(new_unit) = deque.pop_front() { 259 | let mut stmt = db.prepare_cached("SELECT unit, peer FROM dependencies JOIN unhandled_joints USING(unit) WHERE depends_on_unit=?",)?; 260 | 261 | let unit_rows = stmt 262 | .query_map(&[&new_unit.unit], |row| TempUnitProp { 263 | unit: row.get(0), 264 | peer: row.get(1), 265 | })?.collect::<::std::result::Result, _>>()?; 266 | 267 | let units_str = unit_rows 268 | .iter() 269 | .map(|s| format!("'{}'", s.unit)) 270 | .collect::>() 271 | .join(", "); 272 | 273 | for row in unit_rows { 274 | deque.push_back(row); 275 | } 276 | let err_str = err.to_owned(); 277 | 278 | queries.add_query(move |db| { 279 | let sql = format!( 280 | "INSERT OR IGNORE INTO known_bad_joints (unit, json, error) \ 281 | SELECT unit, json, ? FROM unhandled_joints WHERE unit IN({})", 282 | units_str 283 | ); 284 | let mut stmt = db.prepare(&sql)?; 285 | stmt.execute(&[&err_str])?; 286 | 287 | let sql = format!("DELETE FROM unhandled_joints WHERE unit IN({})", units_str); 288 | let mut stmt = db.prepare(&sql)?; 289 | stmt.execute(&[])?; 290 | 291 | let sql = format!("DELETE FROM dependencies WHERE unit IN({})", units_str); 292 | let mut stmt = db.prepare(&sql)?; 293 | stmt.execute(&[])?; 294 | Ok(()) 295 | }); 296 | 297 | f(&new_unit.unit, &new_unit.peer, err); 298 | } 299 | Ok(()) 300 | } 301 | 302 | fn purge_uncovered_nonserial_joints(mut by_existence_of_children: bool) -> Result<()> { 303 | use joint::WRITER_MUTEX; 304 | let mut db = db::DB_POOL.get_connection(); 305 | 306 | loop { 307 | let units = { 308 | let mut stmt = if by_existence_of_children { 309 | // by_existence_of_children = true 310 | db.prepare_cached( 311 | "SELECT unit FROM units INDEXED BY bySequence \ 312 | WHERE (SELECT 1 FROM parenthoods WHERE parent_unit=unit LIMIT 1) IS NULL AND sequence IN('final-bad','temp-bad') AND content_hash IS NULL \ 313 | AND NOT EXISTS (SELECT * FROM dependencies WHERE depends_on_unit=units.unit) \ 314 | AND NOT EXISTS (SELECT * FROM balls WHERE balls.unit=units.unit) \ 315 | AND EXISTS ( \ 316 | SELECT DISTINCT address FROM units AS wunits CROSS \ 317 | JOIN unit_authors USING(unit) CROSS JOIN my_witnesses USING(address) \ 318 | WHERE wunits.rowid > units.rowid \ 319 | LIMIT 6,1 \ 320 | )", 321 | // FIXME: LIMIT = config::MAJORITY_OF_WITNESSES - 1; 322 | )? 323 | } else { 324 | db.prepare_cached( 325 | "SELECT unit FROM units \ 326 | WHERE is_free=1 AND sequence IN('final-bad','temp-bad') AND content_hash IS NULL \ 327 | AND NOT EXISTS (SELECT * FROM dependencies WHERE depends_on_unit=units.unit) \ 328 | AND NOT EXISTS (SELECT * FROM balls WHERE balls.unit=units.unit) \ 329 | AND EXISTS ( \ 330 | SELECT DISTINCT address FROM units AS wunits CROSS \ 331 | JOIN unit_authors USING(unit) CROSS JOIN my_witnesses USING(address) \ 332 | WHERE wunits.rowid > units.rowid LIMIT 6,1 \ 333 | )", 334 | // FIXME: LIMIT = config::MAJORITY_OF_WITNESSES - 1; 335 | )? 336 | }; 337 | let rows = stmt.query_map(&[], |row| row.get(0))?; 338 | rows.collect::<::std::result::Result, _>>()? 339 | }; 340 | 341 | if units.is_empty() { 342 | if !by_existence_of_children { 343 | return Ok(()); 344 | } else { 345 | break; 346 | } 347 | } 348 | 349 | for unit in units { 350 | info!("--------------- archiving uncovered unit {}", unit); 351 | let joint = storage::read_joint(&db, &unit) 352 | .or_else(|e| bail!("nonserial unit not found?, err={}", e))?; 353 | 354 | let g = WRITER_MUTEX.lock().unwrap(); 355 | let mut queries = db::DbQueries::new(); 356 | storage::generate_queries_to_archive_joint( 357 | &db, 358 | &joint, 359 | storage::ArchiveJointReason::Uncovered, 360 | &mut queries, 361 | )?; 362 | let tx = db.transaction()?; 363 | queries.execute(&tx)?; 364 | tx.commit()?; 365 | drop(g); 366 | storage::forget_unit(&unit); 367 | } 368 | 369 | by_existence_of_children = true; 370 | } 371 | 372 | if !by_existence_of_children { 373 | return Ok(()); 374 | } 375 | 376 | let mut stmt = db.prepare_cached( 377 | "UPDATE units SET is_free=1 WHERE is_free=0 AND main_chain_index IS NULL \ 378 | AND (SELECT 1 FROM parenthoods WHERE parent_unit=unit LIMIT 1) IS NULL", 379 | )?; 380 | stmt.execute(&[])?; 381 | 382 | Ok(()) 383 | } 384 | 385 | pub fn purge_uncovered_nonserial_joints_under_lock() -> Result<()> { 386 | lazy_static! { 387 | static ref PURGE_UNCOVERED: Mutex<()> = Mutex::new(()); 388 | } 389 | 390 | if PURGE_UNCOVERED.try_lock().is_ok() { 391 | return purge_uncovered_nonserial_joints(false); 392 | } 393 | Ok(()) 394 | } 395 | -------------------------------------------------------------------------------- /src/spec.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use config; 4 | use obj_ser; 5 | use object_hash::get_base64_hash; 6 | use serde_json::Value; 7 | 8 | #[derive(Clone, Debug, Serialize, Deserialize)] 9 | #[serde(deny_unknown_fields)] 10 | pub struct Author { 11 | pub address: String, 12 | #[serde(skip_serializing_if = "HashMap::is_empty")] 13 | pub authentifiers: HashMap, 14 | #[serde(default)] 15 | #[serde(skip_serializing_if = "Value::is_null")] 16 | pub definition: Value, 17 | } 18 | 19 | #[derive(Clone, Debug, Serialize, Deserialize)] 20 | pub struct SpendProof { 21 | pub spend_proof: String, 22 | #[serde(skip_serializing_if = "Option::is_none")] 23 | pub address: Option, 24 | } 25 | 26 | // TODO: Input struct is from type 27 | #[derive(Clone, Debug, Serialize, Deserialize, Default)] 28 | pub struct Input { 29 | #[serde(skip_serializing_if = "Option::is_none")] 30 | pub address: Option, 31 | #[serde(skip_serializing_if = "Option::is_none")] 32 | pub amount: Option, 33 | #[serde(skip_serializing_if = "Option::is_none")] 34 | pub from_main_chain_index: Option, 35 | #[serde(skip_serializing_if = "Option::is_none")] 36 | pub serial_number: Option, 37 | #[serde(skip_serializing_if = "Option::is_none")] 38 | pub message_index: Option, 39 | #[serde(skip_serializing_if = "Option::is_none")] 40 | #[serde(rename = "type")] 41 | pub kind: Option, 42 | #[serde(skip_serializing_if = "Option::is_none")] 43 | pub output_index: Option, 44 | #[serde(skip_serializing_if = "Option::is_none")] 45 | pub to_main_chain_index: Option, 46 | #[serde(skip_serializing_if = "Option::is_none")] 47 | pub unit: Option, 48 | #[serde(skip_serializing_if = "Option::is_none")] 49 | pub blinding: Option, 50 | } 51 | 52 | #[derive(Clone, Debug, Serialize, Deserialize, Default)] 53 | pub struct Message { 54 | pub app: String, 55 | pub payload: Option, 56 | pub payload_hash: String, 57 | pub payload_location: String, 58 | #[serde(skip_serializing_if = "Option::is_none")] 59 | pub payload_uri: Option, 60 | #[serde(skip_serializing_if = "Option::is_none")] 61 | pub payload_uri_hash: Option, 62 | #[serde(skip_serializing_if = "Vec::is_empty")] 63 | #[serde(default)] 64 | pub spend_proofs: Vec, 65 | } 66 | 67 | #[derive(Clone, Debug, Serialize, Deserialize)] 68 | pub struct Output { 69 | pub address: String, 70 | pub amount: i64, 71 | } 72 | 73 | #[derive(Clone, Debug, Serialize, Deserialize)] 74 | #[serde(untagged)] 75 | pub enum Payload { 76 | Text(String), 77 | Payment(Payment), 78 | Other(Value), 79 | } 80 | 81 | #[derive(Clone, Debug, Serialize, Deserialize)] 82 | pub struct Payment { 83 | #[serde(skip_serializing_if = "Option::is_none")] 84 | pub address: Option, 85 | #[serde(skip_serializing_if = "Option::is_none")] 86 | pub asset: Option, 87 | #[serde(skip_serializing_if = "Option::is_none")] 88 | pub definition_chash: Option, 89 | #[serde(skip_serializing_if = "Option::is_none")] 90 | pub denomination: Option, 91 | pub inputs: Vec, 92 | pub outputs: Vec, 93 | } 94 | 95 | #[derive(Clone, Debug, Serialize, Deserialize)] 96 | pub struct HeaderCommissionShare { 97 | pub address: String, 98 | pub earned_headers_commission_share: i64, 99 | } 100 | 101 | #[derive(Clone, Debug, Serialize, Deserialize)] 102 | pub struct Ball { 103 | // TODO: need a real definition 104 | pub unit: String, 105 | } 106 | 107 | // TODO: use specific struct for address and hash 108 | #[derive(Clone, Debug, Serialize, Deserialize)] 109 | pub struct Unit { 110 | pub alt: String, 111 | pub authors: Vec, 112 | #[serde(skip_serializing_if = "Option::is_none")] 113 | pub content_hash: Option, // this may not exist 114 | #[serde(skip_serializing_if = "Vec::is_empty")] 115 | #[serde(default)] 116 | pub earned_headers_commission_recipients: Vec, 117 | #[serde(skip_serializing_if = "Option::is_none")] 118 | pub headers_commission: Option, // default 0 119 | #[serde(skip_serializing_if = "Option::is_none")] 120 | pub last_ball: Option, 121 | #[serde(skip_serializing_if = "Option::is_none")] 122 | pub last_ball_unit: Option, 123 | #[serde(skip_serializing_if = "Option::is_none")] 124 | pub main_chain_index: Option, 125 | pub messages: Vec, 126 | #[serde(skip_serializing_if = "Vec::is_empty")] 127 | #[serde(default)] 128 | pub parent_units: Vec, 129 | #[serde(skip_serializing_if = "Option::is_none")] 130 | pub payload_commission: Option, // default 0 131 | #[serde(skip_serializing_if = "Option::is_none")] 132 | pub timestamp: Option, 133 | #[serde(skip_serializing_if = "Option::is_none")] 134 | pub unit: Option, // this may not exist 135 | pub version: String, 136 | #[serde(skip_serializing_if = "Vec::is_empty")] 137 | #[serde(default)] 138 | pub witnesses: Vec, 139 | #[serde(skip_serializing_if = "Option::is_none")] 140 | pub witness_list_unit: Option, 141 | } 142 | 143 | #[derive(Debug, Clone)] 144 | /// internally used struct 145 | pub struct StaticUnitProperty { 146 | pub level: u32, 147 | pub witnessed_level: u32, 148 | pub best_parent_unit: Option, 149 | pub witness_list_unit: Option, 150 | } 151 | 152 | #[derive(Debug, Clone)] 153 | /// internally used struct 154 | pub struct UnitProps { 155 | pub unit: String, 156 | pub level: u32, 157 | pub latest_included_mc_index: Option, 158 | pub main_chain_index: Option, 159 | pub is_on_main_chain: u32, 160 | pub is_free: u32, 161 | pub is_stable: u32, 162 | } 163 | 164 | #[inline] 165 | lazy_static! { 166 | static ref GENESIS_UNIT: String = ::config::get_genesis_unit(); 167 | } 168 | 169 | pub fn is_genesis_unit(unit: &str) -> bool { 170 | unit == *GENESIS_UNIT 171 | } 172 | 173 | pub fn is_genesis_ball(ball: &str) -> bool { 174 | lazy_static! { 175 | //GENESIS_UNIT's parent and skiplist is null 176 | static ref GENESIS_BALL: String = 177 | ::object_hash::get_ball_hash(&GENESIS_UNIT, &Vec::new(), &Vec::new(), false); 178 | } 179 | ball == *GENESIS_BALL 180 | } 181 | 182 | impl Unit { 183 | pub fn is_genesis_unit(&self) -> bool { 184 | match self.unit { 185 | Some(ref hash) => is_genesis_unit(hash), 186 | _ => false, 187 | } 188 | } 189 | 190 | fn get_naked_unit(&self) -> Unit { 191 | let mut naked_unit: Unit = self.clone(); 192 | naked_unit.unit = None; 193 | naked_unit.headers_commission = None; 194 | naked_unit.payload_commission = None; 195 | naked_unit.main_chain_index = None; 196 | naked_unit.timestamp = None; 197 | 198 | for message in &mut naked_unit.messages { 199 | message.payload = None; 200 | message.payload_uri = None; 201 | } 202 | 203 | naked_unit 204 | } 205 | 206 | pub fn get_unit_content_hash(&self) -> String { 207 | get_base64_hash(&self.get_naked_unit()).expect("get_unit_content_hash failed") 208 | } 209 | 210 | pub fn get_unit_hash(&self) -> String { 211 | if self.content_hash.is_some() { 212 | return get_base64_hash(&self.get_naked_unit()).expect("get_unit_hash naked failed"); 213 | } 214 | 215 | #[derive(Debug, Serialize)] 216 | struct Address { 217 | address: String, 218 | } 219 | 220 | #[derive(Debug, Serialize)] 221 | struct StrippedUnit { 222 | alt: String, 223 | #[serde(skip_serializing_if = "Vec::is_empty")] 224 | authors: Vec
, 225 | content_hash: String, 226 | #[serde(skip_serializing_if = "Option::is_none")] 227 | last_ball: Option, 228 | #[serde(skip_serializing_if = "Option::is_none")] 229 | last_ball_unit: Option, 230 | #[serde(skip_serializing_if = "Vec::is_empty")] 231 | parent_units: Vec, 232 | version: String, 233 | #[serde(skip_serializing_if = "Vec::is_empty")] 234 | witnesses: Vec, 235 | #[serde(skip_serializing_if = "Option::is_none")] 236 | witness_list_unit: Option, 237 | } 238 | 239 | let mut stripped_unit = StrippedUnit { 240 | alt: self.alt.clone(), 241 | authors: self 242 | .authors 243 | .iter() 244 | .map(|a| Address { 245 | address: a.address.clone(), 246 | }).collect::>(), 247 | content_hash: self.get_unit_content_hash(), 248 | last_ball: None, 249 | last_ball_unit: None, 250 | parent_units: self.parent_units.clone(), 251 | version: self.version.clone(), 252 | witnesses: Vec::new(), 253 | witness_list_unit: None, 254 | }; 255 | 256 | if self.witness_list_unit.is_some() { 257 | stripped_unit.witness_list_unit = self.witness_list_unit.clone(); 258 | } else { 259 | stripped_unit.witnesses = self.witnesses.clone(); 260 | } 261 | 262 | if !self.parent_units.is_empty() { 263 | stripped_unit.last_ball = self.last_ball.clone(); 264 | stripped_unit.last_ball_unit = self.last_ball_unit.clone(); 265 | } 266 | 267 | get_base64_hash(&stripped_unit).expect("get_unit_hash failed") 268 | } 269 | 270 | pub fn get_unit_hash_to_sign(&self) -> Vec { 271 | use sha2::{Digest, Sha256}; 272 | 273 | let mut naked_unit = self.get_naked_unit(); 274 | for author in &mut naked_unit.authors { 275 | author.authentifiers.clear(); 276 | } 277 | 278 | let obj_str = obj_ser::to_string(&naked_unit).expect("naked_unit to string failed"); 279 | 280 | Sha256::digest(obj_str.as_bytes()).to_vec() 281 | } 282 | 283 | pub fn get_header_size(&self) -> u32 { 284 | if self.content_hash.is_some() { 285 | error!("trying to get headers size of stripped unit"); 286 | return 0; 287 | } 288 | 289 | let mut header = self.clone(); 290 | header.unit = None; 291 | header.headers_commission = None; 292 | header.payload_commission = None; 293 | header.main_chain_index = None; 294 | header.timestamp = None; 295 | header.messages.clear(); 296 | header.parent_units.clear(); 297 | 298 | const PARENT_UNITS_SIZE: u32 = 2 * 44; 299 | 300 | let size = match obj_ser::obj_size(&header) { 301 | Ok(s) => s as u32, 302 | Err(e) => { 303 | error!("failed to get header size, err={}", e); 304 | 0 305 | } 306 | }; 307 | 308 | size + PARENT_UNITS_SIZE 309 | } 310 | 311 | pub fn get_payload_size(&self) -> u32 { 312 | if self.content_hash.is_some() { 313 | error!("trying to get payload size of stripped unit"); 314 | return 0; 315 | } 316 | 317 | match obj_ser::obj_size(&self.messages) { 318 | Ok(s) => s as u32, 319 | Err(e) => { 320 | error!("failed to get payload size, err={}", e); 321 | 0 322 | } 323 | } 324 | } 325 | } 326 | 327 | impl Default for Unit { 328 | fn default() -> Self { 329 | Unit { 330 | alt: config::ALT.to_string(), 331 | authors: Vec::new(), 332 | content_hash: None, 333 | earned_headers_commission_recipients: Vec::new(), 334 | headers_commission: None, 335 | last_ball: None, 336 | last_ball_unit: None, 337 | main_chain_index: None, 338 | messages: Vec::new(), 339 | parent_units: Vec::new(), 340 | payload_commission: None, 341 | timestamp: None, 342 | unit: None, 343 | version: config::VERSION.to_string(), 344 | witnesses: Vec::new(), 345 | witness_list_unit: None, 346 | } 347 | } 348 | } 349 | 350 | #[test] 351 | fn test_unit_hash() { 352 | use serde_json; 353 | let unit = r#"{ 354 | "unit":"nIcYRvz1AiAwoMWhOz/h5tRL3fZvI2CdEg4tNo7hhLk=", 355 | "version":"1.0", 356 | "alt":"1", 357 | "witness_list_unit":"MtzrZeOHHjqVZheuLylf0DX7zhp10nBsQX5e/+cA3PQ=", 358 | "last_ball_unit":"dimZTmLvmjNfo7I6Go9juCIokk5I+tgyxAfNPlg16G4=", 359 | "last_ball":"SVnrEYhIOKmku91eWlwnPMV2gf/lMYpg36AL/zfakag=", 360 | "headers_commission":344, 361 | "payload_commission":157, 362 | "main_chain_index":65936, 363 | "timestamp":1527218469, 364 | "parent_units":[ 365 | "Y+A+trJA30+P6PsC0hX5CwhNDj80w4OmJMcnq5Ou1FU=" 366 | ], 367 | "authors":[ 368 | { 369 | "address":"D27P6DGHLPO5A7MSOZABHOOWQ3BJ56ZI", 370 | "authentifiers":{ 371 | "r":"+/d2BCSgLE30z8M1XUHQc6slv9w+Srf8yOQZf7IZQP4i1Xzmyj2ycce5yKnQOj3ZBupX28cQ+FWB1DRbkTrn2g==" 372 | } 373 | } 374 | ], 375 | "messages":[ 376 | { 377 | "app":"payment", 378 | "payload_hash":"15LThwlDEC1nRe48EGg5giJsMkQ9Bhe3Z/kRyZ0RmNY=", 379 | "payload_location":"inline", 380 | "payload":{ 381 | "inputs":[ 382 | { 383 | "unit":"rHwZyXWZRFeU/LA3Kga+xGvjijNXYQwTbufMjqdxmPg=", 384 | "message_index":0, 385 | "output_index":0 386 | } 387 | ], 388 | "outputs":[ 389 | { 390 | "address":"D27P6DGHLPO5A7MSOZABHOOWQ3BJ56ZI", 391 | "amount":82375 392 | } 393 | ] 394 | } 395 | } 396 | ] 397 | }"#; 398 | 399 | let unit: Unit = serde_json::from_str(unit).unwrap(); 400 | assert_eq!( 401 | unit.get_unit_hash(), 402 | "nIcYRvz1AiAwoMWhOz/h5tRL3fZvI2CdEg4tNo7hhLk=" 403 | ); 404 | assert_eq!(unit.get_header_size(), 344); 405 | assert_eq!(unit.get_payload_size(), 157); 406 | } 407 | 408 | #[test] 409 | fn test_unit_json() { 410 | use serde_json; 411 | let data = r#" 412 | { 413 | "version": "1.0", 414 | "alt": "1", 415 | "messages": [ 416 | { 417 | "app": "payment", 418 | "payload_location": "inline", 419 | "payload_hash": "5CYeTTa4VQxgF4b1Tn33NBlKilJadddwBMLvtp1HIus=", 420 | "payload": { 421 | "outputs": [ 422 | { 423 | "address": "7JXBJQPQC3466UPK7C6ABA6VVU6YFYAI", 424 | "amount": 10000 425 | }, 426 | { 427 | "address": "JERTY5XNENMHYQW7NVBXUB5CU3IDODA3", 428 | "amount": 99989412 429 | } 430 | ], 431 | "inputs": [ 432 | { 433 | "unit": "lQCxxsMslXLzQKybX2KArOGho8XuNf1Lpds2abdf8O4=", 434 | "message_index": 0, 435 | "output_index": 1 436 | } 437 | ] 438 | } 439 | } 440 | ], 441 | "authors": [ 442 | { 443 | "address": "JERTY5XNENMHYQW7NVBXUB5CU3IDODA3", 444 | "authentifiers": { 445 | "r": "tHLxvXNYVwDnQg3N4iNHtHZ4mXvqRW+ZMPkQadev6MpAWbEPVcIpme1Vz1nyskWYgueREZoEbQeEWtC/oCQbxQ==" 446 | }, 447 | "definition": [ 448 | "sig", 449 | { 450 | "pubkey": "A0gKwkLedQgzm32JtEo6KmuRcyZa3beikS3xfrwdXAMU" 451 | } 452 | ] 453 | } 454 | ], 455 | "parent_units": [ 456 | "uPbobEuZL+FY1ujTNiYZnM9lgC3xysxuDIpSbvnmbac=" 457 | ], 458 | "last_ball": "oiIA6Y+87fk6/QyrbOlwqsQ/LLr82Rcuzcr1G/GoHlA=", 459 | "last_ball_unit": "vxrlKyY517Z+BGMNG35ExiQsYv3ncp/KU414SqXKXTk=", 460 | "witness_list_unit": "MtzrZeOHHjqVZheuLylf0DX7zhp10nBsQX5e/+cA3PQ=", 461 | "headers_commission": 391, 462 | "payload_commission": 197 463 | }"#; 464 | 465 | let u: Unit = serde_json::from_str(data).unwrap(); 466 | assert_eq!(u.authors[0].definition[0], json!("sig")); 467 | assert_eq!( 468 | u.authors[0].definition[1], 469 | json!({"pubkey": "A0gKwkLedQgzm32JtEo6KmuRcyZa3beikS3xfrwdXAMU"}) 470 | ); 471 | } 472 | --------------------------------------------------------------------------------