├── synth_node_bin ├── .gitignore ├── Cargo.toml ├── scripts │ └── run_sn.sh └── src │ ├── action │ ├── quick_connect_and_then_clean_disconnect.rs │ ├── quick_connect_with_improper_disconnect.rs │ ├── send_get_addr_and_forever_sleep.rs │ ├── constantly_ask_for_random_blocks.rs │ ├── rt_s1_collector.rs │ ├── mod.rs │ ├── rt_s1_tainter.rs │ └── advanced_sn_for_s001.rs │ └── main.rs ├── .gitignore ├── fuzz ├── .gitignore ├── fuzz_targets │ └── decoder.rs └── Cargo.toml ├── logo.png ├── src ├── tests │ ├── conformance │ │ ├── invalid_message │ │ │ ├── mod.rs │ │ │ └── reject.rs │ │ ├── mod.rs │ │ ├── handshake │ │ │ ├── mod.rs │ │ │ ├── complete_handshake.rs │ │ │ └── reject_version.rs │ │ ├── query │ │ │ ├── mod.rs │ │ │ └── get_data.rs │ │ ├── unsolicited_response.rs │ │ └── peering.rs │ ├── performance │ │ └── mod.rs │ ├── mod.rs │ ├── resistance │ │ └── mod.rs │ └── idle_node_in_the_background.rs ├── lib.rs ├── protocol │ ├── mod.rs │ ├── payload │ │ ├── codec.rs │ │ ├── version.rs │ │ ├── reject.rs │ │ ├── addr.rs │ │ ├── inv.rs │ │ ├── filter.rs │ │ └── mod.rs │ └── message │ │ └── constants.rs ├── setup │ └── mod.rs ├── tools │ ├── mod.rs │ ├── crawler │ │ ├── rpc.rs │ │ ├── README.md │ │ ├── network.rs │ │ └── metrics.rs │ └── message_filter.rs └── vectors │ ├── block-test-0-000-003.txt │ ├── block-test-0-000-004.txt │ ├── block-test-0-000-005.txt │ ├── block-test-0-000-006.txt │ ├── block-test-0-000-007.txt │ ├── block-test-0-000-008.txt │ ├── block-test-0-000-009.txt │ ├── block-test-0-000-010.txt │ ├── block-test-0-000-001.txt │ ├── block-test-0-000-002.txt │ ├── block-test-0-280-000.txt │ ├── block-test-0-584-000.txt │ ├── block-test-0-903-800.txt │ ├── block-test-1-599-200.txt │ ├── block-test-1-599-201.txt │ ├── block-test-0-000-000.txt │ ├── block-test-1-028-500.txt │ ├── block-test-1-599-199.txt │ ├── mod.rs │ └── block-test-0-207-500.txt ├── .rustfmt.toml ├── .github └── workflows │ ├── check-and-lint.yml │ ├── build-zcashd.yml │ ├── zcashd-nightly.yml │ ├── crawler.yml │ ├── zebra.yml │ └── README.md ├── LICENCE-MIT ├── flake.nix ├── Cargo.toml └── flake.lock /synth_node_bin/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | 3 | .envrc 4 | .direnv/ 5 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | corpus 3 | artifacts 4 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/runziggurat/zcash/HEAD/logo.png -------------------------------------------------------------------------------- /src/tests/conformance/invalid_message/mod.rs: -------------------------------------------------------------------------------- 1 | mod disconnect; 2 | mod reject; 3 | -------------------------------------------------------------------------------- /src/tests/performance/mod.rs: -------------------------------------------------------------------------------- 1 | mod connections; 2 | mod getdata_blocks; 3 | mod ping_pong; 4 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod conformance; 2 | mod idle_node_in_the_background; 3 | mod performance; 4 | mod resistance; 5 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod protocol; 2 | pub mod setup; 3 | pub mod tools; 4 | pub mod vectors; 5 | 6 | #[cfg(test)] 7 | pub mod tests; 8 | -------------------------------------------------------------------------------- /src/tests/conformance/mod.rs: -------------------------------------------------------------------------------- 1 | mod handshake; 2 | mod invalid_message; 3 | mod peering; 4 | mod query; 5 | mod unsolicited_response; 6 | -------------------------------------------------------------------------------- /src/protocol/mod.rs: -------------------------------------------------------------------------------- 1 | //! An implementation of the Zcash network protocol types and messages. 2 | 3 | pub mod message; 4 | pub mod payload; 5 | -------------------------------------------------------------------------------- /src/setup/mod.rs: -------------------------------------------------------------------------------- 1 | //! Utilities for setting up and tearing down node instances (`zcashd` or `zebra`). 2 | 3 | mod config; 4 | pub mod node; 5 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | 2 | # Stable configurations 3 | edition = "2018" 4 | 5 | # Nightly configurations 6 | imports_granularity = "Crate" 7 | group_imports = "StdExternalCrate" 8 | -------------------------------------------------------------------------------- /src/tests/conformance/handshake/mod.rs: -------------------------------------------------------------------------------- 1 | mod complete_handshake; 2 | mod ignore_message_inplace_of_verack; 3 | mod ignore_message_inplace_of_version; 4 | mod reject_version; 5 | -------------------------------------------------------------------------------- /src/tests/resistance/mod.rs: -------------------------------------------------------------------------------- 1 | mod corrupt_message; 2 | mod random_bytes; 3 | mod stress_test; 4 | mod zeroes; 5 | 6 | use std::time::Duration; 7 | 8 | const ITERATIONS: usize = 50; 9 | const DISCONNECT_TIMEOUT: Duration = Duration::from_secs(5); 10 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/decoder.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | 4 | use tokio_util::codec::Decoder; 5 | use ziggurat::tools::synthetic_node::MessageCodec; 6 | 7 | fuzz_target!(|data: &[u8]| { 8 | let mut codec = MessageCodec::default(); 9 | let _ = codec.decode(&mut data.into()); 10 | }); 11 | -------------------------------------------------------------------------------- /synth_node_bin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "synth_node_bin" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | anyhow = "1.0" 8 | async-trait = "0.1" 9 | clap = { version = "4.2", features = ["derive"] } 10 | pea2pea = "0.46" 11 | rand = "0.8" 12 | serde = { version = "1.0", features = ["derive"] } 13 | serde_json = "1.0" 14 | tokio = { version = "1", features = ["time"] } 15 | tracing-subscriber = "0.3" 16 | ziggurat-zcash = { path = "../" } 17 | 18 | [dependencies.tracing] 19 | version = "0.1" 20 | default-features = false 21 | -------------------------------------------------------------------------------- /synth_node_bin/scripts/run_sn.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # A quick and a simple script to be used for any debugging or testing. 4 | 5 | NUM_NODES=$1 6 | ADDR=$2 7 | ACTION=$3 8 | 9 | ## Possible actions: 10 | # SendGetAddrAndForeverSleep / AdvancedSnForS001 / QuickConnectAndThenCleanDisconnect / 11 | # QuickConnectWithImproperDisconnect / ConstantlyAskForRandomBlocks 12 | 13 | TRACE_LOG=info 14 | BIN=../target/debug/synth_node_bin 15 | 16 | for i in $(seq 1 $NUM_NODES); 17 | do 18 | RUST_LOG=$TRACE_LOG $BIN -t -s -n $ADDR -a $ACTION & 19 | done 20 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ziggurat-fuzz" 3 | version = "0.0.0" 4 | authors = ["Automatically generated"] 5 | publish = false 6 | edition = "2021" 7 | 8 | [package.metadata] 9 | cargo-fuzz = true 10 | 11 | [dependencies] 12 | libfuzzer-sys = "0.4" 13 | tokio-util = { version = "0.7", features = ["codec"] } 14 | 15 | [dependencies.ziggurat] 16 | path = ".." 17 | 18 | # Prevent this from interfering with workspaces 19 | [workspace] 20 | members = ["."] 21 | 22 | [[bin]] 23 | name = "decoder" 24 | path = "fuzz_targets/decoder.rs" 25 | test = false 26 | doc = false 27 | -------------------------------------------------------------------------------- /.github/workflows/check-and-lint.yml: -------------------------------------------------------------------------------- 1 | name: basics 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | core: 11 | uses: runziggurat/ziggurat-core/.github/workflows/check-and-lint.yml@main 12 | 13 | extra: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | steps: 19 | - name: test-ignored 20 | run: ci-test-ignored 21 | - name: check-crawler 22 | run: ci-check-crawler 23 | steps: 24 | - uses: actions/checkout@v3 25 | - uses: Swatinem/rust-cache@v2 26 | - uses: cachix/install-nix-action@v19 27 | with: 28 | github_access_token: ${{ secrets.GITHUB_TOKEN }} 29 | - name: ${{ matrix.steps.name }} 30 | run: | 31 | nix develop --command \ 32 | ${{ matrix.steps.run }} 33 | -------------------------------------------------------------------------------- /.github/workflows/build-zcashd.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_call: 3 | outputs: 4 | commit-hash: 5 | value: ${{ jobs.build-zcashd.outputs.commit-hash }} 6 | 7 | jobs: 8 | build-zcashd: 9 | runs-on: ubuntu-latest 10 | outputs: 11 | commit-hash: ${{ steps.commit-hash.outputs.commit-hash }} 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | repository: zcash/zcash 16 | - name: Build zcashd 17 | run: ./zcutil/build.sh -j$(nproc) 18 | - name: Grab short commit hash and store in env 19 | id: commit-hash 20 | run: echo commit-hash=$(git log -n 1 --pretty=format:"%h") >> "$GITHUB_OUTPUT" 21 | - uses: actions/upload-artifact@v3 22 | with: 23 | name: zcashd-fetch-params 24 | path: ./zcutil/fetch-params.sh 25 | - uses: actions/upload-artifact@v3 26 | with: 27 | name: node-executable 28 | path: ./src/zcashd 29 | -------------------------------------------------------------------------------- /src/protocol/payload/codec.rs: -------------------------------------------------------------------------------- 1 | //! Traits for encoding and decoding network message types. 2 | 3 | use std::io; 4 | 5 | use bytes::{Buf, BufMut}; 6 | 7 | use super::VarInt; 8 | 9 | /// A trait for unifying encoding and decoding. 10 | pub trait Codec { 11 | /// Encodes the payload into the supplied buffer. 12 | fn encode(&self, buffer: &mut B) -> io::Result<()>; 13 | 14 | /// Decodes the bytes and returns the payload. 15 | fn decode(bytes: &mut B) -> io::Result 16 | where 17 | Self: Sized; 18 | } 19 | 20 | impl Codec for Vec { 21 | fn encode(&self, buffer: &mut B) -> io::Result<()> { 22 | VarInt(self.len()).encode(buffer)?; 23 | for element in self { 24 | element.encode(buffer)?; 25 | } 26 | 27 | Ok(()) 28 | } 29 | 30 | fn decode(bytes: &mut B) -> io::Result 31 | where 32 | Self: Sized, 33 | { 34 | let length = *VarInt::decode(bytes)?; 35 | (0..length).map(|_| T::decode(bytes)).collect() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENCE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Equilibrium OY 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 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Nix development environment for Ziggurat"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | ziggurat-core.url = "github:runziggurat/ziggurat-core"; 8 | }; 9 | 10 | outputs = { nixpkgs, flake-utils, ziggurat-core, ... }: 11 | flake-utils.lib.eachDefaultSystem (system: 12 | let 13 | pkgs = import nixpkgs { inherit system; }; 14 | 15 | # You can define additional CI scripts here, e.g. 16 | # scripts = { 17 | # test-ignored = "cargo test --ignored"; 18 | # } // ziggurat-core.scripts; 19 | scripts = { 20 | test-ignored = "cargo test -- --test-threads=1 --ignored --skip dev"; 21 | check-crawler = "cargo check --features=crawler"; 22 | } // ziggurat-core.scripts; 23 | in 24 | { 25 | devShells.default = pkgs.mkShell { 26 | # Enter additional build dependencies here. 27 | buildInputs = [ ] 28 | # Contains all the necessities. 29 | ++ ziggurat-core.buildInputs.${system} 30 | ++ (ziggurat-core.lib.${system}.mkCiScripts scripts); 31 | }; 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /synth_node_bin/src/action/quick_connect_and_then_clean_disconnect.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use anyhow::Result; 4 | use ziggurat_zcash::tools::synthetic_node::SyntheticNode; 5 | 6 | use super::{ActionCfg, SynthNodeAction}; 7 | 8 | pub(super) struct Action; 9 | 10 | pub(super) fn action() -> Box { 11 | Box::new(Action {}) 12 | } 13 | 14 | #[async_trait::async_trait] 15 | impl SynthNodeAction for Action { 16 | fn info(&self) -> &str { 17 | "a synth node which only connects and immediately disconnects properly" 18 | } 19 | 20 | fn config(&self) -> ActionCfg { 21 | ActionCfg::default() 22 | } 23 | 24 | #[allow(unused_variables)] 25 | async fn run(&self, synth_node: &mut SyntheticNode, addr: Option) -> Result<()> { 26 | let addr = if let Some(addr) = addr { 27 | addr 28 | } else { 29 | anyhow::bail!("address not provided"); 30 | }; 31 | 32 | println!("Synthetic node connected to {addr}!"); 33 | 34 | // An optional short sleep. 35 | //tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; 36 | 37 | println!("Synthetic node disconnecting!"); 38 | Ok(()) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/tools/mod.rs: -------------------------------------------------------------------------------- 1 | //! Utilities for network testing. 2 | 3 | pub mod fuzzing; 4 | pub mod message_filter; 5 | pub mod synthetic_node; 6 | 7 | use std::time::Duration; 8 | 9 | /// Default timeout for connection operations in seconds. 10 | /// TODO: move to config file. 11 | pub const LONG_TIMEOUT: Duration = Duration::from_secs(30); 12 | /// Default timeout for response-specific reads in seconds. 13 | pub const RECV_TIMEOUT: Duration = Duration::from_millis(300); 14 | 15 | /// Waits until an expression is true or times out. 16 | /// 17 | /// Uses polling to cut down on time otherwise used by calling `sleep` in tests. 18 | #[macro_export] 19 | macro_rules! wait_until { 20 | ($wait_limit: expr, $condition: expr $(, $sleep_duration: expr)?) => { 21 | let now = std::time::Instant::now(); 22 | loop { 23 | if $condition { 24 | break; 25 | } 26 | 27 | // Default timeout. 28 | let sleep_duration = std::time::Duration::from_millis(10); 29 | // Set if present in args. 30 | $(let sleep_duration = $sleep_duration;)? 31 | tokio::time::sleep(sleep_duration).await; 32 | if now.elapsed() > $wait_limit { 33 | panic!("timed out!"); 34 | } 35 | } 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /synth_node_bin/src/action/quick_connect_with_improper_disconnect.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use anyhow::Result; 4 | use ziggurat_zcash::tools::synthetic_node::SyntheticNode; 5 | 6 | use super::{ActionCfg, SynthNodeAction}; 7 | 8 | pub(super) struct Action; 9 | 10 | pub(super) fn action() -> Box { 11 | Box::new(Action {}) 12 | } 13 | 14 | #[async_trait::async_trait] 15 | impl SynthNodeAction for Action { 16 | fn info(&self) -> &str { 17 | "a synth node which only connects and immediately disconnects improperly" 18 | } 19 | 20 | fn config(&self) -> ActionCfg { 21 | ActionCfg { 22 | allow_proper_shutdown: false, 23 | ..Default::default() 24 | } 25 | } 26 | 27 | #[allow(unused_variables)] 28 | async fn run(&self, synth_node: &mut SyntheticNode, addr: Option) -> Result<()> { 29 | let addr = if let Some(addr) = addr { 30 | addr 31 | } else { 32 | anyhow::bail!("address not provided"); 33 | }; 34 | 35 | println!("Synthetic node connected to {addr}!"); 36 | 37 | // An optional short sleep. 38 | //tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; 39 | 40 | println!("Synthetic node disconnecting!"); 41 | Ok(()) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /synth_node_bin/src/action/send_get_addr_and_forever_sleep.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use anyhow::Result; 4 | use tokio::time::{sleep, Duration}; 5 | use ziggurat_zcash::{protocol::message::Message, tools::synthetic_node::SyntheticNode}; 6 | 7 | use super::{ActionCfg, SynthNodeAction}; 8 | 9 | pub(super) struct Action; 10 | 11 | pub(super) fn action() -> Box { 12 | Box::new(Action {}) 13 | } 14 | 15 | #[async_trait::async_trait] 16 | impl SynthNodeAction for Action { 17 | fn info(&self) -> &str { 18 | "request an GetAddr and then sleeps forever" 19 | } 20 | 21 | fn config(&self) -> ActionCfg { 22 | ActionCfg::default() 23 | } 24 | 25 | #[allow(unused_variables)] 26 | async fn run(&self, synth_node: &mut SyntheticNode, addr: Option) -> Result<()> { 27 | println!("Synthetic node performs an action."); 28 | 29 | let addr = if let Some(addr) = addr { 30 | addr 31 | } else { 32 | anyhow::bail!("address not provided"); 33 | }; 34 | 35 | // Custom code goes here, example: 36 | sleep(Duration::from_millis(5000)).await; 37 | 38 | let msg = Message::GetAddr; 39 | tracing::info!("unicast {msg:?}\n"); 40 | if synth_node.unicast(addr, msg.clone()).is_err() { 41 | tracing::warn!("failed to send {msg:?}\n"); 42 | anyhow::bail!("connection closed"); 43 | } 44 | 45 | loop { 46 | let (_, msg) = synth_node.try_recv_message().await?; 47 | tracing::info!("message received: {msg:?}"); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/tools/crawler/rpc.rs: -------------------------------------------------------------------------------- 1 | use std::{net::SocketAddr, sync::Arc}; 2 | 3 | use jsonrpsee::server::{RpcModule, ServerBuilder, ServerHandle}; 4 | use parking_lot::Mutex; 5 | use tracing::debug; 6 | use ziggurat_core_crawler::summary::NetworkSummary; 7 | 8 | pub struct RpcContext(Arc>); 9 | 10 | /// Allow JSON-RPC response size to be up to 200MB 11 | pub const MAX_RESPONSE_SIZE: u32 = 200_000_000; 12 | 13 | impl RpcContext { 14 | /// Creates a new RpcContext. 15 | pub fn new(known_network: Arc>) -> RpcContext { 16 | RpcContext(known_network) 17 | } 18 | } 19 | 20 | impl std::ops::Deref for RpcContext { 21 | type Target = Mutex; 22 | 23 | fn deref(&self) -> &Self::Target { 24 | &self.0 25 | } 26 | } 27 | 28 | pub async fn initialize_rpc_server(rpc_addr: SocketAddr, rpc_context: RpcContext) -> ServerHandle { 29 | let server = ServerBuilder::default() 30 | .max_response_body_size(MAX_RESPONSE_SIZE) 31 | .build(rpc_addr) 32 | .await 33 | .unwrap(); 34 | let module = create_rpc_module(rpc_context); 35 | 36 | debug!("Starting RPC server at {:?}", server.local_addr().unwrap()); 37 | let server_handle = server.start(module).unwrap(); 38 | 39 | debug!("RPC server was successfully started"); 40 | server_handle 41 | } 42 | 43 | fn create_rpc_module(rpc_context: RpcContext) -> RpcModule { 44 | let mut module = RpcModule::new(rpc_context); 45 | 46 | module 47 | .register_method("getmetrics", |_, rpc_context| { 48 | Ok(rpc_context.lock().clone()) 49 | }) 50 | .unwrap(); 51 | 52 | module 53 | } 54 | -------------------------------------------------------------------------------- /.github/workflows/zcashd-nightly.yml: -------------------------------------------------------------------------------- 1 | name: zcashd-nightly 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 6 * * *' # Every day at 6:00 AM UTC. 7 | 8 | jobs: 9 | build-zcashd: 10 | uses: runziggurat/zcash/.github/workflows/build-zcashd.yml@main 11 | 12 | build-ziggurat: 13 | uses: runziggurat/ziggurat-core/.github/workflows/build-ziggurat.yml@main 14 | 15 | run-test-suite: 16 | runs-on: ubuntu-latest 17 | needs: [ build-zcashd, build-ziggurat ] 18 | steps: 19 | - uses: actions/download-artifact@v3 20 | with: 21 | name: node-executable 22 | path: ./ 23 | - run: chmod +x zcashd 24 | - uses: actions/download-artifact@v3 25 | with: 26 | name: zcashd-fetch-params 27 | path: ./zcash 28 | - name: Fetch zcashd params 29 | run: | 30 | chmod +x zcash/fetch-params.sh 31 | ./zcash/fetch-params.sh 32 | - name: Create Ziggurat config file 33 | run: | 34 | mkdir ~/.ziggurat/ 35 | echo 'kind = "zcashd"' > ~/.ziggurat/config.toml 36 | echo 'path = "/home/runner/work/zcash/zcash"' >> ~/.ziggurat/config.toml 37 | echo 'start_command = "./zcashd -debug=1 -printtoconsole -logips=1 -dnsseed=0 -dns=0 -listenonion=0"' >> ~/.ziggurat/config.toml 38 | - name: Run Ziggurat test suite 39 | uses: runziggurat/ziggurat-core@main 40 | with: 41 | node-name: zcashd 42 | commit-hash: ${{ needs.build-zcashd.outputs.commit-hash }} 43 | 44 | process-results: 45 | needs: [ run-test-suite ] 46 | uses: runziggurat/ziggurat-core/.github/workflows/process-results.yml@main 47 | with: 48 | name: zcashd 49 | repository: zcash 50 | secrets: 51 | gcp_credentials: ${{ secrets.GCP_CREDENTIALS }} 52 | -------------------------------------------------------------------------------- /.github/workflows/crawler.yml: -------------------------------------------------------------------------------- 1 | name: crawler 2 | 3 | on: 4 | workflow_dispatch: 5 | # schedule: 6 | # - cron: '0 6 * * *' # Every day at 6:00 AM UTC. 7 | 8 | jobs: 9 | crawl-network: 10 | runs-on: self-hosted 11 | steps: 12 | - uses: actions/checkout@v3 13 | - name: Begin crawling 14 | run: | 15 | nix develop --command \ 16 | cargo run --release --features crawler --bin crawler -- --seed-addrs "dnsseed.z.cash" "dnsseed.str4d.xyz" "mainnet.seeder.zfnd.org" "mainnet.is.yolo.money" --rpc-addr 127.0.0.1:54321 & 17 | # After 30 min, query rpc and send SIGINT. 18 | sleep 30m 19 | curl --data-binary '{"jsonrpc": "2.0", "id":0, "method": "getmetrics", "params": [] }' -H 'content-type: application/json' http://127.0.0.1:54321/ > latest.json 20 | kill -2 $(pidof crawler) 21 | - name: Check for error 22 | run: | 23 | # If the result contains any error, fail workflow 24 | if grep "error" latest.json; then 25 | echo "Aborting. Crawler results contained an error" 26 | exit 1 27 | fi 28 | cat latest.json 29 | - uses: actions/upload-artifact@v3 30 | with: 31 | name: latest-result 32 | path: latest.json 33 | 34 | call-build-viz-state-workflow: 35 | needs: [ crawl-network ] 36 | uses: runziggurat/ziggurat-core/.github/workflows/build-viz-state.yml@main 37 | with: 38 | filter: Zcash 39 | 40 | call-process-results-workflow: 41 | needs: [ call-build-viz-state-workflow ] 42 | uses: runziggurat/ziggurat-core/.github/workflows/process-results.yml@main 43 | with: 44 | name: crawler 45 | extension: json 46 | repository: zcash 47 | secrets: 48 | gcp_credentials: ${{ secrets.GCP_CREDENTIALS }} 49 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ziggurat-zcash" 3 | version = "0.1.0" 4 | authors = ["Niklas Long , Mirko von Leipzig ", "Ziggurat contributors"] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | assert_matches = "1.5" 9 | async-trait = "0.1" 10 | bytes = "1" 11 | chrono = "0.4" 12 | dns-lookup = "2.0" 13 | hex = "0.4" 14 | home = "0.5" 15 | lazy_static = "1.4" 16 | metrics = "0.21" 17 | metrics-util = "0.15" 18 | parking_lot = "0.12" 19 | pea2pea = "0.46" 20 | rand = "0.8" 21 | rand_chacha = "0.3" 22 | regex = "1" 23 | sha2 = "0.10" 24 | spectre = { git = "https://github.com/niklaslong/spectre", rev = "9a0664f" } 25 | tabled = "0.10" 26 | time = "0.3" 27 | toml = "0.7" 28 | ziggurat-core-crawler = { git = "https://github.com/runziggurat/ziggurat-core", rev = "33ef131" } 29 | ziggurat-core-metrics = { git = "https://github.com/runziggurat/ziggurat-core", rev = "33ef131" } 30 | ziggurat-core-utils = { git = "https://github.com/runziggurat/ziggurat-core", rev = "33ef131" } 31 | 32 | [dependencies.clap] 33 | version = "4.2" 34 | features = ["derive"] 35 | optional = true 36 | 37 | [dependencies.futures-util] 38 | version = "0.3" 39 | features = ["sink"] 40 | 41 | [dependencies.jsonrpsee] 42 | version = "0.16" 43 | features = ["server"] 44 | optional = true 45 | 46 | [dependencies.serde] 47 | version = "1" 48 | features = ["derive"] 49 | 50 | [dependencies.tokio] 51 | version = "1" 52 | features = ["full"] 53 | 54 | [dependencies.tokio-util] 55 | version = "0.7" 56 | features = ["codec"] 57 | 58 | [dependencies.tracing] 59 | version = "0.1" 60 | default-features = false 61 | 62 | [dependencies.tracing-subscriber] 63 | version = "0.3" 64 | features = ["env-filter", "fmt"] 65 | 66 | [features] 67 | crawler = ["clap", "jsonrpsee"] 68 | 69 | [[bin]] 70 | name = "crawler" 71 | path = "src/tools/crawler/main.rs" 72 | required-features = ["crawler"] 73 | -------------------------------------------------------------------------------- /.github/workflows/zebra.yml: -------------------------------------------------------------------------------- 1 | name: zebra 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 6 * * *' # Every day at 6:00 AM UTC. 7 | 8 | jobs: 9 | build-zebra: 10 | runs-on: ubuntu-latest 11 | outputs: 12 | commit-hash: ${{ steps.commit-hash.outputs.commit-hash }} 13 | steps: 14 | - uses: actions/checkout@v3 15 | with: 16 | repository: ZcashFoundation/zebra 17 | - name: Build Zebra and download artifacts 18 | run: cargo build --release 19 | - name: Grab short commit hash and store in env 20 | id: commit-hash 21 | run: echo commit-hash=$(git log -n 1 --pretty=format:"%h") >> "$GITHUB_OUTPUT" 22 | - uses: actions/upload-artifact@v3 23 | with: 24 | name: node-executable 25 | path: ./target/release/zebrad 26 | 27 | build-ziggurat: 28 | uses: runziggurat/ziggurat-core/.github/workflows/build-ziggurat.yml@main 29 | 30 | run-test-suite: 31 | runs-on: ubuntu-latest 32 | needs: [ build-zebra, build-ziggurat ] 33 | steps: 34 | - uses: actions/download-artifact@v3 35 | with: 36 | name: node-executable 37 | path: ./ 38 | - run: chmod +x zebrad 39 | - name: Create Ziggurat config file 40 | run: | 41 | mkdir ~/.ziggurat/ 42 | echo 'kind = "zebra"' > ~/.ziggurat/config.toml 43 | echo 'path = "/home/runner/work/zcash/zcash"' >> ~/.ziggurat/config.toml 44 | echo 'start_command = "./zebrad start"' >> ~/.ziggurat/config.toml 45 | - name: Run Ziggurat test suite 46 | uses: runziggurat/ziggurat-core@main 47 | with: 48 | node-name: zebrad 49 | commit-hash: ${{ needs.build-zebra.outputs.commit-hash }} 50 | 51 | process-results: 52 | needs: [ run-test-suite ] 53 | uses: runziggurat/ziggurat-core/.github/workflows/process-results.yml@main 54 | with: 55 | name: zebra 56 | repository: zcash 57 | secrets: 58 | gcp_credentials: ${{ secrets.GCP_CREDENTIALS }} 59 | -------------------------------------------------------------------------------- /src/tests/conformance/handshake/complete_handshake.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | setup::node::{Action, Node}, 3 | tools::{synthetic_node::SyntheticNode, LONG_TIMEOUT}, 4 | wait_until, 5 | }; 6 | 7 | #[tokio::test] 8 | async fn c001_handshake_when_node_receives_connection() { 9 | // ZG-CONFORMANCE-001 10 | 11 | // Spin up a node instance. 12 | let mut node = Node::new().unwrap(); 13 | node.initial_action(Action::WaitForConnection) 14 | .start() 15 | .await 16 | .unwrap(); 17 | 18 | // Create a synthetic node and enable handshaking. 19 | let synthetic_node = SyntheticNode::builder() 20 | .with_full_handshake() 21 | .build() 22 | .await 23 | .unwrap(); 24 | 25 | // Connect to the node and initiate the handshake. 26 | synthetic_node.connect(node.addr()).await.unwrap(); 27 | 28 | // This is only set post-handshake (if enabled). 29 | assert!(synthetic_node.is_connected(node.addr())); 30 | 31 | // Gracefully shut down the nodes. 32 | synthetic_node.shut_down().await; 33 | node.stop().unwrap(); 34 | } 35 | 36 | #[tokio::test] 37 | async fn c002_handshake_when_node_initiates_connection() { 38 | // ZG-CONFORMANCE-002 39 | 40 | // Create a synthetic node and enable handshaking. 41 | let synthetic_node = SyntheticNode::builder() 42 | .with_full_handshake() 43 | .build() 44 | .await 45 | .unwrap(); 46 | 47 | // Spin up a node and set the synthetic node as an initial peer. 48 | let mut node = Node::new().unwrap(); 49 | node.initial_peers(vec![synthetic_node.listening_addr()]) 50 | .start() 51 | .await 52 | .unwrap(); 53 | 54 | // Check the connection has been established (this is only set post-handshake). We can't check 55 | // for the addr as nodes use ephemeral addresses when initiating connections. 56 | wait_until!(LONG_TIMEOUT, synthetic_node.num_connected() == 1); 57 | 58 | // Gracefully shut down the nodes. 59 | synthetic_node.shut_down().await; 60 | node.stop().unwrap(); 61 | } 62 | -------------------------------------------------------------------------------- /src/tests/conformance/query/mod.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use crate::{ 4 | protocol::{ 5 | message::Message, 6 | payload::{block::Block, Nonce}, 7 | }, 8 | setup::node::{Action, Node}, 9 | tools::{synthetic_node::SyntheticNode, RECV_TIMEOUT}, 10 | }; 11 | 12 | mod basic_query; 13 | mod get_blocks; 14 | mod get_data; 15 | mod get_headers; 16 | 17 | lazy_static::lazy_static!( 18 | /// The blocks that the node is seeded with for this test module. 19 | static ref SEED_BLOCKS: Vec = { 20 | Block::initial_testnet_blocks() 21 | }; 22 | ); 23 | 24 | /// Starts a node seeded with the initial testnet chain, connects a single 25 | /// SyntheticNode and sends a query. The node's responses to this query is 26 | /// then returned. 27 | async fn run_test_query(query: Message) -> io::Result> { 28 | // Spin up a node instance with knowledge of the initial testnet-chain. 29 | let mut node = Node::new().unwrap(); 30 | node.initial_action(Action::SeedWithTestnetBlocks(SEED_BLOCKS.len())) 31 | .start() 32 | .await?; 33 | 34 | // Create a synthetic node. 35 | let mut synthetic_node = SyntheticNode::builder() 36 | .with_full_handshake() 37 | .with_all_auto_reply() 38 | .build() 39 | .await?; 40 | 41 | // Connect to the node and initiate handshake. 42 | synthetic_node.connect(node.addr()).await?; 43 | 44 | // Send the query. 45 | synthetic_node.unicast(node.addr(), query)?; 46 | 47 | // Send a Ping - once we receive the matching Pong we know our query has been fully processed. 48 | let nonce = Nonce::default(); 49 | synthetic_node.unicast(node.addr(), Message::Ping(nonce))?; 50 | 51 | // Receive messages until we receive the matching Pong, or we timeout. 52 | let mut messages = Vec::new(); 53 | loop { 54 | match synthetic_node.recv_message_timeout(RECV_TIMEOUT).await? { 55 | (_, Message::Pong(rx_nonce)) if rx_nonce == nonce => break, 56 | (_, message) => messages.push(message), 57 | } 58 | } 59 | 60 | // Gracefully shut down the nodes. 61 | synthetic_node.shut_down().await; 62 | node.stop()?; 63 | 64 | Ok(messages) 65 | } 66 | -------------------------------------------------------------------------------- /src/protocol/message/constants.rs: -------------------------------------------------------------------------------- 1 | //! Useful message constants. 2 | //! 3 | //! The `*_COMMAND` constants are to be included in message headers to indicate which message is 4 | //! being sent. 5 | 6 | /// Magic length (4 bytes) 7 | pub const MAGIC_LEN: usize = 4; 8 | 9 | /// Message header length (24 bytes). 10 | pub const HEADER_LEN: usize = 24; 11 | /// Maximum message length (2 MiB). 12 | pub const MAX_MESSAGE_LEN: usize = 2 * 1024 * 1024; 13 | 14 | /// The current network protocol version number. 15 | pub const PROTOCOL_VERSION: u32 = 170_140; 16 | /// The current network version identifier. 17 | pub const MAGIC_TESTNET: [u8; MAGIC_LEN] = [0xfa, 0x1a, 0xf9, 0xbf]; 18 | pub const MAGIC_MAINNET: [u8; MAGIC_LEN] = [0x24, 0xe9, 0x27, 0x64]; 19 | 20 | /// Version message user agent 21 | pub const USER_AGENT: &str = "MagicBean:5.4.2"; 22 | 23 | #[cfg(test)] 24 | pub const MAGIC: [u8; MAGIC_LEN] = MAGIC_TESTNET; 25 | #[cfg(all(not(test), not(feature = "crawler")))] 26 | pub const MAGIC: [u8; MAGIC_LEN] = MAGIC_MAINNET; 27 | #[cfg(all(not(test), feature = "crawler"))] 28 | pub const MAGIC: [u8; MAGIC_LEN] = MAGIC_MAINNET; 29 | 30 | pub const COMMAND_LEN: usize = 12; 31 | 32 | // Message command bytes. 33 | pub const VERSION_COMMAND: [u8; COMMAND_LEN] = *b"version\0\0\0\0\0"; 34 | pub const VERACK_COMMAND: [u8; COMMAND_LEN] = *b"verack\0\0\0\0\0\0"; 35 | pub const PING_COMMAND: [u8; COMMAND_LEN] = *b"ping\0\0\0\0\0\0\0\0"; 36 | pub const PONG_COMMAND: [u8; COMMAND_LEN] = *b"pong\0\0\0\0\0\0\0\0"; 37 | pub const GETADDR_COMMAND: [u8; COMMAND_LEN] = *b"getaddr\0\0\0\0\0"; 38 | pub const ADDR_COMMAND: [u8; COMMAND_LEN] = *b"addr\0\0\0\0\0\0\0\0"; 39 | pub const GETHEADERS_COMMAND: [u8; COMMAND_LEN] = *b"getheaders\0\0"; 40 | pub const HEADERS_COMMAND: [u8; COMMAND_LEN] = *b"headers\0\0\0\0\0"; 41 | pub const GETBLOCKS_COMMAND: [u8; COMMAND_LEN] = *b"getblocks\0\0\0"; 42 | pub const BLOCK_COMMAND: [u8; COMMAND_LEN] = *b"block\0\0\0\0\0\0\0"; 43 | pub const GETDATA_COMMAND: [u8; COMMAND_LEN] = *b"getdata\0\0\0\0\0"; 44 | pub const INV_COMMAND: [u8; COMMAND_LEN] = *b"inv\0\0\0\0\0\0\0\0\0"; 45 | pub const NOTFOUND_COMMAND: [u8; COMMAND_LEN] = *b"notfound\0\0\0\0"; 46 | pub const MEMPOOL_COMMAND: [u8; COMMAND_LEN] = *b"mempool\0\0\0\0\0"; 47 | pub const TX_COMMAND: [u8; COMMAND_LEN] = *b"tx\0\0\0\0\0\0\0\0\0\0"; 48 | pub const REJECT_COMMAND: [u8; COMMAND_LEN] = *b"reject\0\0\0\0\0\0"; 49 | pub const FILTERLOAD_COMMAND: [u8; COMMAND_LEN] = *b"filterload\0\0"; 50 | pub const FILTERADD_COMMAND: [u8; COMMAND_LEN] = *b"filteradd\0\0\0"; 51 | pub const FILTERCLEAR_COMMAND: [u8; COMMAND_LEN] = *b"filterclear\0"; 52 | pub const ALERT_COMMAND: [u8; COMMAND_LEN] = *b"alert\0\0\0\0\0\0\0"; 53 | -------------------------------------------------------------------------------- /.github/workflows/README.md: -------------------------------------------------------------------------------- 1 | # Ziggurat CI/CD 2 | 3 | This documentation details information on how this implementation handles CI/CD, and can be used as a reference for setting up your own CI/CD pipeline with Ziggurat. 4 | 5 | Currently the Ziggurat CI/CD pipeline includes three concurrent workflows that run daily, these are the test suites for `zcashd`, `zebra` and the network crawler; and another workflow that runs on commits and pull requests to the main branch to check that formatting rules are followed and that checks are passed. Each workflow is described in more detail below: 6 | 7 | ## Test Suite 8 | 9 | The test suite workflows can be broken down into the following 5 steps: 10 | 1. Build a selected node from source. 11 | 2. Compile Ziggurat unit tests. 12 | 3. Create the Ziggurat config file. 13 | 4. Run the Ziggurat tests executable. 14 | 5. Process the results. 15 | 16 | ## Network Crawler 17 | 18 | The network crawler workflow can be broken down into the following 4 steps: 19 | 1. (Optional) Build a `zcashd` node from source. 20 | 2. Run the crawler binary, fetching node addresses via DNS or connecting to the network through a local node. 21 | 3. Wait 30 minutes, then query metrics via RPC and kill the running crawler. 22 | 4. Process the results. 23 | 24 | Details on how to run the crawler, including the required arguments and how to work with the RPC, can be found [here](../../src/tools/crawler/README.md). 25 | 26 | ## Check and Lint 27 | 28 | The check and lint workflow currently performs a set of six different checks, these are: 29 | * **Core checks** (inherited from [`ziggurat-core`](https://github.com/runziggurat/ziggurat-core)): 30 | 1. check - `cargo check --all-targets`. 31 | 2. fmt - `cargo fmt --all -- --check`. 32 | 3. clippy - `cargo clippy --all-targets -- -D warnings`. 33 | 4. sort - `cargo-sort --check --workspace`. 34 | 35 | * **Extra checks**: 36 | 5. test-ignored - `cargo test -- --test-threads=1 --ignored --skip dev`. 37 | 6. check-crawler - `cargo check --features=crawler`. 38 | 39 | For details regarding implementation and how to extend these tests, please refer to [this section](https://github.com/runziggurat/ziggurat-core#Nix) of the `ziggurat-core` documentation. 40 | 41 | ## Workflow References 42 | 43 | - [Test Suite (`zcashd`)](./zcashd-nightly.yml) 44 | - [Test Suite (`zebra`)](./zebra.yml) 45 | - [Network Crawler](./crawler.yml) 46 | - [Build `zcashd`](./build-zcashd.yml) 47 | 48 | ### Ziggurat Core Workflows 49 | 50 | Most workflows will also reference a set of core utilities that are used throughout the Ziggurat ecosystem. These can all be found in the `ziggurat-core` repository, which can be found [here](https://github.com/runziggurat/ziggurat-core/blob/main/.github/workflows/README.md). 51 | -------------------------------------------------------------------------------- /src/tools/crawler/README.md: -------------------------------------------------------------------------------- 1 | # Network crawler 2 | 3 | ## Running 4 | 5 | The network crawler uses optional features and dependencies, which **must** be enabled in order for the binary to compile. These can be enabled by supplying `--features crawler` when running the command. 6 | 7 | The following command will print the command line options for the crawler: 8 | 9 | ```fish 10 | $ cargo run --release --features crawler --bin crawler -- --help 11 | 12 | OPTIONS: 13 | -c, --crawl-interval 14 | The main crawling loop interval in seconds [default: 5] 15 | 16 | -h, --help 17 | Print help information 18 | 19 | -r, --rpc-addr 20 | If present, start an RPC server at the specified address 21 | 22 | -s, --seed-addrs ... 23 | A list of initial standalone IP addresses and/or DNS servers to connect to 24 | 25 | -n, --node-listening-port 26 | Default port used for connecting to the nodes [default: 8233] 27 | 28 | -V, --version 29 | Print version information 30 | ``` 31 | 32 | `--seed-addrs` \ `--dns-seed` is the only required argument and needs at least one specified address for it to run. 33 | 34 | ## Metrics 35 | 36 | The crawler collects some data for each node it visits, then aggregates it and compiles related metrics. By default, it will only print and log these on exit (`Ctrl-C`) to a file called `crawler-log.txt`, unless the `--rpc-addr` argument is supplied, in which case these metrics will also be made available to RPC requests. 37 | 38 | Fetching metrics from the RPC via `cURL` (piping through [`jq`](https://github.com/stedolan/jq) for prettier output): 39 | 40 | ```fish 41 | $ curl --data-binary '{"jsonrpc": "2.0", "id":0, "method": "ge 42 | tmetrics", "params": [] }' -H 'content-type: application/json' 43 | http://127.0.0.1:54321/ | jq .result 44 | ``` 45 | 46 | A sample of the data we collect and metrics we compute (obtained via RPC): 47 | 48 | ```json 49 | { 50 | "num_known_nodes": 13654, 51 | "num_good_nodes": 2066, 52 | "num_known_connections": 1888893, 53 | "num_versions": 2019, 54 | "protocol_versions": { 55 | "170017": 10, 56 | "170018": 1958, 57 | "170016": 1, 58 | "170100": 50 59 | }, 60 | "user_agents": { 61 | "/MagicBean:4.0.1/": 1, 62 | "/MagicBean:5.1.0-rc1/": 2, 63 | "/MagicBean:5.1.0/": 28, 64 | "/MagicBean:5.0.2/": 7, 65 | "/MagicBean:5.2.0/": 2, 66 | "/MagicBean:6.0.0/": 1957, 67 | "/MagicBean:5.1.1/": 1, 68 | "/Zebra:1.0.0-beta.10/": 1, 69 | "/MagicBean:6.0.0(bitcore)/": 1, 70 | "/MagicBean:5.0.0/": 19 71 | }, 72 | "crawler_runtime": { 73 | "secs": 145, 74 | "nanos": 298240944 75 | }, 76 | "density": 0.020101430617415265, 77 | "degree_centrality_delta": 1178, 78 | "avg_degree_centrality": 274 79 | } 80 | ``` 81 | -------------------------------------------------------------------------------- /src/tests/conformance/unsolicited_response.rs: -------------------------------------------------------------------------------- 1 | //! Contains test cases which cover ZG-CONFORMANCE-010. 2 | //! 3 | //! The node should ignore the following unsolicited messages: 4 | //! 5 | //! Reject, NotFound, Pong, Tx, Block, Header, Addr 6 | 7 | use std::io; 8 | 9 | use crate::{ 10 | protocol::{ 11 | message::Message, 12 | payload::{ 13 | block::{Block, Headers}, 14 | Addr, Inv, Nonce, 15 | }, 16 | }, 17 | setup::node::{Action, Node}, 18 | tools::{synthetic_node::SyntheticNode, RECV_TIMEOUT}, 19 | }; 20 | 21 | #[tokio::test] 22 | #[allow(non_snake_case)] 23 | async fn c010_t1_PONG() { 24 | // zcashd: pass 25 | // zebra: pass 26 | run_test_case(Message::Pong(Nonce::default())) 27 | .await 28 | .unwrap(); 29 | } 30 | 31 | #[tokio::test] 32 | #[allow(non_snake_case)] 33 | async fn c010_t2_HEADERS() { 34 | // zcashd: pass 35 | // zebra: pass 36 | run_test_case(Message::Headers(Headers::empty())) 37 | .await 38 | .unwrap(); 39 | } 40 | 41 | #[tokio::test] 42 | #[allow(non_snake_case)] 43 | async fn c010_t3_ADDR() { 44 | // zcashd: pass 45 | // zebra: pass 46 | run_test_case(Message::Addr(Addr::empty())).await.unwrap(); 47 | } 48 | 49 | #[tokio::test] 50 | #[allow(non_snake_case)] 51 | async fn c010_t4_BLOCK() { 52 | // zcashd: pass 53 | // zebra: pass 54 | run_test_case(Message::Block(Box::new(Block::testnet_genesis()))) 55 | .await 56 | .unwrap(); 57 | } 58 | 59 | #[tokio::test] 60 | #[allow(non_snake_case)] 61 | async fn c010_t5_NOT_FOUND() { 62 | // zcashd: pass 63 | // zebra: pass 64 | run_test_case(Message::NotFound(Inv::new(vec![ 65 | Block::testnet_1().txs[0].inv_hash() 66 | ]))) 67 | .await 68 | .unwrap(); 69 | } 70 | 71 | #[tokio::test] 72 | #[allow(non_snake_case)] 73 | async fn c010_t6_TX() { 74 | // zcashd: pass 75 | // zebra: pass 76 | run_test_case(Message::Tx(Block::testnet_2().txs[0].clone())) 77 | .await 78 | .unwrap(); 79 | } 80 | 81 | async fn run_test_case(message: Message) -> io::Result<()> { 82 | // Setup a fully handshaken connection between a node and synthetic node. 83 | let mut node = Node::new()?; 84 | node.initial_action(Action::WaitForConnection) 85 | .start() 86 | .await?; 87 | let mut synthetic_node = SyntheticNode::builder() 88 | .with_full_handshake() 89 | .with_all_auto_reply() 90 | .build() 91 | .await?; 92 | synthetic_node.connect(node.addr()).await?; 93 | 94 | synthetic_node.unicast(node.addr(), message)?; 95 | 96 | // A response to ping would indicate the previous message was ignored. 97 | let result = synthetic_node 98 | .ping_pong_timeout(node.addr(), RECV_TIMEOUT) 99 | .await; 100 | 101 | // Gracefully shut down the nodes. 102 | synthetic_node.shut_down().await; 103 | node.stop()?; 104 | 105 | result?; 106 | Ok(()) 107 | } 108 | -------------------------------------------------------------------------------- /src/vectors/block-test-0-000-003.txt: -------------------------------------------------------------------------------- 1 | 04000000181ac2d6dc0ae73a82d7db472ac2879a7c24d8b12e5f73efc33a55549ea4f10050953b3d2510d8708ca94daf3daf9a220997fa8a0c892334f9aea314f8f33b4a000000000000000000000000000000000000000000000000000000000000000034901358ffff0720000086f667ba6cc97829497a0e1b8f20ab15b3e930aabc445e613bac0eb30000fd4005001be24c401d01d9b72310669956a0b87f75fee8f6127c2117d005045283f955dcd03cd783d990ff199c08142849aae7ca97965243ef2238cd3d5179edb4a809e610fe8ee0697f8472c5480bc9065e635f3d5e7f0cd6b1a7018830c7c07ba69d48495a977a611dc5ad12044db353c95729ad01424788f804f92b48db257112e0e5468692bedadd9b03bfc8387044f612bde6244d2a15fa0d186363a04355c10576bb3f26825b66d50078cbfccbd303e9c6a4e978af7af4f7913fbe76420e9554efa0d06d673455728e4cf97d7dd7dc9d3b5602543374d16222452e8a54dbe6c75a531dc5dde6a64e982f0a299a8c773a36865ce2fcefbb57bedca75d069da903795d0d3188468275ebdb3b1e8085b6068a11ae35b8620db5ac967832f6029dcb136cac7d27d010af925cdd2e3d457ec68323da25f7564a851433a9136b9d64e2299499f177718a80181ecf64bd5e4f6f005dd63747149ae0af7ca048812ce495c3a2b4629b04666ff27847d1544e9682221fa0dbed35840e3a45099d7c36058829eb6fc5b1d999a6619d76834e50af1c450b69a7aaf041bf9f23853655ac0e584e5d88970f84e1b917ceae9b8352b587fd6428e5828ade986d2263b2c0b2a87e73e18e82e671a5a5aa025cd4ee611ba123ade8695ad1a6f6e558c4b8ecca4a64f593242b39ac094caa778f58a463ba15c9aa69247d2a56a502013b72f657aa094e8ab3378ca53fa141100ede78124011a3deb1e0bfd65a22fd666a50b50dfbbc44c221d603a4ef2ce0f3cc636634adf7fae21a95b8b3f03ad0bdf86450caf3d9f9290eabf39f5eb9bd3c4de306c8405f9ac2688556d4409e7945362cd0a6d4716109ef3dd735a47d7fde7a328892c358071d54b9dfcc0c9fd7bc76844195d75b732bec4a1ce97471f2c4061fcecde4998ea838f0331239bb5a996ace40fe1b1201b0bc4eb3a3f2cb2ee5a40f64ac821346859c857238cffed09823381d3d5809eb915c90a67c853976ce092969d11b5e2a97ddbf10b03893f231bd025b144426f85ebe0d5acc599f88a3752637b30dc0df325eaa05365fd87532f8f9a746bc0bb16126e359223c265e2328d5ec2c09c919b23a582108c46753b9709e6e340835f41eeb86122ebf1234827f59cb2d69037089c7108bb769001565936a199591f54053aa5fae3f1ad701b989f576d1f4437b91011af2f35aba8bac3f3bfc15e717ae400744aa7553220939f250e5a0cdf7b09316cf6c6b0d06832977c6f378cdbf161f69683c735d35e8621614919bb69048a3775821b2ca9d7f1b4e3403f6ef6c79093ff89dcdc39415736f29638a12091005e55672eac17e33f485b46138fc52bb1f8cfd0e47067f61d32187fe6c50f4642cd8d88d7dff13735ed45de02d819e9ad03fe58ec844fbef3302ba4eb8821d020df8b7b71d1cc1b45ac3fe995db0a952cdd9e39c319d1691c1d310277bbcf5c0fc68f64992e7dc99b0195339b2eda0d96d70feb82a8572e26674bd1e05ff48ea7eae9bd27a6b5867b95dc152314f376c9d5ac5070abeffa61e56a1018c50f9dd088b2a8f0e76a76d550c8b4a01d6df03e5146695adce140e141ed8f3eb13b26296ab573414ba7416ccc8fd5e165ff656a40345d2e6d231b96b6dfc54285cb8ee552e2ade7756b309886e4a941b738d2c8920e4f04062b94756d369e80a4bf062900858430e8d20cef434561f51dd1d75450ff5d0b2c6d681f8f2ac5269b078080e90a57549a05f0bac317eb13f038cf4280a634198c25bfef47fe70dbf20f071db395f1145047693a919a92db5f8d7270efa7e0bbb1ccc8191dde4d7abdaf631f018b8842f29b4156f0d5123a705374371ab7eda9110d8abe7945e74ce02dcde3383a252e8c14ce8ec2682c7567c840101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03530105ffffffff02f049020000000000232102b24776315475d2db96268d6f60cf66db445a3c50f6a3a7a2cb07be45e87b8346ac7c9200000000000017a914ef775f1f997f122a062fff1a2d7443abd1f9c6428700000000 2 | -------------------------------------------------------------------------------- /src/vectors/block-test-0-000-004.txt: -------------------------------------------------------------------------------- 1 | 04000000453d3894900251af96173487ba0c01acdc2c8093ad06a4035b0320dae841e1000bf847a21ad1ce24a12ea04cb38029e592dbdfb58d78d810f33011eb6ded7333000000000000000000000000000000000000000000000000000000000000000080901358ffff0720010051fee29af2af0763d676ccde3905e3bdd94eceb50ec735e4ae507e620000fd400501e4876165d8a5feda2bf303ecdcaec1a588b792581b29173a2516a0dd3a5521e6b4d20cda02cb1d16ca1348d3b733e5956dda564bed73f6565f84827cfd352713a2433dd5e3ebd306dac7cbdef0676c211eb4340b5297fe3a078aef438d949d27e3535647f11e2e6f4d047f0250152d53affbe78f414248d2552dbf5ad30d7e2cb96648e64bac1ea3a16d37644add3658afd112de84c44806dbafdd6592fd26a50f8e0994d1bea302392273102614a9b70b9196eee8d095e8523435cd1d0fa787068ea9f8ef2f93a72ce3e3df24ecbf8d5a1769f26b4f966c03487d618e67b129a98de25947cc1e04c612f7d55d893837b48f8f4ee4fae87b9e76b002ce471a29c7414597edc11e366a6f8550f09a21c41c3ac6b02b1eb27978a5c2b48470673dce121d635f1700b2afabeb5439f2941327e52af84a940579c2821e2a743aa9c8aa6d0f7f12699ad50cdd57a18e69b5056db6ff5b4febc8b69ef44bedad99220992f2590b5584adbd4956fc1fe7edb68a977031c5d36d4ff59814276389a9af90018b4add95747a0f8767779cdeac1d585c83849c4a571b66e2f9c0ca68c5884edaba6b0ad30919d0262e4f57bed38634561c2d1409d1c7072ccda4b0a7ec3dbfb83814da9f2a1d91848d2fb1ba4721fc9a26daeb72f6ea06819aea8526160bbfa5d077c6174d7b76967dd99a3b74f4f0732f10837fd91a08936ec2ea9263dff29bd69f63ba361228d3198dee2ffcb526a90d026ae830c5ff05f7073e0440daeb7c359f96cd4d51ee530ed90417e0f4b75663c234431c68fd1fc43a728f9fa6745a67a97e5c3f7f39ddcbf80c97d4b395e01a77cfa573b807d28ec95791111b5944c25d8c35da01a2ddaab47b5cda6e95d6887b385e10959964291f3a35463387d2234624c681007d761019259de4e56120ad7ce264aec2a8897305d87906e902a0659f29dcf31beafbb0dded9ce504bf95d8f48b161f1d3d5107678a628be192922978d4ece0353e672ec34efb6cf2b361e8c59c8999fbe6772591dc8079401aed5bae348663c8d6e63dd1ee1be2c924f9a6220a435401f5eb5b4fc45e015421b14a2eb47977838424475b20f88bf2d51661d783f767ac3628785524ee0c047879019ddb01bee450e2d190dc5077f52a23a73819270443344809baefc78bf5fc3495ed7d6fabce0cdfad678ddde9d0fea0aaa430748712d51d5902042e7e4e2f044d6e15b0eda3789e792c5900ddd5f0a70e2ad48f5322157d3b16c38ff0ea1c0d0f41b9fd76122b66db925fd0e7b9e8828344f66f4a23f31edb3d1225fd4e0a0a4145a74d7240ca923b0bbbe95ed633145bec3aee4571055263920621e2f9c0acd6fe906e162967b7c85d9601ea1432d124ad53a9e8e5141e481add1dd63b2fc0b787afd59924f0504b4af7fbe99e03922f85ca2bb42d7d1373c385d676b6c7a73f52421658fad5c159a774ce55954ddbe3cbf76855dc864d0d6979128654edf99716833605e729c2a83b5b81b91a3d6f95d456571f47b3e250bff456e4cccbb5a8b704621be030c9ef5ace0d9367a852d9825894f8638607f96b154b65ab09f053240123e16aa1446a7fac030535c911d6a08ef5679d72a2a46cbff8f3da3b012a5c728599409e30acf902066263b4d115b6bb3c38f303f4b9eb97c30ecf307ae13fa21cba0459898d9eb1146b432a9b46655e437a53d77fa8f2a35dc8fd6c8b0689309602937818b53ef14cb57def2c6268a8156607aac28eb1934b9d119f23d77849a1623a92f675e617778a39755aac5d2cb324e1cd618812601b75ff972d24ad3123ce67f3c4cbd44bcfbde909ccd83cb8aa271c05b2d60ed58d9304844ff6bfe472682cdec63a7aa577e24be4c5bff405682c86db407e92d63553e30101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03540109ffffffff02400d03000000000023210387c8c8f56c95c1a8d40efb54db56158fcdeb5a26a8f57d8be47b7b826223bf76ac50c300000000000017a914ef775f1f997f122a062fff1a2d7443abd1f9c6428700000000 2 | -------------------------------------------------------------------------------- /src/vectors/block-test-0-000-005.txt: -------------------------------------------------------------------------------- 1 | 0400000075ef21ee166efb6223f039afb800896ac0a949e02b77a38a79479036ae746b03ee7bcc689bc163057062897c62e689c396a9fe9288d7b9399e0a58c2f780644700000000000000000000000000000000000000000000000000000000000000008f911358ffff072000007e734241db55374b13093622fbfea74696b2ce28e5abb2c8e989b8450000fd4005004b0b0380a079754eb8a20959e9f83d631d18496d0a4a26600d4e36447a92a1f7efe3ce1284537c3ecd16ad112d78a3428f38bf373f1e6959125679db218729e67ab858627aa1ebec06c3c94c8c96a0f27bf29e11879579a713ecdf1e7ab199d0c77182585a95dc224fe5dbdf4a7215d99ccc45c2804f1cf648a51e241a2f11535e4dccc944aa67a81e5eec079778a19c3e313053442a77543142b5a9d501c44d33f1867e19a88b03bd00c7ae9a269bfbd8c2e27662296740591d126f19f755746bd45561e89714f3d92af31d5a36922e830c3d3d0727465ae971134199b6dcbe7129a9b908d831baae4497a6c5c5e7c1d380fb1ea541cf509e3b3e112683f07c4c7e509292965849d3af759ea651c2fb4b9d5b7c4f2fffdb9483f55a89c82ed368fb3ebb7b13d58ee531e671d7e6faf8486275d87ea386bd40e22942a742bab2fa61a9a4260499f96136b6c07dfa95039fe4961e160f5eb2bfc0f4dd3ac7f8e0b64e5e4c48bba44f4deb612d81acf71b5c5545fa3358f3967d07df7e7ba08d5738769be08c4ea1789372249d51850bf30112c0d2bbc5999e180a05d59262eb4f5a5bbf085d0d870770db99b997f2c3bd50bf9d9bfb1fc0b31f6d2287f08a6416ee8a72801edaa6a334055a43b516fe7fe6010efc62b60992762c7ebb4940ab8a32b831943a9b9861330b71026980a4e7981efe4db87bac03f17459f15545a5698cf05e106be3e932df76e009232d6d89bfcb9b155912a3d01fd45fc26896fa26410dd14c5a223367b9c3b7c5c27efbc6dec6fc5eb9a927e781e0cf954b332406d2b11f2515862008bdc97c05a40f31732912d9faec90ea2257b0f990accf92dd18ccd5905e8b4af49308f1d3f8ca1626e6e7f8ebc90988c789be90d5138b52e38161f9f7ad01529e22c12d3dce6693974b55664e17ec0b7e61a215777de23700f9e105d35fe579864d61dcede0c45591fc5ca2c13841da4cc49575533e63842e77d0213dfc0b52f3f5246839f306ce0169afd6c27affdc96ea1dd13470e34267343c3b7131b1b632a56922fad72dba4bfd3c3202be0a821057a674f750f292cff8aaded117fdda4107a748ec1c04cdbb4b3da20f89fcc7ad7ade12be802d72acdbdacde6b29eb623ccdf3c4e6147933a4c50318e7b3650cdc2beacf9b490cbbfa3724d9d928697048673900405a2314046b19345a3ce1d589c7f5c5b1ed80a7ff1a42487cd3366cb68c7812266e39fc0f705d0314b8d5ca67182e1236b034a53f1dcb1fae1342f17d748b528d2a5a8f10692fd6fb1c628e8b656b5051f6b2706961e9155e001f91d2150bcee4652e19f17a7b23e354b1e9d15e962daecb65ff265ccd3c7231bb4c58a86ebcd0b8aa8065e42fcbbaa138b74f4c63d36b670f2efca1bf06b783c99e3f6e37ed0fed9e7017d4f6b6c481f4a92a277060c69ee41ff4ed1219813683e45ab491a57b09b96c584e3725dd044fa60660dae8ac00729b973c75ed23bee451d72aa97769322173811cb61cc8f63ac38a54eb25492a5c20b5b326d024c5c7c88a92bd9cc64402a4e916a2d5c652be6931503b575438a06dbcfcfa238409bac21349152279c26f2bc3be523f1a979ffc2ec0ca7a640db7d2c2ae431f07db5e11c80b5840517a091745c32465c9be8b50362f143911f421d624b23d46e421dd5a5b058e89806c3627151c8219f682247941f3dbe1617093596ea20acf77565345f5fe502f37ddac962c710583fd85e5251bf858af3f6a1f37dc57bd2ee06be2691f32ecb0401d7e4f7e0bacb26f7146c1ad6730b1bc27f2ca04c95c46c3579f289de2d07fcdccf9e0350b71dabd6048db1c9d1adfd037483c074fcf05efd8dc27965550e9b6bf478d4889bded6417eb1496437917d5dad240101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03550103ffffffff0290d0030000000000232102171c07a26a7b7f5e8fed66755b01b0ad0d36121ef1979679ca4bddc29da664b9ac24f400000000000017a914ef775f1f997f122a062fff1a2d7443abd1f9c6428700000000 2 | -------------------------------------------------------------------------------- /src/vectors/block-test-0-000-006.txt: -------------------------------------------------------------------------------- 1 | 040000001e2fae054aea69254a5c830f1fba7a4149b5238b31bb0841e4e81883006e6600ab572c332b8774335745715c260810ed0713cfecaceadf09eb5f828d40f8da23000000000000000000000000000000000000000000000000000000000000000029931358ffff0720010046f28cf56b736b908558004e821fe51569a2b61059b9b983e130778e0000fd4005002b168f966710339db5e4d51c708b4d47fdddc8022f5fb581f9d923772ac3f47b7de424a1c647bd8cf10e2be7c97090455a9700b543cbcd0ac31153ddc151359e75bed32269438e12f67a6beec586b99e7ab40a0203d641375486f1f38f81d1796429a1b294d53f270b8dd8ed9f8d67e77e1c449ae0f2a8be011f3a43110aa701111050fe738c37b84bb8450d9618a0d726863e69ff2ba4d0e063eee65475842666d26b6cf43e8003d2f71a0b679e7dc3d143da6ac9ac7bc554deb33f31147519321372abc75279b9ebd2009ec7a9df564709fbd7521f03ee422a9f27e7b7d572aa0498b7e83b150a5cc2370597df45a2e2086fb329809f54a8f50d110bbe0636058aa28eb2989162ccf46388269edee86362b31c1ae27deb7783e89c36dcac626d4cbbe8e9170817cc1acc08ad00ad04b04552611eb256f6580e5e94b606ffe6e38f58b10762c56ca192bc043f7ef10071dd48fbc0c6f7944cd03df3eda964152d0e18772547ae16370cfadda050a4f7617d03562f7e1ea5f6023660309782eeefae566341c6fb0fecfbd8b5ff20400db6b5d319d061fbea6529b24203621ca814b59401aac65564082b74d997c425d0a23c728221dfc73401d5562e0b0e37297493d3132af2db1903e2ac89b509e7ecdcf22230d72f5653001e450955a10d6e4bbe0b88db4d11a00f111ec432eb64b01786115ab806ed05111ea44d323f33bcf9614aaf44e440806fc5f9be635d7f566d9d33bb3ac338a892dbc9e27c88b7e3e420c93a95464981b84dd08423c8b7a5a1eb3b9d614623a7dfd3f58991ee9d87292a9e63b2b78e87fee59b06c53c4d5caa92bfda8fb90810e899c26f0eddcecb071dcc1b66682fd1df9b5678106fad4a3bc852a9bc13170ffed758522ad2628572aacfd5ed978c8fa7481ee80e955196b561a5eb969fabcaf94383321cd54a00cf976ca9ad11cd843e117d25a786a0652e75ed8d165b964a59d959bb5aed22353fcf9cc8f35655152508be66fee821860d24e0b4e0024a426206fadc204216a8cebef04bb2d4eab9834586f58f15fbe21854a003b0596a2a59c8cee21311f33b9c7760a750ac0b2a15a222347bb8ae7dd5b3c43f05a95dc6453d7b27ce03c6256c100fb07d178070bce6946ae0ccf00f47ce17d5cfac26b05dc7acd4124c5149c7d9f4363ce39e0378710f171bd79be82ac0fe37f5445a31d25deba304081e506a5f1f35b02640c51289ecea16ab57dd3509ed4cb0e3184279b9b370ce2d98ec7881dbbbeaf4103636103ca3bf55e0b0410c9b3d0a5495594b0fea053639052ec20454e7fe90c0142649e1091e91e36122ab21dfa60fa342fb8114dfc46a4eaa4ae9bca6f82c0d3749de27493d6a5036ea8fd836c6b0157cea2c2e5f9cb5962b391b87f7c5c59f7ba649e265dc0080019676369b245d372cabd81a8a4a054ac9e2ddb1942744eb1f0c17c8e2c556e4400fd5cc4ed253192702058226c70a20b3c7473f365754e0c821d8c0b25b6c502adb162631abfbaba7a672be6a054210e65f2f7102de979406cf8fbad303a80f9a4fcf120a50b7164929a533d217e42c2991ee89a94ce1223e85e2755d99083004a98686bc04dbf330a28d7ce3dbbd417df9532dd364204d555f070e9e0786437d73ee761cdf1c28041c6a156f1984c7760b868e99e63aade32137b9a80d29bd170af31027f15a41e44aa01fc2ba629fbcaf05aba68c0e8367c8cd3d6920bfe020f2f3f5dcd8381f74a7b4756801b5b906a2584dd3f610bf1d9bfcb904afa3ca3cd26f171fa1038c2ccb952674049d04740b9344dedbe5b37d65a05500e54faf5d8c2f30dd4011ae78946c0d501ced1781ad5ffc7e24b3ee87bb4b1d67b930984d55c1476164c19bfe2cb947e217b1960101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03560102ffffffff02e093040000000000232103aa3e2d213d453dab046457a98d908e81b8e79ee71bb400e3b1868844d85de054acf82401000000000017a914ef775f1f997f122a062fff1a2d7443abd1f9c6428700000000 2 | -------------------------------------------------------------------------------- /src/vectors/block-test-0-000-007.txt: -------------------------------------------------------------------------------- 1 | 04000000b8863fb0b1361519059c4ad552bf007d321c2a9a710c6f71dda84050eb637e06f21731288dc961bc400ecc1f70099b0bb4b887bb319a51199c81597100bdae4700000000000000000000000000000000000000000000000000000000000000005a951358ffff07200300a345864053594c184c6750b57343974a3eaf2aa3e7c4426f524eb2460000fd4005008eeb245cc7cf1d37fcf0a704f9985aca2e36ef05295984fb42ea40a78ae523869e70cd617937b31ef6028713e9f9ce07bf7bf582cd6b2f5096888019b63f13a224146d4e9805917ee44d64fa0b014805db796903e671e5110132baf4be7506d86315833075bcfaf621c97b778758543f679d632d0e72d6f63a45b6dab775f41d31b525f11d66cc1c9d1efe971f41933a5b327a2137b4fae268fb841028853ddd50aacd9c58325c05bd34653c4337a0bf771484185362e26ec2dbb956069f415ad59c980fe1f1924741cd656e0ae39cc7fc0c9d45586bcef71079be7357b11c60f1c55e2e9b2f24080ccbc4155e4f315333eb62e88a9d166f4c9b201160a09ff71f047b8bd0b2c266be5953a1995f174011a22a384de1d87171f8e3502e44f720dfb29e472d156aa6bbeb29e39b95b344a6a0e84f4af4a03aa8713af7022522a14f5f294883e4b860069e041291cd6800a0db3987d48577b37d9277ee2bbe68ceb2957aa63206f6cd0a9a3791efad876057eaa4a28ec9dd59970d63b21ef644d7aa7361b383044f1992c93dfdf2e2118e08e4bd0b9741f117f6fe69d188fa936915672b0fe546fbd12c20cf7e5d43c0fca470cd5aa77178c81bff7c5b5fcebadfc37954a44d324b06cb34586b841b027cae76c851969f0af2232ce97e19e8f39977831b2af7191c601fb34f52e69cc7df9d9b68621c39ac00c4563e50913398cbb760a170499628f1c9a90ee20cc8fd26b6e1c697a024419e9153d1b08fa4f6a8101f774e5140540777974ff3c3cd3fc82daa6e3a752e4652caf331924ed4e23137c3206c5be20aad9275531013834072167002bd02515c59161f88c4f49df57a1c0376512c54547ab6b655c52ef0f1b99aeebefff81bc487cdba34309daff946fce5e20c1336e01c220e229013bd195436659fdb087b404b57ce29483393f000c266da348a9119924065ed556165f6758b9e6c4a1fffb75b3a4d0d8d2372aa9240739026f4525fe4de161832bd72d1e40f3f1c02a61672a42e3bb192c90747605ea8e2983023b4eb96ff0ae9aa2a22a0d9cbe804eb40fbbb119423ca080540067796aeb75cfd97be1de1126cf16865fbbe35342953d6325a6c703cc6b8267c731d0f342d7bc4a9a36afc2592ae0fa95310af6023779d685d5047b06c79e7e755949af2aad9b9c904b76a95f3054814ad7a415cbfa39bfe85937901df32dca63ad05ba7abc3d2845ce3dab2b35bb53d859e0d8a03823a87f035d57c252237c997cd983e70f58b2fcf9a721b1e8ded257398814fdc9af69757f7027f058801155f8f5ee7652b15787bcb5882dad01a82011dbbe94ab38b0117e3cbb24e69d22bd4eec21918e3095d8fd1b716493b9fa053c7227d0d43c3ff5fed28289c4457561bc521b43f93a7df724b69ea9576f30d026727a276c41b95f4fa1862e4694b7f21c3fcf16f4a5cc496646508a7b35415f68b40251a9fa4355a8318f34bb0904edbd7cf21024c5b5141ddaad3d600962348c311788f71c3ab8512999874bf651734f6e57d05bdfcbafc4da352ffd004c27565010b7ad3deb6914a9a9260e19623430ae538fa39f936f25d369b6094079f8ea7c510132563f5b31a88467436e78cf9fcf0207e5d29bfe92f1b8d6642a63c4e03b5cc1514ab990423222216d3d9fda362a2c9f1d631158e085c25e20d6c9d31886271012eb46587f0e23a2da22aef1e2c11d3ed6710cf876e8df3c71002509345d329b9e3a618b44c7665528db4d2b0595f2e7989cf6720dc17f6042f8187d1e54fdbb653f5f02dba00feebf73cd7370b494790b1d2fd5f9e905319a0b1258248fe7b04080de14668aadb52911125882befd99346851dd5dc1f27c212ae3797a3057197a2c329c691293e74180c1e0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03570101ffffffff023057050000000000232103b5ca53d1dbbc1f9896f3a5fd59715d066f98cecc1f15dfc33d0762f8409e29ddaccc5501000000000017a914ef775f1f997f122a062fff1a2d7443abd1f9c6428700000000 2 | -------------------------------------------------------------------------------- /src/vectors/block-test-0-000-008.txt: -------------------------------------------------------------------------------- 1 | 040000009602d22138bce598c396bc4691d4cef0b67fd3a7f84a9a35a60d921881052f0343a443c43f281258a8aba067ddb775c1423ffdecf0007273d8c908e22b98f8290000000000000000000000000000000000000000000000000000000000000000cd951358ffff072001000d3233ddab5818d448041684dfe38f0448cf2e12ea44818478f35ad70000fd40050068d4c9b1c19f884b49d8bcc74b398a97a1ba17d028602fab3820f2ad2f194345c73555f1079fb5a93c33f15ebe85f99efbebd349cb78e27e369ccd9642e5581a7f9c9c180b92c56f86890d71a7bb7f2fbd84470650fe26fdc7fb55c5f7c221137252711e4cb483710e12590419661ed14a0624aad7e094d9e29a19eba116c0cd3eeb2245839d29668aad5b16a31aaf5b16c829c1d4d26e99a7c55a1543c060e783e57cfb2fc6ec00793892031eed07a0f0e170e9f47bfdf7e897dea319ecb7f5b53976c9e72c8911bd50677b16635fc5b80e4c0ee0334ef5a771ffc639fff15a05b4c9b39f201743ec30206e119f8aa33489b5b084ea75d7d41ac500d17ed2ebb78389fe67f1c4617c6e632ce6dcbb471b1ea11fbc19e969dcdf424286ff213974253b03e3061ba25a92f40b81d68d956fd5bef0e6693ebabfae156f1a224a8ff9ef4542a460ce78e5522e4c9bbe3707cfb6920aeebdf1edd85205b617a96564ab6c723639f3a49b5727d8cd447e640ff73deee9357e92b39709509b9ee5026126fbad62587a25b1349a4c4c54dd108aabb1ae9c444592087176e830e3acc4a113d4f90c5cc9c995138f2aef85f63566ee8de29d249aae0f1920ce13b3212e0b4d1288093dd12d9eec457921eb1262ef1f39e3e50931ca65d9ab78d439eff1b071975691dce80e60933f9f7335deb24700a61f7f5ed69a11e73e9c6914627b80d2e46dd376d3e5ee3db07ceb178a3e052020097d73bba236a7df20c4b41c14c8b914f80662cc4b5e1fa237372ad7c57cc332933dadb831e354619e536c3b896ec36a42497ec4ffec111f52130bb497f2c5710356d795a2eb7c6ac1c20e9bcfc4318e63e71a5b3e3bc3eb16bcfb3c4e71b677cfa3e81560ad909ce362c9835371ac6226b344b22c94936317970aa653cd54e1d20124a868e4d215593d32e55e0101cac9690ed901e9af411e6dd9108050a6e3e07d17918526a86a6f2be652f427367c10b9f8679eba7a18ddb2614ac7479f0ce6c42f3c35852a76c99f8b2f2e069ed13718d3133ed225b0b8b2f68a56225e2f81019034eb9d031ca9a66567dd68729e86b6295cb9350df1087aad8a34dfc8e694a83acb39d97726b378ee0d236f6ee7b19dabbd5887fa9ad439d6a87d1e10714b98a5e459727205fdf6150735ed8035f8d6567c820594328f51754a65d0a7935cabc17871f0ccb3b66c2654cb2ab4e459b9d9f983af5b49486e60b615290e0843f9fe528aee3dfa74959ae3cc1601d94efe1c2d164be7ad17de0e15f12f22a6d8b0ee4d89276c55950e8c83a465050b926a94e5203e7f6109b298717dab18d95e2dda7a19a1d61a433c02efda5db75ed97900101ea48cf890a91cc706927dd6769bce34725b4b6e18837c71c665f41bebce22aa2350d679c1b5b4682001a7ebb73e1f3c8d7281f0cb95e684f061d5c3b52104888bc1a37733d1bc5ba389875a93154937afe8733806b633444f26370b96341ea36205929b52f4efca5615e65cf660ec815b0978dceed6b89bc417de3d6904a91219b0882803431c10665b399e6aa130b8e66c1edaef47b50c5137ea7fd60c576e5d4d86d370185114808256ec1a9fd9d0fd46686bf0046eec525870bc33bed6dc74d115774f0b7496c9455155c2ee1bfa390caa737e5acca8772e3ef25d6e7c7c475c25fcf75e31d65f94671f709d5261b3955b504ede2d6c9fd0fc19e3169a46983296fa0601cab0765b44843f857b4c1c365179e660b0f5107982d0ce23fad13b372c95541389068b709b47cb4c4e884a83696c562ded73b67e271ff5db14a3122d35f4652f05548bc7d179beaf962bf2cb85d15c569755534335ad4ed7a8fc447cc240320a4472e31db763ac1643b1a6f55a2dd2b3b7e91f0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03580102ffffffff02801a0600000000002321035e017ce17274f75539e35e93ffaae48b91c2cbdf1a982fe37a7ff0e97cac9b19aca08601000000000017a914ef775f1f997f122a062fff1a2d7443abd1f9c6428700000000 2 | -------------------------------------------------------------------------------- /src/vectors/block-test-0-000-009.txt: -------------------------------------------------------------------------------- 1 | 040000003dae23446feb7b12c2d68c735985ea2d7667208ff684d86877c989dd32d5a4051c78590780f87bccbc7eb483f92fce9f2236c3c3cb729461a02af7f5dc40ec9e00000000000000000000000000000000000000000000000000000000000000007d971358ffff0720000022fa6efc3e750a2fa56348232d3cd50ed5d1f75ef6c9de076cff4f140000fd4005003cb52025dcd523b0117646aae7559dd7ca35723a2f51fbd4dbb2c9c3c942440f12db079327cd194d880a480311552574bb88a5e24a03bf9d2d4ca65fb08018e792142f67ee85876cc4a647ab3a7dcff28e882f04ac6c8142d22e00ad7e107147450ed025249d1b473dcafbbc809bf88f5ba2c453b2e31c4efb1ebd35ee085295c73612c04b6e33830878ecf01273d053a8970ac581dcd90603d75931d134f8b75935f276930e0413f87d7b02cd628d0d9193f660d53c461885fc690d2c245642eb4e3c1111bfa435e2b7c736c3529ed3d82a9022b0621dfc2bc24eb68ffbc68fee434a7eaa36349ac4b1e50df379ab36b3d260a915f2c7a1bf3dba193cca15ab50d57541a074c491fd14b534afd063d8256e8bb658e5e2854026034b94f0b0cd1975787c002d7acd8ab4d162ddc9a6c5669eecd67167ca5428f13b0867c0991f7b2fe36a14a1044cb21adc911c9dc000882047ea81b8bd946601f5319287063c2637f23c0cc18439ad83a6ac6275a450faf35211ab8f1053ff40b28320ac27f2c196b148fbe2d798ea59ecd8d1a261b56c148021eab1a6bb36b3a6d50ae1db4cf0f5f6026253433a4e3a3bb431d0e78cc831706530fc2f4c69a24e602fdb562736ff7be4f7ebcdbb09d57d5baa037ba9176e1784d4bcd6a284d8e127ba5692bd82601e382a8a6d9ca04322c983200758f693ac515e76f00d2a87fab1917c0d1c9901397630d75583ba529c8a3999fd5fff0ed6573268190dbb782be286b5fd208b19f4237dc7509875509ba5c85ec5a97ef432394f911eb3650550d4bd21ca5de624c5c68a6e2536ffe21b1376ff143e237ff1f131317e9c7261530a39dc5d443f019361159a55e3f08218bd8350eceba5d77ff9141a0804c89a86ff068605ac0d316b42f78c52feee072b4324b959d37f6382c9858b054f8dd3002918faa900584e6faa8aa2e586f8721015a2e52d5b1db2fc2213a193a312b1f985f4e60614ff78f4f9f811f77f8d0a03c6a48d6f74e3d395e5913be4b9e24f6ef3928840e0eb2fbd95a6f0ca9ab74d9f3c835e4f4a9837bf04fe86597be6a74bee251112a6a3f958bd09c9aeb5600acb83da6b93f3fa5f8659a3cf2fb9c9991dda9f0d798e88dd45a6aa9ed86747cdd3604237bad9cdaa2426cf3eec6dce9198ae72bf6f58fcfa2cfb5feadd0b9819443a11c97fee8c93a062f58f758105b5f23c1898e3045e4c62f55e44860d3255d851a3dfff492a15a7860ab46916d37dd3f60b713ef92e489b77da6767717431226304c19860b79c776b797e83c65abe7c1bc1a2ce188da1d6c4ce444a63708862de34d99e5c4cdb3d35c36130b751e935fe9335c555c55693308037c42dea27924cb391bf45c427b8fc966f4d1fa72e48278c3449e45f71d6a6798ddffbfedbabafffa378012c6367fddeb097bc4341565045ee95d3ab5dbfee14f8ae724d0827fa7226e37ef24c96bd8b3efac033185a8b91be31f2adff36a80ff27a02f2adb8de9a5c1a54e8fe1f628871e9a9c7dfe166d9628bdbf7f97a149c360049b24d25e26f23d38153e1c6a34cfe32cf2c1163131d18d1b4eec2536580454395957eb806ae328045e5d762137533d1958a94545a096cde5377403c8cb67cfcaa7163dd72a76bb0bc46bdf187919daa028157918c905a5302d0906bf81b24188d1a33190a15a629d437956520ede9543ba12bd5818f205eb43f225762c39df2ab43fee6527e8ce3e222f4575bd2f86aa95cd3eb338725ed291987e3cf85130bf49bdab50433a5e8a10f096900a73114c429aec5ca2732f98e21546588d54d271ebf75a5504df0c13185b29e32820a473181480933a083fe01d60264c380ca4c8825010e5945d09d0dd78c950ee401e8f0d6e1474aefc9f40101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03590101ffffffff02d0dd060000000000232103aa5dc719593db872a724acc3b73f1aa9b9f27ef495732a610ba1a1c7a26bbbefac74b701000000000017a914ef775f1f997f122a062fff1a2d7443abd1f9c6428700000000 2 | -------------------------------------------------------------------------------- /src/vectors/block-test-0-000-010.txt: -------------------------------------------------------------------------------- 1 | 04000000b6088ea3f1c30d9ab14b551f18fca4441ff4fd8f723b82bb901b981bcdcc35022edaa1188cc82324f77ab6c52d37a30c2837b4c985681beae1256c321b188738000000000000000000000000000000000000000000000000000000000000000081971358ffff07200100059812b5da85543a1c4f96478e93a3b4184d3bce80d12d78bb5e6c660000fd40050021f6b9661ee6bfa83b25520fc1df97c3195e9bef12938416af931943089413c5a4a087a5e206966ab5164dd67522722751c2c5d495053ee06e3d6a5bd0a618ef0a831fe75f9197dab273a2c6a06e3f971d8a9a0afced428e66788b8dd0b33c5a5f8ec6d23078d07c20b10ed32b332cddbb3a064189c075d2013058d8780bc22509cf247ba330688798624785066e261517a610d77a3ead8b74dcde5ad6106bc933c9c4213dd03214746b7fe9e9ccebe69163b72fd759d0febd5accd023b451f32763851bfd5f72a995f0ca9546301ac5ab24c13636f49cf37db96a2375f8db3fa4fcc59faa1766f3e5518aadac05acb6f6a96bf69cb263a4deafc014e09332adb0feffa613a5066ba8e2eeceee3916a751ea0dd1f46b3c89a2a82547903dd4879dfcbd682622d96a55e7ce956d4c01f7b3af737d9e6bcb1bd6b527aac51e018aa88dbdfc3cc45071036fd1ba1f1b4b03a27207262b13855a31823980a5030d0c033fcb532ee2a626d714e883442ac6082fdd6126808cfee394164adf1a26cd01df48bc05e2cc627b5398dcde9edb242486e9679db29334b5e5aae5f8c6ae10c1dd9df305e827c6a7887bf784d601a21313c37e4a309a9ef013cf328e17dd41710839926b065902e49f39e8c67b0e88a4fbbac3f5cc5b15b656c4e32706395857fb3a2da5a6b76aec7b598898d7687a692f1af67c9f3e370d3c42c06c3d1e37fe2a547de0df7aa9e70e51fdb432ebab7730996cbddd01b37b5aa87026c3f2b7eb7f1ad4ca08d691c7fff8ad77e1efddeafed82f3835363046f643c5b34ff9f581971d8dc26085cec7743937129cd2e45a8fc47f5b05418f015012ca372c74b5902ba95efb568f47c484c594c920b5b0a39d0b1ede7e3a169d9daa6303738276b4be2e5423c2253b7a36844c627dd4189b70e73cd04610fed6dc7246645e5de10109fdb2d7d469b974ca406a1dd928d82890d64a5a19fbe150bc2834c7e22012b9eb5d3644c361da345b0ce38a0e87edb1bda4d665f09dfeb285d9c51062f022710fc6fb4a51d14557456eccea5a464c3d1a924c0af371e9f8573fe12ac461aab171214de83cfe36610d28a3feca0e9712adf1031b94e9d2170d0199cd7d10ccbe569488ea824c1362ea2a7d515186bf11b7eb4d7d62e9b5d7bdf76edb49897160d076dd64b8b0910266ea9a158a50f5be0923c5fb1f246aafa17cc25d113ec4fce458c943d68462c15fc10a39d6a7fefed0336c2f09d216f84ba3216504903ac5ba3920f943703451d7a4855944f534cef72abaee55fdf7c29a224f03880999f91a73ab594e51fe7f184a90d87648bdb52585d18c264ee201161732b2db56acb4c3401df3970574e5493acbc82a8682e59f136a09176e547de26a1b3863351787c581760081b3dc3a1fa517f6162ef305845a8dfdcbf948a713d1b49bb103657a38151cd6320fb6a181d48c5ff88063dd6cf69de225f0361d241603596ee3e647dbc50b559a1e5c35c1cf6dde923226312faafd3be345ed5229bcf4fdbe0f0191fd294d08542cd3cf095e6ad52e81796a225964614f189aca10186d975ed9d65d8563b1968bca4d41b5b69fa0de1f3e49415a24b97f58b3d2b6fdd0f1fdb6c2de99aa2de3cfa3e5e9503b60d98790956c16fa3cf2de56570ae43d32dcb20fdfa286846a3d348f9b7204fbe7930c2e29ae6dd2e917d457b26c187c36e282a09efcf918286f6920604843ad0446f3074168838bd97ec08874d5971491dddeadd6060c12b9738b8f0964f9649e168ff378340c780bcd7478ae32dc74fb604fdb22a55849a7f0c763207d6a35a9071567925a4c12d3b3a2182f38d2c8882627ab9dc18f0f9077e972c1306fc21d90b9c259235677ac31f60415e20bb65426d5308a0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff035a0101ffffffff0220a1070000000000232103a42464b10ec3441beddd662ba25898dd244e7dacc4f06c01a23af82decf95759ac48e801000000000017a914ef775f1f997f122a062fff1a2d7443abd1f9c6428700000000 2 | -------------------------------------------------------------------------------- /src/vectors/block-test-0-000-001.txt: -------------------------------------------------------------------------------- 1 | 04000000382c4a332661c7ed0671f32a34d724619f086c61873bce7c99859dd9920aa605755f7c7d27a811596e9fae6dd30ca45be86e901d499909de35b6ff1f699f7ef30000000000000000000000000000000000000000000000000000000000000000e9851358ffff0720000056c2264c31261d597c6fcea7c5e00160cf6be1cd89ca96a0389473e50000fd40050053f4438864bc5d6dfc009d4bba545ac5e5feaaf46f9455b975b02115f842a966e26517ce678f1c074d09cc8d0049a190859eb505af5f3e760312fbbe54da115db2bc03c96408f39b679891790b539d2d9d17a801dc6af9af14ca3f6ba060edce2a1dd45aa45f11fe37dbaf1eb2647ae7c393f6680c3d5d7e53687e34530f48edf58924a04d3e0231c150b1c8218998f674bc171edd222bcb4ac4ba4ea52d7baa86399f371d5284043e1e166f9069dd0f2904ff94c7922a70fa7c660e0553cc40a20d9ee08eb3f47278485801ddae9c270411360773f0b74e03db2d92c50952c9bd4924bbca2a260e1235e99df51fe71e75744232f2d641ef94f394110a5ad05f51a057e4cb515b92c16cb1404a8cdcc43d4a4bb2caa54ca35dccf41aa7d832da65123b7029223c46ed2a13387d598d445435d3cb32fdad9e27672903864c90d86353b162033078327b5b7aaffc89b40096ae004f2d5c6bd2c99188574348518db66e9b6020f93f12ee1c06f7b00fe346fefceaffb1da9e3cdf08285057f549733eb10825737fcd1431bfdfb155f323f24e95a869212baacf445b30f2670206645779110e6547d5da90a5f2fe5151da911d5ecd5a833023661d1356b6c395d85968947678d53efd4db7b06f23b21125e74492644277ea0c1131b80d6a4e3e8093b82332556fbb3255a55ac3f0b7e4844c0e12bf577c37fd02323ae5ef4781772ed501d63b568032a3d31576c5104a48c01ac54f715286932351a8adc8cf2467a84a0572e99f366ee00f82c3735545fd4bb941d591ce70070425a81304272db89887949bc7dd8236bb7e82190f9815da938cd6e8fec7660e91354326a7a9bfe38120e97997fca3c289d54513ed00286c2b825fbe84f91a39528f335674b5e957425a6edfdd00f2feb2c2df575616197998c1e964e069875d4d934f419a9b02b100848d023b76d47bd4e284c3895ef9227a40d8ea8826e86c7155d6aa95b8f9175812523a32cd611efc700688e03f7c245c5bff01718281b5d75cefe8318b2c08962236b14a0bf79534c203df735fd9cced97cbae07c2b4ee9cda8c9993f3f6277ff3fec261fb94d3961c4befe4b0893dcf67b312c7d8d6ff7adc8539cb2b1d3534fccf109efddd07a9f1e77b94ab1e505b164221dca1c34621b1e9d234c31a032a401267d95f65b800d579a2482638dfeade804149c81e95d7ef5510ac0b6212231506b1c635a2e1d2f0c9712989f9f246762fadb4c55c20f707dcc0e510a33e9465fc5d5bdbfa524dab0d7a1c6a1baaa36869cf542aa2257c5c44ef07547a570343442c6091e13bc04d559dc0e6db5b001861914bf956816edce2a86b274bd97f27e2dbb08608c16a3e5d8595952faa91fb162d7fa6a7a47e849a1ad8fab3ba620ee3295a04fe13e5fb655ac92ae60d01020b8999526af8d56b28733e69c9ffb285de27c61edc0bf62261ac0787eff347d0fcd62257301ede9603106ea41650a3e3119bd5c4e86a7f6a3f00934f3a545f7f21d41699f3e35d38cf925a8bdaf2bf7eedea11c31c3d8bf6c527c77c6378281cdf02211a58fa5e46d28d7e7c5fb79d69b31703fd752395da115845952cf99aaeb2155c2ab951a69f67d938f223185567e52cfa3e57b62c790bf78674c4b02c12b7d3225fe8f705b408ba11c24245b3924482e2f3480994461b550641a88cd941d371139f3498afacdcba1249631402b20695760eaada5376e68df0e45139c410700effc9420dc3726515e7fcb3f349320f30511451964bd9b6530682efec65910ceb548aa2ab05ac3309e803161697213631ae8e13cc7d223ac28446c1bf94a19a8782ac16ff57df7ee4f10fb6e488c02c68d6b6dee6987f6d2c39227da366c59f54ff67e312ca530e7c467c3dc80101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0250c30000000000002321025229e1240a21004cf8338db05679fa34753706e84f6aebba086ba04317fd8f99acd43000000000000017a914ef775f1f997f122a062fff1a2d7443abd1f9c6428700000000 2 | 3 | -------------------------------------------------------------------------------- /src/vectors/block-test-0-000-002.txt: -------------------------------------------------------------------------------- 1 | 04000000238d665a062b9007836a7d8f968ba2f3847af5f542733389a952cf9b86795502d5b3ccfd5e7828c4b2d221bae3178c500e21d33399c39a2508a0a82d53c022580000000000000000000000000000000000000000000000000000000000000000898c1358ffff072000005b7d311fedc046109a4482bd0eccdf21a6089c99fce43633d03352530000fd400500282db5ba22a2c122aba42724a991196cd01cc2b7013318f691d61171db79202d2e5979b0b6d5692cd129eb37b7541ff51b7b2a840b45273f86d2509acfaa39183bb3850f9916ec8246ad9ee785462c9cda0ea202f12c7ccba8cc155096016a87a6fc40a6a44f5f3b309a868f30d9080b128c75234a6c3d019fb7fbe29708d01daaab84e4891a00d15ffdd9a598dd678899102265e2090a4b7b26ec55764febbfd1f5fe791aef8500619578359cb2abfb19844981b5c0e67e86b6549765bd638f3a701b0fc8a56d00c4efb87b55883f1d81012e5b02d44e10ec9bf620f20ad62ca549be934d11103f9142b3201ff31b7791c1636aaeadc512b2c0f801a64959300d5d526c2fc58e2838162659fe59168f2214b41d1ec963d543e2660ea630d031e2671c8c841fa9c1b58f7475bfe958724cf89c038d29306dc0043b93148ab95f6e99017ec5863db2f9130c48187bfd013924b34eaefd1db6b2818f4f6a604185fa774c2c069455c6eb195a5382403404c47db312b29b5d84e44c4a645f06ef7899f0b78962e95d5476bc989dcb9458c24733a435b151c9fb46c32cbebd3318cf3ed7310533cfa0b087d58c687505070858765968d1f7077f0e6dd75ebf15d74bb8c7466ef2f4974df537f5dd64097f4adf8b1cff61c00181ad26f94cca9c01960db31edc454c1650da4f4390b4aa0efd46c9ed527ed8bb068494285da2680993e8d355caf6a0bd42a4d681780db2312eef129aa76c6081ceff231311ea93fd65fb0b80df077c49ca69d8996b1ebcfd74bad793d7914b4748af45f31ad018dd12997bfcf809df3894bbd7a10cc585797915356389b744f71ee58ac25484b635fe12a1b20909c97edb1d7ae20b1ef1c776260ebab06e16a34c2789fb5555ddc954cc38f07ac551ac3b46c1370f4fa1ba22ac35d82134eaa0d3f3198eeef723030072214ede447893a03f706d389ea60ef8a81ff7e20ae920cb1a5219e93e1bd2058df6296dc8ba39e4d403199d2c209c3d6b3b2d462aecc04b0e248e7f1e9d204a1f6b7df51073ae2faab55be3353713743c5db40555bebda203a5c79fc3324cb8fb7629c9391931a1268c97fad6d6ab7ebc583330356372df8c5e9e885422c75ab956932351858452c5904a5aad3d9fd840e356abd580479d9a531cda6de08277008bb3a1bdf08604c1df0d8e8cd475cb455161bfe5d9710bc0dfac4214afeeed4bca6148b54241b8f4ab221502917f261705715c7388d9e91d3685a1f67c484a24b716912fe33a89ca7ebc3a5b77e6b36657c7e2b2e1e8b2b699690ca749339b7b2673fb1083496bbc753fdfc49ff6e921c5e35f27d9175afa7da3793d6a7ad5eb0ab8c9ee0e60a8ae438e3145cf64b5f6417625ca934b9d7acc150efa9b53618c15b4d8318be5de0855696d92ef2e018d97ebd24102e824bbd040aed958a51016a91661180e64932f2316b5ee55923263d19d293996118f511f22912e8c9c435af630099fc47b7c4ab581b7d760418314c0e6e814b3bbddb486517436d56ddc774701072cd77beada2cc94cad87f3d1cb83db4da05c2e141bc175bc8848e548c27584995333ef1d9eadd8d7f028cae60d5b0e9642db7245d388ff58c6a31556319a29c0dc14362b957b6220a428966d66d6c3339a12a503806de7dc4c4561bab6968245e41b27ae89fffbfd1bd2f4104d074f0f2b759576f9e16c0e2f73bedc7c0e0da97698e07ca1d729b113938a6b29c3b19d9b8225303f80d6dc0e39bec343ac9a2d23926ab414666e079e68745998ff73dacbe1645bd004dd06236f615f11d47e9b2d54cf8573d9a295bda88af4b2c6c603db0c0757485a543725b1cc04f7e9f38445ed875ec54317418600b4113600e137417e5d1fc333159a7e365d0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03520103ffffffff02a086010000000000232102acce9f6c16986c525fd34759d851ef5b4b85b5019a57bd59747be0ef1ba62523aca86100000000000017a914ef775f1f997f122a062fff1a2d7443abd1f9c6428700000000 2 | 3 | -------------------------------------------------------------------------------- /src/vectors/block-test-0-280-000.txt: -------------------------------------------------------------------------------- 1 | 04000000401689162e846bd8689cef63c48101ce2492ca24453be253e3d6541de6b410009afaeea8a44f1f634c1cf6925c0494bdcca95b07501621627fd859e88b5db5cefbc2f4300c01f0b7820d00e3347c8da4ee614674376cbc45359daa54f9b5493e553e825b58bc111f3500937faa7f5a52439087e33d01f984c4f1988f45e31cd5827eb84ee2860000fd4005004317d1a8a41c857e41051080f92dcbe723ffe9e11b8381a800174d04e0a141e5d77b19b1cb3af40b8813de1fe6a266f8598dfbb2ec26caeb01c5363055d12f018b561edc46e3b9ebe5ad60cb7d0de48f54d60c025db73b4888ccc352e1707b8ca5ef55a312b3b039083184df9113074b1162a372fbe39855580abdf4650f95b0ec055be20d72f20260353704f9f22d9576fe2cba0f675b4ff45947c3972f3061ea521c5e38fb4e00e2ba1b99820f10ac3fc2a79575404d289f3027fe160eae5153a384df86f1d2c266e7e2c6159f355229206affbca0617cf71e8ee50bb66b50559b2c79ffdb2f63c1f7c518ec71e6e7244c596294bb3699bbe1b5016435c2ad9875672c96a18dd1c94d917b0cf25da411fa50a852103cc9608eb4a7513c5a326097ff12871710132642ebaa47ca5c62c06cb46669e2245d82b42a640eeeba155d43bb8e73fe65aee4b59bd978313200b6bba095b438dfe56e04a16756a3ba5071ff4a9f038be859e8d7263f8b8ce385c77d503a512adc68350e8af79aa31f7e15c012b90072fea702cd763d1c1335855d7490198b6d6659b67372ce665e61617a04ee0e665b3a6b99c5897a00f17adb4b965913938dd5b72a01adb63f30076d939d458deb2e1aa5f24c5ec6cc2392d56a9b736731fe5203bc299ff875b7301a43ad543bc6debe9ad53579325aad3bea5856bc61d7dc1500e2355c83235187348b10a78b3a934e526cf451c909aa8523b6e59f97b90042a9949fb07d49735f5aa83734af6df9600881f014542154411fc918ebaa696a432f6227c017c4e136c2e64fdebd265732577da410076d9a89c5c90f2c729fa10a8f6bdc2ab03136e70a0e6525e50e0aafbec15ae258b7bd9c05ef49d409f823643f4093b958f1ed171603e46648ce291e17244247d0a61650abe377963c4501513a7f76ef5bfece840082570b9fe86b3dd95482ae013b7fccbb0d2a9ef7242fa78efce60eff4521390356e965df2bac7b6e6e025cbf7d80dff0fb6a00679fd0f3974b4844dce78a15139cf789632a5d697543438276aed555dadf072b077d3a07c6176a1386fce5bfc5f66171ac011362ed1059a57e9489e798d3cf156d0468fd9a0c66b2ade41ee1b4744b8d58eec649f63e8962fc0b0a647d30f7292955e16f68773d735c8ad67dfcbc3af3aefea79c12a06dbeaf6c19bdeb2da68ab1dd762b69c93fb9f05f1496a0aae8ffdff42bd693ab46d44abcee7dac9f21d2e7aa8fb5cb4de433e49c684606f9cd15b3704a2a4b6e3901ec6ec7988f0609564c68c5fe4356949013fc63442bf94653fd6e88b2f0ecfe56b8ec1b61c93d18e4822015b80f194674e91f5c4033172e3d287c15523223c3d85ebd21e4c746a1d113b31e849a8a8c1fdb9de871536ee54eeba771f9d508f62d47d3314702e91fcd0c80e661946cd1e4cc17da2bd9eedfd53823c78ef0fa54c23369256283c1e63c30ef82f5f35e0c672b7590cccd08c671b29eab30a5e1eeea8faeb9131b2162a6517011801702017d46ab0da1ce7ef9310f88c2edb2da271b34e700fb1bbe8242ab31f671040f9031b19dc9402f3115127685b5fcaa3301957dbc151ee0b213911a2ddb69a7a9ef55bce69687f6fc8e35ce47ce91e33dc3d6b6e5c4a12e63b61859d0de31061f55251a8951633d8cf27b15ad18327d4e5b3d0313e134dfaa4a0bd1a1fad1b6cc42d706a621b80fbf064e68be60dce9bdffb2741199d3302ae4b1d7c9b00bf7d4bbd7cc02c6c3d66261f2af2b3cc6c2ec29200af2f2f1de143195163e00b6fab4a6b893a5ea1614255901976d1c9947cd5405eb915f6b7dcf41f023b934beea10cc14a439fbe235e959b9339e3690ffd0e33e8e72a67a66f3e1866eadcad472c78f95ff9fcc93010400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603c045040101ffffffff0200ca9a3b000000001976a914e3618ddab5d2f78a090297c8f2152e15e3a3e11588ac80b2e60e0000000017a9142a71f51b268f74eb3ee5631090589bbf6239bc0c8700000000000000000000000000000000000000 2 | -------------------------------------------------------------------------------- /src/vectors/block-test-0-584-000.txt: -------------------------------------------------------------------------------- 1 | 040000007347d23b09fe698ef82338b8d30edc7fd3592d9fb2c352711ed698dd89f25a00f6d6cf234c52a42469a9ca5d52cd12f2c532282d947c431b545802fd4ba79f9552d65fd807f79e306c607c2724b0f547c68b1f23865df805dddd6c420c6c7413e7f8635dffff0720010085099b4583750a062e34e1bf7c1074de4801627e8ee84db8e603ee3f0000fd400500345110a8e8a1dd9eea0661b97489d64ebc193a386c35c469691e99ff554168351cd5103793d3bf4cbe10672ae5f08728126c7dc3c2f37a8eee7fbff65cc82b6be64ad44f7bef5b29747e8e453ae559109f7b6104a8af9039272725702ad2cb42bdaeacdac6b37e200df1dcf2d9d448b59f8121386dc714f72b15fefabf686a66f7992e43ad83f20a8435646446f4ff782b5c7b782cec39b1dcf192cda85e54580bcec95c7d15d4022487d93fc3156b53bdd045ce2519a433fe56f04042a613d28dd7d57f4368c9a441661aa6852f972e8d170c0f9ebf218e67c3039900ddd805aaf5549d57f71790837bec6f7ce5d7c442f15f3bcd1e0dec9cc9d7082e70d8458b1da8652f170855ff7c8b8279ff9fdf57849d76046c4c65bebf55e13fc34836d1bd599bde15124da8f292e8d507ecf218613c1ae53c2b3971a2207d3d271f4ec6cfff55c2fb9abd0a7677889dd048006f91467f1baa57f941ed9f1e7617276b28de0f410aded4006417fa6fc8c532e81d1e59fbb29cff5c82175388ef20ca97d94c61f22b7fcf1f0521aef79bde3583fc00ca8ebea9090cb78ae3bfb1a298b31d1e9303a53d41f1518b534d7a2293267c087a663555e1d92cdd02ae84ce3b9ea5a8459a683121c694ab78d66a1a0eb433f787f550f6e7749c43d9abc61a635af6b624d3c219b35dd0f75ea5f263d43579cd283ecc12a403e87e1fc0497b8d3caee22d2cc658896fe0ef40b41a7e7b6af98bb0eddee5a28c55de257ae7f518b1253fd0643dc8d9d319a48c1450d7d8a0aa1e827159b74a6c45ecf859bd23ecc7f5ff693c41c9d526dfa5db050ccf97d199e8a7395288ef0a7d94fe5d931aa5bc3d8fefc7ba573091c6e8d6e1a3d6dc9f4e70db3b6e10dee7507505ff808c6577fd76753ff2a72f1b452b1a9e49b0d90c077d359d78e319c7d24e5cc27e545c0063feb6389c6165bca412f6221a2bf0c937936f8b0084a5759f0fdf66cfd52049347733b78dfd5ce39d1d3565c76793beb3772332fdb14ac2da143352c01552cdcc7ae4e35f5b4e65d87a634cd55ab053599ac90394fac4296ce287c7cbc43f4a551132e7cbd7c1ed1d6e44612893c4dd23212345a5e0d51ba9609dedcd0f76e60f2b0dcf059b82191cfedae8eb0c3a3ce5a91960a47c6817fe2dd414e1cb183c89a5955df6eb21025ecb9b94d4d0f77d13576a2962ee42696275098617a8740bbe4d40a7f88a0216de190be4e56ff2eb73051186fa29a7620b56e262af36fc30759bf69371a5238e461b591364b1feff93db986125d20129928371036adb1e14d5b5af039b361e56e3849616d73217a814d3344303c8e7c94f651bb6e06e561af0dfddc5f61d5d3ce3601ba01f134d23830bf1a9222f24b2ca5050101aaa95d70a0f61a9a9a2e8fe344366435f63a003143a2b86b38d63c42e70a52617187c564c9b0f7a2ba25f313b6c07f7962b6304d29dd2719cdad7e4320500bf7e7043f7ac480461f8406e49ae5f0afbfeb31ef105b15ecef869268bb90cade31352c2e9b6d3070a5294102815e58ed60d231987bfaf09c855fcbaff23ac7555082aa5b5812ad3ce352d71fd00a4b24e7e0e11d5da6c0c065ed83a617ee16691dd4761fdbe2f42ccad1a455d7c4324dcd83b6b6bd17ed197bf0cb0033e6d30e549c04c87a584d31ea709e37a8c5ef5a2065b7a152597ec7d7baff19b4f4aac82a8875f382605669874ac2241c568dd03422a9fe9610a622edd8006178d362315e1b6f9fb60e91d882a28d688da815006347235ef455b37bd2c63735b78f10e3ce8b71bfa415bf3869354a9f0e50bd59051cf44a96a391248df16a401c163da94a7b6d851bfb595b2ea3649bf32c11b239eaa9f15758ac909a2230e4524614b7c93b1a7010400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff060340e9080101ffffffff020065cd1d000000001976a9142e861a970eaaadc9914dcc1c03e6f7ed88683b5788ac405973070000000017a91453dafe420ac703a7ec9cc3f593778054b459a8318700000000000000000000000000000000000000 2 | -------------------------------------------------------------------------------- /src/vectors/block-test-0-903-800.txt: -------------------------------------------------------------------------------- 1 | 04000000fbc17e4de96391a456c659fdc97177247afe246bbc878df518385bb0b09612000d0b77bd49621afb12977f34faa006535e076ca3065219048965aae9c0a86bd90000000000000000000000000000000000000000000000000000000000000000157bb05effff07200c00242462fed51d000393f2a3e1cde696e9b2b5f7c919d5dfa358397b6b0000fd4005006444054102c3ee24f8b3ef5ffedd31f84833082405348abf3a0df56799fa7712d04bd4b6c4eebaaef20b238bc2d291044701f51400d57805159a6b8cff9462a7bcc11ba62a0fb4bd167a3d54595f03205e99d801fd974ada469bc679c4087e9873b9f69470d6f9500c9ebe35df49b5c1846e0176e0c0944598a6dfa4830513c08a4c81b60aec7ffae13277d64311175a341e1282fc35889cd48ffa4a958026430f39c82171759f08d9e4a1c166fb5973ea1b5a02713ac348b75ef4271cc8d6535a55a0dedce24221d7e19cf5522a116a6c0a56333b4bca439ad6b434c85a41c755ad317322c70f3b70d93344abe12c20c232644f87ccb945773c870dd8afd71511bf73e7cd679942e4bbcfab38fe6c881c9899e7bb17176d1b0892fa43ffc032f6fb7e587c137538d018626277ed3b46b07963abe2a6895664e3654effc4a85d295f663c98e8aa69f0f2775a38384900dc9ae867dc6a6923b741d92be90fbcc9fd99be0f38218a33382eb891834c467c12f077e9a770cdfe4205ca251f524ba930c97714859248be35ec6b77065516d77f78cdc7e639f196a88d2d765a4eaf5cf624e00b534e77dd14e129ec8f32aecc2fec12e671fddbca1140e4020084ec8343a995ae3a2dba9ea3c03a27a31d81bf08088c6fecfd622208baea1f122db67db6fc1ec9adff8cdb861ce61c12742f20689ad7725f8baf026f30c5a651b31716fec25f79c38673b21f5e27e6074c174e0b0fcae3a0f1d241b54735a0e09655c2170ab680dafb5707039c23730979ef1e1d23808feb5c321186726a9557c337bc95b3576f59a985b83eb0bf0825f043a25731055c40c407aba3784599f1f5f9cb0f59f1723f5cd251ce045244334c3f9518a50c4bf10d4de44fdc9b658bdea4796dce75e27685df9690a24a2c1b231514dd9decac95a3b0d5a3ea9e085a4b1b009f5182d000f24f8bd4d401a67f10c625eb5736360ab3e4aa731a965d13c8d19b3bec4a08bf696c5b4a08055bdc9358d0d904c7f5e6f1487ba675ddf4a69e2327cefe20a1bdf1f6f46326e92aa580d43e9f560909f4e480f3cd93b110ce6648e639a706c1281fc5a2123c7a3ebc9694fd4fd2a1613a9dd2020e33bf252d2b247e7761e0ac1d58cde38e61abd92928d6d2d7c82e689284381e284948803693b0b61c76a53d38a000016458c011dbc379047950dc535fb14d37acfe5ed9238d76bcf19af820df9884d0a8f89ebd6e563484150613d5b852d927a77f68822b25ffdfcdd04d950e0b1efdae45f4764dbbff801222f966851b097799a899026f2c500062fc953f7fa5c4de5fa87db5ac1edad123abb152838d0616b2fcf79ec85a2ffe444b55128f03dcc4f730238be5a89870890a6a6fa0efd18bc02c2a28d5604f0f6a40845183f26e3c495a48583cf6a400f99ecbc6cc9212a544325962241f96661bd5f5ee039aa4132582aba86372a8789868c1c231e55b2a20047caa1116940a2faefd330584a33388da499a26181a9645b10af7ff51d78441d912a14a52fc8b9c9acd03b0e0b49256a7d3f6b046ac2bf757a1bf789cb71e436ea7e222f3ebc3b354847af459047931d252bb3412e5eaa12daea9f18ce1f2a3167faa69dab4b4884c3288955eac5d81310a9e55fd6c549155d592b8bbb7081ef26331e74911faa7e3d9c5a23352ded81dd4df1eb7f1a6a85c54b5f86d631d2ae91c056fa558718c1200638804dd4a0fa4825a21da6f04deddaa77a8dc20eba3bec6a09e673b36a443baaa27316ca88f1a9c09be536c6956df9778f0d394dcc43ab26d7259a087141fd25dcb6402a7845b140b2b2514c9e948ff23aa23205fde39daf1ff75301295bf424595a615551af625ac3de0e34fa6f4abddd8fb1475e6ce8bf79b2d29010400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff060378ca0d0101ffffffff020065cd1d000000001976a914d4df035521dca6e49c407c2928e074d2c27e62cc88ac405973070000000017a914c6fb0a1ea5887b25b8e1a7b71a1e13c736953a1c8700000000000000000000000000000000000000 2 | -------------------------------------------------------------------------------- /src/vectors/block-test-1-599-200.txt: -------------------------------------------------------------------------------- 1 | 04000000ed34eead710be6b5da373edd2e5515407a0d9f5f2b511f09f1ddabd3764e27008039c40bd3ab96700f2242b1200608d4a1c74264bc71b3f956a2339b4606957ba7d9248f4d9300eca388220a40b4e98ab8f0459af40d74dff3bb07a9691df4487e365d61a94e6d1f2a00ab6acaf882cba8e6f322ab507bf2c349664286203c6f194b1708250c0000fd40050008feecb65909d17f1120a372873dacc148305f700e82b5b34e8898135857f4a4f3cc568ad4d0bed15913e4461ed0d39e56a038737e664414b53b5db2d763666c1d99096ce943ebc9c9fb7fd665be8c049b6218178d33323a94502f1afa91eb9552f254b9796afa3f2a80dff1dc1dfae350eb733ee840512630fef7658525ad7740256720d1a469725d0b2fe8f6a8161807843933a43b83245073b4fd161c38cdce0ac57bd9629e0ce5961d33848464ee2fb1cc64529f1588820e06e62d52a476721bdf95add623872aef7cc13622b58103182b969f0d66b38f8351f20d0919e3a111e3f74f843746877d349a75eed5df74983fc3726e15fbf1bcce1054e38a2096ef4fc087f10c6fae6fb1a0fd78c4cd343bb3f52d939d36d204e3b55c3e26599ed2dcb2ee2b541195d06757a58908a536c04d83b67581bed02e3f72c584122ff27dadd6657e61b5375f092cfb4cf700785920fd8e3b92f58ba3d98be05a799257dd02945c8c9b0637a5d39bc3ac16d491f7355af88a9be77212d248bace52cab1dab316e83dfa7ae208a532d7ab16157abd0b299b377927d3db496e832db82739b9a91abeef2ea7942c2f1893b310969ff051786352f5884e8a572c8317d659efe1089f7a4e68bef3135ea9501e9b15c4ec91cfa1c875435cbbfda4421eab78f0d42439bde9230965617ef5c5b099fff24a52d139d07e07f1388f8caf6e25a7be438a487ad9b2f46e3d029d3831cb9337b6fcb3bb53c4ee9cc79d4b894ebd02d60e125f220c543ed1f5adb320cc3da5026bf41ecc9b15497a39662a66315de4f488f2cbe4f57d4b0d14e80be341a3678ccfb9aa1011c86934d8619324cea3a71768a37dd2ce37d59730484e5dccde868ee3fc2aef1befe9c117504c01a6325a5de16824c70e73f9267844666bc043d848fd72d7289a83ec15a2c1eefb08cd0085657436e2fca122681375d86109d986a6b6a1d110cc656138cd90b89422aae8a27da27b6e7abf9dcf0b21c3370cf453a9eafe12331a72a85338dddf45af34234cd3f50db21b6c5a67eea8e2e4965618bf9f3e0abeec58e91d4d55b138416743b3f179496addcb7633b9dfcacf128ff17e471368c5e7469246145e690c0b54f492d4c50cf7d40f80d36e7b25607c8d2a4bfe19073da2b82a1ce3ca6ac4fc12f6b4c9ea185ca61401cbfd34f09f1cff1ae9119c9fc09d00f07c56e01e0ce223c7b1c7066accf135917ac60e79f2a117c72e108012c5342f79c9938f32f1ca77445cca50ef81dc2a95ad681f4c6b44be22e4afc8bc7b11718b58ea3e02b4a48b4a8f90cefb4b804443b6b5e83b0af55a7a1ebb7b4409cfa2134c5e291dd2750eaac6131c0d4710bbda6d9b4ead5ce43681cd7ac8dc27c3081e76775f959f2692a1c5d3e8bd0629c54cadae408bd5149a0129e966b8091e2eba31b294ff5d47c0f91655691917ef791dc912f5c78bc373285d36d57ea68f9971ce0bb0fb4e4d1970637a8fa24dc858e914bfbfa9953320dfd474a36bcc39ffcff67becfb7122c2885a0aa605b09470e5456d349b5eb32685fdb460ce7bcaf75f13b927536de5a29530b6e27da228736cae7abe4a490b358c38424ce53ac123e419acec6aa1c28936b38d10edcb2c2790a282af0d8302fe734d58f507fec0470410fd20bb615f65f516a0921bd8ca8cc494ade81313ac0b2411e24d57d3f5ca742dd91e12ae27dfe74e07f9fc1c1b26ba7574d544095d30495efb0efe3eaf438c2e1c6b1a635f8ea4d4fd362948e20d85de15f80d22a3e8815f995f58bce5afe3cd79918dcdb25a09160a1a59d1d0d33b6535a1fe58920f06cca21d562716b2ee3a91df66639bb4921b93ff4b58bab61668ab295b49f77367d513e0c9088e55dada66fe65dc7d5601050000800a27a7262196513700000000e0661800010000000000000000000000000000000000000000000000000000000000000000ffffffff0603e066180102ffffffff0480b2e60e000000001976a9146e82b7c719334e3aac3916872a088b548c43c9fb88ac286bee000000000017a9140c0bcca02f3cba01a5d7423ac3903d40586399eb8738c94d010000000017a914221c95f83bf073cfb76724ed23af737c1ee3bb498740787d010000000017a91471e1df05024288a00802de81e08c437859586c8787000000 2 | -------------------------------------------------------------------------------- /src/vectors/block-test-1-599-201.txt: -------------------------------------------------------------------------------- 1 | 04000000da438160fc9c57ca00c5251e689f9c8b21e1f84fcd645b95c0a85e9948e43100f6c3f2ef2b6f542ad77967e75314f45c509033eb887b09828ff9137c2b73ef4752da86ea6380c2353ec868cba9a16edb3b1a483a2d084ae76ab4404b2d929b048f365d61149c6c1f1c007c736075333a175f237e246890a5906e0c065d4adfedcb16fecaca250000fd4005002699b2262a65cdf85c128de3aaa471314b5c2cbb0e307fbdc2046603836141116974b80890ed989b7b19890642cc641f17fd31b7a92c7d74af40cbdefda224896b818b9baad78d22e2fa0ba66f2dd7251fe241095ced9f9249c4d7955392c4a228fa6e6bfb973c6e0d632ec3bcc389eaa15bb3c3193536862f4071a3b92bb0f227c10e3120f9b42331204428d6585b1abf396dda3c47b31ef2a5887979ae9a7efc534c7c3c3cc407355da6c927245f6ea0e15f0d1d7ed13b73d48f8021b415f63d93e04fa3a4644e3c529926a4d976d81e13a7c965cfb7e1b9d812b7c01af0bff6a4aa9ad00819e92754c1d9c5c12427e4434c2670c33df93d6eb41fbc7684220ec890a62a93ec3e4c7eb5329c5cece735eebdfa670d97c50b6689b9e0fb713f8e39fdd8e4223e3aa8abe23de3e4fc94b6fcf2708930abf88aac55ca4dd5f2d80998c678285e7ad52186ffef7f229800a24f98f9d8b0995beff6e980d17f4e73f9da5b3603da84b20f876d57eb53b3eedbcf17b6cf561e9ccf2f41570f009e4131c3f983d81ef3fb65a66d70e6114255c5b46559b0fdca9c4a51fee1c146d117fb235c1150d22b2b45848bec53a5fc7bb1a98757003e0f2c49d98e949cdd69b7340be5df7ddb553668425efb6a123bd981e425cb29e96d31898f250815c644b9365d1835316d520e234ccd22c8c459488fbee8e6fcb66202c83ed96dd298933a32f251ddc96f69b788759c7d1319baada1924e137eede59400512bea082a71ad020c120aa559c72bc7690ad163b7af65a1dc7d1d635c3a78154f57a5fec9ed1327f9d2f6749a0a08d72a4e05e1e840e82b3d61e15cc3684a4847f79cfdbd1b2506a998fc018c636c6dcc74a4fc61690abd06da5be806c1a9e44184dcc0f602b340aa72716a03f435671a2230a4699d568a44cbe043261a46eff0de687852470138a4b0bd20b685bd011a1e89fc9c731ba35c2ba90f8d17a86d3069f38ef3827076a8a4553b844d5dc60289e4638707f5aecfdaf25255d62b54b4032c8d5f086306f1b34466407508012dd59d14ea80025dd0270372bce36d49e8c9db0c0224e118e2455c42cf038b0cd955cda1b15ac9964ce1132f24daf05f43d0e8c226f9fe7080187c997798136b013dbb876b4d1c314b566af314b11f5e779614b5b8a878439d8727bb45a601eeebaf58d3f6c93f77a17b5370eb199e33d698010b345f0e0649677e985e923d12b6ab7d0c3aaf48720741d65e611979edd512368ab8e6b5a5bb9777fd245079733d5e57324b2089c61f5bbf921ecebd970eb6036f56c937cba3fba4dca34c293e01aa7e7fdaf1dd127df1c17d8d7b19dad363cbfe69c79d24107840aa0bfc447158535d772b3453fa2ce7a99e87bbd8f6a5160c7538a993e9b1251c8396e8bc678a56353662b401b72494df058c6e4941f13ad05655ca34d571f93d2e175a2cb8937259668dd3dcf9e4c81edc3fd77464167affc3c39e34bff852c3c45cbde28f9b9a5f8a7e54763dc2fd970271cade79ae49d40a8706853eb116185a6eeef82745d18957e2ce01169c38bbc31c68684a47dbe3de542463771e9711de6c3476291034f5032ba18e8e924c4f30c4df96dbe15f2a5a55503a9cdd40a8f62fc5a70107df34a68cc6756c33a137be63540d2551218a248fe92dae21d922bef56a42d7bda739383ad9fd27d63c74d1d2253c752fc1f57e852e8bc912a8cb8f12d5b50d97569560d8661afdb00b7c711e23a31d1d025f13a9779343268b5233f9af589a9b2a17b7e9915dcc65c39c4a5612b3f74b6a30c8762280352b122afad7caafd1ae1883e7c5de831d539c605c21376469eb113e88abbcf5578c6b2e56445bdc6f6321b8eff24e97d43ecfcc62d1f4ebd4acfd63d4c69701050000800a27a7262196513700000000e1661800010000000000000000000000000000000000000000000000000000000000000000ffffffff0603e166180101ffffffff0480b2e60e000000001976a9146e82b7c719334e3aac3916872a088b548c43c9fb88ac286bee000000000017a9140c0bcca02f3cba01a5d7423ac3903d40586399eb8738c94d010000000017a914221c95f83bf073cfb76724ed23af737c1ee3bb498740787d010000000017a91471e1df05024288a00802de81e08c437859586c8787000000 2 | -------------------------------------------------------------------------------- /src/vectors/block-test-0-000-000.txt: -------------------------------------------------------------------------------- 1 | 040000000000000000000000000000000000000000000000000000000000000000000000db4d7a85b768123f1dff1d4c4cece70083b2d27e117b4ac2e31d087988a5eac40000000000000000000000000000000000000000000000000000000000000000a11e1358ffff07200600000000000000000000000000000000000000000000000000000000000000fd400500a6a51259c3f6732481e2d035197218b7a69504461d04335503cd69759b2d02bd2b53a9653f42cb33c608511c953673fa9da76170958115fe92157ad3bb5720d927f18e09459bf5c6072973e143e20f9bdf0584058c96b7c2234c7565f100d5eea083ba5d3dbaff9f0681799a113e7beff4a611d2b49590563109962baa149b628aae869af791f2f70bb041bd7ebfa658570917f6654a142b05e7ec0289a4f46470be7be5f693b90173eaaa6e84907170f32602204f1f4e1c04b1830116ffd0c54f0b1caa9a5698357bd8aa1f5ac8fc93b405265d824ba0e49f69dab5446653927298e6b7bdc61ee86ff31c07bde86331b4e500d42e4e50417e285502684b7966184505b885b42819a88469d1e9cf55072d7f3510f85580db689302eab377e4e11b14a91fdd0df7627efc048934f0aff8e7eb77eb17b3a95de13678004f2512293891d8baf8dde0ef69be520a58bbd6038ce899c9594cf3e30b8c3d9c7ecc832d4c19a6212747b50724e6f70f6451f78fd27b58ce43ca33b1641304a916186cfbe7dbca224f55d08530ba851e4df22baf7ab7078e9cbea46c0798b35a750f54103b0cdd08c81a6505c4932f6bfbd492a9fced31d54e98b6370d4c96600552fcf5b37780ed18c8787d03200963600db297a8f05dfa551321d17b9917edadcda51e274830749d133ad226f8bb6b94f13b4f77e67b35b71f52112ce9ba5da706ad9573584a2570a4ff25d29ab9761a06bdcf2c33638bf9baf2054825037881c14adf3816ba0cbd0fca689aad3ce16f2fe362c98f48134a9221765d939f0b49677d1c2447e56b46859f1810e2cf23e82a53e0d44f34dae932581b3b7f49eaec59af872cf9de757a964f7b33d143a36c270189508fcafe19398e4d2966948164d40556b05b7ff532f66f5d1edc41334ef742f78221dfe0c7ae2275bb3f24c89ae35f00afeea4e6ed187b866b209dc6e83b660593fce7c40e143beb07ac86c56f39e895385924667efe3a3f031938753c7764a2dbeb0a643fd359c46e614873fd0424e435fa7fac083b9a41a9d6bf7e284eee537ea7c50dd239f359941a43dc982745184bf3ee31a8dc850316aa9c6b66d6985acee814373be3458550659e1a06287c3b3b76a185c5cb93e38c1eebcf34ff072894b6430aed8d34122dafd925c46a515cca79b0269c92b301890ca6b0dc8b679cdac0f23318c105de73d7a46d16d2dad988d49c22e9963c117960bdc70ef0db6b091cf09445a516176b7f6d58ec29539166cc8a38bbff387acefffab2ea5faad0e8bb70625716ef0edf61940733c25993ea3de9f0be23d36e7cb8da10505f9dc426cd0e6e5b173ab4fff8c37e1f1fb56d1ea372013d075e0934c6919393cfc21395eea20718fad03542a4162a9ded66c814ad8320b2d7c2da3ecaf206da34c502db2096d1c46699a91dd1c432f019ad434e2c1ce507f91104f66f491fed37b225b8e0b2888c37276cfa0468fc13b8d593fd9a2675f0f5b20b8a15f8fa7558176a530d6865738ddb25d3426dab905221681cf9da0e0200eea5b2eba3ad3a5237d2a391f9074bf1779a2005cee43eec2b058511532635e0fea61664f531ac2b356f40db5c5d275a4cf5c82d468976455af4e3362cc8f71aa95e71d394aff3ead6f7101279f95bcd8a0fedce1d21cb3c9f6dd3b182fce0db5d6712981b651f29178a24119968b14783cafa713bc5f2a65205a42e4ce9dc7ba462bdb1f3e4553afc15f5f39998fdb53e7e231e3e520a46943734a007c2daa1eda9f495791657eefcac5c32833936e568d06187857ed04d7b97167ae207c5c5ae54e528c36016a984235e9c5b2f0718d7b3aa93c7822ccc772580b6599671b3c02ece8a21399abd33cfd3028790133167d0a97e7de53dc8ff0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff071f0104455a6361736830623963346565663862376363343137656535303031653335303039383462366665613335363833613763616331343161303433633432303634383335643334ffffffff010000000000000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000 2 | 3 | -------------------------------------------------------------------------------- /src/vectors/block-test-1-028-500.txt: -------------------------------------------------------------------------------- 1 | 0400000080a6a24840e9e0d73a8a8d726ce9eba4ee01476a41271d874be3a470c7d52f0074b2c8c9dd9760a1f9d08da10fc21b4567ce011ede93854067630f6094eca31aa80d15fcbd133c0af05ddf0a0d8c8f962e0ee94ab296cd00aa1cb9ee34f49f7ab07a285fffff0720c801de5b330f5c7dec0b76be05498638bac71b2ef4b72257ceceab3b3bfd0000fd4005000991e9011af64533d0a39c832403021f2856fd482a2e41b505712e4ff633f465c56f62bd34bef9f6b20c943c57ab1c8c155532f3c5fe33edb952954dc95e22fd6f818b14d4f1def772d4a41c780e7a453a95990094f34131c2db4ea6ea32b33799794dc7a11608fe4bfc7d1ff4a19e755db8161aa7769f31d80c74ab0e29062e36899fe3d11a2462cb299ff4f74c729b0fa757f3dbcb9e5b02cf48f1c918c8cfe8e3c9fd7f64ce0972211d0911e58dddd7c44e9ef963a9ba7b1cd996280944cc1c0f63208fba58a60d6dd866f24f9841341131b13254564833f350c1227d2603be65ea77869322e0f42d804a718edb9cb3af6f326e1eb9519b69140f13539fe48fde6b83b9347df07b826304951cdefb148b1ab3dfaf859fd59a728c12f47f25b20bdf02fb0fff27f0d72bebfbfccac16fff363e59af21f90b1d421b6ffa7e521185223025bb4e38726afd1adb71ce002f5b6d2431086ffc44c132c3e638d29cb45cf39e136f1e69a1c819b8f71716654d501029a7f394e1ab57569ad78e1b2a85ca5356d933eb128aa602bc0ade62271f3dab616e339e4536f88270a4d2335b9b0490036367343de237cd2917011be14c20f2adf65d099904f7ec8f45ee5fe9ccb259af29f2bb7aba923ea7270b393b1c53cb9bd9d29b86e9a9c36aa67c05dc07791187865fe94984af3c6998b14ff80be6d58656e79507ad14a145831d9fad41581028d8f47fa035fdeb0711cdb0c719c4be4fc5b45289f7c25f1e9978da9f8813f48a103e290205ccd851ce974a3e6603e337921315bac19bca4cdc29c3480399376f7ecce7402958040c0a997294c9fee72708d712c4d337feee2dfd5c702a60f51a4f9f28259a27362b73cc03d61aa97fe91a17e85ebf899939b7152a5259653f553640029e935d23b4697c8693d174a2e0146cf26ea759266cbac7d9010d3b5769e36759813590ee723d5b56b262f76572150536ebded1035d560ff7925e4ca5a2c12f79a63a13b11b5cf7d9809cd794630640a04f1d13b236941a1bb6f5678a081b5c4d6db376d33f7655cd2336273c0a4168e719cea99df7514366987d94a186a574f7bc28ae15ab19cfc4d31deb695401ff0ac72f3afaa44f172f543ced9a695f49dbc3da38dce535ca64d2957120a6db273a74e4e9cf35a87bf1f22f365db518d6e7088447ecdb53dfb2e8d7267fd44d433e751e1fff2e5a4ad62637f0636bc2bca7e288f28966feb89e1c3c0a5e7d7c9a8f6aa5e2ec05a1525b75faae09be26bd1ae5d981db4d7db28db9727c64df8f74f6a4bbec2d0d2fba8fde62b4bf3ca0155d6a55559a61191d8fe81fed03b04626d529e932b965cfdd4dca98c855b50e2a3dcaf9e1a2a65f988b134c77efe345e8d1958f744aad14c8941e2d55287ee6140141319188aaafe6ef01baeec39dccfb7273f38221d5e5c80191423060600ef8c3bb2b5b36bdb1508228bf3d6df29f84dbf6640423faf4be435626c2b3cb9b85fc9b7f7e465f10e30cb55e4133c89048a00484af1adb9aad40e4f2ae0d35c9fb8da0e02cffb2e9b47bbe78286a0e7b1f208b515f1ba2ef96e6812819270e59ed4ddf1ba1d8f4bc4ec386c00960825dc57a470e98db69422eb39c65be50af5e71e5d92215bbfe36ecb5e95e0e16ba1e67040a6fd99231046cf7695414d881c9befa4a02f25f7c174571d878f97461f72156ca795f16f9c454d95dd90eb7e33d95e02bfd94fe68df475553ba694d946806815a448d866e4abdc95c392485d7fb02b07ddba12910077e447ad1b3bde2e6619038f96f831fa9fe78dd325144e125a9b657e782153a595b744606f83a78bc13be290c4ee378e5902fa1b80bd19091fd9eba3bfa200a2e47f25af2771161739839bb6446579fb83574010400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff060394b10f0101ffffffff040065cd1d000000001976a9149ffd720c6f877d95941ce73a8682ba95b19888fa88ac70929b020000000017a91402db6bf7d524268b04edbb986ca4b3ba3528045f8750d6dc010000000017a9140c0bcca02f3cba01a5d7423ac3903d40586399eb8780f0fa020000000017a91471e1df05024288a00802de81e08c437859586c878700000000000000000000000000000000000000 2 | -------------------------------------------------------------------------------- /src/vectors/block-test-1-599-199.txt: -------------------------------------------------------------------------------- 1 | 04000000b26e0c796bca49d9f3728e922af97a3e256f4ec5e742f3ac04c68284fd344900b21c06e5efbab781a9e24cd6e05fae07d5fc7ba2afa9fb041e251c4cfffcf9d437333ddce980f8998f03dcb9dabd6e604f9316db5928918fe7630b3bc944b02764365d617e046f1f22000d646c8255fc55033c901d155342d73ad69d7651df2eb9b8988392b50000fd4005014a93d51a057de5f208135b6de23b09892f4ee19724bcb23afb5c6c3721487509d52fc42a4a67b3d8bb0ea1b14adc87ee60ec7426b9fbdd605735af9aafd423fe9473f9554c835e0e14d1acef041239f676c8a301e5b3afc812c8b7943dc6637b6eb8761bf9d68add1131b5fc8d8b4a5a6ec574fa43d19442af5fd62df20a250cf824ca91fcaadc00e9f0188cea505fd6a7665fa51587afbb1125f71016963fdae7ca3c827775a70c7d9080905607ef4ddbd664dac41e420b8abb8bf81e1d03af4c9fc21944629800cacf375705d3f9a94312f9dd29f6ddb56fc0aa65f2a4b35b264af57c67dd5157b68651d715c3fea987f96070d1b63e9ad2f0170d729772064e4a1f446c431136d5d47edc8a592636156c1a3e4dca9d070a0764c3534bd35f47b21dd61112213d82dded6453a0bfc301b3a735da4c5977457e22451c9c2c1312ebbd9fe3b067d926515420528a1e0199d8c0c80e2e48d02da0258fe70c008b7fb3ee7435635b145d11301ba85cd541d95029da7f1f7611e60e21e6ef49c5c6c717a67137dd24b248aa35a82ac612c27be3c278cea5dff6a39783557e7581bcd0db5c0cfdfdbaa2cebc4bd84894b742f9504263d87593d31cbad0e9d20c3527919df7cbf1de3e2e414f9e66c30d8d8ceefc8a1851345e829263c0ed8ed279ffaeb65f002b4af7a83aa9f49e25f590f22d71ed00997e43049d0c85b1e1eee12672f4fcdd6025e943da133f1227f8e6a8f531c801dfc5a7a85f75bee5f1f37a8e0c12c9ae80d617a3d6f4ed742d6e3c5965d7d81e21f73911c7d41d558745cd021673bc53da121a6dff82510688453d908674dcf52e54e8c7c25c5d8fd33c796a1788a2a86724743db33b321c13c3ac1103c35c47b21c596c946d8c974e997083d28a676e211f23145afb272fee8037b35fc1de9b22d2b599c84e5f52df43d30217585e790b609d0f3b90397237bba93a8c9ac0fa0ef6ceabe9a9329b6982793492f7bb6eda5ad96e92041682b5b1918ae531b061709136740cd656b3862705203f89e2d41caea4408136f1dd544920a811bc95022ff575af24e3c7ec7a262b7144c8263d40f84db42c27e379ba0b417d77e694b46bdc71fb7791bdb5d80403b2141d5be888e9214118eaad58718c891532f1146bacc28fdff5afef82414a1d9e0054976eb407e6037ad7cace999a6743eb32e9e8b8d71da5c92e5c001c1edd03ed70e137b0da02c6723b8d83142e1ce09603db9b8132891975876f35f2ab347eaa1f7534279a06f25f2e30f78b91d048817c1cc1cc058328edc268145baaf3f6cb42f17f9732bbe142d435d45c9c8cd777fce3e58a6f057f9e40f7ffe66f58da209b5d69da1fbaa3bb3f11d27f9f8b83ab6ea74809c6b877932a1fcd99abe1a43469ceaf155aa0aee7496223b3260502430a072816567bf563a24eeef9a4bd7082d53cf6103c6d25ac65a9a9e55ba827cbda3f1a8692feace204bb95a74814544b2cc0f15eeaca87ec74c1f1d4ef3c86f6538cb394dfa3df7569ba48f79e32bf5246dd092f3518b01a1f45699a64ea51c01ddb47121b5d7c35699ef9d590bab9f216d6bdbc65a085e19a8f44d30ec9a92dc00c47d69f4d741ec1ee3deee7e0b9021134191cdecc1215e366e4bb031dfea632d7f75a588e05c586298173dbe9a19a11e254a50ad12aa82bcf345263b306de295cff73ce17ff914cdcc6655e1dc3820870ef26d749e6ab7c7071bbc8e98821ab025220f908a36e11ca4f29bac17324188acfcbe11acbd03c9a0a338b5ed0d63f86c0f23608d5b8552d92f799ab0c3e6995c2981c79ad5d9195d04fe0e713108added942a1adde96ca14d9bb525f697cefac2f23e62720e254da847763f1efd15f10b857ed9c2eb1ae3d23d1d81010400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff0603df66180101ffffffff0480b2e60e000000001976a91403e105ea5dbb243cddada1ec31cebd92266cd22588ac286bee000000000017a9140c0bcca02f3cba01a5d7423ac3903d40586399eb8738c94d010000000017a914221c95f83bf073cfb76724ed23af737c1ee3bb498740787d010000000017a91471e1df05024288a00802de81e08c437859586c878700000000000000000000000000000000000000 2 | -------------------------------------------------------------------------------- /synth_node_bin/src/action/constantly_ask_for_random_blocks.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, net::SocketAddr}; 2 | 3 | use anyhow::Result; 4 | use rand::{ 5 | distributions::{Distribution, Uniform}, 6 | rngs::StdRng, 7 | SeedableRng, 8 | }; 9 | use ziggurat_zcash::{ 10 | protocol::{ 11 | message::Message, 12 | payload::{inv::InvHash, Hash, Inv}, 13 | }, 14 | tools::synthetic_node::SyntheticNode, 15 | }; 16 | 17 | use super::{ActionCfg, SynthNodeAction}; 18 | 19 | pub(super) struct Action; 20 | 21 | pub(super) fn action() -> Box { 22 | Box::new(Action {}) 23 | } 24 | 25 | const BLOCKS_FOR_AVG: u128 = 100; 26 | 27 | #[async_trait::async_trait] 28 | impl SynthNodeAction for Action { 29 | fn info(&self) -> &str { 30 | "constantly ask for random blocks using getdata command" 31 | } 32 | 33 | fn config(&self) -> ActionCfg { 34 | ActionCfg::default() 35 | } 36 | 37 | #[allow(unused_variables)] 38 | async fn run(&self, synth_node: &mut SyntheticNode, addr: Option) -> Result<()> { 39 | println!("Synthetic node performs an action."); 40 | 41 | let addr = if let Some(addr) = addr { 42 | addr 43 | } else { 44 | anyhow::bail!("address not provided"); 45 | }; 46 | 47 | let mut min = u128::MAX; 48 | let mut max = 0; 49 | let mut avg = 0; 50 | let mut count = 0; 51 | 52 | let mut rng = StdRng::from_entropy(); 53 | 54 | let jstring = fs::read_to_string("hashes.json").expect("could not open hashes file"); 55 | let hashes: Vec = serde_json::from_str(&jstring).unwrap(); 56 | let die = Uniform::new(0, hashes.len() - 1); 57 | 58 | loop { 59 | let msg = 60 | Message::GetData(Inv::new(vec![InvHash::Block(hashes[die.sample(&mut rng)])])); 61 | 62 | tracing::info!("unicast {msg:?}\n"); 63 | if synth_node.unicast(addr, msg.clone()).is_err() { 64 | tracing::warn!("failed to send {msg:?}\n"); 65 | anyhow::bail!("connection closed"); 66 | } 67 | let start = std::time::Instant::now(); 68 | 69 | loop { 70 | let (_, msg) = synth_node.try_recv_message().await?; 71 | tracing::info!("message received: {msg:?}"); 72 | match msg { 73 | Message::Block(block) => { 74 | let end = std::time::Instant::now(); 75 | let elapsed = end - start; 76 | let elapsed = elapsed.as_millis(); 77 | if elapsed > max { 78 | max = elapsed; 79 | } 80 | if elapsed < min { 81 | min = elapsed; 82 | } 83 | 84 | avg += elapsed; 85 | 86 | count += 1; 87 | 88 | if count == BLOCKS_FOR_AVG { 89 | println!( 90 | "min: {} ms, max: {} ms, avg: {} ms", 91 | min, 92 | max, 93 | avg / BLOCKS_FOR_AVG 94 | ); 95 | min = u128::MAX; 96 | max = 0; 97 | avg = 0; 98 | count = 0; 99 | } 100 | break; 101 | } 102 | _ => continue, 103 | } 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /synth_node_bin/src/main.rs: -------------------------------------------------------------------------------- 1 | //! A synthetic node binary can be used to interact with the node in the 2 | //! background from a different runtime environment. 3 | use std::{net::SocketAddr, process::ExitCode}; 4 | 5 | use action::{ActionHandler, ActionType}; 6 | use anyhow::Result; 7 | use clap::Parser; 8 | use ziggurat_zcash::tools::synthetic_node::SyntheticNode; 9 | 10 | use crate::ActionType::SendGetAddrAndForeverSleep; 11 | 12 | mod action; 13 | 14 | /// A synthetic node which can connect to the node and preform some actions independently. 15 | #[derive(Parser)] 16 | #[command(author, version, about, long_about = None)] 17 | struct CmdArgs { 18 | /// An address of the node in the : format. 19 | #[arg(short = 'n', long)] 20 | node_addr: Option, 21 | 22 | /// Always reconnect in the case the connection fails - synthetic node never dies. 23 | #[arg(short = 's', long, default_value_t = false)] 24 | stubborn: bool, 25 | 26 | /// Enable tracing. 27 | #[arg(short = 't', long, default_value_t = false)] 28 | tracing: bool, 29 | 30 | /// A desired listening port. 31 | #[arg(short = 'p', long)] 32 | desired_listening_port: Option, 33 | 34 | /// Possible actions: 35 | /// SendGetAddrAndForeverSleep / AdvancedSnForS001 / QuickConnectAndThenCleanDisconnect / 36 | /// QuickConnectWithImproperDisconnect / ConstantlyAskForRandomBlocks / RtS1Collector / RtS1Tainter 37 | #[arg(short = 'a', long, default_value_t = SendGetAddrAndForeverSleep)] 38 | action_type: ActionType, 39 | } 40 | 41 | #[tokio::main] 42 | async fn main() -> ExitCode { 43 | let args = CmdArgs::parse(); 44 | let node_addr = args.node_addr; 45 | 46 | if args.tracing { 47 | println!("Enabling tracing."); 48 | use tracing_subscriber::{fmt, EnvFilter}; 49 | 50 | fmt() 51 | .with_test_writer() 52 | .with_env_filter(EnvFilter::from_default_env()) 53 | .init(); 54 | } 55 | 56 | loop { 57 | println!("Starting a synthetic node."); 58 | 59 | if let Err(e) = 60 | run_synth_node(node_addr, args.action_type, args.desired_listening_port).await 61 | { 62 | eprintln!("The synthetic node stopped: {e:?}."); 63 | } 64 | 65 | // Use the stubborn option to run the synth node infinitely. 66 | if !args.stubborn { 67 | break; 68 | } 69 | } 70 | 71 | ExitCode::SUCCESS 72 | } 73 | 74 | async fn run_synth_node( 75 | node_addr: Option, 76 | action_type: ActionType, 77 | desired_listening_port: Option, 78 | ) -> Result<()> { 79 | // Select an action. 80 | let action = ActionHandler::new(action_type); 81 | 82 | let mut net_cfg = action.cfg.network_cfg.clone(); 83 | // A user can always override a default value from an action. 84 | if desired_listening_port.is_some() { 85 | net_cfg.desired_listening_port = desired_listening_port; 86 | } 87 | 88 | // Create a synthetic node and enable handshaking. 89 | let mut synth_node = SyntheticNode::builder() 90 | .with_network_config(net_cfg) 91 | .with_full_handshake() 92 | .with_message_filter(action.cfg.msg_filter.clone()) 93 | .build() 94 | .await?; 95 | 96 | // Perform the handshake. 97 | if let Some(addr) = node_addr { 98 | synth_node.connect(addr).await?; 99 | } 100 | 101 | // Run the wanted action with the node. 102 | action.execute(&mut synth_node, node_addr).await?; 103 | 104 | if action.cfg.allow_proper_shutdown { 105 | // Stop the synthetic node. 106 | synth_node.shut_down().await; 107 | } 108 | 109 | Ok(()) 110 | } 111 | -------------------------------------------------------------------------------- /src/protocol/payload/version.rs: -------------------------------------------------------------------------------- 1 | //! Version payload types. 2 | 3 | use std::{io, net::SocketAddr}; 4 | 5 | use bytes::{Buf, BufMut}; 6 | use time::OffsetDateTime; 7 | 8 | use crate::protocol::{ 9 | message::constants::USER_AGENT, 10 | payload::{ 11 | addr::NetworkAddr, codec::Codec, read_n_bytes, read_timestamp, Nonce, ProtocolVersion, 12 | VarStr, 13 | }, 14 | }; 15 | 16 | /// A version payload. 17 | #[derive(Debug, PartialEq, Eq, Clone)] 18 | pub struct Version { 19 | /// The protocol version of the sender. 20 | pub version: ProtocolVersion, 21 | /// The services supported by the sender. 22 | pub services: u64, 23 | /// The timestamp of the message. 24 | pub timestamp: OffsetDateTime, 25 | /// The receiving address of the message. 26 | pub addr_recv: NetworkAddr, 27 | /// The sender of the message. 28 | pub addr_from: NetworkAddr, 29 | /// The nonce associated with this message. 30 | pub nonce: Nonce, 31 | /// The user agent of the sender. 32 | pub user_agent: VarStr, 33 | /// The start last block received by the sender. 34 | pub start_height: i32, 35 | /// Specifies if the receiver should relay transactions. 36 | pub relay: bool, 37 | } 38 | 39 | impl Version { 40 | /// Constructs a `Version`, where `addr_recv` is the remote `zcashd`/`zebra` node address and 41 | /// `addr_from` is our local node address. 42 | pub fn new(addr_recv: SocketAddr, addr_from: SocketAddr) -> Self { 43 | Self { 44 | version: ProtocolVersion::current(), 45 | services: 1, 46 | timestamp: OffsetDateTime::now_utc(), 47 | addr_recv: NetworkAddr { 48 | last_seen: None, 49 | services: 1, 50 | addr: addr_recv, 51 | }, 52 | addr_from: NetworkAddr { 53 | last_seen: None, 54 | services: 1, 55 | addr: addr_from, 56 | }, 57 | nonce: Nonce::default(), 58 | // Let's pretend to be a ZCashd node 5.4.2 59 | user_agent: VarStr(String::from(USER_AGENT)), 60 | start_height: 0, 61 | relay: false, 62 | } 63 | } 64 | 65 | /// Sets the protocol version. 66 | pub fn with_version(mut self, version: u32) -> Self { 67 | self.version = ProtocolVersion(version); 68 | self 69 | } 70 | } 71 | 72 | impl Codec for Version { 73 | fn encode(&self, buffer: &mut B) -> io::Result<()> { 74 | self.version.encode(buffer)?; 75 | buffer.put_u64_le(self.services); 76 | buffer.put_i64_le(self.timestamp.unix_timestamp()); 77 | 78 | self.addr_recv.encode_without_timestamp(buffer)?; 79 | self.addr_from.encode_without_timestamp(buffer)?; 80 | 81 | self.nonce.encode(buffer)?; 82 | self.user_agent.encode(buffer)?; 83 | buffer.put_i32_le(self.start_height); 84 | buffer.put_u8(self.relay as u8); 85 | 86 | Ok(()) 87 | } 88 | 89 | fn decode(bytes: &mut B) -> io::Result { 90 | let version = ProtocolVersion::decode(bytes)?; 91 | let services = u64::from_le_bytes(read_n_bytes(bytes)?); 92 | let timestamp = read_timestamp(bytes)?; 93 | 94 | let addr_recv = NetworkAddr::decode_without_timestamp(bytes)?; 95 | let addr_from = NetworkAddr::decode_without_timestamp(bytes)?; 96 | 97 | let nonce = Nonce::decode(bytes)?; 98 | let user_agent = VarStr::decode(bytes)?; 99 | 100 | let start_height = i32::from_le_bytes(read_n_bytes(bytes)?); 101 | let relay = u8::from_le_bytes(read_n_bytes(bytes)?) != 0; 102 | 103 | Ok(Self { 104 | version, 105 | services, 106 | timestamp, 107 | addr_recv, 108 | addr_from, 109 | nonce, 110 | user_agent, 111 | start_height, 112 | relay, 113 | }) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/tests/conformance/handshake/reject_version.rs: -------------------------------------------------------------------------------- 1 | use assert_matches::assert_matches; 2 | 3 | use crate::{ 4 | protocol::{ 5 | message::Message, 6 | payload::{reject::CCode, Version}, 7 | }, 8 | setup::node::{Action, Node}, 9 | tools::{synthetic_node::SyntheticNode, LONG_TIMEOUT}, 10 | wait_until, 11 | }; 12 | 13 | #[tokio::test] 14 | #[allow(non_snake_case)] 15 | async fn c007_PING_reusing_nonce() { 16 | // ZG-CONFORMANCE-007 17 | // 18 | // The node rejects connections reusing its nonce (usually indicative of self-connection). 19 | // 20 | // zcashd: closes the write half of the stream, doesn't close the socket. 21 | // zebra: pass 22 | 23 | // Create a synthetic node, no handshake, no message filters. 24 | let mut synthetic_node = SyntheticNode::builder().build().await.unwrap(); 25 | 26 | // Spin up a node instance with the synthetic node set as an initial peer. 27 | let mut node = Node::new().unwrap(); 28 | node.initial_peers(vec![synthetic_node.listening_addr()]) 29 | .start() 30 | .await 31 | .unwrap(); 32 | 33 | // Receive a Version. 34 | let (source, version) = synthetic_node 35 | .recv_message_timeout(LONG_TIMEOUT) 36 | .await 37 | .unwrap(); 38 | let nonce = assert_matches!(version, Message::Version(version) => version.nonce); 39 | 40 | // Send a Version. 41 | let mut bad_version = Version::new(node.addr(), synthetic_node.listening_addr()); 42 | bad_version.nonce = nonce; 43 | synthetic_node 44 | .unicast(source, Message::Version(bad_version)) 45 | .unwrap(); 46 | 47 | // Assert on disconnect. 48 | wait_until!(LONG_TIMEOUT, synthetic_node.num_connected() == 0); 49 | 50 | // Gracefully shut down the nodes. 51 | synthetic_node.shut_down().await; 52 | node.stop().unwrap(); 53 | } 54 | 55 | #[tokio::test] 56 | #[allow(non_snake_case)] 57 | async fn c008_VERSION_with_obsolete_number() { 58 | // ZG-CONFORMANCE-008 59 | // 60 | // The node rejects connections with obsolete node versions. 61 | // 62 | // zebra: doesn't send a reject, the connection gets dropped. 63 | // zcashd: sends reject before closing the write half of the stream, doesn't close the socket. 64 | 65 | let obsolete_version_numbers: Vec = (170000..170012).collect(); 66 | 67 | // Spin up a node instance. 68 | let mut node = Node::new().unwrap(); 69 | node.initial_action(Action::WaitForConnection) 70 | .start() 71 | .await 72 | .unwrap(); 73 | 74 | // Configuration for all synthetic nodes, no handshake, no message filter. 75 | let node_builder = SyntheticNode::builder(); 76 | 77 | for obsolete_version_number in obsolete_version_numbers { 78 | // Create a synthetic node. 79 | let mut synthetic_node = node_builder.build().await.unwrap(); 80 | 81 | // Connect to the node and send a Version with an obsolete version. 82 | synthetic_node.connect(node.addr()).await.unwrap(); 83 | synthetic_node 84 | .unicast( 85 | node.addr(), 86 | Message::Version( 87 | Version::new(node.addr(), synthetic_node.listening_addr()) 88 | .with_version(obsolete_version_number), 89 | ), 90 | ) 91 | .unwrap(); 92 | 93 | // Expect a reject message. 94 | let (_, reject) = synthetic_node 95 | .recv_message_timeout(LONG_TIMEOUT) 96 | .await 97 | .unwrap(); 98 | assert_matches!(reject, Message::Reject(reject) if reject.ccode == CCode::Obsolete); 99 | 100 | // Expect the connection to be dropped. 101 | wait_until!(LONG_TIMEOUT, synthetic_node.num_connected() == 0); 102 | 103 | // Gracefully shut down the synthetic node. 104 | synthetic_node.shut_down().await; 105 | } 106 | 107 | // Gracefully shut down the node. 108 | node.stop().unwrap(); 109 | } 110 | -------------------------------------------------------------------------------- /src/protocol/payload/reject.rs: -------------------------------------------------------------------------------- 1 | //! Reject payload types. 2 | 3 | use std::io::{self, Read}; 4 | 5 | use bytes::{Buf, BufMut}; 6 | 7 | use crate::protocol::payload::{codec::Codec, VarStr}; 8 | 9 | /// A reject message payload. 10 | #[derive(Debug, PartialEq, Eq, Clone)] 11 | pub struct Reject { 12 | /// The type of message rejected. 13 | pub message: VarStr, 14 | /// The code of the reason for rejection. 15 | pub ccode: CCode, 16 | /// The reason. 17 | pub reason: VarStr, 18 | /// Optional extra data provided by some errors. 19 | /// Currently, all errors which provide this field fill it with 20 | /// the TXID or block header hash of the object being rejected, 21 | /// so the field is 32 bytes. 22 | /// 23 | /// We support any length data to fully adhere to the spec. 24 | pub data: Vec, 25 | } 26 | 27 | impl Codec for Reject { 28 | fn encode(&self, buffer: &mut B) -> io::Result<()> { 29 | self.message.encode(buffer)?; 30 | self.ccode.encode(buffer)?; 31 | self.reason.encode(buffer)?; 32 | buffer.put_slice(&self.data); 33 | 34 | Ok(()) 35 | } 36 | 37 | fn decode(bytes: &mut B) -> io::Result { 38 | let message = VarStr::decode(bytes)?; 39 | let ccode = CCode::decode(bytes)?; 40 | let reason = VarStr::decode(bytes)?; 41 | 42 | // Current usage of the data field is `Option<[u8; 32]>`, 43 | // but the spec allows for any length [u8], so we support that case. 44 | let mut data = Vec::new(); 45 | bytes.reader().read_to_end(&mut data)?; 46 | 47 | Ok(Self { 48 | message, 49 | ccode, 50 | reason, 51 | data, 52 | }) 53 | } 54 | } 55 | 56 | const MALFORMED_CODE: u8 = 0x01; 57 | const INVALID_CODE: u8 = 0x10; 58 | const OBSOLETE_CODE: u8 = 0x11; 59 | const DUPLICATE_CODE: u8 = 0x12; 60 | const NON_STANDARD_CODE: u8 = 0x40; 61 | const DUST_CODE: u8 = 0x41; 62 | const INSUFFICIENT_FEE_CODE: u8 = 0x42; 63 | const CHECKPOINT_CODE: u8 = 0x43; 64 | const OTHER_CODE: u8 = 0x50; 65 | 66 | /// The code specifying the reject reason. 67 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 68 | pub enum CCode { 69 | Malformed, 70 | Invalid, 71 | Obsolete, 72 | Duplicate, 73 | NonStandard, 74 | Dust, 75 | InsufficientFee, 76 | Checkpoint, 77 | Other, 78 | } 79 | 80 | impl Codec for CCode { 81 | fn encode(&self, buffer: &mut B) -> io::Result<()> { 82 | let code: u8 = match self { 83 | Self::Malformed => MALFORMED_CODE, 84 | Self::Invalid => INVALID_CODE, 85 | Self::Obsolete => OBSOLETE_CODE, 86 | Self::Duplicate => DUPLICATE_CODE, 87 | Self::NonStandard => NON_STANDARD_CODE, 88 | Self::Dust => DUST_CODE, 89 | Self::InsufficientFee => INSUFFICIENT_FEE_CODE, 90 | Self::Checkpoint => CHECKPOINT_CODE, 91 | Self::Other => OTHER_CODE, 92 | }; 93 | 94 | buffer.put_u8(code); 95 | 96 | Ok(()) 97 | } 98 | 99 | fn decode(bytes: &mut B) -> io::Result { 100 | if bytes.remaining() == 0 { 101 | return Err(io::ErrorKind::InvalidData.into()); 102 | } 103 | 104 | match bytes.get_u8() { 105 | MALFORMED_CODE => Ok(Self::Malformed), 106 | INVALID_CODE => Ok(Self::Invalid), 107 | OBSOLETE_CODE => Ok(Self::Obsolete), 108 | DUPLICATE_CODE => Ok(Self::Duplicate), 109 | NON_STANDARD_CODE => Ok(Self::NonStandard), 110 | DUST_CODE => Ok(Self::Dust), 111 | INSUFFICIENT_FEE_CODE => Ok(Self::InsufficientFee), 112 | CHECKPOINT_CODE => Ok(Self::Checkpoint), 113 | OTHER_CODE => Ok(Self::Other), 114 | b => Err(io::Error::new( 115 | io::ErrorKind::InvalidData, 116 | format!("Invalid CCode {b:#x}"), 117 | )), 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/tools/crawler/network.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::{HashMap, HashSet}, 3 | net::SocketAddr, 4 | time::{Duration, Instant}, 5 | }; 6 | 7 | use parking_lot::RwLock; 8 | use ziggurat_core_crawler::connection::KnownConnection; 9 | use ziggurat_zcash::protocol::payload::{ProtocolVersion, VarStr}; 10 | 11 | /// The elapsed time before a connection should be regarded as inactive. 12 | pub const LAST_SEEN_CUTOFF: u64 = 10 * 60; 13 | 14 | #[derive(Debug, Default, Clone, Copy, PartialEq)] 15 | pub enum ConnectionState { 16 | /// The node is not connected. 17 | #[default] 18 | Disconnected, 19 | /// The node is connected. 20 | Connected, 21 | } 22 | 23 | /// A node encountered in the network or obtained from one of the peers. 24 | #[derive(Debug, Default, Clone)] 25 | pub struct KnownNode { 26 | // The address is omitted, as it's a key in the owning HashMap. 27 | /// The last time the node was successfully connected to. 28 | pub last_connected: Option, 29 | /// The time it took to complete a connection. 30 | pub handshake_time: Option, 31 | /// The node's protocol version. 32 | pub protocol_version: Option, 33 | /// The node's user agent. 34 | pub user_agent: Option, 35 | /// The node's block height. 36 | pub start_height: Option, 37 | /// The number of services supported by the node. 38 | pub services: Option, 39 | /// The number of subsequent connection errors. 40 | pub connection_failures: u8, 41 | /// The node's state. 42 | pub state: ConnectionState, 43 | } 44 | 45 | /// The list of nodes and connections the crawler is aware of. 46 | #[derive(Default)] 47 | pub struct KnownNetwork { 48 | pub nodes: RwLock>, 49 | pub connections: RwLock>, 50 | } 51 | 52 | impl KnownNetwork { 53 | /// Extends the list of known nodes and connections. 54 | pub fn add_addrs(&self, source: SocketAddr, listening_addrs: &[SocketAddr]) { 55 | { 56 | let connections = &mut self.connections.write(); 57 | for addr in listening_addrs { 58 | connections.insert(KnownConnection::new(source, *addr)); 59 | } 60 | } 61 | let mut nodes = self.nodes.write(); 62 | nodes.entry(source).or_default(); 63 | listening_addrs.iter().for_each(|addr| { 64 | nodes.entry(*addr).or_default(); 65 | }); 66 | } 67 | 68 | /// Sets the node's connection state. 69 | pub fn set_node_state(&self, addr: SocketAddr, state: ConnectionState) { 70 | if let Some(node) = self.nodes.write().get_mut(&addr) { 71 | node.state = state; 72 | } 73 | } 74 | 75 | /// Returns a snapshot of the known connections. 76 | pub fn connections(&self) -> HashSet { 77 | self.connections.read().clone() 78 | } 79 | 80 | /// Returns a snapshot of the known nodes. 81 | pub fn nodes(&self) -> HashMap { 82 | self.nodes.read().clone() 83 | } 84 | 85 | /// Returns the number of known connections. 86 | pub fn num_connections(&self) -> usize { 87 | self.connections.read().len() 88 | } 89 | 90 | /// Returns the number of known nodes. 91 | pub fn num_nodes(&self) -> usize { 92 | self.nodes.read().len() 93 | } 94 | 95 | /// Prunes the list of known connections by removing connections last seen long ago. 96 | pub fn remove_old_connections(&self) { 97 | let mut old_conns: HashSet = HashSet::new(); 98 | for conn in self.connections() { 99 | if conn.last_seen.elapsed().as_secs() > LAST_SEEN_CUTOFF { 100 | old_conns.insert(conn); 101 | } 102 | } 103 | 104 | if !old_conns.is_empty() { 105 | let mut conns = self.connections.write(); 106 | for conn in old_conns { 107 | conns.remove(&conn); 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/protocol/payload/addr.rs: -------------------------------------------------------------------------------- 1 | //! Network address types. 2 | 3 | use std::{ 4 | convert::TryInto, 5 | io, 6 | net::{IpAddr::*, Ipv6Addr, SocketAddr}, 7 | }; 8 | 9 | use bytes::{Buf, BufMut}; 10 | use time::OffsetDateTime; 11 | 12 | use crate::protocol::payload::{codec::Codec, read_n_bytes, read_short_timestamp}; 13 | 14 | /// A list of network addresses, used for peering. 15 | #[derive(Debug, PartialEq, Eq, Clone)] 16 | pub struct Addr { 17 | pub addrs: Vec, 18 | } 19 | 20 | impl Addr { 21 | /// Returns an `Addr` with no addresses. 22 | pub fn empty() -> Self { 23 | Self { addrs: Vec::new() } 24 | } 25 | 26 | /// Returns an `Addr` with the given addresses. 27 | pub fn new(addrs: Vec) -> Self { 28 | Addr { addrs } 29 | } 30 | 31 | /// Returns an iterator over the list of network addresses. 32 | pub fn iter(&self) -> std::slice::Iter { 33 | self.addrs.iter() 34 | } 35 | } 36 | 37 | impl Codec for Addr { 38 | fn encode(&self, buffer: &mut B) -> io::Result<()> { 39 | self.addrs.encode(buffer) 40 | } 41 | 42 | fn decode(bytes: &mut B) -> io::Result { 43 | Ok(Self::new(Vec::decode(bytes)?)) 44 | } 45 | } 46 | 47 | /// A network address. 48 | #[derive(Debug, Clone, PartialEq, Eq)] 49 | pub struct NetworkAddr { 50 | /// The last time this address was seen. 51 | /// Note: Present only when version is >= 31402 52 | pub last_seen: Option, 53 | /// The services supported by this address. 54 | pub services: u64, 55 | /// The socket address. 56 | pub addr: SocketAddr, 57 | } 58 | 59 | impl NetworkAddr { 60 | /// Creates a new NetworkAddr with the given socket address, 61 | /// `last_seen=OffsetDateTime::now_utc()`, 62 | /// and `services=1` (only `NODE_NETWORK` is enabled). 63 | pub fn new(addr: SocketAddr) -> Self { 64 | Self { 65 | last_seen: Some(OffsetDateTime::now_utc()), 66 | services: 1, 67 | addr, 68 | } 69 | } 70 | 71 | pub fn encode_without_timestamp(&self, buffer: &mut B) -> io::Result<()> { 72 | buffer.put_u64_le(self.services); 73 | 74 | let (ip, port) = match self.addr { 75 | SocketAddr::V4(v4) => (v4.ip().to_ipv6_mapped(), v4.port()), 76 | SocketAddr::V6(v6) => (*v6.ip(), v6.port()), 77 | }; 78 | 79 | buffer.put_slice(&ip.octets()); 80 | buffer.put_u16(port); 81 | 82 | Ok(()) 83 | } 84 | 85 | pub(super) fn decode_without_timestamp(bytes: &mut B) -> io::Result { 86 | let services = u64::from_le_bytes(read_n_bytes(bytes)?); 87 | 88 | if bytes.remaining() < 16 { 89 | return Err(io::ErrorKind::InvalidData.into()); 90 | } 91 | 92 | let mut octets = [0u8; 16]; 93 | bytes.copy_to_slice(&mut octets); 94 | let v6_addr = Ipv6Addr::from(octets); 95 | 96 | let ip_addr = match v6_addr.to_ipv4() { 97 | Some(v4_addr) => V4(v4_addr), 98 | None => V6(v6_addr), 99 | }; 100 | 101 | let port = u16::from_be_bytes(read_n_bytes(bytes)?); 102 | 103 | Ok(Self { 104 | last_seen: None, 105 | services, 106 | addr: SocketAddr::new(ip_addr, port), 107 | }) 108 | } 109 | } 110 | 111 | impl Codec for NetworkAddr { 112 | fn encode(&self, buffer: &mut B) -> io::Result<()> { 113 | let timestamp: u32 = self 114 | .last_seen 115 | .expect("missing timestamp") 116 | .unix_timestamp() 117 | .try_into() 118 | .unwrap(); 119 | buffer.put_u32_le(timestamp); 120 | 121 | self.encode_without_timestamp(buffer)?; 122 | 123 | Ok(()) 124 | } 125 | 126 | fn decode(bytes: &mut B) -> io::Result { 127 | let timestamp = read_short_timestamp(bytes)?; 128 | let without_timestamp = Self::decode_without_timestamp(bytes)?; 129 | 130 | Ok(Self { 131 | last_seen: Some(timestamp), 132 | ..without_timestamp 133 | }) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/vectors/mod.rs: -------------------------------------------------------------------------------- 1 | //! Test vectors ordered by block height. 2 | //! 3 | //! Please note, these vectors have been copied across from [zebra](https://github.com/ZcashFoundation/zebra/tree/main/zebra-test/src/vectors). 4 | 5 | use hex::FromHex; 6 | use lazy_static::lazy_static; 7 | 8 | lazy_static! { 9 | /// Testnet genesis block (pre-overwinter). 10 | pub static ref BLOCK_TESTNET_GENESIS_BYTES: Vec = 11 | >::from_hex(include_str!("block-test-0-000-000.txt").trim()).unwrap(); 12 | /// Testnet block at height `1` (pre-overwinter). 13 | pub static ref BLOCK_TESTNET_0_000_001_BYTES: Vec = 14 | >::from_hex(include_str!("block-test-0-000-001.txt").trim()).unwrap(); 15 | /// Testnet block at height `2` (pre-overwinter). 16 | pub static ref BLOCK_TESTNET_0_000_002_BYTES: Vec = 17 | >::from_hex(include_str!("block-test-0-000-002.txt").trim()).unwrap(); 18 | /// Testnet block at height `3` (pre-overwinter). 19 | pub static ref BLOCK_TESTNET_0_000_003_BYTES: Vec = 20 | >::from_hex(include_str!("block-test-0-000-003.txt").trim()).unwrap(); 21 | /// Testnet block at height `4` (pre-overwinter). 22 | pub static ref BLOCK_TESTNET_0_000_004_BYTES: Vec = 23 | >::from_hex(include_str!("block-test-0-000-004.txt").trim()).unwrap(); 24 | /// Testnet block at height `5` (pre-overwinter). 25 | pub static ref BLOCK_TESTNET_0_000_005_BYTES: Vec = 26 | >::from_hex(include_str!("block-test-0-000-005.txt").trim()).unwrap(); 27 | /// Testnet block at height `6` (pre-overwinter). 28 | pub static ref BLOCK_TESTNET_0_000_006_BYTES: Vec = 29 | >::from_hex(include_str!("block-test-0-000-006.txt").trim()).unwrap(); 30 | /// Testnet block at height `7` (pre-overwinter). 31 | pub static ref BLOCK_TESTNET_0_000_007_BYTES: Vec = 32 | >::from_hex(include_str!("block-test-0-000-007.txt").trim()).unwrap(); 33 | /// Testnet block at height `8` (pre-overwinter). 34 | pub static ref BLOCK_TESTNET_0_000_008_BYTES: Vec = 35 | >::from_hex(include_str!("block-test-0-000-008.txt").trim()).unwrap(); 36 | /// Testnet block at height `9` (pre-overwinter). 37 | pub static ref BLOCK_TESTNET_0_000_009_BYTES: Vec = 38 | >::from_hex(include_str!("block-test-0-000-009.txt").trim()).unwrap(); 39 | /// Testnet block at height `10` (pre-overwinter). 40 | pub static ref BLOCK_TESTNET_0_000_010_BYTES: Vec = 41 | >::from_hex(include_str!("block-test-0-000-010.txt").trim()).unwrap(); 42 | /// Testnet block at height `207500` (first overwinter). 43 | pub static ref BLOCK_TESTNET_0_207_500_BYTES: Vec = 44 | >::from_hex(include_str!("block-test-0-207-500.txt").trim()).unwrap(); 45 | /// Testnet block at height `280000` (first sapling). 46 | pub static ref BLOCK_TESTNET_0_280_000_BYTES: Vec = 47 | >::from_hex(include_str!("block-test-0-280-000.txt").trim()).unwrap(); 48 | /// Testnet block at height `584000` (first blossom). 49 | pub static ref BLOCK_TESTNET_0_584_000_BYTES: Vec = 50 | >::from_hex(include_str!("block-test-0-584-000.txt").trim()).unwrap(); 51 | /// Testnet block at height `903800` (first heartwood). 52 | pub static ref BLOCK_TESTNET_0_903_800_BYTES: Vec = 53 | >::from_hex(include_str!("block-test-0-903-800.txt").trim()).unwrap(); 54 | /// Testnet block at height `1028500` (first canopy). 55 | pub static ref BLOCK_TESTNET_1_028_500_BYTES: Vec = 56 | >::from_hex(include_str!("block-test-1-028-500.txt").trim()).unwrap(); 57 | /// Testnet block at height `1599199` (last canopy). 58 | pub static ref BLOCK_TESTNET_1_599_199_BYTES: Vec = 59 | >::from_hex(include_str!("block-test-1-599-199.txt").trim()).unwrap(); 60 | /// Testnet block at height `1599200` (first nu5). 61 | pub static ref BLOCK_TESTNET_1_599_200_BYTES: Vec = 62 | >::from_hex(include_str!("block-test-1-599-200.txt").trim()).unwrap(); 63 | /// Testnet block at height `1599200` (second nu5). 64 | pub static ref BLOCK_TESTNET_1_599_201_BYTES: Vec = 65 | >::from_hex(include_str!("block-test-1-599-201.txt").trim()).unwrap(); 66 | } 67 | -------------------------------------------------------------------------------- /src/vectors/block-test-0-207-500.txt: -------------------------------------------------------------------------------- 1 | 04000000f98e6534bd40eeeed5631b1609a80058b024f3e728a4be7f5ca97921a3220100957a39e0d86892d39ef31a857e8aafdc3e85bd3e254023f69f0d51c31f4a0c460000000000000000000000000000000000000000000000000000000000000000e6acb25a8956021f0301cfa8b5651f3f1ad3f74efafbaafe44dc8c713fc6078d64a8fd9abcd50000fd4005004da77660939bbff11cd1612cd6c8710b8acc42200686cab300042f1e8a5501213c1a31e25eee5cc6a90cf3bf899aa09b6b2840a12a47cb174aa3b69635a627f00a3bf011f8a2ee666660754e4f4e17aa9af55501726d243a90bc52cae923b2f52c8c2520835c53be3dff353cc7dc4bdfe4353804dcdfd4ca3fdb1be62f04b81f70231a057ffc343538dbb5506635f79cd8660dbc26b8794d622bfd03b2029094cbfe53a0f4e2f104d1b9771ec81e8cb318607e1e90d808a7c7b5615f2a78cdc93ecf1a6495329534de574b0d9af5ffa19907ec12a07b8f5b159dc7c3acd1a0bfd95d82753a4f0be753871443c740743c2358286455c363085f08090d62374b9d2d1d2fa98f81aebe3f597177a40f033332602beeae165fe9cffd479eb1c172cff1c9bfc48f2d039f59626a9ca98183141e29468b8329de399fa945575b5cf852a5972b90b4b83c28e22e9e797c57b50316456508d63b6f199142e81d2659118c4d3f6dd105af5654ff1ff4d1f4d4b23254b9f73da15bff89ce035a14b15389b7630e2ce45e0e334fad47d2f998d7169ac7c67bc5b486cf9058d58be3d88760157f542d03c2dbb35a939c41753e136289aa83922dc2f1882f1a3f10f3da21613bafdba1b77d69ff8e2b783a8845110adef19a14f2bbf279f2a04930a9fedef9387d4354d623c206f95f45d987355f1a4a2f8a288a1e2744117ccd280da8c631c16186d74f647562df49bceec71fcae708df9e3774f3c5c369edc1cecfbf187fc955234677c5d918722be1d4c59e18d922358046b3c6ca4d243f98676d9cd3b6d4351e3afcef8963a40e5f9c1181e7239ea98ecdab1f727c32f6a3e0b71f2e30b0424edc70391534bee8c254734254bffa2793b263c8319a54fc3ca3f0e1a2a073fe4fcae6a237d7365f563459727588e1059bdbf19542ea6d802e5c137531b3018cb17ec6c55817aa7ab5b65eaf04999d5992d0dd01d46ae5a9fd987bf25c1526c56c2ea5af0830cc7724828f6636f3e0f3a329959ca27ec6cdab80d4ed16287ae946f3f0a927c1b443d6dfb1b7be3ba3ba01760773e291d9cd4d76a572811a5edef4b4ccdd362b86131a2eca3e79f5a5d8faf133a5a9532197821c37141915a424c4314ccdaeac42face185ab707dc5fbe235729e74edbba1e7de14b560b3855eaf741df9a87db03b2022bada813f1ce21a27e482b80b9c4d4b0adce130be8c51c86125effa632ee06e999ccf55311465912380f6305a0dc6fc2fd960a49ea392f71d7bce73a2d32ed1e499b6457730da4e0c75e599e684b5fe83606b89592434b73ce622cf10200951bdabaafb821ea08f7c559ff726109e0550502cc39a426776dbf12be1353b3beccd3e186a11ca177171fc251ca1f5d7438177641470e89d7cede10e4fd724ed04d5d007f3c2802911b25bf9c9ca7bbb832886ce9ed9a967e1dd26929766bef2bd2f437e095f3a58baefcaf2d48f9777c2a9d5220836e1e7bfe333501e0c9b3fbe1547f231e437ef60ba2d98233167fd86dda6911c239ca1386620b591dfb8f15c61929d2d6363571d846fb1a18a12f14b4f37a8e2f48e3a9bdd1c4db39b5acff281d479052c7ef7fe496a0717e14c89837d283ce3054b358306757db45ce20b2fd98dd499661fd4916ad2497daea063a020f135c5abfa0bc8594e73a6212d15377db55323649a4ec58ba6165e135247a7ca2b586c89f3a7118a40c976a90775d08fab616a0d5762e666b58f44528f67aafd3ebdd6588b3c332d53cc66a171bd264d911bfa25403dde6c72afe5cde00ed56f7811cdd8e5b1ca85b03ad0cbe94885414272fa16c5226d77223661ecb04d626205e3796d843a17fd9fccaa3863832f95a0eef2364eaad33c29fd81cc96465aebac0771ab503030000807082c403010000000000000000000000000000000000000000000000000000000000000000ffffffff06038c2a03010dffffffff0267cc9a3b000000001976a9148c435f0110a329dbadaa29edf4f79925d9a6b1b588ac80b2e60e0000000017a91447498d569e268944c3b6993376009d369140e39a87000000000000000000030000807082c4030188181a70f1d6ed94f5758f21b49fbd1754e3bd5761154c7ae846a0ebd1260202010000006a47304402200f26e69865d2b5e5627ff6a10fec1b145d6279d79efb7150d97abd6e8f312a9b02204002fd035869f2436c05f9c39fe5e91212eb6cfe07525e201a267593d5757df10121023cae2d1c1d27ba72c3829b9f5abd0146f1cd00f25dcf2e8fb4e3ce1e358887b2feffffff02451a2592010000001976a9149786ac0d9a725515459a3775a18285d2e630c77188aca0031d3a000000001976a91449297529d193d7c7fd6816f953e135dba091c42d88ac812a0300a02a030000030000807082c4030225c42128fb4ebdf6871d486bd06e7ceba0039398681abc542cb9978bf30aa77a000000006a473044022013eaa49a753e866deb4acfb2e090eb75ab61fb15547576ce0a3563ec10997706022044c11a81a358a5f7b89a019304a13f698516389c26b8470b133731422940834901210349afc48c054a24269f3ccc11dbe27f40a975884943763911e328aa492e7d7a93feffffffc4599ac200ddc0a602f388d4c873b8ba00b3baa8929a4f9000959aeee46f9a78010000006a47304402205001f1b5277052f354a15bdec54157095156efacfdcf8b5f3a74edaeb86726a502202976df5867ec988312cc1ddb8af763cd890d21a795a957a69270d1b2648f0fc50121023728cec3c821e446f541364484e2750a0a3d6760380d4db1244bc658761c5033feffffff02547a4800000000001976a914df450b20b3750d45fb8c45855ba89ac2392844df88acf3e21c3a000000001976a91449297529d193d7c7fd6816f953e135dba091c42d88ac812a0300a02a030000 2 | -------------------------------------------------------------------------------- /src/tools/message_filter.rs: -------------------------------------------------------------------------------- 1 | //! Message filtering types and utilities. 2 | 3 | use crate::protocol::{ 4 | message::Message, 5 | payload::{block::Headers, Addr}, 6 | }; 7 | 8 | /// Controls the filter response of [`MessageFilter`] to messages it receives. 9 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 10 | pub enum Filter { 11 | /// Do not filter message 12 | Disabled, 13 | /// Filter message 14 | Enabled, 15 | /// Filter message and reply with a default response 16 | AutoReply, 17 | } 18 | 19 | /// A message filter that can map requests to default responses. 20 | /// 21 | /// This can be used to wait for a message event that you actually care about, 22 | /// while skipping over spurious requests e.g. [`Ping`]. 23 | /// 24 | /// Currently supports filters for the following message types: 25 | /// - [`Ping`] 26 | /// - [`GetHeaders`] 27 | /// - [`GetAddr`] 28 | /// - [`GetData`] 29 | /// 30 | /// [`Ping`]: Message::Ping 31 | /// [`GetHeaders`]: Message::GetHeaders 32 | /// [`GetAddr`]: Message::GetAddr 33 | /// [`GetData`]: Message::GetData 34 | #[derive(Debug, Clone)] 35 | pub struct MessageFilter { 36 | ping: Filter, 37 | getheaders: Filter, 38 | getaddr: Filter, 39 | getdata: Filter, 40 | // todo: inv 41 | // todo: getblocks 42 | // todo: mempool 43 | } 44 | 45 | impl MessageFilter { 46 | /// Constructs a `MessageFilter` which will filter no messages. 47 | pub fn with_all_disabled() -> Self { 48 | use Filter::Disabled; 49 | 50 | Self { 51 | ping: Disabled, 52 | getheaders: Disabled, 53 | getaddr: Disabled, 54 | getdata: Disabled, 55 | } 56 | } 57 | 58 | /// Constructs a `MessageFilter` which will filter all supported message types. 59 | pub fn with_all_enabled() -> Self { 60 | use Filter::Enabled; 61 | 62 | Self { 63 | ping: Enabled, 64 | getheaders: Enabled, 65 | getaddr: Enabled, 66 | getdata: Enabled, 67 | } 68 | } 69 | 70 | /// Constructs a `MessageFilter` which will filter and reply to all supported message types. 71 | pub fn with_all_auto_reply() -> Self { 72 | use Filter::AutoReply; 73 | 74 | Self { 75 | ping: AutoReply, 76 | getheaders: AutoReply, 77 | getaddr: AutoReply, 78 | getdata: AutoReply, 79 | } 80 | } 81 | 82 | /// Sets the [`Filter`] response for [`GetHeaders`] messages. 83 | /// 84 | /// [`GetHeaders`]: Message::GetHeaders 85 | pub fn with_getheaders_filter(mut self, filter: Filter) -> Self { 86 | self.getheaders = filter; 87 | self 88 | } 89 | 90 | /// Sets the [`Filter`] response for [`GetAddr`] messages. 91 | /// 92 | /// [`GetAddr`]: Message::GetAddr 93 | pub fn with_getaddr_filter(mut self, filter: Filter) -> Self { 94 | self.getaddr = filter; 95 | self 96 | } 97 | 98 | /// Sets the [`Filter`] response for [`GetData`] messages. 99 | /// 100 | /// [`GetData`]: Message::GetData 101 | pub fn with_getdata_filter(mut self, filter: Filter) -> Self { 102 | self.getdata = filter; 103 | self 104 | } 105 | 106 | /// Sets the [`Filter`] response for [`Ping`] messages. 107 | /// 108 | /// [`Ping`]: Message::Ping 109 | pub fn with_ping_filter(mut self, filter: Filter) -> Self { 110 | self.ping = filter; 111 | self 112 | } 113 | 114 | /// Returns the set [`Filter`] for the message type. 115 | pub fn message_filter_type(&self, message: &Message) -> Filter { 116 | match message { 117 | Message::Ping(_) => self.ping, 118 | Message::GetAddr => self.getaddr, 119 | Message::GetHeaders(_) => self.getheaders, 120 | Message::GetData(_) => self.getdata, 121 | _ => Filter::Disabled, 122 | } 123 | } 124 | 125 | /// Returns the appropriate reply for the message. 126 | pub fn reply_message(&self, message: &Message) -> Message { 127 | match message { 128 | Message::Ping(nonce) => Message::Pong(*nonce), 129 | Message::GetAddr => Message::Addr(Addr::empty()), 130 | Message::GetHeaders(_) => Message::Headers(Headers::empty()), 131 | Message::GetData(inv) => Message::NotFound(inv.clone()), 132 | _ => unimplemented!(), 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/protocol/payload/inv.rs: -------------------------------------------------------------------------------- 1 | //! Inventory vector types. 2 | 3 | use std::io; 4 | 5 | use bytes::{Buf, BufMut}; 6 | 7 | use crate::protocol::payload::{codec::Codec, read_n_bytes, Hash}; 8 | 9 | /// An inventory vector. 10 | #[derive(Debug, PartialEq, Eq, Clone)] 11 | pub struct Inv { 12 | pub inventory: Vec, 13 | } 14 | 15 | impl Inv { 16 | /// Returns a new inventory vector from the supplied hashes. 17 | pub fn new(inventory: Vec) -> Self { 18 | Self { inventory } 19 | } 20 | 21 | // Returns a new empty inventory vector. 22 | pub fn empty() -> Self { 23 | Self::new(Vec::new()) 24 | } 25 | } 26 | 27 | impl Codec for Inv { 28 | fn encode(&self, buffer: &mut B) -> io::Result<()> { 29 | self.inventory.encode(buffer) 30 | } 31 | 32 | fn decode(bytes: &mut B) -> io::Result { 33 | Ok(Self { 34 | inventory: Vec::decode(bytes)?, 35 | }) 36 | } 37 | } 38 | 39 | /// An inventory hash which refers to some advertised or requested data. 40 | /// 41 | /// Bitcoin calls this an "inventory vector" but it is just a typed hash, not a 42 | /// container, so we do not use that term to avoid confusion with `Vec`. 43 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] 44 | pub enum InvHash { 45 | /// Any data of this kind may be ignored. 46 | Error, 47 | /// The hash is that of a transaction. 48 | Tx(Hash), 49 | /// The hash is that of a block. 50 | Block(Hash), 51 | /// The hash is that of a block header. 52 | FilteredBlock(Hash), 53 | /// A pair with the hash of a V5 transaction and the Authorizing Data Commitment (auth_digest). 54 | /// 55 | /// Introduced by [ZIP-239][zip239], which is analogous to Bitcoin's [BIP-339][bip339]. 56 | MsgWtx(WtxId), 57 | } 58 | 59 | impl InvHash { 60 | /// Returns the serialized Zcash network protocol code for the current variant. 61 | fn code(&self) -> u32 { 62 | match self { 63 | Self::Error => 0, 64 | Self::Tx(_) => 1, 65 | Self::Block(_) => 2, 66 | Self::FilteredBlock(_) => 3, 67 | Self::MsgWtx(_) => 5, 68 | } 69 | } 70 | } 71 | 72 | impl Codec for InvHash { 73 | fn encode(&self, buffer: &mut B) -> io::Result<()> { 74 | buffer.put_u32_le(self.code()); 75 | 76 | match self { 77 | Self::Tx(hash) | Self::Block(hash) | Self::FilteredBlock(hash) => { 78 | hash.encode(buffer)?; 79 | } 80 | Self::MsgWtx(wtx_id) => wtx_id.encode(buffer)?, 81 | _ => (), 82 | } 83 | 84 | Ok(()) 85 | } 86 | 87 | fn decode(bytes: &mut B) -> io::Result { 88 | let value = u32::from_le_bytes(read_n_bytes(bytes)?); 89 | 90 | let kind = match value { 91 | 0 => Self::Error, 92 | 1 => Self::Tx(Hash::decode(bytes)?), 93 | 2 => Self::Block(Hash::decode(bytes)?), 94 | 3 => Self::FilteredBlock(Hash::decode(bytes)?), 95 | 5 => Self::MsgWtx(WtxId::decode(bytes)?), 96 | _ => { 97 | return Err(io::Error::new( 98 | io::ErrorKind::InvalidData, 99 | format!("unknown inv hash value type: {value}"), 100 | )) 101 | } 102 | }; 103 | 104 | Ok(kind) 105 | } 106 | } 107 | 108 | /// A witnessed transaction ID, which uniquely identifies unmined v5 transactions. 109 | /// 110 | /// Witnessed transaction IDs are not used for transaction versions 1-4. 111 | /// 112 | /// [ZIP-239]: https://zips.z.cash/zip-0239 113 | /// [Spec: Transaction Identifiers]: https://zips.z.cash/protocol/protocol.pdf#txnidentifiers 114 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 115 | pub struct WtxId { 116 | /// The non-malleable transaction ID for this transaction's effects. 117 | pub id: Hash, 118 | 119 | /// The authorizing data digest for this transactions signatures, proofs, and scripts. 120 | pub auth_digest: Hash, 121 | } 122 | 123 | impl Codec for WtxId { 124 | fn encode(&self, buffer: &mut B) -> io::Result<()> { 125 | self.id.encode(buffer)?; 126 | self.auth_digest.encode(buffer)?; 127 | 128 | Ok(()) 129 | } 130 | 131 | fn decode(bytes: &mut B) -> io::Result { 132 | let id = Hash::decode(bytes)?; 133 | let auth_digest = Hash::decode(bytes)?; 134 | 135 | Ok(Self { id, auth_digest }) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/protocol/payload/filter.rs: -------------------------------------------------------------------------------- 1 | //! Bloom filtering types, see [BIP 37](https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki). 2 | 3 | use std::io::{self, Cursor, ErrorKind, Read}; 4 | 5 | use bytes::{Buf, BufMut}; 6 | 7 | use crate::protocol::payload::{codec::Codec, read_n_bytes}; 8 | 9 | /// A modification to an existing filter. 10 | #[derive(Debug, PartialEq, Eq, Default, Clone)] 11 | pub struct FilterAdd { 12 | /// The data element to add to the current filter. 13 | pub data: Vec, 14 | } 15 | 16 | /// A new filter on the connection. 17 | #[derive(Debug, PartialEq, Eq, Default, Clone)] 18 | pub struct FilterLoad { 19 | /// The filter itself. 20 | pub filter: Vec, 21 | /// The number of hash functions to use in this filter. 22 | pub hash_fn_count: u32, 23 | /// A random value to add to the hash function's seed. 24 | pub tweak: u32, 25 | /// Flags that control how matched items are added to the filter. 26 | pub flags: u8, 27 | } 28 | 29 | impl Codec for FilterAdd { 30 | fn encode(&self, buffer: &mut B) -> io::Result<()> { 31 | buffer.put_slice(&self.data); 32 | 33 | Ok(()) 34 | } 35 | 36 | fn decode(bytes: &mut B) -> io::Result 37 | where 38 | Self: Sized, 39 | { 40 | let mut data = Vec::new(); 41 | bytes.reader().read_to_end(&mut data)?; 42 | 43 | if data.len() > 520 { 44 | return Err(io::Error::new( 45 | ErrorKind::InvalidData, 46 | format!( 47 | "Maximum FilterAdd data length is 520, but got {}", 48 | data.len() 49 | ), 50 | )); 51 | } 52 | 53 | Ok(Self { data }) 54 | } 55 | } 56 | 57 | impl Codec for FilterLoad { 58 | fn encode(&self, buffer: &mut B) -> io::Result<()> { 59 | buffer.put_slice(&self.filter); 60 | buffer.put_u32_le(self.hash_fn_count); 61 | buffer.put_u32_le(self.tweak); 62 | buffer.put_u8(self.flags); 63 | 64 | Ok(()) 65 | } 66 | 67 | fn decode(bytes: &mut B) -> io::Result 68 | where 69 | Self: Sized, 70 | { 71 | // Have to read to end in order to get size of filter. 72 | // (we only know the final 9 bytes are reserved for the other fields) 73 | let mut buffer = Vec::new(); 74 | let bytes_read = bytes.reader().read_to_end(&mut buffer)?; 75 | 76 | const NON_FILTER_BYTES: usize = 4 + 4 + 1; 77 | if bytes_read < NON_FILTER_BYTES { 78 | return Err(io::Error::new( 79 | ErrorKind::InvalidData, 80 | format!( 81 | "Minimum FilterLoad bytes required is {NON_FILTER_BYTES} but only got {bytes_read}" 82 | ), 83 | )); 84 | } 85 | let filter_bytes = bytes_read - NON_FILTER_BYTES; 86 | // maximum filter size is 36k bytes 87 | const MAX_FILTER_BYTES: usize = 36_000; 88 | if filter_bytes > MAX_FILTER_BYTES { 89 | return Err(io::Error::new( 90 | ErrorKind::InvalidData, 91 | format!("Maximum filter bytes is {MAX_FILTER_BYTES} but got {filter_bytes}"), 92 | )); 93 | } 94 | 95 | let mut cursor = Cursor::new(&buffer[..]); 96 | 97 | let mut filter = vec![0; filter_bytes]; 98 | cursor.read_exact(&mut filter)?; 99 | 100 | let hash_fn_count = u32::from_le_bytes(read_n_bytes(&mut cursor)?); 101 | let tweak = u32::from_le_bytes(read_n_bytes(&mut cursor)?); 102 | let flags = u8::from_le_bytes(read_n_bytes(&mut cursor)?); 103 | 104 | Ok(Self { 105 | filter, 106 | hash_fn_count, 107 | tweak, 108 | flags, 109 | }) 110 | } 111 | } 112 | 113 | #[cfg(test)] 114 | mod tests { 115 | use super::*; 116 | 117 | #[test] 118 | #[ignore] 119 | fn filter_load_roundtrip() { 120 | let original = FilterLoad::default(); 121 | 122 | let mut buffer = Vec::new(); 123 | original.encode(&mut buffer).unwrap(); 124 | 125 | let mut cursor = Cursor::new(&buffer[..]); 126 | let decoded = FilterLoad::decode(&mut cursor).unwrap(); 127 | assert_eq!(decoded, original); 128 | } 129 | 130 | #[test] 131 | #[ignore] 132 | fn filter_add_roundtrip() { 133 | let original = FilterAdd::default(); 134 | 135 | let mut buffer = Vec::new(); 136 | original.encode(&mut buffer).unwrap(); 137 | 138 | let mut cursor = Cursor::new(&buffer[..]); 139 | let decoded = FilterAdd::decode(&mut cursor).unwrap(); 140 | assert_eq!(decoded, original); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /synth_node_bin/src/action/rt_s1_collector.rs: -------------------------------------------------------------------------------- 1 | use std::net::{IpAddr, Ipv4Addr, SocketAddr}; 2 | 3 | use anyhow::Result; 4 | use pea2pea::Config as NodeConfig; 5 | use rand::{seq::SliceRandom, thread_rng}; 6 | use tokio::time::{interval, Duration}; 7 | use ziggurat_zcash::{ 8 | protocol::{ 9 | message::Message, 10 | payload::{addr::NetworkAddr, Addr}, 11 | }, 12 | tools::{message_filter::MessageFilter, synthetic_node::SyntheticNode}, 13 | }; 14 | 15 | use super::{ActionCfg, SynthNodeAction}; 16 | 17 | // Configurable status printout interval. 18 | const BROADCAST_INTERVAL_SEC: Duration = Duration::from_secs(60); 19 | const DBG_INFO_LOG_INTERVAL_SEC: Duration = Duration::from_secs(10); 20 | 21 | const MAX_PEER_LIST_LEN: usize = 1000; 22 | 23 | pub(super) struct Action; 24 | 25 | pub(super) fn action() -> Box { 26 | Box::new(Action {}) 27 | } 28 | 29 | #[async_trait::async_trait] 30 | impl SynthNodeAction for Action { 31 | fn info(&self) -> &str { 32 | "a node which sends an Addr message every three seconds containing all connected peers" 33 | } 34 | 35 | fn config(&self) -> ActionCfg { 36 | ActionCfg { 37 | msg_filter: MessageFilter::with_all_disabled(), 38 | network_cfg: NodeConfig { 39 | listener_ip: Some(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), 40 | desired_listening_port: Some(18233), 41 | max_connections: 3000, 42 | ..Default::default() 43 | }, 44 | allow_proper_shutdown: true, 45 | } 46 | } 47 | 48 | #[allow(unused_variables)] 49 | async fn run(&self, synth_node: &mut SyntheticNode, addr: Option) -> Result<()> { 50 | let mut broadcast_msgs_interval = interval(BROADCAST_INTERVAL_SEC); 51 | let mut dbg_info_interval = interval(DBG_INFO_LOG_INTERVAL_SEC); 52 | let mut num_connected = synth_node.num_connected(); 53 | 54 | loop { 55 | tokio::select! { 56 | // Print some info about active connections. 57 | _ = dbg_info_interval.tick() => { 58 | trace_debug_info(synth_node).await; 59 | }, 60 | // Broadcast an Addr message to all peers. 61 | _ = broadcast_msgs_interval.tick() => { 62 | let num_connected_new = synth_node.num_connected(); 63 | 64 | if num_connected == num_connected_new { 65 | continue; 66 | } 67 | 68 | num_connected = num_connected_new; 69 | let _ = broadcast_addr_msg(synth_node); 70 | }, 71 | // Clear inbound queue. 72 | Ok(_) = synth_node.try_recv_message() => (), 73 | } 74 | } 75 | } 76 | } 77 | 78 | async fn trace_debug_info(synth_node: &SyntheticNode) { 79 | let peer_infos = synth_node.connected_peer_infos(); 80 | let peer_cnt = peer_infos.len(); 81 | 82 | let mut log = format!("\nNumber of peers: {peer_cnt}\n"); 83 | 84 | // Let's sort by the connection's time value. 85 | let mut peer_infos: Vec<_> = peer_infos.iter().collect(); 86 | peer_infos.sort_by(|a, b| a.1.stats().created().cmp(&b.1.stats().created())); 87 | 88 | for (addr, info) in peer_infos.iter() { 89 | let stats = info.stats(); 90 | 91 | // Align all possible IP addresses (both v4 and v6) vertically 92 | // Using this value, just like the INET6_ADDRSTRLEN constant in Linux, has 46 bytes 93 | const MAX_IPV6_ADDR_LEN: usize = 46; 94 | 95 | // Print basic info. 96 | log.push_str(&format!( 97 | "{side:?}: {addr:>ident$} - connection established for {time:?}\n\n", 98 | addr = addr, 99 | ident = MAX_IPV6_ADDR_LEN, 100 | side = info.side(), 101 | time = stats.created().elapsed() 102 | )); 103 | } 104 | 105 | tracing::info!("{log}"); 106 | } 107 | 108 | fn broadcast_addr_msg(synth_node: &mut SyntheticNode) -> Result<()> { 109 | let mut addrs = synth_node.connected_peers(); 110 | 111 | if addrs.len() > MAX_PEER_LIST_LEN { 112 | addrs.shuffle(&mut thread_rng()); 113 | addrs.truncate(MAX_PEER_LIST_LEN); 114 | } 115 | 116 | let msg = Message::Addr(Addr::new( 117 | addrs 118 | .into_iter() 119 | .map(|addr| { 120 | let ip = addr.ip(); 121 | let port = if let Some(hs_info) = synth_node.handshake_info(&addr) { 122 | hs_info.addr_from.addr.port() 123 | } else { 124 | // A random choice, this shouldn't ever happen. 125 | 8233 126 | }; 127 | 128 | NetworkAddr::new(SocketAddr::new(ip, port)) 129 | }) 130 | .collect::>(), 131 | )); 132 | 133 | for addr in synth_node.connected_peers() { 134 | if synth_node.unicast(addr, msg.clone()).is_err() { 135 | tracing::warn!("failed to send {msg:?}\n"); 136 | anyhow::bail!("connection closed"); 137 | } 138 | } 139 | 140 | Ok(()) 141 | } 142 | -------------------------------------------------------------------------------- /src/tests/conformance/invalid_message/reject.rs: -------------------------------------------------------------------------------- 1 | //! Contains test cases which cover ZG-CONFORMANCE-009. 2 | //! 3 | //! The node should reject the following messages post-handshake: 4 | //! 5 | //! Version - Duplicate 6 | //! Verack - Duplicate 7 | //! Inv(mixed types) - Invalid 8 | //! Inv(multiple blocks) - Invalid 9 | //! Bloom filter add - Obsolete 10 | //! Bloom filter load - Obsolete 11 | //! Bloom filter clear - Obsolete 12 | 13 | use std::io; 14 | 15 | use crate::{ 16 | protocol::{ 17 | message::Message, 18 | payload::{block::Block, reject::CCode, FilterAdd, FilterLoad, Inv, Version}, 19 | }, 20 | setup::node::{Action, Node}, 21 | tools::{ 22 | synthetic_node::{PingPongError, SyntheticNode}, 23 | RECV_TIMEOUT, 24 | }, 25 | }; 26 | 27 | #[tokio::test] 28 | #[allow(non_snake_case)] 29 | async fn c009_t1_VERSION_post_handshake() { 30 | // zcashd: pass 31 | // zebra: fail (connection terminated) 32 | let version = Message::Version(Version::new( 33 | "0.0.0.0:0".parse().unwrap(), 34 | "0.0.0.0:0".parse().unwrap(), 35 | )); 36 | 37 | run_test_case(version, CCode::Duplicate).await.unwrap(); 38 | } 39 | 40 | #[tokio::test] 41 | #[allow(non_snake_case)] 42 | async fn c009_t2_VERACK_post_handshake() { 43 | // zcashd: fail (ignored) 44 | // zebra: fail (connection terminated) 45 | run_test_case(Message::Verack, CCode::Duplicate) 46 | .await 47 | .unwrap(); 48 | } 49 | 50 | #[tokio::test] 51 | #[allow(non_snake_case)] 52 | async fn c009_t3_INV_mixed_inventory() { 53 | // TODO: is this the desired behaviour, https://github.com/ZcashFoundation/zebra/issues/2107 54 | // might suggest it is. 55 | // zcashd: fail (ignored) 56 | // zebra: fail (ignored) 57 | let genesis_block = Block::testnet_genesis(); 58 | let mixed_inv = vec![genesis_block.inv_hash(), genesis_block.txs[0].inv_hash()]; 59 | 60 | run_test_case(Message::Inv(Inv::new(mixed_inv)), CCode::Invalid) 61 | .await 62 | .unwrap(); 63 | } 64 | 65 | #[tokio::test] 66 | #[allow(non_snake_case)] 67 | async fn c009_t4_INV_multi_block_inventory() { 68 | // zcashd: fail (ignored) 69 | // zebra: fail (ignored) 70 | let multi_block_inv = vec![ 71 | Block::testnet_genesis().inv_hash(), 72 | Block::testnet_1().inv_hash(), 73 | Block::testnet_2().inv_hash(), 74 | ]; 75 | 76 | run_test_case(Message::Inv(Inv::new(multi_block_inv)), CCode::Invalid) 77 | .await 78 | .unwrap(); 79 | } 80 | 81 | #[tokio::test] 82 | #[allow(non_snake_case)] 83 | async fn c009_t5_BLOOM_FILTER_ADD() { 84 | // zcashd: fail (ccode: Malformed) 85 | // zebra: fail (ignored) 86 | run_test_case(Message::FilterAdd(FilterAdd::default()), CCode::Obsolete) 87 | .await 88 | .unwrap(); 89 | } 90 | 91 | #[tokio::test] 92 | #[allow(non_snake_case)] 93 | async fn c009_t6_BLOOM_FILTER_LOAD() { 94 | // zcashd: fail (ccode: Malformed) 95 | // zebra: fail (ignored) 96 | run_test_case(Message::FilterLoad(FilterLoad::default()), CCode::Obsolete) 97 | .await 98 | .unwrap(); 99 | } 100 | 101 | #[tokio::test] 102 | #[allow(non_snake_case)] 103 | async fn c009_t7_BLOOM_FILTER_CLEAR() { 104 | // zcashd: fail (ignored) 105 | // zebra: fail (ignored) 106 | run_test_case(Message::FilterClear, CCode::Obsolete) 107 | .await 108 | .unwrap(); 109 | } 110 | 111 | async fn run_test_case(message: Message, expected_code: CCode) -> io::Result<()> { 112 | // Setup a fully handshaken connection between a node and synthetic node. 113 | let mut node = Node::new()?; 114 | node.initial_action(Action::WaitForConnection) 115 | .start() 116 | .await?; 117 | let mut synthetic_node = SyntheticNode::builder() 118 | .with_full_handshake() 119 | .with_all_auto_reply() 120 | .build() 121 | .await?; 122 | synthetic_node.connect(node.addr()).await?; 123 | 124 | // Send the message to be rejected. 125 | synthetic_node.unicast(node.addr(), message)?; 126 | 127 | // Use Ping-Pong to check the node's response to our query. We expect a Reject message. 128 | let result = match synthetic_node 129 | .ping_pong_timeout(node.addr(), RECV_TIMEOUT) 130 | .await 131 | { 132 | Ok(_) => Err(io::Error::new(io::ErrorKind::Other, "Message was ignored")), 133 | Err(PingPongError::Unexpected(msg)) => match *msg { 134 | Message::Reject(reject) if reject.ccode == expected_code => Ok(()), 135 | Message::Reject(reject) => { 136 | return Err(io::Error::new( 137 | io::ErrorKind::Other, 138 | format!( 139 | "Incorrect rejection ccode: {:?} instead of {:?}", 140 | reject.ccode, expected_code 141 | ), 142 | )) 143 | } 144 | unexpected => { 145 | return Err(io::Error::new( 146 | io::ErrorKind::Other, 147 | format!("Unexpected message received: {unexpected:?}"), 148 | )) 149 | } 150 | }, 151 | Err(err) => Err(err.into()), 152 | }; 153 | 154 | // clean-up 155 | synthetic_node.shut_down().await; 156 | node.stop()?; 157 | 158 | result 159 | } 160 | -------------------------------------------------------------------------------- /synth_node_bin/src/action/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::{self, Display}, 3 | net::{IpAddr, Ipv4Addr, SocketAddr}, 4 | str::FromStr, 5 | }; 6 | 7 | use anyhow::Result; 8 | use pea2pea::Config as NodeConfig; 9 | use ziggurat_zcash::tools::{message_filter::MessageFilter, synthetic_node::SyntheticNode}; 10 | 11 | mod advanced_sn_for_s001; 12 | mod constantly_ask_for_random_blocks; 13 | mod quick_connect_and_then_clean_disconnect; 14 | mod quick_connect_with_improper_disconnect; 15 | mod rt_s1_collector; 16 | mod rt_s1_tainter; 17 | mod send_get_addr_and_forever_sleep; 18 | 19 | /// Defines properties of any action for a synth node binary. 20 | /// 21 | /// It simplifies adding new actions and allows to separate different actions with modules. 22 | #[async_trait::async_trait] 23 | trait SynthNodeAction { 24 | /// Action description. 25 | /// 26 | /// It can be displayed during the runtime. 27 | fn info(&self) -> &str; 28 | 29 | /// Action configuration. 30 | /// 31 | /// Allows preconfiguration settings before the action execution starts. 32 | fn config(&self) -> ActionCfg; 33 | 34 | /// Defines the core action functionality. 35 | /// 36 | /// All the program logic happens here. 37 | async fn run(&self, synth_node: &mut SyntheticNode, addr: Option) -> Result<()>; 38 | } 39 | 40 | /// List of available actions. 41 | #[derive(Clone, Copy)] 42 | pub enum ActionType { 43 | SendGetAddrAndForeverSleep, 44 | AdvancedSnForS001, 45 | QuickConnectAndThenCleanDisconnect, 46 | QuickConnectWithImproperDisconnect, 47 | ConstantlyAskForRandomBlocks, 48 | RtS1Collector, 49 | RtS1Tainter, 50 | } 51 | 52 | impl Display for ActionType { 53 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 54 | write!( 55 | f, 56 | "{}", 57 | match self { 58 | Self::SendGetAddrAndForeverSleep => "SendGetAddrAndForeverSleep", 59 | Self::AdvancedSnForS001 => "AdvancedSnForS001", 60 | Self::QuickConnectAndThenCleanDisconnect => "QuickConnectAndThenCleanDisconnect", 61 | Self::QuickConnectWithImproperDisconnect => "QuickConnectWithImproperDisconnect", 62 | Self::ConstantlyAskForRandomBlocks => "ConstantlyAskForRandomBlocks", 63 | Self::RtS1Collector => "RtS1Collector", 64 | Self::RtS1Tainter => "RtS1Tainter", 65 | } 66 | ) 67 | } 68 | } 69 | 70 | impl FromStr for ActionType { 71 | type Err = &'static str; 72 | 73 | fn from_str(s: &str) -> Result { 74 | match s { 75 | "SendGetAddrAndForeverSleep" => Ok(Self::SendGetAddrAndForeverSleep), 76 | "AdvancedSnForS001" => Ok(Self::AdvancedSnForS001), 77 | "QuickConnectAndThenCleanDisconnect" => Ok(Self::QuickConnectAndThenCleanDisconnect), 78 | "QuickConnectWithImproperDisconnect" => Ok(Self::QuickConnectWithImproperDisconnect), 79 | "ConstantlyAskForRandomBlocks" => Ok(Self::ConstantlyAskForRandomBlocks), 80 | "RtS1Collector" => Ok(Self::RtS1Collector), 81 | "RtS1Tainter" => Ok(Self::RtS1Tainter), 82 | _ => Err("Invalid action type"), 83 | } 84 | } 85 | } 86 | 87 | /// Action configuration options. 88 | pub struct ActionCfg { 89 | /// A message filter for a synthetic node. 90 | pub msg_filter: MessageFilter, 91 | 92 | /// Network configuration. 93 | pub network_cfg: NodeConfig, 94 | 95 | /// When enabled, the shutdown API in synthetic node is skipped. 96 | pub allow_proper_shutdown: bool, 97 | } 98 | 99 | impl Default for ActionCfg { 100 | fn default() -> Self { 101 | Self { 102 | msg_filter: MessageFilter::with_all_auto_reply(), 103 | network_cfg: NodeConfig { 104 | listener_ip: Some(IpAddr::V4(Ipv4Addr::LOCALHOST)), 105 | ..Default::default() 106 | }, 107 | allow_proper_shutdown: true, 108 | } 109 | } 110 | } 111 | 112 | /// Action handler. 113 | pub struct ActionHandler { 114 | /// Internal action. 115 | action: Box, 116 | 117 | /// Action startup configuration. 118 | pub cfg: ActionCfg, 119 | } 120 | 121 | impl ActionHandler { 122 | /// Creates a new [`ActionHandler`] for a given [`ActionType`]. 123 | pub fn new(action_type: ActionType) -> Self { 124 | let action = match action_type { 125 | ActionType::SendGetAddrAndForeverSleep => send_get_addr_and_forever_sleep::action(), 126 | ActionType::AdvancedSnForS001 => advanced_sn_for_s001::action(), 127 | ActionType::QuickConnectAndThenCleanDisconnect => { 128 | quick_connect_and_then_clean_disconnect::action() 129 | } 130 | ActionType::QuickConnectWithImproperDisconnect => { 131 | quick_connect_with_improper_disconnect::action() 132 | } 133 | ActionType::ConstantlyAskForRandomBlocks => constantly_ask_for_random_blocks::action(), 134 | ActionType::RtS1Collector => rt_s1_collector::action(), 135 | ActionType::RtS1Tainter => rt_s1_tainter::action(), 136 | }; 137 | let cfg = action.config(); 138 | 139 | println!( 140 | "Running a synth node which performs the following:\n\t{}", 141 | action.info() 142 | ); 143 | 144 | Self { action, cfg } 145 | } 146 | 147 | /// Runs the underlying action. 148 | pub async fn execute( 149 | &self, 150 | synth_node: &mut SyntheticNode, 151 | addr: Option, 152 | ) -> Result<()> { 153 | self.action.run(synth_node, addr).await 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/tools/crawler/metrics.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, net::SocketAddr}; 2 | 3 | use regex::Regex; 4 | use spectre::{edge::Edge, graph::Graph}; 5 | use ziggurat_core_crawler::summary::{NetworkSummary, NetworkType}; 6 | 7 | use crate::{ 8 | network::{KnownNode, LAST_SEEN_CUTOFF}, 9 | Crawler, 10 | }; 11 | 12 | const MIN_BLOCK_HEIGHT: i32 = 2_000_000; 13 | pub const ZCASH_P2P_DEFAULT_MAINNET_PORT: u16 = 8233; 14 | pub const ZCASH_P2P_DEFAULT_TESTNET_PORT: u16 = 18233; 15 | 16 | #[derive(Default)] 17 | pub struct NetworkMetrics { 18 | graph: Graph, 19 | } 20 | 21 | impl NetworkMetrics { 22 | /// Updates the network graph with new connections. 23 | pub fn update_graph(&mut self, crawler: &Crawler) { 24 | for conn in crawler.known_network.connections() { 25 | let edge = Edge::new(conn.a, conn.b); 26 | if conn.last_seen.elapsed().as_secs() > LAST_SEEN_CUTOFF { 27 | self.graph.remove(&edge); 28 | } else { 29 | self.graph.insert(edge); 30 | } 31 | } 32 | } 33 | 34 | /// Requests a summary of the network metrics. 35 | pub fn request_summary(&mut self, crawler: &Crawler) -> NetworkSummary { 36 | new_network_summary(crawler, &self.graph) 37 | } 38 | } 39 | 40 | // Updates the node's network type. 41 | // 42 | // The decision of whether a node belongs to the Zcash network is based on these factors: 43 | // `start_height` - this MUST match 44 | // and one of the additional factors also must match as an extra confirmation: 45 | // `port` 46 | // `agent` 47 | fn recognize_network_types( 48 | nodes: &HashMap, 49 | good_nodes: &Vec, 50 | ) -> Vec { 51 | let num_good_nodes = good_nodes.len(); 52 | let mut node_network_types = Vec::with_capacity(num_good_nodes); 53 | for node in good_nodes { 54 | let mut agent_matches = false; 55 | 56 | let port_matches = node.port() == ZCASH_P2P_DEFAULT_MAINNET_PORT 57 | || node.port() == ZCASH_P2P_DEFAULT_TESTNET_PORT; 58 | 59 | let agent = if let Some(agent) = &nodes[node].user_agent { 60 | agent.0.clone() 61 | } else { 62 | "".to_string() 63 | }; 64 | let zcash_regex = Regex::new(r"^/MagicBean:(\d)\.(\d)\.(\d)/$").unwrap(); 65 | let zebra_regex = Regex::new(r"^/Zebra:(\d)\.(\d)\.(\d)").unwrap(); 66 | 67 | // Look for zcash agent like "/MagicBean:5.4.2/" 68 | let cap_zc = zcash_regex.captures(agent.as_str()); 69 | if let Some(cap) = cap_zc { 70 | let major = cap.get(1).unwrap().as_str().parse::().unwrap(); 71 | if major < 6 { 72 | // Accept all zcash versions < 6 (6 is Flux) 73 | agent_matches = true; 74 | } else if major == 6 { 75 | // Block all zcash versions 6 (Flux) even if they are on the right port 76 | node_network_types.push(NetworkType::Unknown); 77 | continue; 78 | } 79 | } 80 | 81 | // Look for zebra agent like "/Zebra:1.0.0-rc.4/" 82 | let cap_ze = zebra_regex.captures(agent.as_str()); 83 | if cap_ze.is_some() { 84 | // Accept all zebra versions 85 | agent_matches = true; 86 | } 87 | 88 | // Check if the height is alright - this is a mandatory check for any zcash node implementation. 89 | let height = nodes[node].start_height.unwrap_or(0); 90 | if height < MIN_BLOCK_HEIGHT { 91 | node_network_types.push(NetworkType::Unknown); 92 | continue; 93 | } 94 | 95 | // When a block height is correct, we still need one additional confirmation: 96 | // In rare cases, the agent or the port won't use a commonly used value. 97 | if port_matches || agent_matches { 98 | node_network_types.push(NetworkType::Zcash); 99 | } else { 100 | node_network_types.push(NetworkType::Unknown); 101 | } 102 | } 103 | 104 | node_network_types 105 | } 106 | 107 | /// Constructs a new NetworkSummary from given nodes. 108 | pub fn new_network_summary(crawler: &Crawler, graph: &Graph) -> NetworkSummary { 109 | let nodes = crawler.known_network.nodes(); 110 | let connections = crawler.known_network.connections(); 111 | 112 | let num_known_nodes = nodes.len(); 113 | let num_known_connections = connections.len(); 114 | 115 | let good_nodes = nodes 116 | .clone() 117 | .into_iter() 118 | .filter_map(|(addr, node)| node.last_connected.map(|_| addr)) 119 | .collect::>(); 120 | 121 | let num_good_nodes = good_nodes.len(); 122 | 123 | let mut protocol_versions = HashMap::with_capacity(num_known_nodes); 124 | let mut user_agents = HashMap::with_capacity(num_known_nodes); 125 | 126 | for (_, node) in nodes.iter() { 127 | if node.protocol_version.is_some() { 128 | protocol_versions 129 | .entry(node.protocol_version.unwrap().0) 130 | .and_modify(|count| *count += 1) 131 | .or_insert(1); 132 | user_agents 133 | .entry(node.user_agent.clone().unwrap().0) 134 | .and_modify(|count| *count += 1) 135 | .or_insert(1); 136 | } 137 | } 138 | 139 | let node_network_types = recognize_network_types(&nodes, &good_nodes); 140 | 141 | let num_versions = protocol_versions.values().sum(); 142 | let nodes_indices = graph.get_filtered_adjacency_indices(&good_nodes); 143 | 144 | NetworkSummary { 145 | num_known_nodes, 146 | num_good_nodes, 147 | num_known_connections, 148 | num_versions, 149 | protocol_versions, 150 | user_agents, 151 | crawler_runtime: crawler.start_time.elapsed(), 152 | node_addrs: good_nodes, 153 | node_network_types, 154 | nodes_indices, 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1681202837, 9 | "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "cfacdce06f30d2b68473a46042957675eebb3401", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "flake-utils_2": { 22 | "inputs": { 23 | "systems": "systems_2" 24 | }, 25 | "locked": { 26 | "lastModified": 1681202837, 27 | "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", 28 | "owner": "numtide", 29 | "repo": "flake-utils", 30 | "rev": "cfacdce06f30d2b68473a46042957675eebb3401", 31 | "type": "github" 32 | }, 33 | "original": { 34 | "owner": "numtide", 35 | "repo": "flake-utils", 36 | "type": "github" 37 | } 38 | }, 39 | "flake-utils_3": { 40 | "inputs": { 41 | "systems": "systems_3" 42 | }, 43 | "locked": { 44 | "lastModified": 1681202837, 45 | "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", 46 | "owner": "numtide", 47 | "repo": "flake-utils", 48 | "rev": "cfacdce06f30d2b68473a46042957675eebb3401", 49 | "type": "github" 50 | }, 51 | "original": { 52 | "owner": "numtide", 53 | "repo": "flake-utils", 54 | "type": "github" 55 | } 56 | }, 57 | "nixpkgs": { 58 | "locked": { 59 | "lastModified": 1681303793, 60 | "narHash": "sha256-JEdQHsYuCfRL2PICHlOiH/2ue3DwoxUX7DJ6zZxZXFk=", 61 | "owner": "nixos", 62 | "repo": "nixpkgs", 63 | "rev": "fe2ecaf706a5907b5e54d979fbde4924d84b65fc", 64 | "type": "github" 65 | }, 66 | "original": { 67 | "owner": "nixos", 68 | "ref": "nixos-unstable", 69 | "repo": "nixpkgs", 70 | "type": "github" 71 | } 72 | }, 73 | "nixpkgs_2": { 74 | "locked": { 75 | "lastModified": 1681303793, 76 | "narHash": "sha256-JEdQHsYuCfRL2PICHlOiH/2ue3DwoxUX7DJ6zZxZXFk=", 77 | "owner": "nixos", 78 | "repo": "nixpkgs", 79 | "rev": "fe2ecaf706a5907b5e54d979fbde4924d84b65fc", 80 | "type": "github" 81 | }, 82 | "original": { 83 | "owner": "nixos", 84 | "ref": "nixos-unstable", 85 | "repo": "nixpkgs", 86 | "type": "github" 87 | } 88 | }, 89 | "nixpkgs_3": { 90 | "locked": { 91 | "lastModified": 1681358109, 92 | "narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=", 93 | "owner": "NixOS", 94 | "repo": "nixpkgs", 95 | "rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9", 96 | "type": "github" 97 | }, 98 | "original": { 99 | "owner": "NixOS", 100 | "ref": "nixpkgs-unstable", 101 | "repo": "nixpkgs", 102 | "type": "github" 103 | } 104 | }, 105 | "root": { 106 | "inputs": { 107 | "flake-utils": "flake-utils", 108 | "nixpkgs": "nixpkgs", 109 | "ziggurat-core": "ziggurat-core" 110 | } 111 | }, 112 | "rust-overlay": { 113 | "inputs": { 114 | "flake-utils": "flake-utils_3", 115 | "nixpkgs": "nixpkgs_3" 116 | }, 117 | "locked": { 118 | "lastModified": 1681438695, 119 | "narHash": "sha256-TSDCZwJ+mp1qOoOAvgK6hXeqn7wNacWT9RTxBJ6aZj4=", 120 | "owner": "oxalica", 121 | "repo": "rust-overlay", 122 | "rev": "db7bf4a2dd295adeeaa809d36387098926a15487", 123 | "type": "github" 124 | }, 125 | "original": { 126 | "owner": "oxalica", 127 | "repo": "rust-overlay", 128 | "type": "github" 129 | } 130 | }, 131 | "systems": { 132 | "locked": { 133 | "lastModified": 1681028828, 134 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 135 | "owner": "nix-systems", 136 | "repo": "default", 137 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 138 | "type": "github" 139 | }, 140 | "original": { 141 | "owner": "nix-systems", 142 | "repo": "default", 143 | "type": "github" 144 | } 145 | }, 146 | "systems_2": { 147 | "locked": { 148 | "lastModified": 1681028828, 149 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 150 | "owner": "nix-systems", 151 | "repo": "default", 152 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 153 | "type": "github" 154 | }, 155 | "original": { 156 | "owner": "nix-systems", 157 | "repo": "default", 158 | "type": "github" 159 | } 160 | }, 161 | "systems_3": { 162 | "locked": { 163 | "lastModified": 1681028828, 164 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 165 | "owner": "nix-systems", 166 | "repo": "default", 167 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 168 | "type": "github" 169 | }, 170 | "original": { 171 | "owner": "nix-systems", 172 | "repo": "default", 173 | "type": "github" 174 | } 175 | }, 176 | "ziggurat-core": { 177 | "inputs": { 178 | "flake-utils": "flake-utils_2", 179 | "nixpkgs": "nixpkgs_2", 180 | "rust-overlay": "rust-overlay" 181 | }, 182 | "locked": { 183 | "lastModified": 1681473617, 184 | "narHash": "sha256-1w/EP0qwtT3shdmhucK112oWrTW89NMP58JaG0Uvisg=", 185 | "owner": "runziggurat", 186 | "repo": "ziggurat-core", 187 | "rev": "566c13093c10f04381537b44ab105f39fe64c080", 188 | "type": "github" 189 | }, 190 | "original": { 191 | "owner": "runziggurat", 192 | "repo": "ziggurat-core", 193 | "type": "github" 194 | } 195 | } 196 | }, 197 | "root": "root", 198 | "version": 7 199 | } 200 | -------------------------------------------------------------------------------- /synth_node_bin/src/action/rt_s1_tainter.rs: -------------------------------------------------------------------------------- 1 | use std::net::{IpAddr, Ipv4Addr, SocketAddr}; 2 | 3 | use anyhow::Result; 4 | use pea2pea::Config as NodeConfig; 5 | use tokio::time::{interval, Duration}; 6 | use ziggurat_zcash::{ 7 | protocol::{ 8 | message::Message, 9 | payload::{addr::NetworkAddr, Addr, Nonce}, 10 | }, 11 | tools::{ 12 | message_filter::{Filter, MessageFilter}, 13 | synthetic_node::SyntheticNode, 14 | }, 15 | }; 16 | 17 | use super::{ActionCfg, SynthNodeAction}; 18 | 19 | // Configurable status printout interval. 20 | const DBG_INFO_LOG_INTERVAL_SEC: Duration = Duration::from_secs(10); 21 | const BROADCAST_INTERVAL_SEC: Duration = Duration::from_secs(120); 22 | 23 | pub(super) struct Action; 24 | 25 | pub(super) fn action() -> Box { 26 | Box::new(Action {}) 27 | } 28 | 29 | #[async_trait::async_trait] 30 | impl SynthNodeAction for Action { 31 | fn info(&self) -> &str { 32 | "a listener node which sends a tainted Addr message to all its peers" 33 | } 34 | 35 | fn config(&self) -> ActionCfg { 36 | ActionCfg { 37 | msg_filter: MessageFilter::with_all_auto_reply().with_getaddr_filter(Filter::Disabled), 38 | network_cfg: NodeConfig { 39 | listener_ip: Some(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), 40 | desired_listening_port: Some(8233), 41 | max_connections: 1000, 42 | ..Default::default() 43 | }, 44 | allow_proper_shutdown: true, 45 | } 46 | } 47 | 48 | #[allow(unused_variables)] 49 | async fn run(&self, synth_node: &mut SyntheticNode, addr: Option) -> Result<()> { 50 | let addr = if let Some(addr) = addr { 51 | addr 52 | } else { 53 | anyhow::bail!("address not provided"); 54 | }; 55 | 56 | let mut dbg_info_interval = interval(DBG_INFO_LOG_INTERVAL_SEC); 57 | let mut broadcast_msgs_interval = interval(BROADCAST_INTERVAL_SEC); 58 | let mut tainted_addr_msg = Message::Addr(Addr::new(vec![NetworkAddr::new( 59 | synth_node.listening_addr(), 60 | )])); 61 | 62 | loop { 63 | tokio::select! { 64 | _ = dbg_info_interval.tick() => { 65 | trace_debug_info(synth_node).await; 66 | }, 67 | _ = broadcast_msgs_interval.tick() => { 68 | let _ = broadcast_periodic_msgs(synth_node, &tainted_addr_msg); 69 | }, 70 | Ok((src, msg)) = synth_node.try_recv_message() => { 71 | tracing::info!("message received from {src}:\n{msg:?}"); 72 | 73 | // Store the tainted peer list from our collector. 74 | if src == addr && matches!(msg, Message::Addr(_)) { 75 | tainted_addr_msg = msg; 76 | continue; 77 | } 78 | 79 | // Or otherwise just handle the message. 80 | // We are only handling a GetAddr for now. 81 | if msg != Message::GetAddr { 82 | continue; 83 | } 84 | 85 | if synth_node.unicast(src, tainted_addr_msg.clone()).is_err() { 86 | tracing::warn!("failed to send {tainted_addr_msg:?}\n"); 87 | anyhow::bail!("connection closed"); 88 | } 89 | }, 90 | } 91 | } 92 | } 93 | } 94 | 95 | async fn trace_debug_info(synth_node: &SyntheticNode) { 96 | let peer_infos = synth_node.connected_peer_infos(); 97 | let peer_cnt = peer_infos.len(); 98 | 99 | let mut log = format!("\nNumber of peers: {peer_cnt}\n"); 100 | 101 | // Let's sort by the connection's time value. 102 | let mut peer_infos: Vec<_> = peer_infos.iter().collect(); 103 | peer_infos.sort_by(|a, b| a.1.stats().created().cmp(&b.1.stats().created())); 104 | 105 | for (addr, info) in peer_infos.iter() { 106 | let stats = info.stats(); 107 | 108 | // Align all possible IP addresses (both v4 and v6) vertically 109 | // Using this value, just like the INET6_ADDRSTRLEN constant in Linux, has 46 bytes 110 | const MAX_IPV6_ADDR_LEN: usize = 46; 111 | 112 | // Print some handshake details first - it's easier to see the IP when it it is shown after 113 | // these details. 114 | if let Some(hs_info) = synth_node.handshake_info(addr) { 115 | log.push_str(&format!( 116 | "{:?} - Services({}) - UserAgent({}) - AddrFrom({}) - Timestamp({}) - StartHeight({})\n", 117 | hs_info.version, hs_info.services, hs_info.user_agent.0, hs_info.addr_from.addr, hs_info.timestamp, hs_info.start_height 118 | )); 119 | } 120 | 121 | // Print basic info. 122 | log.push_str(&format!( 123 | "{side:?}: {addr:>ident$} - connection established for {time:?}\n\n", 124 | addr = addr, 125 | ident = MAX_IPV6_ADDR_LEN, 126 | side = info.side(), 127 | time = stats.created().elapsed() 128 | )); 129 | } 130 | 131 | tracing::info!("{log}"); 132 | } 133 | 134 | fn broadcast_periodic_msgs(synth_node: &mut SyntheticNode, addr_msg: &Message) -> Result<()> { 135 | if let Err(e) = broadcast_ping_msg(synth_node) { 136 | tracing::warn!("failed to broadcast Ping messages: {e}"); 137 | } else if let Err(e) = broadcast_addr_msg(synth_node, addr_msg) { 138 | tracing::warn!("failed to broadcast Addr messages: {e}"); 139 | } else if let Err(e) = broadcast_get_addr_msg(synth_node) { 140 | tracing::warn!("failed to broadcast GetAddr messages: {e}"); 141 | } 142 | 143 | Ok(()) 144 | } 145 | 146 | fn broadcast_ping_msg(synth_node: &mut SyntheticNode) -> Result<()> { 147 | let msg = Message::Ping(Nonce::default()); 148 | 149 | for addr in synth_node.connected_peers() { 150 | if synth_node.unicast(addr, msg.clone()).is_err() { 151 | tracing::error!("failed to send {msg:?} to {addr}\n"); 152 | anyhow::bail!("connection closed"); 153 | } 154 | } 155 | 156 | Ok(()) 157 | } 158 | 159 | fn broadcast_get_addr_msg(synth_node: &mut SyntheticNode) -> Result<()> { 160 | let msg = Message::GetAddr; 161 | 162 | for addr in synth_node.connected_peers() { 163 | if synth_node.unicast(addr, msg.clone()).is_err() { 164 | tracing::error!("failed to send {msg:?} to {addr}\n"); 165 | anyhow::bail!("connection closed"); 166 | } 167 | } 168 | 169 | Ok(()) 170 | } 171 | 172 | fn broadcast_addr_msg(synth_node: &mut SyntheticNode, addr_msg: &Message) -> Result<()> { 173 | for dst in synth_node.connected_peers() { 174 | if synth_node.unicast(dst, addr_msg.clone()).is_err() { 175 | tracing::warn!("failed to send {addr_msg:?}\n"); 176 | anyhow::bail!("connection closed"); 177 | } 178 | } 179 | 180 | Ok(()) 181 | } 182 | -------------------------------------------------------------------------------- /src/tests/conformance/query/get_data.rs: -------------------------------------------------------------------------------- 1 | //! Contains test cases which cover ZG-CONFORMANCE-018 2 | //! 3 | //! The node responds to `GetData` requests with the appropriate transaction or block as requested by the peer. 4 | //! 5 | //! Note: Zebra does not support seeding with chain data and as such cannot run any of these tests successfully. 6 | //! 7 | //! Note: Zcashd currently ignores requests for non-existent blocks. We expect a [`Message::NotFound`] response. 8 | 9 | use crate::{ 10 | protocol::{ 11 | message::Message, 12 | payload::{inv::InvHash, Hash, Inv}, 13 | }, 14 | tests::conformance::query::{run_test_query, SEED_BLOCKS}, 15 | }; 16 | 17 | mod single_block { 18 | use super::*; 19 | 20 | #[tokio::test] 21 | #[allow(non_snake_case)] 22 | async fn c018_t1_GET_DATA_block_last() { 23 | // zcashd: pass 24 | let block = SEED_BLOCKS.last().unwrap().clone(); 25 | let query = Message::GetData(Inv::new(vec![block.inv_hash()])); 26 | let expected = vec![Message::Block(block.into())]; 27 | let response = run_test_query(query).await.unwrap(); 28 | assert_eq!(response, expected); 29 | } 30 | 31 | #[tokio::test] 32 | #[allow(non_snake_case)] 33 | async fn c018_t2_GET_DATA_block_first() { 34 | // zcashd: pass 35 | let block = SEED_BLOCKS.first().unwrap().clone(); 36 | let query = Message::GetData(Inv::new(vec![block.inv_hash()])); 37 | let expected = vec![Message::Block(block.into())]; 38 | let response = run_test_query(query).await.unwrap(); 39 | assert_eq!(response, expected); 40 | } 41 | 42 | #[tokio::test] 43 | #[allow(non_snake_case)] 44 | async fn c018_t3_GET_DATA_block_5() { 45 | // zcashd: pass 46 | let block = SEED_BLOCKS[5].clone(); 47 | let query = Message::GetData(Inv::new(vec![block.inv_hash()])); 48 | let expected = vec![Message::Block(block.into())]; 49 | let response = run_test_query(query).await.unwrap(); 50 | assert_eq!(response, expected); 51 | } 52 | 53 | #[tokio::test] 54 | #[allow(non_snake_case)] 55 | async fn c018_t4_GET_DATA_non_existent() { 56 | // zcashd: fail (ignores non-existent block) 57 | let inv = Inv::new(vec![InvHash::Block(Hash::new([17; 32]))]); 58 | let query = Message::GetData(inv.clone()); 59 | let expected = vec![Message::NotFound(inv)]; 60 | let response = run_test_query(query).await.unwrap(); 61 | assert_eq!(response, expected); 62 | } 63 | } 64 | 65 | mod multiple_blocks { 66 | use super::*; 67 | 68 | #[tokio::test] 69 | #[allow(non_snake_case)] 70 | async fn c018_t5_GET_DATA_all() { 71 | // zcashd: pass 72 | let blocks = &SEED_BLOCKS; 73 | let inv_hash = blocks.iter().map(|block| block.inv_hash()).collect(); 74 | let query = Message::GetData(Inv::new(inv_hash)); 75 | let expected = blocks 76 | .iter() 77 | .map(|block| Message::Block(Box::new(block.clone()))) 78 | .collect::>(); 79 | let response = run_test_query(query).await.unwrap(); 80 | assert_eq!(response, expected); 81 | } 82 | 83 | #[tokio::test] 84 | #[allow(non_snake_case)] 85 | async fn c018_t6_GET_DATA_out_of_order() { 86 | // zcashd: pass 87 | let blocks = vec![&SEED_BLOCKS[3], &SEED_BLOCKS[1], &SEED_BLOCKS[7]]; 88 | let inv_hash = blocks.iter().map(|block| block.inv_hash()).collect(); 89 | let query = Message::GetData(Inv::new(inv_hash)); 90 | let expected = blocks 91 | .iter() 92 | .map(|&block| Message::Block(Box::new(block.clone()))) 93 | .collect::>(); 94 | let response = run_test_query(query).await.unwrap(); 95 | assert_eq!(response, expected); 96 | } 97 | 98 | #[tokio::test] 99 | #[allow(non_snake_case)] 100 | async fn c018_t7_GET_DATA_blocks_1_to_8() { 101 | // zcashd: pass 102 | let blocks = &SEED_BLOCKS[1..=8]; 103 | let inv_hash = blocks.iter().map(|block| block.inv_hash()).collect(); 104 | let query = Message::GetData(Inv::new(inv_hash)); 105 | let expected = blocks 106 | .iter() 107 | .map(|block| Message::Block(Box::new(block.clone()))) 108 | .collect::>(); 109 | let response = run_test_query(query).await.unwrap(); 110 | assert_eq!(response, expected); 111 | } 112 | 113 | #[tokio::test] 114 | #[allow(non_snake_case)] 115 | async fn c018_t8_GET_DATA_non_existent_blocks() { 116 | // zcashd: fails (ignores non-existent blocks). 117 | let inv = Inv::new(vec![ 118 | InvHash::Block(Hash::new([17; 32])), 119 | InvHash::Block(Hash::new([211; 32])), 120 | InvHash::Block(Hash::new([74; 32])), 121 | ]); 122 | 123 | let query = Message::GetData(inv.clone()); 124 | let expected = vec![Message::NotFound(inv)]; 125 | let response = run_test_query(query).await.unwrap(); 126 | assert_eq!(response, expected); 127 | } 128 | 129 | #[tokio::test] 130 | #[allow(non_snake_case)] 131 | async fn c018_t9_GET_DATA_mixed_existent_and_non_existent_blocks() { 132 | // Test a mixture of existent and non-existent blocks 133 | // interwoven together. 134 | // 135 | // We expect the response to contain a Block for each 136 | // existing request, and a single NotFound containing 137 | // hashes for all non-existent blocks. The order of 138 | // these is undefined, but since zcashd currently 139 | // does not send NotFound at all, it does not matter. 140 | // 141 | // zcashd: fails (ignores non-existent blocks). 142 | 143 | let non_existent_inv = vec![ 144 | InvHash::Block(Hash::new([17; 32])), 145 | InvHash::Block(Hash::new([211; 32])), 146 | InvHash::Block(Hash::new([74; 32])), 147 | ]; 148 | let blocks = SEED_BLOCKS 149 | .iter() 150 | .skip(3) 151 | .take(non_existent_inv.len()) 152 | .collect::>(); 153 | 154 | let expected_blocks = blocks 155 | .iter() 156 | .map(|&block| Message::Block(Box::new(block.clone()))) 157 | .collect::>(); 158 | let expected_non_existent = Message::NotFound(Inv::new(non_existent_inv.clone())); 159 | 160 | let mixed_inv = blocks 161 | .iter() 162 | .map(|block| block.inv_hash()) 163 | .zip(non_existent_inv) 164 | .flat_map(|(a, b)| [a, b]) 165 | .collect(); 166 | 167 | let query = Message::GetData(Inv::new(mixed_inv)); 168 | let response = run_test_query(query).await.unwrap(); 169 | 170 | // Should contain expected_blocks[..] and expected_non_existent. Not sure 171 | // what order we should expect (if any). But since the node currently ignores 172 | // non-existent block queries we are free to assume any order. 173 | let mut expected = expected_blocks; 174 | expected.push(expected_non_existent); 175 | assert_eq!(response, expected); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/tests/conformance/peering.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use assert_matches::assert_matches; 4 | 5 | use crate::{ 6 | protocol::{ 7 | message::Message, 8 | payload::{addr::NetworkAddr, Addr}, 9 | }, 10 | setup::node::{Action, Node}, 11 | tools::{ 12 | message_filter::{Filter, MessageFilter}, 13 | synthetic_node::SyntheticNode, 14 | LONG_TIMEOUT, 15 | }, 16 | wait_until, 17 | }; 18 | 19 | #[tokio::test] 20 | async fn c013_eagerly_crawls_network_for_peers() { 21 | // ZG-CONFORMANCE-013 22 | // 23 | // The node crawls the network for new peers and eagerly connects. 24 | // 25 | // Test procedure: 26 | // 27 | // 1. Create a set of peer nodes, listening concurrently 28 | // 2. Connect to node with another main peer node 29 | // 3. Wait for `GetAddr` 30 | // 4. Send set of peer listener node addresses 31 | // 5. Expect the node to connect to each peer in the set 32 | // 33 | // zcashd: Has different behaviour depending on connection direction. 34 | // If we initiate the main connection it sends Ping, GetHeaders, 35 | // but never GetAddr. 36 | // If the node initiates then it does send GetAddr, but it never connects 37 | // to the peers. 38 | // 39 | // zebra: Fails, unless we keep responding on the main connection. 40 | // If we do not keep responding then the peer connections take really long to establish, 41 | // failing the test completely. 42 | // 43 | // Nu5: fails, caches the addresses but doesn't open new connections, peer protocol 44 | // tbc. 45 | // 46 | // Related issues: https://github.com/ZcashFoundation/zebra/pull/2154 47 | // https://github.com/ZcashFoundation/zebra/issues/2163 48 | 49 | // Spin up a node instance. 50 | let mut node = Node::new().unwrap(); 51 | node.initial_action(Action::WaitForConnection) 52 | .start() 53 | .await 54 | .unwrap(); 55 | 56 | // Create 5 synthetic nodes. 57 | const N: usize = 5; 58 | let (synthetic_nodes, addrs) = SyntheticNode::builder() 59 | .with_full_handshake() 60 | .with_all_auto_reply() 61 | .build_n(N) 62 | .await 63 | .unwrap(); 64 | 65 | let addrs = addrs 66 | .iter() 67 | .map(|&addr| NetworkAddr::new(addr)) 68 | .collect::>(); 69 | 70 | // Adjust the config so it lets through GetAddr message and start a "main" synthetic node which 71 | // will provide the peer list. 72 | let synthetic_node = SyntheticNode::builder() 73 | .with_full_handshake() 74 | .with_message_filter( 75 | MessageFilter::with_all_auto_reply().with_getaddr_filter(Filter::Disabled), 76 | ) 77 | .build() 78 | .await 79 | .unwrap(); 80 | 81 | // Connect and handshake. 82 | synthetic_node.connect(node.addr()).await.unwrap(); 83 | 84 | // Expect GetAddr, this used to be necessary, as of Nu5, it may not be anymore. 85 | // let (_, getaddr) = synthetic_node.recv_message_timeout(TIMEOUT).await.unwrap(); 86 | // assert_matches!(getaddr, Message::GetAddr); 87 | 88 | // Respond with peer list. 89 | synthetic_node 90 | .unicast(node.addr(), Message::Addr(Addr::new(addrs))) 91 | .unwrap(); 92 | 93 | // Expect the synthetic nodes to get a connection request from the node. 94 | for node in synthetic_nodes { 95 | wait_until!(LONG_TIMEOUT, node.num_connected() == 1); 96 | 97 | node.shut_down().await; 98 | } 99 | 100 | // Gracefully shut down the node. 101 | node.stop().unwrap(); 102 | } 103 | 104 | #[tokio::test] 105 | async fn c014_correctly_lists_peers() { 106 | // ZG-CONFORMANCE-014 107 | // 108 | // The node responds to a `GetAddr` with a list of peers it’s connected to. This command 109 | // should only be sent once, and by the node initiating the connection. 110 | // 111 | // In addition, this test case exercises the known zebra bug: https://github.com/ZcashFoundation/zebra/pull/2120 112 | // 113 | // Test procedure 114 | // 1. Establish N peer listeners 115 | // 2. Start node which connects to these N peers 116 | // 3. Create i..M new connections which, 117 | // a) Connect to the node 118 | // b) Query GetAddr 119 | // c) Receive Addr == N peer addresses 120 | // 121 | // This test currently fails for both zcashd and zebra. 122 | // 123 | // Current behaviour: 124 | // 125 | // zcashd: Never responds. Logs indicate `Unknown command "getaddr" from peer=1` if we initiate 126 | // the connection. If the node initiates the connection then the command is recoginized, 127 | // but likely ignored (because only the initiating node is supposed to send it). 128 | // 129 | // zebra: Never responds: "zebrad::components::inbound: ignoring `Peers` request from remote peer during network setup" 130 | // 131 | // Nu5: never responds, not sure why, adding a timeout after the node start removes the setup error. 132 | // 133 | // Can be coaxed into responding by sending a non-empty Addr in 134 | // response to node's GetAddr. This still fails as it includes previous inbound 135 | // connections in its address book (as in the bug listed above). 136 | 137 | // Create 5 synthetic nodes. 138 | const N: usize = 5; 139 | let node_builder = SyntheticNode::builder() 140 | .with_full_handshake() 141 | .with_all_auto_reply(); 142 | let (synthetic_nodes, expected_addrs) = node_builder.build_n(N).await.unwrap(); 143 | 144 | // Start node with the synthetic nodes as initial peers. 145 | let mut node = Node::new().unwrap(); 146 | node.initial_action(Action::WaitForConnection) 147 | .initial_peers(expected_addrs.clone()) 148 | .start() 149 | .await 150 | .unwrap(); 151 | 152 | // This fixes the "setup incomplete" issue. 153 | // tokio::time::sleep(std::time::Duration::from_secs(10)).await; 154 | 155 | // Connect to node and request GetAddr. We perform multiple iterations to exercise the #2120 156 | // zebra bug. 157 | for _ in 0..N { 158 | let mut synthetic_node = node_builder.build().await.unwrap(); 159 | 160 | synthetic_node.connect(node.addr()).await.unwrap(); 161 | synthetic_node 162 | .unicast(node.addr(), Message::GetAddr) 163 | .unwrap(); 164 | 165 | let (_, addr) = synthetic_node 166 | .recv_message_timeout(LONG_TIMEOUT) 167 | .await 168 | .unwrap(); 169 | let addrs = assert_matches!(addr, Message::Addr(addrs) => addrs); 170 | 171 | // Check that ephemeral connections were not gossiped. 172 | let addrs: Vec = addrs.iter().map(|network_addr| network_addr.addr).collect(); 173 | assert_eq!(addrs, expected_addrs); 174 | 175 | synthetic_node.shut_down().await; 176 | } 177 | 178 | // Gracefully shut down nodes. 179 | for synthetic_node in synthetic_nodes { 180 | synthetic_node.shut_down().await; 181 | } 182 | 183 | node.stop().unwrap(); 184 | } 185 | -------------------------------------------------------------------------------- /synth_node_bin/src/action/advanced_sn_for_s001.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | net::{IpAddr, Ipv4Addr, SocketAddr}, 3 | str::FromStr, 4 | }; 5 | 6 | use anyhow::Result; 7 | use pea2pea::Config as NodeConfig; 8 | use tokio::time::{interval, sleep, Duration}; 9 | use ziggurat_zcash::{ 10 | protocol::{ 11 | message::Message, 12 | payload::{addr::NetworkAddr, Addr, Nonce}, 13 | }, 14 | tools::{ 15 | message_filter::{Filter, MessageFilter}, 16 | synthetic_node::SyntheticNode, 17 | }, 18 | }; 19 | 20 | use super::{ActionCfg, SynthNodeAction}; 21 | 22 | // Configurable status printout interval. 23 | const DBG_INFO_LOG_INTERVAL_SEC: Duration = Duration::from_secs(10); 24 | const BROADCAST_INTERVAL_SEC: Duration = Duration::from_secs(120); 25 | 26 | pub(super) struct Action; 27 | 28 | pub(super) fn action() -> Box { 29 | Box::new(Action {}) 30 | } 31 | 32 | #[async_trait::async_trait] 33 | impl SynthNodeAction for Action { 34 | fn info(&self) -> &str { 35 | "an advanced SN node created for RT-S001 which can do the following 36 | - listens for inbound connections and prints the connection status periodically 37 | - periodically requests and sends Ping/GetAddr/Addr messages to all peers" 38 | } 39 | 40 | fn config(&self) -> ActionCfg { 41 | ActionCfg { 42 | msg_filter: MessageFilter::with_all_auto_reply().with_getaddr_filter(Filter::Disabled), 43 | network_cfg: NodeConfig { 44 | listener_ip: Some(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), 45 | desired_listening_port: Some(8233), 46 | ..Default::default() 47 | }, 48 | allow_proper_shutdown: true, 49 | } 50 | } 51 | 52 | #[allow(unused_variables)] 53 | async fn run(&self, synth_node: &mut SyntheticNode, addr: Option) -> Result<()> { 54 | // Sleep for three seconds before taking any actions - so the GetHeaders is handled before we 55 | // send GetAddr to the zcashd node for our only outbound connection. 56 | sleep(Duration::from_secs(3)).await; 57 | 58 | let mut dbg_info_interval = interval(DBG_INFO_LOG_INTERVAL_SEC); 59 | let mut broadcast_msgs_interval = interval(BROADCAST_INTERVAL_SEC); 60 | 61 | loop { 62 | tokio::select! { 63 | _ = dbg_info_interval.tick() => { 64 | trace_debug_info(synth_node).await; 65 | }, 66 | _ = broadcast_msgs_interval.tick() => { 67 | let _ = broadcast_periodic_msgs(synth_node); 68 | }, 69 | Ok((src, msg)) = synth_node.try_recv_message() => { 70 | handle_rx_msg(synth_node, src, msg).await?; 71 | }, 72 | } 73 | } 74 | } 75 | } 76 | 77 | async fn trace_debug_info(synth_node: &SyntheticNode) { 78 | let peer_infos = synth_node.connected_peer_infos(); 79 | let peer_cnt = peer_infos.len(); 80 | 81 | let mut log = format!("\nNumber of peers: {peer_cnt}\n"); 82 | 83 | // Let's sort by the connection's time value. 84 | let mut peer_infos: Vec<_> = peer_infos.iter().collect(); 85 | peer_infos.sort_by(|a, b| a.1.stats().created().cmp(&b.1.stats().created())); 86 | 87 | for (addr, info) in peer_infos.iter() { 88 | let stats = info.stats(); 89 | 90 | // Align all possible IP addresses (both v4 and v6) vertically 91 | // Using this value, just like the INET6_ADDRSTRLEN constant in Linux, has 46 bytes 92 | const MAX_IPV6_ADDR_LEN: usize = 46; 93 | 94 | // Print some handshake details first - it's easier to see the IP when it it is shown after 95 | // these details. 96 | if let Some(hs_info) = synth_node.handshake_info(addr) { 97 | log.push_str(&format!( 98 | "{:?} - Services({}) - UserAgent({}) - AddrFrom({}) - Timestamp({}) - StartHeight({})\n", 99 | hs_info.version, hs_info.services, hs_info.user_agent.0, hs_info.addr_from.addr, hs_info.timestamp, hs_info.start_height 100 | )); 101 | } 102 | 103 | // Print basic info. 104 | log.push_str(&format!( 105 | "{side:?}: {addr:>ident$} - connection established for {time:?}\n\n", 106 | addr = addr, 107 | ident = MAX_IPV6_ADDR_LEN, 108 | side = info.side(), 109 | time = stats.created().elapsed() 110 | )); 111 | } 112 | 113 | tracing::info!("{log}"); 114 | } 115 | 116 | fn broadcast_periodic_msgs(synth_node: &mut SyntheticNode) -> Result<()> { 117 | if let Err(e) = broadcast_ping_msg(synth_node) { 118 | tracing::warn!("failed to broadcast Ping messages: {e}"); 119 | } else if let Err(e) = broadcast_addr_msg(synth_node) { 120 | tracing::warn!("failed to broadcast Addr messages: {e}"); 121 | } else if let Err(e) = broadcast_get_addr_msg(synth_node) { 122 | tracing::warn!("failed to broadcast GetAddr messages: {e}"); 123 | } 124 | 125 | Ok(()) 126 | } 127 | 128 | fn broadcast_ping_msg(synth_node: &mut SyntheticNode) -> Result<()> { 129 | let msg = Message::Ping(Nonce::default()); 130 | 131 | for addr in synth_node.connected_peers() { 132 | if synth_node.unicast(addr, msg.clone()).is_err() { 133 | tracing::error!("failed to send {msg:?} to {addr}\n"); 134 | anyhow::bail!("connection closed"); 135 | } 136 | } 137 | 138 | Ok(()) 139 | } 140 | 141 | fn broadcast_get_addr_msg(synth_node: &mut SyntheticNode) -> Result<()> { 142 | let msg = Message::GetAddr; 143 | 144 | for addr in synth_node.connected_peers() { 145 | if synth_node.unicast(addr, msg.clone()).is_err() { 146 | tracing::error!("failed to send {msg:?} to {addr}\n"); 147 | anyhow::bail!("connection closed"); 148 | } 149 | } 150 | 151 | Ok(()) 152 | } 153 | 154 | async fn handle_rx_msg( 155 | synth_node: &mut SyntheticNode, 156 | src: SocketAddr, 157 | msg: Message, 158 | ) -> Result<()> { 159 | tracing::info!("message received from {src}:\n{msg:?}"); 160 | 161 | // We are only handling a GetAddr for now. 162 | if msg == Message::GetAddr { 163 | unicast_addr_msg(synth_node, src)?; 164 | } 165 | 166 | Ok(()) 167 | } 168 | 169 | fn unicast_addr_msg(synth_node: &mut SyntheticNode, dst: SocketAddr) -> Result<()> { 170 | // These are all our IPs 171 | let addrs = vec![ 172 | // Our zebrad 173 | NetworkAddr::new(SocketAddr::from_str("35.210.208.185:8233").unwrap()), 174 | // Our zcashd 175 | NetworkAddr::new(SocketAddr::from_str("35.205.233.245:8233").unwrap()), 176 | // Our synth node 177 | NetworkAddr::new(SocketAddr::from_str("35.205.233.245:46313").unwrap()), 178 | ]; 179 | let msg = Message::Addr(Addr::new(addrs)); 180 | 181 | tracing::info!("unicast {msg:?}\n"); 182 | if synth_node.unicast(dst, msg.clone()).is_err() { 183 | tracing::warn!("failed to send {msg:?}\n"); 184 | anyhow::bail!("connection closed"); 185 | } 186 | 187 | Ok(()) 188 | } 189 | 190 | fn broadcast_addr_msg(synth_node: &mut SyntheticNode) -> Result<()> { 191 | for addr in synth_node.connected_peers() { 192 | unicast_addr_msg(synth_node, addr)?; 193 | } 194 | 195 | Ok(()) 196 | } 197 | -------------------------------------------------------------------------------- /src/protocol/payload/mod.rs: -------------------------------------------------------------------------------- 1 | //! Network message payload types. 2 | 3 | use std::io; 4 | 5 | use bytes::{Buf, BufMut}; 6 | use rand::{thread_rng, Rng}; 7 | use serde::{Deserialize, Serialize}; 8 | use time::OffsetDateTime; 9 | 10 | pub mod addr; 11 | pub use addr::Addr; 12 | 13 | pub mod block; 14 | 15 | pub mod inv; 16 | pub use inv::Inv; 17 | 18 | pub mod tx; 19 | pub use tx::Tx; 20 | 21 | pub mod version; 22 | pub use version::Version; 23 | 24 | pub mod reject; 25 | pub use reject::Reject; 26 | 27 | use self::codec::Codec; 28 | use crate::protocol::message::constants::{MAX_MESSAGE_LEN, PROTOCOL_VERSION}; 29 | 30 | pub mod codec; 31 | 32 | pub mod filter; 33 | pub use filter::{FilterAdd, FilterLoad}; 34 | 35 | /// A `u64`-backed nonce. 36 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] 37 | pub struct Nonce(u64); 38 | 39 | impl Default for Nonce { 40 | fn default() -> Self { 41 | Self(thread_rng().gen()) 42 | } 43 | } 44 | 45 | impl Codec for Nonce { 46 | fn encode(&self, buffer: &mut B) -> io::Result<()> { 47 | buffer.put_u64_le(self.0); 48 | 49 | Ok(()) 50 | } 51 | 52 | fn decode(bytes: &mut B) -> io::Result { 53 | if bytes.remaining() < 8 { 54 | return Err(io::ErrorKind::InvalidData.into()); 55 | } 56 | let nonce = bytes.get_u64_le(); 57 | 58 | Ok(Self(nonce)) 59 | } 60 | } 61 | 62 | /// Specifies the protocol version. 63 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 64 | pub struct ProtocolVersion(pub u32); 65 | 66 | impl ProtocolVersion { 67 | /// The current protocol version. 68 | pub fn current() -> Self { 69 | Self(PROTOCOL_VERSION) 70 | } 71 | } 72 | 73 | impl Codec for ProtocolVersion { 74 | fn encode(&self, buffer: &mut B) -> io::Result<()> { 75 | buffer.put_u32_le(self.0); 76 | 77 | Ok(()) 78 | } 79 | 80 | fn decode(bytes: &mut B) -> io::Result { 81 | let version = u32::from_le_bytes(read_n_bytes(bytes)?); 82 | 83 | Ok(Self(version)) 84 | } 85 | } 86 | 87 | /// A variable length integer. 88 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 89 | pub struct VarInt(usize); 90 | 91 | impl VarInt { 92 | pub fn new(value: usize) -> Self { 93 | Self(value) 94 | } 95 | } 96 | 97 | impl std::ops::Deref for VarInt { 98 | type Target = usize; 99 | 100 | fn deref(&self) -> &Self::Target { 101 | &self.0 102 | } 103 | } 104 | 105 | impl Codec for VarInt { 106 | fn encode(&self, buffer: &mut B) -> io::Result<()> { 107 | // length of the payload to be written. 108 | let l = self.0; 109 | match l { 110 | 0x0000_0000..=0x0000_00fc => { 111 | buffer.put_u8(l as u8); 112 | } 113 | 0x0000_00fd..=0x0000_ffff => { 114 | buffer.put_u8(0xfdu8); 115 | buffer.put_u16_le(l as u16); 116 | } 117 | 0x0001_0000..=0xffff_ffff => { 118 | buffer.put_u8(0xfeu8); 119 | buffer.put_u32_le(l as u32); 120 | } 121 | _ => { 122 | buffer.put_u8(0xffu8); 123 | buffer.put_u64_le(l as u64); 124 | } 125 | }; 126 | 127 | Ok(()) 128 | } 129 | 130 | fn decode(bytes: &mut B) -> io::Result { 131 | let flag = u8::from_le_bytes(read_n_bytes(bytes)?); 132 | 133 | let len = match flag { 134 | len @ 0x00..=0xfc => len as u64, 135 | 0xfd => u16::from_le_bytes(read_n_bytes(bytes)?) as u64, 136 | 0xfe => u32::from_le_bytes(read_n_bytes(bytes)?) as u64, 137 | 0xff => u64::from_le_bytes(read_n_bytes(bytes)?), 138 | }; 139 | 140 | if len > MAX_MESSAGE_LEN as u64 { 141 | return Err(io::Error::new( 142 | io::ErrorKind::InvalidData, 143 | format!("VarInt length of {len} exceeds max message length of {MAX_MESSAGE_LEN}"), 144 | )); 145 | } 146 | 147 | Ok(VarInt(len as usize)) 148 | } 149 | } 150 | 151 | /// A variable length string. 152 | #[derive(Debug, PartialEq, Eq, Clone)] 153 | pub struct VarStr(pub String); 154 | 155 | impl VarStr { 156 | fn encode(&self, buffer: &mut B) -> io::Result<()> { 157 | VarInt(self.0.len()).encode(buffer)?; 158 | buffer.put(self.0.as_bytes()); 159 | 160 | Ok(()) 161 | } 162 | 163 | fn decode(bytes: &mut B) -> io::Result { 164 | let str_len = VarInt::decode(bytes)?; 165 | 166 | if *str_len > MAX_MESSAGE_LEN { 167 | return Err(io::Error::new( 168 | io::ErrorKind::InvalidData, 169 | format!( 170 | "VarStr length of {} exceeds max message length of {}", 171 | *str_len, MAX_MESSAGE_LEN 172 | ), 173 | )); 174 | } 175 | 176 | if bytes.remaining() < str_len.0 { 177 | return Err(io::ErrorKind::InvalidData.into()); 178 | } 179 | 180 | let mut buffer = vec![0u8; str_len.0]; 181 | bytes.copy_to_slice(&mut buffer); 182 | 183 | Ok(VarStr(String::from_utf8(buffer).map_err(|err| { 184 | std::io::Error::new(std::io::ErrorKind::InvalidData, err.to_string()) 185 | })?)) 186 | } 187 | } 188 | 189 | /// A general purpose hash of length `32`. 190 | #[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] 191 | pub struct Hash([u8; 32]); 192 | 193 | impl Hash { 194 | /// Creates a `Hash` instance. 195 | pub fn new(hash: [u8; 32]) -> Self { 196 | Hash(hash) 197 | } 198 | 199 | /// Returns a `Hash` with only `0`s. 200 | pub fn zeroed() -> Self { 201 | Self([0; 32]) 202 | } 203 | } 204 | 205 | impl Codec for Hash { 206 | fn encode(&self, buffer: &mut B) -> io::Result<()> { 207 | buffer.put_slice(&self.0); 208 | 209 | Ok(()) 210 | } 211 | 212 | fn decode(bytes: &mut B) -> io::Result { 213 | if bytes.remaining() < 32 { 214 | return Err(io::ErrorKind::InvalidData.into()); 215 | } 216 | 217 | let mut hash = Hash([0u8; 32]); 218 | bytes.copy_to_slice(&mut hash.0); 219 | 220 | Ok(hash) 221 | } 222 | } 223 | 224 | /// Reads `n` bytes from the bytes. 225 | pub fn read_n_bytes(bytes: &mut B) -> io::Result<[u8; N]> { 226 | if bytes.remaining() < N { 227 | return Err(io::ErrorKind::InvalidData.into()); 228 | } 229 | 230 | let mut buffer = [0u8; N]; 231 | bytes.copy_to_slice(&mut buffer); 232 | 233 | Ok(buffer) 234 | } 235 | 236 | /// Reads a timestamp encoded as 8 bytes. 237 | pub fn read_timestamp(bytes: &mut B) -> io::Result { 238 | let timestamp_i64 = i64::from_le_bytes(read_n_bytes(bytes)?); 239 | OffsetDateTime::from_unix_timestamp(timestamp_i64) 240 | .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Bad UTC timestamp")) 241 | } 242 | 243 | /// Reads a timestamp encoded as 4 bytes. 244 | pub fn read_short_timestamp(bytes: &mut B) -> io::Result { 245 | let timestamp_u32 = u32::from_le_bytes(read_n_bytes(bytes)?); 246 | OffsetDateTime::from_unix_timestamp(timestamp_u32.into()) 247 | .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Bad UTC timestamp")) 248 | } 249 | -------------------------------------------------------------------------------- /src/tests/idle_node_in_the_background.rs: -------------------------------------------------------------------------------- 1 | //! Development test suite 2 | //! 3 | //! Some helper tools: 4 | //! - Change process niceness: 5 | //! sudo renice -n -19 -p $(pidof rippled) 6 | //! 7 | use std::net::SocketAddr; 8 | 9 | use chrono::{DateTime, Utc}; 10 | use ziggurat_core_utils::err_constants::{ERR_NODE_BUILD, ERR_SYNTH_CONNECT}; 11 | 12 | use crate::{ 13 | setup::node::{Action, Node}, 14 | tools::synthetic_node::{self, SyntheticNode}, 15 | }; 16 | 17 | // Directory files are always placed under ~/.ziggurat since 18 | // this implementation doesn't support arbitrary paths. 19 | /// The directory where node data gets stored. 20 | const NODE_DIR_PATH: &str = "~/.ziggurat"; 21 | 22 | #[derive(Default)] 23 | enum NodeLogToStdout { 24 | #[default] 25 | Off, 26 | On, 27 | } 28 | 29 | impl NodeLogToStdout { 30 | fn is_on(&self) -> bool { 31 | matches!(self, NodeLogToStdout::On) 32 | } 33 | } 34 | 35 | #[derive(PartialEq, Default)] 36 | enum TracingOpt { 37 | #[default] 38 | Off, 39 | On, 40 | } 41 | 42 | #[derive(Default)] 43 | #[allow(non_camel_case_types)] 44 | enum SyntheticNodeOpt { 45 | #[default] 46 | Off, 47 | On_OnlyListening, 48 | On_TryToConnect, 49 | } 50 | 51 | /// A simple configuration for the dev test customization. 52 | #[derive(Default)] 53 | struct DevTestCfg { 54 | /// Print the node's log to the stdout. 55 | log_to_stdout: NodeLogToStdout, 56 | 57 | /// Enable tracing. 58 | tracing: TracingOpt, 59 | 60 | // Attach a synthetic node to the node. 61 | synth_node: SyntheticNodeOpt, 62 | } 63 | 64 | #[tokio::test] 65 | #[allow(non_snake_case)] 66 | #[ignore = "convenience test to tinker with a running node for dev purposes"] 67 | async fn dev001_t1_RUN_NODE_FOREVER_with_logs() { 68 | // This test is used for testing/development purposes. 69 | 70 | let cfg = DevTestCfg { 71 | log_to_stdout: NodeLogToStdout::On, 72 | tracing: TracingOpt::On, 73 | ..Default::default() 74 | }; 75 | node_run_forever(cfg).await; 76 | 77 | panic!("the node shouldn't have died"); 78 | } 79 | 80 | #[tokio::test] 81 | #[allow(non_snake_case)] 82 | #[ignore = "convenience test to tinker with a running node for dev purposes"] 83 | async fn dev001_t2_RUN_NODE_FOREVER_no_logs() { 84 | // This test is used for testing/development purposes. 85 | 86 | let cfg = DevTestCfg::default(); 87 | node_run_forever(cfg).await; 88 | 89 | panic!("the node shouldn't have died"); 90 | } 91 | 92 | #[tokio::test] 93 | #[allow(non_snake_case)] 94 | #[ignore = "convenience test to tinker with a running node for dev purposes"] 95 | async fn dev002_t1_MONITOR_NODE_FOREVER_WITH_SYNTH_NODE_sn_is_conn_initiator() { 96 | // This test is used for testing/development purposes. 97 | 98 | let cfg = DevTestCfg { 99 | synth_node: SyntheticNodeOpt::On_TryToConnect, 100 | ..Default::default() 101 | }; 102 | node_run_forever(cfg).await; 103 | 104 | panic!("the node shouldn't have died"); 105 | } 106 | 107 | #[tokio::test] 108 | #[allow(non_snake_case)] 109 | #[ignore = "convenience test to tinker with a running node for dev purposes"] 110 | async fn dev002_t2_MONITOR_NODE_FOREVER_WITH_SYNTH_NODE_sn_is_conn_responder() { 111 | // This test is used for testing/development purposes. 112 | 113 | let cfg = DevTestCfg { 114 | log_to_stdout: NodeLogToStdout::On, 115 | tracing: TracingOpt::On, 116 | synth_node: SyntheticNodeOpt::On_OnlyListening, 117 | }; 118 | node_run_forever(cfg).await; 119 | 120 | panic!("the node shouldn't have died"); 121 | } 122 | 123 | /// Runs the node forever! 124 | /// The test asserts the node process won't be killed. 125 | /// 126 | /// Function complexity is increased due to many customization options, 127 | /// which is not nice but it is what it is. 128 | /// 129 | /// In short, here are the customization options which are provided via the cfg arg: 130 | /// 131 | /// - enable/disable node's logs to stdout [cfg.log_to_stdout] 132 | /// 133 | /// - enable/disable tracing [cfg.tracing] 134 | /// 135 | /// - enable/disable attaching a single synthetic node to the node [cfg.synth_node] 136 | /// - suboption: choose the initiator for the connection 137 | /// 138 | async fn node_run_forever(cfg: DevTestCfg) { 139 | let log_to_stdout = cfg.log_to_stdout.is_on(); 140 | 141 | // Enable tracing possibly. 142 | if cfg.tracing == TracingOpt::On { 143 | synthetic_node::enable_tracing(); 144 | } 145 | 146 | // SyntheticNode is spawned only if option is chosen in cfg options. 147 | let mut initial_peers = vec![]; 148 | let mut synth_node: Option = match cfg.synth_node { 149 | SyntheticNodeOpt::On_TryToConnect => { 150 | let synthetic_node = SyntheticNode::builder() 151 | .with_full_handshake() 152 | .build() 153 | .await 154 | .unwrap(); 155 | Some(synthetic_node) 156 | } 157 | SyntheticNodeOpt::On_OnlyListening => { 158 | let synthetic_node = SyntheticNode::builder() 159 | .with_full_handshake() 160 | .build() 161 | .await 162 | .unwrap(); 163 | initial_peers.push(synthetic_node.listening_addr()); 164 | Some(synthetic_node) 165 | } 166 | _ => None, 167 | }; 168 | 169 | let mut node = node_start(log_to_stdout, initial_peers.clone()).await; 170 | let addr = node.addr(); 171 | 172 | if let Some(synth_node) = synth_node.as_ref() { 173 | // Alternative check to the On_TryToConnect option. 174 | if initial_peers.is_empty() { 175 | synth_node.connect(addr).await.expect(ERR_SYNTH_CONNECT); 176 | } 177 | } 178 | 179 | // Print received messages from another thread. 180 | if let Some(synth_node) = synth_node.take() { 181 | spawn_periodic_msg_recv(synth_node).await; 182 | } 183 | 184 | // The node should run forever unless something bad happens to it. 185 | node.wait_until_exit().await; 186 | 187 | println!("\tThe node has stopped running ({})", current_time_str()); 188 | } 189 | 190 | /// Create and start the node and print the extra useful debug info. 191 | async fn node_start(log_to_stdout: bool, initial_peers: Vec) -> Node { 192 | println!("\tTime before the node is started: {}", current_time_str()); 193 | 194 | let mut node = Node::new().unwrap(); 195 | node.initial_action(Action::WaitForConnection) 196 | .log_to_stdout(log_to_stdout) 197 | .initial_peers(initial_peers.clone()) 198 | .start() 199 | .await 200 | .expect(ERR_NODE_BUILD); 201 | 202 | println!("\tThe node directory files are located at {NODE_DIR_PATH}"); 203 | println!("\tThe node has started running ({})", current_time_str()); 204 | println!("\tInitial peers: {initial_peers:?}"); 205 | println!("\tThe node is listening on {}", node.addr()); 206 | 207 | if !log_to_stdout { 208 | let log_path = format!("{NODE_DIR_PATH}/testnet3/debug.log"); 209 | println!("\tThe node logs can be found at {log_path}"); 210 | } 211 | 212 | node 213 | } 214 | 215 | /// Use recv_message to clear up the inbound queue and print out 216 | /// the received messages. 217 | async fn spawn_periodic_msg_recv(mut synth_node: SyntheticNode) { 218 | tokio::spawn(async move { 219 | loop { 220 | let (_, msg) = synth_node.recv_message().await; 221 | tracing::info!("message received: {msg:?}"); 222 | } 223 | }); 224 | } 225 | 226 | fn current_time_str() -> String { 227 | let now: DateTime = Utc::now(); 228 | now.format("%T %a %b %e %Y").to_string() 229 | } 230 | --------------------------------------------------------------------------------