Skip to content

Commit

Permalink
Integrate SPV cross-validation checks into the headers thread
Browse files Browse the repository at this point in the history
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: rust-bitcoin/rust-bitcoin#511
  • Loading branch information
shesek committed Dec 17, 2020
1 parent 318455e commit 74f614f
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 4 deletions.
5 changes: 4 additions & 1 deletion subprojects/gdk_rust/Cargo.toml
Expand Up @@ -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" }
Expand All @@ -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" ] }
3 changes: 3 additions & 0 deletions subprojects/gdk_rust/gdk_electrum/Cargo.toml
Expand Up @@ -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" ] }
3 changes: 2 additions & 1 deletion subprojects/gdk_rust/gdk_electrum/src/interface.rs
Expand Up @@ -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};
Expand Down Expand Up @@ -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),
Expand Down
26 changes: 26 additions & 0 deletions subprojects/gdk_rust/gdk_electrum/src/lib.rs
Expand Up @@ -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;
Expand All @@ -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<Aes256, Pkcs7>;

pub struct Syncer {
Expand All @@ -82,6 +85,7 @@ pub struct Tipper {
pub struct Headers {
pub store: Store,
pub checker: ChainOrVerifier,
pub cross_validator: Option<SpvCrossValidator>,
}

#[derive(Clone)]
Expand Down Expand Up @@ -420,9 +424,12 @@ impl Session<Error> 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();
Expand All @@ -431,6 +438,7 @@ impl Session<Error> 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) {
Expand Down Expand Up @@ -477,6 +485,12 @@ impl Session<Error> for ElectrumSession {
}
Err(e) => warn!("error in getting proofs {:?}", e),
}

if round % CROSS_VALIDATION_RATE == 0 {
headers.cross_validate();
}

round = round.wrapping_add(1);
}
}
});
Expand Down Expand Up @@ -993,6 +1007,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)]
Expand Down
4 changes: 2 additions & 2 deletions 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};
Expand All @@ -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)]
Expand All @@ -27,7 +27,7 @@ pub struct SpvCrossValidator {
last_result: CrossValidationResult,
}

#[derive(Clone, Debug)]
#[derive(Clone, Serialize, Deserialize, Debug)]
pub enum CrossValidationResult {
Valid,

Expand Down
4 changes: 4 additions & 0 deletions 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;
Expand Down Expand Up @@ -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<CrossValidationResult>,
}

/// RawStore contains data that are not extractable from xpub+blockchain
Expand Down

0 comments on commit 74f614f

Please sign in to comment.