diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index d8f9d53..ff54f5a 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -42,7 +42,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: check - args: --all --bins --examples --tests + args: --all --bins --examples --tests --all-features - name: tests uses: actions-rs/cargo@v1 @@ -56,7 +56,7 @@ jobs: uses: actions-rs/tarpaulin@v0.1 with: version: '0.15.0' - args: '-t 1200' + args: '-t 1200 --all-features' - name: Upload to Codecov if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request') diff --git a/Cargo.toml b/Cargo.toml index a470965..4ed7d2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,7 @@ serde = { version = "1.0", features = ["derive"] } bincode = "1.3" derive_builder = "0.12" num = { version = "0.4.0", default-features = false, features = ["serde", "num-bigint"]} + +[features] +default = [] +incremental = [] diff --git a/Makefile b/Makefile index 78f6ed1..3ac8e7c 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: AGPL-3.0-or-later define test_core - cargo test --no-fail-fast + cargo test --no-fail-fast --all-features endef default: ## Build library in debug mode diff --git a/src/lib.rs b/src/lib.rs index cf8b208..5df5dd6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,7 +50,94 @@ pub struct Config { pub salt: String, } +/// Return value of incremental prooving. When proof is ready, IncrementalSolve::Work is returned +/// and when proof is not ready, IncrementalSolve::Intermediate is returned +#[cfg(feature = "incremental")] +pub enum IncrementalSolve { + /// Intermediate result + Intermediate(u128, u64, Sha256, u128), + /// Final result + Work(PoW), +} + impl Config { + /// + /// step is used to control the number of cycles after which the function should exit, even + /// when the proof isn't ready + /// inter is used to keep track of state and complete proof generation. Set inter to None + /// during first cyle and pass the returned value of the previous cycle to continue proof + /// generation + + #[cfg(feature = "incremental")] + pub fn stepped_prove_work( + &self, + t: &T, + difficulty: u32, + step: usize, + inter: Option>, + ) -> bincode::Result> + where + T: Serialize, + { + bincode::serialize(t) + .map(|v| self.stepped_prove_work_serialized(&v, difficulty, step, inter)) + } + + #[cfg(feature = "incremental")] + /// Create Proof of Work over item of type T. + /// + /// Make sure difficulty is not too high. A 64 bit difficulty, + /// for example, takes a long time on a general purpose processor. + /// The input is assumed to be serialized using network byte order. + /// + /// Make sure difficulty is not too high. A 64 bit difficulty, + /// for example, takes a long time on a general purpose processor. + /// step is used to control the number of cycles after which the function should exit, even + /// when the proof isn't ready + /// inter is used to keep track of state and complete proof generation. Set inter to None + /// during first cyle and pass the returned value of the previous cycle to continue proof + /// generation + /// Returns bincode::Error if serialization fails. + /// Create Proof of Work on an already serialized item of type T. + fn stepped_prove_work_serialized( + &self, + prefix: &[u8], + difficulty: u32, + step: usize, + inter: Option>, + ) -> IncrementalSolve + where + T: Serialize, + { + let (mut result, mut n, prefix_sha, difficulty) = match inter { + Some(IncrementalSolve::Intermediate(result, nonce, prefix, difficulty)) => { + (result, nonce, prefix, difficulty) + } + _ => { + let prefix_sha = Sha256::new().chain(&self.salt).chain(prefix); + let n = 0; + let result = 0; + let difficulty = get_difficulty(difficulty); + (result, n, prefix_sha, difficulty) + } + }; + let mut count = 0; + while result < difficulty { + if count > step { + return IncrementalSolve::Intermediate(result, n, prefix_sha, difficulty); + } else { + count += 1; + } + n += 1; + result = dev::score(prefix_sha.clone(), n); + } + IncrementalSolve::Work(PoW { + nonce: n, + result: result.to_string(), + _spook: PhantomData, + }) + } + /// Create Proof of Work over item of type T. /// /// Make sure difficulty is not too high. A 64 bit difficulty, @@ -262,4 +349,31 @@ mod test { assert!(config.is_sufficient_difficulty(&message.1, DIFFICULTY)); assert!(config.is_valid_proof(&message.1, &target)); } + + #[test] + #[cfg(feature = "incremental")] + fn stepped_solve() { + let phrase = "Ex nihilo nihil fit.".to_owned(); + let config = get_config(); + + let mut inter = None; + loop { + match config.stepped_prove_work(&phrase, 50000, 1000, inter) { + Ok(IncrementalSolve::Intermediate(result, nonce, prefix, difficulty)) => { + println!("Current nonce {nonce}"); + inter = Some(IncrementalSolve::Intermediate( + result, nonce, prefix, difficulty, + )); + continue; + } + + Ok(IncrementalSolve::Work(w)) => { + assert!(config.is_valid_proof(&w, &phrase)); + assert!(config.is_sufficient_difficulty(&w, DIFFICULTY)); + break; + } + Err(e) => panic!("{}", e), + }; + } + } }