From e94a04d47c0d5b91cceb30be8fe24d21ca8712bc Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 4 Jul 2024 03:17:35 -0400 Subject: [PATCH] Restore the reserialize chain binary --- Cargo.lock | 16 + Cargo.toml | 1 + coins/monero/Cargo.toml | 1 - .../ringct/bulletproofs/src/original/mod.rs | 8 +- coins/monero/src/bin/reserialize_chain.rs | 321 ------------------ coins/monero/verify-chain/Cargo.toml | 33 ++ coins/monero/verify-chain/LICENSE | 21 ++ coins/monero/verify-chain/README.md | 7 + coins/monero/verify-chain/src/main.rs | 316 +++++++++++++++++ common/request/src/lib.rs | 2 + 10 files changed, 401 insertions(+), 325 deletions(-) delete mode 100644 coins/monero/src/bin/reserialize_chain.rs create mode 100644 coins/monero/verify-chain/Cargo.toml create mode 100644 coins/monero/verify-chain/LICENSE create mode 100644 coins/monero/verify-chain/README.md create mode 100644 coins/monero/verify-chain/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 6a01d98b..20440b43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4900,6 +4900,22 @@ dependencies = [ "zeroize", ] +[[package]] +name = "monero-serai-verify-chain" +version = "0.1.0" +dependencies = [ + "curve25519-dalek", + "hex", + "monero-rpc", + "monero-serai", + "monero-simple-request-rpc", + "rand_core", + "serde", + "serde_json", + "std-shims", + "tokio", +] + [[package]] name = "monero-simple-request-rpc" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 32da7712..aad93ca0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ members = [ "coins/monero/wallet/seed", "coins/monero/wallet/polyseed", "coins/monero/wallet/util", + "coins/monero/verify-chain", "message-queue", diff --git a/coins/monero/Cargo.toml b/coins/monero/Cargo.toml index d4e92109..08df9a35 100644 --- a/coins/monero/Cargo.toml +++ b/coins/monero/Cargo.toml @@ -50,5 +50,4 @@ std = [ compile-time-generators = ["curve25519-dalek/precomputed-tables", "monero-bulletproofs/compile-time-generators"] multisig = ["monero-clsag/multisig", "std"] -#binaries = ["tokio/rt-multi-thread", "tokio/macros", "http-rpc"] default = ["std", "compile-time-generators"] diff --git a/coins/monero/ringct/bulletproofs/src/original/mod.rs b/coins/monero/ringct/bulletproofs/src/original/mod.rs index 725f4e24..43fa7cfe 100644 --- a/coins/monero/ringct/bulletproofs/src/original/mod.rs +++ b/coins/monero/ringct/bulletproofs/src/original/mod.rs @@ -257,9 +257,11 @@ impl OriginalStruct { let res = OriginalStruct { A, S, T1, T2, tau_x, mu, L, R, a: a[0], b: b[0], t }; #[cfg(debug_assertions)] - let mut verifier = BulletproofsBatchVerifier::default(); - debug_assert!(res.verify(rng, &mut verifier, &commitments_points)); - debug_assert!(verifier.verify()); + { + let mut verifier = BulletproofsBatchVerifier::default(); + debug_assert!(res.verify(rng, &mut verifier, &commitments_points)); + debug_assert!(verifier.verify()); + } res } diff --git a/coins/monero/src/bin/reserialize_chain.rs b/coins/monero/src/bin/reserialize_chain.rs deleted file mode 100644 index d133ed9e..00000000 --- a/coins/monero/src/bin/reserialize_chain.rs +++ /dev/null @@ -1,321 +0,0 @@ -/* TODO -#[cfg(feature = "binaries")] -mod binaries { - pub(crate) use std::sync::Arc; - - pub(crate) use curve25519_dalek::{scalar::Scalar, edwards::EdwardsPoint}; - - pub(crate) use serde::Deserialize; - pub(crate) use serde_json::json; - - pub(crate) use monero_serai::{ - primitives::Commitment, - ringct::{RctPrunable, bulletproofs::BatchVerifier}, - transaction::{Input, Transaction}, - block::Block, - rpc::{RpcError, Rpc, SimpleRequestRpc}, - }; - - pub(crate) use monero_io::decompress_point; - - pub(crate) use tokio::task::JoinHandle; - - pub(crate) async fn check_block(rpc: Arc, block_i: usize) { - let hash = loop { - match rpc.get_block_hash(block_i).await { - Ok(hash) => break hash, - Err(RpcError::ConnectionError(e)) => { - println!("get_block_hash ConnectionError: {e}"); - continue; - } - Err(e) => panic!("couldn't get block {block_i}'s hash: {e:?}"), - } - }; - - // TODO: Grab the JSON to also check it was deserialized correctly - #[derive(Deserialize, Debug)] - struct BlockResponse { - blob: String, - } - let res: BlockResponse = loop { - match rpc.json_rpc_call("get_block", Some(json!({ "hash": hex::encode(hash) }))).await { - Ok(res) => break res, - Err(RpcError::ConnectionError(e)) => { - println!("get_block ConnectionError: {e}"); - continue; - } - Err(e) => panic!("couldn't get block {block_i} via block.hash(): {e:?}"), - } - }; - - let blob = hex::decode(res.blob).expect("node returned non-hex block"); - let block = Block::read(&mut blob.as_slice()) - .unwrap_or_else(|e| panic!("couldn't deserialize block {block_i}: {e}")); - assert_eq!(block.hash(), hash, "hash differs"); - assert_eq!(block.serialize(), blob, "serialization differs"); - - let txs_len = 1 + block.txs.len(); - - if !block.txs.is_empty() { - #[derive(Deserialize, Debug)] - struct TransactionResponse { - tx_hash: String, - as_hex: String, - } - #[derive(Deserialize, Debug)] - struct TransactionsResponse { - #[serde(default)] - missed_tx: Vec, - txs: Vec, - } - - let mut hashes_hex = block.txs.iter().map(hex::encode).collect::>(); - let mut all_txs = vec![]; - while !hashes_hex.is_empty() { - let txs: TransactionsResponse = loop { - match rpc - .rpc_call( - "get_transactions", - Some(json!({ - "txs_hashes": hashes_hex.drain(.. hashes_hex.len().min(100)).collect::>(), - })), - ) - .await - { - Ok(txs) => break txs, - Err(RpcError::ConnectionError(e)) => { - println!("get_transactions ConnectionError: {e}"); - continue; - } - Err(e) => panic!("couldn't call get_transactions: {e:?}"), - } - }; - assert!(txs.missed_tx.is_empty()); - all_txs.extend(txs.txs); - } - - let mut batch = BatchVerifier::new(); - for (tx_hash, tx_res) in block.txs.into_iter().zip(all_txs) { - assert_eq!( - tx_res.tx_hash, - hex::encode(tx_hash), - "node returned a transaction with different hash" - ); - - let tx = Transaction::read( - &mut hex::decode(&tx_res.as_hex).expect("node returned non-hex transaction").as_slice(), - ) - .expect("couldn't deserialize transaction"); - - assert_eq!( - hex::encode(tx.serialize()), - tx_res.as_hex, - "Transaction serialization was different" - ); - assert_eq!(tx.hash(), tx_hash, "Transaction hash was different"); - - if matches!(tx.proofs.prunable, RctPrunable::Null) { - assert_eq!(tx.prefix.version, 1); - assert!(!tx.signatures.is_empty()); - continue; - } - - let sig_hash = tx.signature_hash(); - // Verify all proofs we support proving for - // This is due to having debug_asserts calling verify within their proving, and CLSAG - // multisig explicitly calling verify as part of its signing process - // Accordingly, making sure our signature_hash algorithm is correct is great, and further - // making sure the verification functions are valid is appreciated - match tx.proofs.prunable { - RctPrunable::Null | - RctPrunable::AggregateMlsagBorromean { .. } | - RctPrunable::MlsagBorromean { .. } => {} - RctPrunable::MlsagBulletproofs { bulletproofs, .. } => { - assert!(bulletproofs.batch_verify( - &mut rand_core::OsRng, - &mut batch, - &tx.proofs.base.commitments - )); - } - RctPrunable::Clsag { bulletproofs, clsags, pseudo_outs } => { - assert!(bulletproofs.batch_verify( - &mut rand_core::OsRng, - &mut batch, - &tx.proofs.base.commitments - )); - - for (i, clsag) in clsags.into_iter().enumerate() { - let (amount, key_offsets, image) = match &tx.prefix.inputs[i] { - Input::Gen(_) => panic!("Input::Gen"), - Input::ToKey { amount, key_offsets, key_image } => (amount, key_offsets, key_image), - }; - - let mut running_sum = 0; - let mut actual_indexes = vec![]; - for offset in key_offsets { - running_sum += offset; - actual_indexes.push(running_sum); - } - - async fn get_outs( - rpc: &SimpleRequestRpc, - amount: u64, - indexes: &[u64], - ) -> Vec<[EdwardsPoint; 2]> { - #[derive(Deserialize, Debug)] - struct Out { - key: String, - mask: String, - } - - #[derive(Deserialize, Debug)] - struct Outs { - outs: Vec, - } - - let outs: Outs = loop { - match rpc - .rpc_call( - "get_outs", - Some(json!({ - "get_txid": true, - "outputs": indexes.iter().map(|o| json!({ - "amount": amount, - "index": o - })).collect::>() - })), - ) - .await - { - Ok(outs) => break outs, - Err(RpcError::ConnectionError(e)) => { - println!("get_outs ConnectionError: {e}"); - continue; - } - Err(e) => panic!("couldn't connect to RPC to get outs: {e:?}"), - } - }; - - let rpc_point = |point: &str| { - decompress_point( - hex::decode(point) - .expect("invalid hex for ring member") - .try_into() - .expect("invalid point len for ring member"), - ) - .expect("invalid point for ring member") - }; - - outs - .outs - .iter() - .map(|out| { - let mask = rpc_point(&out.mask); - if amount != 0 { - assert_eq!(mask, Commitment::new(Scalar::from(1u8), amount).calculate()); - } - [rpc_point(&out.key), mask] - }) - .collect() - } - - clsag - .verify( - &get_outs(&rpc, amount.unwrap_or(0), &actual_indexes).await, - image, - &pseudo_outs[i], - &sig_hash, - ) - .unwrap(); - } - } - } - } - assert!(batch.verify()); - } - - println!("Deserialized, hashed, and reserialized {block_i} with {txs_len} TXs"); - } -} - -#[cfg(feature = "binaries")] -#[tokio::main] -async fn main() { - use binaries::*; - - let args = std::env::args().collect::>(); - - // Read start block as the first arg - let mut block_i = args[1].parse::().expect("invalid start block"); - - // How many blocks to work on at once - let async_parallelism: usize = - args.get(2).unwrap_or(&"8".to_string()).parse::().expect("invalid parallelism argument"); - - // Read further args as RPC URLs - let default_nodes = vec![ - "http://xmr-node.cakewallet.com:18081".to_string(), - "https://node.sethforprivacy.com".to_string(), - ]; - let mut specified_nodes = vec![]; - { - let mut i = 0; - loop { - let Some(node) = args.get(3 + i) else { break }; - specified_nodes.push(node.clone()); - i += 1; - } - } - let nodes = if specified_nodes.is_empty() { default_nodes } else { specified_nodes }; - - let rpc = |url: String| async move { - SimpleRequestRpc::new(url.clone()) - .await - .unwrap_or_else(|_| panic!("couldn't create SimpleRequestRpc connected to {url}")) - }; - let main_rpc = rpc(nodes[0].clone()).await; - let mut rpcs = vec![]; - for i in 0 .. async_parallelism { - rpcs.push(Arc::new(rpc(nodes[i % nodes.len()].clone()).await)); - } - - let mut rpc_i = 0; - let mut handles: Vec> = vec![]; - let mut height = 0; - loop { - let new_height = main_rpc.get_height().await.expect("couldn't call get_height"); - if new_height == height { - break; - } - height = new_height; - - while block_i < height { - if handles.len() >= async_parallelism { - // Guarantee one handle is complete - handles.swap_remove(0).await.unwrap(); - - // Remove all of the finished handles - let mut i = 0; - while i < handles.len() { - if handles[i].is_finished() { - handles.swap_remove(i).await.unwrap(); - continue; - } - i += 1; - } - } - - handles.push(tokio::spawn(check_block(rpcs[rpc_i].clone(), block_i))); - rpc_i = (rpc_i + 1) % rpcs.len(); - block_i += 1; - } - } -} - -#[cfg(not(feature = "binaries"))] -fn main() { - panic!("To run binaries, please build with `--feature binaries`."); -} -*/ - -fn main() {} diff --git a/coins/monero/verify-chain/Cargo.toml b/coins/monero/verify-chain/Cargo.toml new file mode 100644 index 00000000..d6a19a9f --- /dev/null +++ b/coins/monero/verify-chain/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "monero-serai-verify-chain" +version = "0.1.0" +description = "A binary to deserialize and verify the Monero blockchain" +license = "MIT" +repository = "https://github.com/serai-dex/serai/tree/develop/coins/monero/verify-chain" +authors = ["Luke Parker "] +edition = "2021" +rust-version = "1.79" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[lints] +workspace = true + +[dependencies] +std-shims = { path = "../../../common/std-shims", version = "^0.1.1", default-features = false } + +rand_core = { version = "0.6", default-features = false, features = ["std"] } + +curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize"] } + +hex = { version = "0.4", default-features = false, features = ["std"] } +serde = { version = "1", default-features = false, features = ["derive", "alloc", "std"] } +serde_json = { version = "1", default-features = false, features = ["alloc", "std"] } + +monero-serai = { path = "..", default-features = false, features = ["std", "compile-time-generators"] } +monero-rpc = { path = "../rpc", default-features = false, features = ["std"] } +monero-simple-request-rpc = { path = "../rpc/simple-request", default-features = false } + +tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "macros"] } diff --git a/coins/monero/verify-chain/LICENSE b/coins/monero/verify-chain/LICENSE new file mode 100644 index 00000000..91d893c1 --- /dev/null +++ b/coins/monero/verify-chain/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022-2024 Luke Parker + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/coins/monero/verify-chain/README.md b/coins/monero/verify-chain/README.md new file mode 100644 index 00000000..4348b2c1 --- /dev/null +++ b/coins/monero/verify-chain/README.md @@ -0,0 +1,7 @@ +# monero-serai Verify Chain + +A binary to deserialize and verify the Monero blockchain. + +This is not complete. This is not intended to be complete. This is intended to +test monero-serai against actual blockchain data. Do not use this as an +inflation checker. diff --git a/coins/monero/verify-chain/src/main.rs b/coins/monero/verify-chain/src/main.rs new file mode 100644 index 00000000..41a212c4 --- /dev/null +++ b/coins/monero/verify-chain/src/main.rs @@ -0,0 +1,316 @@ +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![doc = include_str!("../README.md")] +#![deny(missing_docs)] + +use curve25519_dalek::{scalar::Scalar, edwards::EdwardsPoint}; + +use serde::Deserialize; +use serde_json::json; + +use monero_serai::{ + io::decompress_point, + primitives::Commitment, + ringct::{RctPrunable, bulletproofs::BatchVerifier}, + transaction::{Input, Transaction}, + block::Block, +}; + +use monero_rpc::{RpcError, Rpc}; +use monero_simple_request_rpc::SimpleRequestRpc; + +use tokio::task::JoinHandle; + +async fn check_block(rpc: impl Rpc, block_i: usize) { + let hash = loop { + match rpc.get_block_hash(block_i).await { + Ok(hash) => break hash, + Err(RpcError::ConnectionError(e)) => { + println!("get_block_hash ConnectionError: {e}"); + continue; + } + Err(e) => panic!("couldn't get block {block_i}'s hash: {e:?}"), + } + }; + + // TODO: Grab the JSON to also check it was deserialized correctly + #[derive(Deserialize, Debug)] + struct BlockResponse { + blob: String, + } + let res: BlockResponse = loop { + match rpc.json_rpc_call("get_block", Some(json!({ "hash": hex::encode(hash) }))).await { + Ok(res) => break res, + Err(RpcError::ConnectionError(e)) => { + println!("get_block ConnectionError: {e}"); + continue; + } + Err(e) => panic!("couldn't get block {block_i} via block.hash(): {e:?}"), + } + }; + + let blob = hex::decode(res.blob).expect("node returned non-hex block"); + let block = Block::read(&mut blob.as_slice()) + .unwrap_or_else(|e| panic!("couldn't deserialize block {block_i}: {e}")); + assert_eq!(block.hash(), hash, "hash differs"); + assert_eq!(block.serialize(), blob, "serialization differs"); + + let txs_len = 1 + block.transactions.len(); + + if !block.transactions.is_empty() { + #[derive(Deserialize, Debug)] + struct TransactionResponse { + tx_hash: String, + as_hex: String, + } + #[derive(Deserialize, Debug)] + struct TransactionsResponse { + #[serde(default)] + missed_tx: Vec, + txs: Vec, + } + + let mut hashes_hex = block.transactions.iter().map(hex::encode).collect::>(); + let mut all_txs = vec![]; + while !hashes_hex.is_empty() { + let txs: TransactionsResponse = loop { + match rpc + .rpc_call( + "get_transactions", + Some(json!({ + "txs_hashes": hashes_hex.drain(.. hashes_hex.len().min(100)).collect::>(), + })), + ) + .await + { + Ok(txs) => break txs, + Err(RpcError::ConnectionError(e)) => { + println!("get_transactions ConnectionError: {e}"); + continue; + } + Err(e) => panic!("couldn't call get_transactions: {e:?}"), + } + }; + assert!(txs.missed_tx.is_empty()); + all_txs.extend(txs.txs); + } + + let mut batch = BatchVerifier::new(); + for (tx_hash, tx_res) in block.transactions.into_iter().zip(all_txs) { + assert_eq!( + tx_res.tx_hash, + hex::encode(tx_hash), + "node returned a transaction with different hash" + ); + + let tx = Transaction::read( + &mut hex::decode(&tx_res.as_hex).expect("node returned non-hex transaction").as_slice(), + ) + .expect("couldn't deserialize transaction"); + + assert_eq!( + hex::encode(tx.serialize()), + tx_res.as_hex, + "Transaction serialization was different" + ); + assert_eq!(tx.hash(), tx_hash, "Transaction hash was different"); + + match tx { + Transaction::V1 { prefix: _, signatures } => { + assert!(!signatures.is_empty()); + continue; + } + Transaction::V2 { prefix: _, proofs: None } => { + panic!("proofs were empty in non-miner v2 transaction"); + } + Transaction::V2 { ref prefix, proofs: Some(ref proofs) } => { + let sig_hash = tx.signature_hash().expect("no signature hash for TX with proofs"); + // Verify all proofs we support proving for + // This is due to having debug_asserts calling verify within their proving, and CLSAG + // multisig explicitly calling verify as part of its signing process + // Accordingly, making sure our signature_hash algorithm is correct is great, and further + // making sure the verification functions are valid is appreciated + match &proofs.prunable { + RctPrunable::AggregateMlsagBorromean { .. } | RctPrunable::MlsagBorromean { .. } => {} + RctPrunable::MlsagBulletproofs { bulletproof, .. } | + RctPrunable::MlsagBulletproofsCompactAmount { bulletproof, .. } => { + assert!(bulletproof.batch_verify( + &mut rand_core::OsRng, + &mut batch, + &proofs.base.commitments + )); + } + RctPrunable::Clsag { bulletproof, clsags, pseudo_outs } => { + assert!(bulletproof.batch_verify( + &mut rand_core::OsRng, + &mut batch, + &proofs.base.commitments + )); + + for (i, clsag) in clsags.iter().enumerate() { + let (amount, key_offsets, image) = match &prefix.inputs[i] { + Input::Gen(_) => panic!("Input::Gen"), + Input::ToKey { amount, key_offsets, key_image } => { + (amount, key_offsets, key_image) + } + }; + + let mut running_sum = 0; + let mut actual_indexes = vec![]; + for offset in key_offsets { + running_sum += offset; + actual_indexes.push(running_sum); + } + + async fn get_outs( + rpc: &impl Rpc, + amount: u64, + indexes: &[u64], + ) -> Vec<[EdwardsPoint; 2]> { + #[derive(Deserialize, Debug)] + struct Out { + key: String, + mask: String, + } + + #[derive(Deserialize, Debug)] + struct Outs { + outs: Vec, + } + + let outs: Outs = loop { + match rpc + .rpc_call( + "get_outs", + Some(json!({ + "get_txid": true, + "outputs": indexes.iter().map(|o| json!({ + "amount": amount, + "index": o + })).collect::>() + })), + ) + .await + { + Ok(outs) => break outs, + Err(RpcError::ConnectionError(e)) => { + println!("get_outs ConnectionError: {e}"); + continue; + } + Err(e) => panic!("couldn't connect to RPC to get outs: {e:?}"), + } + }; + + let rpc_point = |point: &str| { + decompress_point( + hex::decode(point) + .expect("invalid hex for ring member") + .try_into() + .expect("invalid point len for ring member"), + ) + .expect("invalid point for ring member") + }; + + outs + .outs + .iter() + .map(|out| { + let mask = rpc_point(&out.mask); + if amount != 0 { + assert_eq!(mask, Commitment::new(Scalar::from(1u8), amount).calculate()); + } + [rpc_point(&out.key), mask] + }) + .collect() + } + + clsag + .verify( + &get_outs(&rpc, amount.unwrap_or(0), &actual_indexes).await, + image, + &pseudo_outs[i], + &sig_hash, + ) + .unwrap(); + } + } + } + } + } + } + assert!(batch.verify()); + } + + println!("Deserialized, hashed, and reserialized {block_i} with {txs_len} TXs"); +} + +#[tokio::main] +async fn main() { + let args = std::env::args().collect::>(); + + // Read start block as the first arg + let mut block_i = args.get(1).expect("no start block specified").parse::().expect("invalid start block"); + + // How many blocks to work on at once + let async_parallelism: usize = + args.get(2).unwrap_or(&"8".to_string()).parse::().expect("invalid parallelism argument"); + + // Read further args as RPC URLs + let default_nodes = vec![ + // "http://xmr-node.cakewallet.com:18081".to_string(), + "http://node.tools.rino.io:18081".to_string(), + // "https://node.sethforprivacy.com".to_string(), + ]; + let mut specified_nodes = vec![]; + { + let mut i = 0; + loop { + let Some(node) = args.get(3 + i) else { break }; + specified_nodes.push(node.clone()); + i += 1; + } + } + let nodes = if specified_nodes.is_empty() { default_nodes } else { specified_nodes }; + + let rpc = |url: String| async move { + SimpleRequestRpc::new(url.clone()) + .await + .unwrap_or_else(|_| panic!("couldn't create SimpleRequestRpc connected to {url}")) + }; + let main_rpc = rpc(nodes[0].clone()).await; + let mut rpcs = vec![]; + for i in 0 .. async_parallelism { + rpcs.push(rpc(nodes[i % nodes.len()].clone()).await); + } + + let mut rpc_i = 0; + let mut handles: Vec> = vec![]; + let mut height = 0; + loop { + let new_height = main_rpc.get_height().await.expect("couldn't call get_height"); + if new_height == height { + break; + } + height = new_height; + + while block_i < height { + if handles.len() >= async_parallelism { + // Guarantee one handle is complete + handles.swap_remove(0).await.unwrap(); + + // Remove all of the finished handles + let mut i = 0; + while i < handles.len() { + if handles[i].is_finished() { + handles.swap_remove(i).await.unwrap(); + continue; + } + i += 1; + } + } + + handles.push(tokio::spawn(check_block(rpcs[rpc_i].clone(), block_i))); + rpc_i = (rpc_i + 1) % rpcs.len(); + block_i += 1; + } + } +} diff --git a/common/request/src/lib.rs b/common/request/src/lib.rs index 60e51019..df9689e1 100644 --- a/common/request/src/lib.rs +++ b/common/request/src/lib.rs @@ -58,6 +58,8 @@ impl Client { res.set_nodelay(true); res.set_reuse_address(true); #[cfg(feature = "tls")] + res.enforce_http(false); + #[cfg(feature = "tls")] let res = HttpsConnectorBuilder::new() .with_native_roots() .expect("couldn't fetch system's SSL roots")