From 03a5dbc1186c26c5f1942897873f1c2505abb9cb Mon Sep 17 00:00:00 2001 From: Nadav Ivgi Date: Tue, 3 Nov 2020 13:30:40 +0200 Subject: [PATCH] Integrate SPV cross-validation checks into the headers thread And keep the latest validation status on the store cache. NOTE: This commit depends on a rust-bitcoin fork with serializable uint types. A PR was sent upstream: https://github.com/rust-bitcoin/rust-bitcoin/pull/511 --- subprojects/gdk_rust/Cargo.toml | 5 +++- subprojects/gdk_rust/gdk_electrum/Cargo.toml | 3 +++ .../gdk_rust/gdk_electrum/src/interface.rs | 3 ++- subprojects/gdk_rust/gdk_electrum/src/lib.rs | 26 +++++++++++++++++++ subprojects/gdk_rust/gdk_electrum/src/spv.rs | 4 +-- .../gdk_rust/gdk_electrum/src/store.rs | 4 +++ 6 files changed, 41 insertions(+), 4 deletions(-) diff --git a/subprojects/gdk_rust/Cargo.toml b/subprojects/gdk_rust/Cargo.toml index f76b7f137..65b65cd5b 100644 --- a/subprojects/gdk_rust/Cargo.toml +++ b/subprojects/gdk_rust/Cargo.toml @@ -12,7 +12,7 @@ crate-type = ["staticlib"] android_log = ["android_logger"] [dependencies] -bitcoin = { version = "0.25", features = [ "use-serde" ] } +bitcoin = { version = "0.25.2", features = [ "use-serde" ] } rand = "0.7.3" gdk-electrum = { path = "gdk_electrum", features = ["android_log"] } gdk-common = { path = "gdk_common" } @@ -39,3 +39,6 @@ panic = 'abort' opt-level = 'z' codegen-units = 1 incremental = false + +[patch.crates-io] +bitcoin = { git = "https://github.com/shesek/rust-bitcoin", branch = "0.25.2-uint-serde", features = [ "use-serde" ] } diff --git a/subprojects/gdk_rust/gdk_electrum/Cargo.toml b/subprojects/gdk_rust/gdk_electrum/Cargo.toml index f685bd999..6ec864fe8 100644 --- a/subprojects/gdk_rust/gdk_electrum/Cargo.toml +++ b/subprojects/gdk_rust/gdk_electrum/Cargo.toml @@ -31,3 +31,6 @@ lazy_static = "1.4.0" # remember to update secp256k1 deps if increasing this one, unfortunately we can't use the rexported one from bitcoin because we need recovery feature bitcoin = { version = "0.25", features = [ "use-serde" ] } elements = { version = "0.13", features = ["serde-feature"] } + +[patch.crates-io] +bitcoin = { git = "https://github.com/shesek/rust-bitcoin", branch = "0.25.2-uint-serde", features = [ "use-serde" ] } diff --git a/subprojects/gdk_rust/gdk_electrum/src/interface.rs b/subprojects/gdk_rust/gdk_electrum/src/interface.rs index d9985e43a..a88628734 100644 --- a/subprojects/gdk_rust/gdk_electrum/src/interface.rs +++ b/subprojects/gdk_rust/gdk_electrum/src/interface.rs @@ -10,6 +10,7 @@ use gdk_common::model::{AddressAmount, Balances, GetTransactionsOpt, SPVVerifyRe use hex; use log::{info, trace}; use rand::Rng; +use serde::{Deserialize, Serialize}; use gdk_common::mnemonic::Mnemonic; use gdk_common::model::{AddressPointer, CreateTransaction, Settings, TransactionMeta}; @@ -41,7 +42,7 @@ pub struct WalletCtx { pub change_max_deriv: u32, } -#[derive(Clone, Debug)] +#[derive(Clone, Serialize, Deserialize, Debug)] pub enum ElectrumUrl { Tls(String, bool), // the bool value indicates if the domain name should be validated Plaintext(String), diff --git a/subprojects/gdk_rust/gdk_electrum/src/lib.rs b/subprojects/gdk_rust/gdk_electrum/src/lib.rs index 7768e2a0f..57a32f54d 100644 --- a/subprojects/gdk_rust/gdk_electrum/src/lib.rs +++ b/subprojects/gdk_rust/gdk_electrum/src/lib.rs @@ -52,6 +52,7 @@ use crate::headers::bitcoin::HeadersChain; use crate::headers::liquid::Verifier; use crate::headers::ChainOrVerifier; use crate::pin::PinManager; +use crate::spv::SpvCrossValidator; use aes::Aes256; use bitcoin::blockdata::constants::DIFFCHANGE_INTERVAL; use block_modes::block_padding::Pkcs7; @@ -66,6 +67,8 @@ use std::hash::Hasher; use std::sync::{mpsc, Arc, RwLock}; use std::thread::JoinHandle; +const CROSS_VALIDATION_RATE: u8 = 4; // Once every 4 thread loop runs, or roughly 28 seconds + type Aes256Cbc = Cbc; pub struct Syncer { @@ -82,6 +85,7 @@ pub struct Tipper { pub struct Headers { pub store: Store, pub checker: ChainOrVerifier, + pub cross_validator: Option, } #[derive(Clone)] @@ -420,9 +424,12 @@ impl Session for ElectrumSession { } }; + let cross_validator = SpvCrossValidator::from_network(&self.network)?; + let mut headers = Headers { store: store.clone(), checker, + cross_validator, }; let headers_url = self.url.clone(); @@ -431,6 +438,7 @@ impl Session for ElectrumSession { let chunk_size = DIFFCHANGE_INTERVAL as usize; let headers_handle = thread::spawn(move || { info!("starting headers thread"); + let mut round = 0u8; 'outer: loop { if wait_or_close(&r, sync_interval) { @@ -477,6 +485,12 @@ impl Session for ElectrumSession { } Err(e) => warn!("error in getting proofs {:?}", e), } + + if round % CROSS_VALIDATION_RATE == 0 { + headers.cross_validate(); + } + + round = round.wrapping_add(1); } } }); @@ -989,6 +1003,18 @@ impl Headers { } Ok(()) } + + pub fn cross_validate(&mut self) { + if let (Some(cross_validator), ChainOrVerifier::Chain(chain)) = + (&mut self.cross_validator, &self.checker) + { + let result = cross_validator.validate(chain); + debug!("cross validation result: {:?}", result); + + let mut store = self.store.write().unwrap(); + store.cache.cross_validation_result = Some(result); + } + } } #[derive(Default)] diff --git a/subprojects/gdk_rust/gdk_electrum/src/spv.rs b/subprojects/gdk_rust/gdk_electrum/src/spv.rs index 01c847b95..d3058ea82 100644 --- a/subprojects/gdk_rust/gdk_electrum/src/spv.rs +++ b/subprojects/gdk_rust/gdk_electrum/src/spv.rs @@ -1,5 +1,6 @@ use log::warn; use rand::seq::SliceRandom; +use serde::{Deserialize, Serialize}; use std::str::FromStr; use bitcoin::blockdata::constants::{max_target, DIFFCHANGE_INTERVAL, DIFFCHANGE_TIMESPAN}; @@ -18,7 +19,6 @@ const MAX_CHUNK_SIZE: u32 = 200; const MAX_FORK_DEPTH: u32 = DIFFCHANGE_INTERVAL * 3; const SERVERS_PER_ROUND: usize = 3; -// XXX when to run - add to existing headers thread? // XXX how to alert when something's up #[derive(Debug)] @@ -27,7 +27,7 @@ pub struct SpvCrossValidator { last_result: CrossValidationResult, } -#[derive(Clone, Debug)] +#[derive(Clone, Serialize, Deserialize, Debug)] pub enum CrossValidationResult { Valid, diff --git a/subprojects/gdk_rust/gdk_electrum/src/store.rs b/subprojects/gdk_rust/gdk_electrum/src/store.rs index 2b59cf3bd..43efd6cfb 100644 --- a/subprojects/gdk_rust/gdk_electrum/src/store.rs +++ b/subprojects/gdk_rust/gdk_electrum/src/store.rs @@ -1,3 +1,4 @@ +use crate::spv::CrossValidationResult; use crate::Error; use aes_gcm_siv::aead::{generic_array::GenericArray, AeadInPlace, NewAead}; use aes_gcm_siv::Aes256GcmSiv; @@ -71,6 +72,9 @@ pub struct RawCache { /// registry icons last modified, used when making the http request pub icons_last_modified: String, + + /// the result of the last spv cross-validation execution + pub cross_validation_result: Option, } /// RawStore contains data that are not extractable from xpub+blockchain