├── .devcontainer └── devcontainer.json ├── .github ├── codecov.yml └── workflows │ └── unit.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── bin ├── swarm-tools │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── swarm-wasm-lib │ ├── Cargo.toml │ ├── src │ │ ├── lib.rs │ │ └── utils.rs │ └── tests │ │ └── web.rs ├── vertex │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── waku-swarm-relay │ ├── Cargo.toml │ └── src │ │ ├── arguments.rs │ │ ├── main.rs │ │ └── protocol.rs └── wasm-playground │ ├── Cargo.toml │ ├── index.html │ └── src │ └── lib.rs ├── crates ├── crates │ ├── metrics │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── config.rs │ │ │ ├── hooks.rs │ │ │ ├── lib.rs │ │ │ ├── logging.rs │ │ │ ├── prometheus.rs │ │ │ ├── recorder.rs │ │ │ ├── server.rs │ │ │ └── tracing.rs │ ├── node-api │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── builder.rs │ │ │ ├── events.rs │ │ │ ├── exit.rs │ │ │ ├── lib.rs │ │ │ ├── lifecycle.rs │ │ │ ├── node.rs │ │ │ └── tasks.rs │ ├── node │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src │ │ │ ├── cli.rs │ │ │ ├── config.rs │ │ │ ├── constants.rs │ │ │ ├── dirs.rs │ │ │ ├── logging.rs │ │ │ ├── main.rs │ │ │ ├── utils.rs │ │ │ └── version.rs │ ├── primitives │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── chunk.rs │ │ │ ├── error.rs │ │ │ ├── lib.rs │ │ │ ├── network.rs │ │ │ └── utils.rs │ ├── storage │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── swarm-api │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── access.rs │ │ │ ├── bandwidth.rs │ │ │ ├── chunk.rs │ │ │ ├── lib.rs │ │ │ ├── network.rs │ │ │ ├── node.rs │ │ │ ├── protocol.rs │ │ │ └── storage.rs │ ├── swarm-core │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── base.rs │ │ │ ├── config.rs │ │ │ ├── factory.rs │ │ │ ├── full.rs │ │ │ ├── incentivized.rs │ │ │ ├── info.rs │ │ │ └── lib.rs │ └── swarmspec │ │ ├── Cargo.toml │ │ └── src │ │ ├── constants.rs │ │ ├── forks.rs │ │ ├── lib.rs │ │ ├── network.rs │ │ └── types.rs ├── file │ ├── Cargo.toml │ └── src │ │ ├── file.rs │ │ ├── lib.rs │ │ └── span.rs ├── logging │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── manifest │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── lib.rs │ │ ├── marshal.rs │ │ ├── node.rs │ │ ├── persist.rs │ │ ├── persist_other.rs │ │ ├── stringer.rs │ │ └── walker.rs ├── network-primitives-traits │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── sync.rs ├── network-primitives │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── named.rs │ │ └── swarm.rs ├── network │ ├── codec │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── handshake │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── proto │ │ │ └── handshake.proto │ │ └── src │ │ │ ├── behaviour.rs │ │ │ ├── codec │ │ │ ├── ack.rs │ │ │ ├── error.rs │ │ │ ├── mod.rs │ │ │ ├── syn.rs │ │ │ └── synack.rs │ │ │ ├── error.rs │ │ │ ├── handler.rs │ │ │ ├── lib.rs │ │ │ └── protocol.rs │ ├── headers │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── proto │ │ │ └── headers.proto │ │ └── src │ │ │ ├── codec.rs │ │ │ ├── instrumented.rs │ │ │ └── lib.rs │ └── pricing │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── proto │ │ └── pricing.proto │ │ └── src │ │ └── lib.rs ├── node │ ├── api │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── components.rs │ │ │ ├── config.rs │ │ │ ├── lib.rs │ │ │ └── types.rs │ ├── cli │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ └── core │ │ ├── Cargo.toml │ │ ├── build.rs │ │ └── src │ │ ├── args │ │ ├── mod.rs │ │ ├── neighbourhood.rs │ │ ├── network.rs │ │ └── wallet.rs │ │ ├── lib.rs │ │ ├── node_config.rs │ │ └── version.rs ├── postage │ ├── Cargo.toml │ └── src │ │ ├── batch.rs │ │ ├── lib.rs │ │ ├── pat.rs │ │ └── stamp.rs ├── storage │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── swarm-api │ ├── Cargo.toml │ └── src │ │ ├── access.rs │ │ ├── bandwidth.rs │ │ ├── chunk.rs │ │ ├── lib.rs │ │ ├── network.rs │ │ ├── node.rs │ │ ├── protocol.rs │ │ ├── storage.rs │ │ └── types.rs ├── swarm-forks │ ├── Cargo.toml │ └── src │ │ ├── display.rs │ │ ├── forkcondition.rs │ │ ├── hardfork │ │ ├── dev.rs │ │ ├── macros.rs │ │ ├── mod.rs │ │ └── swarm.rs │ │ ├── hardforks │ │ ├── mod.rs │ │ └── swarm.rs │ │ └── lib.rs ├── swarmspec │ ├── Cargo.toml │ └── src │ │ ├── api.rs │ │ ├── constants.rs │ │ ├── contracts.rs │ │ ├── info.rs │ │ ├── lib.rs │ │ ├── lightclient.rs │ │ ├── spec.rs │ │ ├── storage.rs │ │ └── token.rs └── vertex-primitives │ ├── Cargo.toml │ └── src │ ├── error.rs │ └── lib.rs └── docs └── architecture.md /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Foundry + Rust Development Container", 3 | "image": "mcr.microsoft.com/devcontainers/base:ubuntu", 4 | "features": { 5 | "ghcr.io/devcontainers/features/common-utils:2": { 6 | "configureZshAsDefaultShell": true, 7 | "installOhMyZsh": false 8 | }, 9 | "ghcr.io/devcontainers/features/rust:1": { 10 | "version": "nightly-2024-11-29", 11 | "profile": "default", 12 | "targets": "wasm32-unknown-unknown" 13 | }, 14 | "ghcr.io/devcontainers-extra/features/apt-get-packages:1": { 15 | "packages": "pkg-config,libssl-dev,clang" 16 | }, 17 | "ghcr.io/lee-orr/rusty-dev-containers/cargo-binstall:0": { 18 | "packages": "just,trunk,leptosfmt,wasm-pack,wasm-opt" 19 | }, 20 | "ghcr.io/nlordell/features/foundry": {}, 21 | "ghcr.io/devcontainers-community/features/llvm": {} 22 | }, 23 | "customizations": { 24 | "vscode": { 25 | "extensions": [ 26 | "rust-lang.rust-analyzer", 27 | "JuanBlanco.solidity" 28 | ] 29 | } 30 | }, 31 | "postCreateCommand": "/usr/local/cargo/bin/rustup component add rust-analyzer && sudo chmod -R g+rwx /usr/local/cargo /usr/local/rustup", 32 | "runArgs": [ 33 | "--network=host" 34 | ] 35 | } 36 | 37 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | target: 80% 6 | threshold: 5% 7 | patch: 8 | default: 9 | target: 85% 10 | threshold: 5% 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | **/*.rs.bk 3 | *.tar.gz 4 | .env 5 | cspell.json 6 | reth 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace.package] 2 | version = "0.1.0" 3 | edition = "2024" 4 | rust-version = "1.87" 5 | authors = ["Nexum Contributors"] 6 | license = "AGPL-3.0-or-later" 7 | homepage = "https://nxm-rs.github.io/vertex" 8 | repository = "https://nxm-rs.github.io/vertex" 9 | 10 | [workspace] 11 | members = [ 12 | "bin/vertex", 13 | "crates/network-primitives", 14 | "crates/network-primitives-traits", 15 | "crates/network/codec", 16 | "crates/network/handshake", 17 | "crates/network/headers", 18 | "crates/network/pricing", 19 | "crates/node/cli", 20 | "crates/node/core", 21 | "crates/swarm-forks", 22 | "crates/swarmspec", 23 | ] 24 | 25 | # Explicitly set the resolver to version 2, which is the default for packages with edition >= 2021 26 | # https://doc.rust-lang.org/edition-guide/rust-2021/default-cargo-resolver.html 27 | resolver = "2" 28 | 29 | [workspace.lints] 30 | rust.unreachable_pub = "warn" 31 | 32 | [workspace.dependencies] 33 | ## nectar 34 | nectar-primitives = { git = "https://github.com/nxm-rs/nectar" } 35 | 36 | digest = "0.10" 37 | 38 | ## vertex 39 | # primitives 40 | vertex-network-primitives = { path = "crates/network-primitives" } 41 | vertex-network-primitives-traits = { path = "crates/network-primitives-traits" } 42 | vertex-swarm-forks = { path = "crates/swarm-forks" } 43 | 44 | # network protocols 45 | vertex-network-codec = { path = "crates/network/codec" } 46 | vertex-network-handshake = { path = "crates/network/handshake" } 47 | vertex-network-headers = { path = "crates/network/headers" } 48 | vertex-network-pricing = { path = "crates/network/pricing" } 49 | 50 | # node 51 | vertex-node-core = { path = "crates/node/core" } 52 | vertex-node-cli = { path = "crates/node/cli" } 53 | 54 | ## alloy 55 | alloy-eip2124 = { version = "0.2", default-features = false } 56 | alloy-primitives = { version = "1.1", default-features = false } 57 | alloy-signer = { version = "1.0" } 58 | alloy-signer-local = { version = "1.0", features = ["eip712", "keystore"] } 59 | alloy-chains = { version = "0.2", default-features = false } 60 | 61 | ## tracing 62 | tracing = "0.1" 63 | tracing-subscriber = { version = "0.3", features = [ 64 | "env-filter", 65 | "ansi", 66 | "fmt", 67 | "std", 68 | "json", 69 | "time", 70 | ] } 71 | 72 | ## p2p 73 | libp2p = { version = "0.55.0", features = [ 74 | "tokio", 75 | "noise", 76 | "tcp", 77 | "yamux", 78 | "dns", 79 | "macros", 80 | "ecdsa", 81 | "identify", 82 | "upnp", 83 | "autonat", 84 | "ping", 85 | ] } 86 | quick-protobuf = "0.8" 87 | quick-protobuf-codec = "0.3.1" 88 | asynchronous-codec = "0.7.0" 89 | libp2p-swarm = "0.46.0" 90 | libp2p-swarm-test = "0.5.0" 91 | quickcheck = "1.0.3" 92 | pb-rs = "0.10" 93 | walkdir = "2.5.0" 94 | 95 | ## tokio 96 | # tokio-stream = "0.1.17" 97 | tokio = { version = "1", default-features = false } 98 | # tokio-util = { version = "0.7.13", features = ["codec"] } 99 | 100 | ## async 101 | futures = "0.3" 102 | 103 | ## concurrency 104 | rayon = "1.10" 105 | thread_local = "1.1" 106 | 107 | ## misc testing 108 | arbitrary = "1.4" 109 | assert_matches = "1.5.0" 110 | criterion = { package = "codspeed-criterion-compat", version = "2.10.1" } 111 | # pprof = "0.14" 112 | proptest = "1.6" 113 | proptest-arbitrary-interop = "0.1" 114 | proptest-derive = "0.5" 115 | # test-fuzz = "7" 116 | 117 | # misc 118 | anyhow = "1.0" 119 | auto_impl = "1" 120 | bytes = { version = "1.10", default-features = false } 121 | clap = "4" 122 | derive_more = { version = "2", default-features = false, features = ["full"] } 123 | dyn-clone = "1.0" 124 | eyre = "0.6" 125 | generic-array = "0.14" 126 | once_cell = { version = "1.21", default-features = false, features = [ 127 | "critical-section", 128 | ] } 129 | critical-section = { version = "1.2", features = ["std"] } 130 | rand = "0.9" 131 | rustc-hash = { version = "2.1", default-features = false } 132 | serde = { version = "1.0", default-features = false } 133 | serde_json = { version = "1.0", default-features = false, features = ["alloc"] } 134 | serde_with = { version = "3", default-features = false, features = ["macros"] } 135 | thiserror = { version = "2.0", default-features = false } 136 | vergen-gitcl = "1" 137 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vertex 2 | 3 | [![CI Status](https://github.com/nullisxyz/vertex/actions/workflows/unit.yml/badge.svg)](https://github.com/nullisxyz/vertex/actions/workflows/unit.yml) 4 | [![codecov](https://codecov.io/gh/nullisxyz/vertex/graph/badge.svg?token=O56JVSX6AB)](https://codecov.io/gh/nullisxyz/vertex) 5 | 6 | **Modular, high-performance implementation of the Ethereum Swarm protocol** 7 | 8 | 12 | 13 | ## What is Vertex? 14 | 15 | Vertex (pronunciation: /ˈvɜːrtɛks/) is a new Ethereum Swarm node implementation focused on being user-friendly, highly modular, and blazing-fast. Vertex is written in Rust and is compatible with all Swarm protocols including postage stamps, push/pull syncing, and the full storage incentives system. Built and driven forward by [Nullis](https://github.com/nullisxyz), Vertex is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0). 16 | 17 | ## Goals 18 | 19 | As a full Ethereum Swarm node, Vertex will allow users to connect to the Swarm network and interact with decentralised storage. This includes uploading and downloading content, participating in the storage incentives system, and being a good network citizen. Building a successful Swarm node requires creating a high-quality implementation that is both secure and efficient, as well as being easy to use on consumer hardware. It also requires building a strong community of contributors who can help support and improve the software. 20 | 21 | More concretely, our goals are: 22 | 23 | 1. **Modularity**: Every component of Vertex is built to be used as a library: well-tested, heavily documented and benchmarked. We envision that developers will import components like network protocols or chunk storage and build innovative solutions on top of them. The project is split into three main repositories: 24 | - `vertex`: The full node implementation 25 | - `nectar`: Core primitives and protocols specific to Ethereum Swarm 26 | - `dipper`: A CLI tool for interacting with Swarm (similar to `cast` in Foundry) 27 | 28 | 2. **Performance**: Vertex aims to be the fastest Swarm implementation. Written in Rust with a focus on concurrent processing and efficient resource usage, we strive to optimize every aspect from chunk processing to network communication. 29 | 30 | 3. **Client Diversity**: The Swarm network becomes more resilient when no single implementation dominates. By building a new client, we hope to contribute to Swarm's decentralisation and anti-fragility. 31 | 32 | 4. **Developer Experience**: Through great documentation, ergonomic APIs, and developer tooling like `dipper`, we want to make it easy for developers to build on Swarm. 33 | 34 | ## Status 35 | 36 | Vertex is under active development and not yet ready for production use. 37 | 38 | ## Getting Help 39 | 40 | If you have questions: 41 | 42 | - Join the [Signal group](https://signal.group/#CjQKIHNV-kWphhtnpwS3zywC7LRr5BEW9Q1XyDl2qZtL2WYqEhAyO0c8tGmrQDmEsY15rALt) to discuss development with the Nullis team 43 | - Open a [discussion](https://github.com/nullisxyz/vertex/discussions/new) with your question 44 | - Open an [issue](https://github.com/nullisxyz/vertex/issues/new) to report a bug 45 | 46 | ## License 47 | 48 | Vertex is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0). See [LICENSE](./LICENSE) for details. 49 | 50 | ## Warning 51 | 52 | This software is currently in development. While we strive for correctness, bugs may exist. Use at your own risk. 53 | -------------------------------------------------------------------------------- /bin/swarm-tools/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "swarm-tools" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /bin/swarm-tools/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /bin/swarm-wasm-lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "swarm-wasm-lib" 3 | version = "0.1.0" 4 | authors = ["mfw78 "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "rlib"] 9 | 10 | [features] 11 | default = ["console_error_panic_hook"] 12 | 13 | [dependencies] 14 | wasm-bindgen = { workspace = true } 15 | wasm-bindgen-futures = { workspace = true } 16 | bmt = { path = "../../crates/bmt" } 17 | postage = { path = "../../crates/postage" } 18 | js-sys = { workspace = true } 19 | web-sys = { version = "0.3.64", features = ['console'] } 20 | hex = { workspace = true } 21 | 22 | # The `console_error_panic_hook` crate provides better debugging of panics by 23 | # logging them with `console.error`. This is great for development, but requires 24 | # all the `std::fmt` and `std::panicking` infrastructure, so isn't great for 25 | # code size when deploying. 26 | console_error_panic_hook = { version = "0.1.7", optional = true } 27 | 28 | [dev-dependencies] 29 | wasm-bindgen-test = { workspace = true } 30 | 31 | [profile.release] 32 | # Tell `rustc` to optimize for small code size. 33 | opt-level = "s" 34 | -------------------------------------------------------------------------------- /bin/swarm-wasm-lib/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod utils; 2 | 3 | // Use cases to meet: 4 | // 1. Given a Javascript function that can be used to retrieve a chunk from the bee api, return a tuple of: 5 | // - the chunk address 6 | // - the span 7 | // - the payload data 8 | // 2. Given a Javascript function that can be used to send a chunk(s) to the bee api, a payload in a Uint8Array, a signing key, and a pat, 9 | // upload the stamped chunk(s) to the bee api and return a tuple of: 10 | // - the chunk address(es) 11 | // - the cac for the payload (if spanning more than 1 chunk) 12 | 13 | pub mod bmt { 14 | use std::convert::TryInto; 15 | 16 | use bmt::chunk::{Chunk, Options}; 17 | use js_sys::Function; 18 | use js_sys::{Promise, Uint8Array}; 19 | use wasm_bindgen::prelude::*; 20 | 21 | pub struct ParsedApiChunk(String, u64, Uint8Array); 22 | 23 | // Asynchronously evaluate the JavaScript Promise 24 | async fn await_promise(promise: Promise) -> Result { 25 | let future = wasm_bindgen_futures::JsFuture::from(promise); 26 | future.await.map_err(|err| err) 27 | } 28 | 29 | // Given a function that can be used to retrieve a raw chunk, return a ChunkInfo struct 30 | #[wasm_bindgen] 31 | pub async fn get_chunk(address: &str, get_chunk_fn: &Function) -> Result { 32 | // Call the TypeScript function with the provided address 33 | let promise = get_chunk_fn.call1(&JsValue::NULL, &JsValue::from_str(address))?; 34 | 35 | // Await the JavaScript Promise in Rust 36 | let ret = await_promise(Promise::from(promise)).await?; 37 | 38 | // Convert the result into a ChunkInfo struct 39 | Ok(get_chunk_info(&Uint8Array::from(ret))) 40 | } 41 | 42 | #[derive(Debug, Clone)] 43 | #[wasm_bindgen(getter_with_clone)] 44 | pub struct ChunkInfo { 45 | pub address: String, 46 | pub span: u64, 47 | pub data: Uint8Array, 48 | } 49 | 50 | // Given a Uint8Array, calculate the chunk address, span, and payload data 51 | #[wasm_bindgen] 52 | pub fn get_chunk_info(data: &Uint8Array) -> ChunkInfo { 53 | // The first 8 bytes of the data are the span in little endian 54 | let span = u64::from_le_bytes(data.slice(0, 8).to_vec().try_into().unwrap()); 55 | // The remainder of the data is the payload 56 | let mut data = data.slice(8, data.length()).to_vec(); 57 | 58 | // Create a new chunk from the data 59 | let chunk = Chunk::new(&mut data, Some(span), Options::default(), None); 60 | 61 | ChunkInfo { 62 | address: hex::encode(chunk.address()), 63 | span, 64 | data: Uint8Array::from(data.as_slice()), 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /bin/swarm-wasm-lib/src/utils.rs: -------------------------------------------------------------------------------- 1 | pub fn set_panic_hook() { 2 | // When the `console_error_panic_hook` feature is enabled, we can call the 3 | // `set_panic_hook` function at least once during initialization, and then 4 | // we will get better error messages if our code ever panics. 5 | // 6 | // For more details see 7 | // https://github.com/rustwasm/console_error_panic_hook#readme 8 | #[cfg(feature = "console_error_panic_hook")] 9 | console_error_panic_hook::set_once(); 10 | } 11 | -------------------------------------------------------------------------------- /bin/swarm-wasm-lib/tests/web.rs: -------------------------------------------------------------------------------- 1 | //! Test suite for the Web and headless browsers. 2 | 3 | #![cfg(target_arch = "wasm32")] 4 | 5 | extern crate wasm_bindgen_test; 6 | use wasm_bindgen_test::*; 7 | 8 | wasm_bindgen_test_configure!(run_in_browser); 9 | 10 | #[wasm_bindgen_test] 11 | fn pass() { 12 | assert_eq!(1 + 1, 2); 13 | } 14 | -------------------------------------------------------------------------------- /bin/vertex/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vertex" 3 | version.workspace = true 4 | edition.workspace = true 5 | homepage.workspace = true 6 | repository.workspace = true 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | libp2p.workspace = true 12 | futures.workspace = true 13 | tokio.workspace = true 14 | vertex-network-primitives-traits.workspace = true 15 | vertex-network-primitives.workspace = true 16 | vertex-network-handshake.workspace = true 17 | vertex-node-core.workspace = true 18 | 19 | tracing.workspace = true 20 | tracing-subscriber.workspace = true 21 | clap = { workspace = true, features = ["derive"] } 22 | -------------------------------------------------------------------------------- /bin/vertex/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use clap::{command, Parser, Subcommand}; 4 | use futures::StreamExt; 5 | use libp2p::{swarm::NetworkBehaviour, Multiaddr}; 6 | use tracing::info; 7 | use vertex_network_handshake::HandshakeBehaviour; 8 | use vertex_node_core::args::NodeCommand; 9 | use vertex_node_core::version::{LONG_VERSION, P2P_CLIENT_VERSION, SHORT_VERSION}; 10 | 11 | #[derive(Debug, Parser)] 12 | #[command(author, version = SHORT_VERSION, long_version = LONG_VERSION, about = "Vertex", long_about = None)] 13 | pub struct Cli { 14 | /// The command to run 15 | #[command(subcommand)] 16 | command: Commands, 17 | } 18 | 19 | impl Cli { 20 | /// Parses only the default CLI arguments 21 | pub fn parse_args() -> Self { 22 | Self::parse() 23 | } 24 | } 25 | 26 | #[derive(Debug, Subcommand)] 27 | pub enum Commands { 28 | /// Start the node 29 | #[command(name = "node")] 30 | Node(NodeCommand), 31 | } 32 | 33 | #[derive(NetworkBehaviour)] 34 | pub struct SwarmBehaviour { 35 | handshake: HandshakeBehaviour, 36 | identify: libp2p::identify::Behaviour, 37 | ping: libp2p::ping::Behaviour, 38 | } 39 | 40 | #[tokio::main] 41 | async fn main() -> Result<(), Box> { 42 | // Initialise the tracing subscriber 43 | tracing_subscriber::fmt() 44 | .with_env_filter( 45 | tracing_subscriber::EnvFilter::from_default_env() 46 | .add_directive("vertex=debug".parse()?), // .add_directive("trace".parse()?), 47 | ) 48 | .with_target(true) 49 | // .with_thread_ids(true) 50 | // .with_thread_names(true) 51 | // .with_file(true) 52 | // .with_line_number(true) 53 | // .with_timer(tracing_subscriber::fmt::time::ChronoUtc::rfc3339()) 54 | .init(); 55 | 56 | info!("Starting vertex node"); 57 | 58 | let cli = Cli::parse_args(); 59 | let args = Arc::new(match cli.command { 60 | Commands::Node(node_command) => node_command, 61 | }); 62 | 63 | let mut swarm = libp2p::SwarmBuilder::with_new_identity() 64 | .with_tokio() 65 | .with_tcp( 66 | libp2p::tcp::Config::default(), 67 | libp2p::noise::Config::new, 68 | libp2p::yamux::Config::default, 69 | )? 70 | .with_dns_config( 71 | libp2p::dns::ResolverConfig::default(), 72 | libp2p::dns::ResolverOpts::default(), 73 | ) 74 | .with_behaviour(|key| SwarmBehaviour { 75 | handshake: HandshakeBehaviour::<1>::new(args.clone()), 76 | identify: libp2p::identify::Behaviour::new( 77 | libp2p::identify::Config::new("/ipfs/id/1.0.0".to_string(), key.public()) 78 | .with_agent_version(P2P_CLIENT_VERSION.to_string()), 79 | ), 80 | ping: libp2p::ping::Behaviour::new(libp2p::ping::Config::default()), 81 | })? 82 | .build(); 83 | 84 | // Listen on localhost port 1634 85 | swarm.listen_on("/ip4/127.0.0.1/tcp/2634".parse()?)?; 86 | 87 | let test = 88 | Some("/ip4/188.245.223.0/tcp/1634/p2p/QmQEjAtTmq3nM6y5cT25XAnBviiw9gBAqsbCuFoxhpPvGy"); 89 | 90 | // If a peer address is provided as an argument, dial it 91 | if let Some(addr) = test { 92 | // Parse the multiaddr 93 | let remote: Multiaddr = addr.parse()?; 94 | info!("Dialing peer at {}", remote); 95 | swarm.dial(remote)?; 96 | } 97 | 98 | // Event loop 99 | loop { 100 | match swarm.select_next_some().await { 101 | libp2p::swarm::SwarmEvent::NewListenAddr { address, .. } => { 102 | info!("Listening on {}", address); 103 | } 104 | libp2p::swarm::SwarmEvent::ConnectionEstablished { peer_id, .. } => { 105 | info!("Connection established with {}", peer_id); 106 | } 107 | libp2p::swarm::SwarmEvent::Behaviour(event) => { 108 | info!("Handshake event: {:?}", event); 109 | } 110 | event => { 111 | info!("Unhandled event: {:?}", event); 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /bin/waku-swarm-relay/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "waku-swarm-relay" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [target.'cfg(not(target_arch = "wasm32"))'.bin] 9 | path = "src/main.rs" 10 | 11 | [dependencies] 12 | once_cell = { workspace = true } 13 | dotenv = { workspace = true } 14 | clap = { workspace = true } 15 | chrono = { workspace = true } 16 | hex = { workspace = true } 17 | prost = { workspace = true } 18 | rand = { workspace = true } 19 | serde = { workspace = true } 20 | serde_json = { workspace = true } 21 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 22 | ethers-core = { workspace = true } 23 | ethers-contract = { workspace = true } 24 | ethers-signers = { workspace = true } 25 | url = "2.3.1" 26 | anyhow = { workspace = true } 27 | thiserror = { workspace = true } 28 | waku-bindings = { git = "https://github.com/waku-org/waku-rust-bindings" } 29 | reqwest = { workspace = true } 30 | tracing = { workspace = true } 31 | tracing-subscriber = { workspace = true} 32 | num-traits = "0.2.15" 33 | logging = { path = "../../crates/logging" } -------------------------------------------------------------------------------- /bin/waku-swarm-relay/src/arguments.rs: -------------------------------------------------------------------------------- 1 | use waku_bindings::Multiaddr; 2 | 3 | #[derive(clap::Parser)] 4 | #[group(skip)] 5 | pub struct Arguments { 6 | #[clap(flatten)] 7 | pub logging: logging::LoggingArguments, 8 | #[clap(long, help = "Private key for relay operations", value_parser = parse_private_key)] 9 | pub private_key: String, 10 | #[clap(long, help = "Peer id of the relay to connect to", value_parser = parse_multiaddr)] 11 | pub peer: Vec, 12 | #[clap(long, help = "enrtree")] 13 | pub enrtree: Option, 14 | #[clap(long, help = "bee API url", default_value = "http://localhost:1633")] 15 | pub bee_api_url: String, 16 | } 17 | 18 | // Write a parser to make sure that the private_key is a valid ethereum private key 19 | // https://ethereum.stackexchange.com/questions/39384/how-to-validate-a-private-key 20 | // 21 | pub fn parse_private_key(private_key: &str) -> Result { 22 | // The private key may be prefixed with 0x 23 | let private_key = private_key.trim_start_matches("0x"); 24 | // The private key must be 32 bytes and a valid hex string 25 | if private_key.len() != 64 || !hex::decode(private_key).is_ok() { 26 | return Err(String::from("Invalid private key")); 27 | } 28 | Ok(String::from(private_key)) 29 | } 30 | 31 | pub fn parse_multiaddr(multiaddr: &str) -> Result { 32 | multiaddr 33 | .parse() 34 | .map_err(|_| String::from("Invalid multiaddr")) 35 | } 36 | -------------------------------------------------------------------------------- /bin/waku-swarm-relay/src/protocol.rs: -------------------------------------------------------------------------------- 1 | use chrono::Utc; 2 | use prost::Message; 3 | 4 | #[derive(Clone, Message)] 5 | pub struct Ping { 6 | #[prost(uint64, tag = "1")] 7 | pub timestamp: u64, 8 | } 9 | 10 | impl Ping { 11 | pub fn new() -> Self { 12 | Ping { 13 | timestamp: Utc::now().timestamp_millis() as u64, 14 | } 15 | } 16 | } 17 | 18 | #[derive(Clone, Message)] 19 | pub struct Pong { 20 | #[prost(uint64, tag = "1")] 21 | pub timestamp: u64, 22 | #[prost(bytes, tag = "2")] 23 | pub address: Vec, 24 | } 25 | 26 | impl Pong { 27 | pub fn new(address: Vec, signature: Vec) -> Self { 28 | Pong { 29 | timestamp: Utc::now().timestamp_millis() as u64, 30 | address, 31 | } 32 | } 33 | } 34 | 35 | #[derive(Clone, Message)] 36 | pub struct RetrievalRequest { 37 | #[prost(bytes, tag = "1")] 38 | pub chunk_address: Vec, 39 | } 40 | 41 | #[derive(Clone, Message)] 42 | pub struct RetrievalDelivery { 43 | #[prost(bytes, tag = "1")] 44 | pub data: Vec, 45 | #[prost(bytes, tag = "2")] 46 | pub stamp: Vec, 47 | } 48 | 49 | #[derive(Clone, Message)] 50 | pub struct PushSyncDelivery { 51 | #[prost(bytes, tag = "1")] 52 | pub data: Vec, 53 | #[prost(bytes, tag = "2")] 54 | pub stamp: Vec, 55 | } 56 | 57 | #[derive(Clone, Message)] 58 | pub struct PushSyncReceipt { 59 | #[prost(bytes, tag = "1")] 60 | pub chunk_address: Vec, 61 | #[prost(bytes, tag = "2")] 62 | pub signature: Vec, 63 | #[prost(bytes, tag = "3")] 64 | pub nonce: Vec, 65 | } 66 | -------------------------------------------------------------------------------- /bin/wasm-playground/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasm-playground" 3 | version = "0.1.0" 4 | authors = ["The wasm-bindgen Developers"] 5 | edition = "2018" 6 | rust-version = "1.56" 7 | 8 | [lib] 9 | crate-type = ["cdylib"] 10 | 11 | [dependencies] 12 | wasm-bindgen = "0.2.87" 13 | wasm-bindgen-futures = "0.4.37" 14 | bmt = { path = "../../crates/bmt" } 15 | postage = { path = "../../crates/postage" } 16 | ethers-signers = { workspace = true } 17 | hex = { workspace = true } 18 | serde = { workspace = true } 19 | serde_json = { workspace = true } 20 | console_error_panic_hook = "0.1.7" 21 | tracing-wasm = "0.2.1" 22 | rand = { workspace = true } 23 | tracing = { workspace = true } 24 | tracing-subscriber = { workspace = true } 25 | 26 | [dependencies.web-sys] 27 | version = "0.3.64" 28 | features = [ 29 | 'Document', 30 | 'Element', 31 | 'HtmlElement', 32 | 'Node', 33 | 'Window', 34 | 'File', 35 | 'FileReader', 36 | 'FileList', 37 | 'HtmlDivElement', 38 | 'HtmlInputElement', 39 | 'HtmlTextAreaElement', 40 | 'Event', 41 | 'HtmlButtonElement' 42 | ] -------------------------------------------------------------------------------- /bin/wasm-playground/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 55 | 56 | -------------------------------------------------------------------------------- /crates/crates/metrics/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vertex-metrics" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "GPL-3.0-or-later" 6 | description = "Metrics and observability for Vertex Swarm" 7 | 8 | [dependencies] 9 | # Internal crates 10 | vertex-primitives = { workspace = true } 11 | 12 | # Metrics 13 | metrics = "0.21" 14 | metrics-exporter-prometheus = "0.12" 15 | metrics-util = "0.15" 16 | 17 | # Tracing 18 | tracing = "0.1" 19 | tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } 20 | tracing-opentelemetry = "0.19" 21 | opentelemetry = { version = "0.19", features = ["rt-tokio"] } 22 | opentelemetry-jaeger = { version = "0.18", features = ["rt-tokio"] } 23 | opentelemetry-otlp = { version = "0.12", features = ["metrics", "logs"] } 24 | 25 | # Async runtime 26 | tokio = { version = "1.28", features = ["sync", "macros", "rt-multi-thread"] } 27 | 28 | # HTTP server for metrics 29 | axum = "0.6" 30 | tower = "0.4" 31 | tower-http = { version = "0.4", features = ["trace"] } 32 | 33 | # Error handling 34 | thiserror = "1.0" 35 | eyre = "0.6" 36 | 37 | # Misc 38 | once_cell = "1.17" 39 | chrono = "0.4" 40 | serde = { version = "1.0", features = ["derive"] } 41 | serde_json = "1.0" 42 | 43 | [dev-dependencies] 44 | tokio-test = "0.4" 45 | tempfile = "3.5" 46 | -------------------------------------------------------------------------------- /crates/crates/metrics/src/hooks.rs: -------------------------------------------------------------------------------- 1 | //! Metrics hooks for executing custom metrics collection code 2 | 3 | use std::sync::Arc; 4 | 5 | /// A trait for metrics hooks that can be executed periodically 6 | pub trait Hook: Fn() + Send + Sync + 'static {} 7 | impl Hook for T {} 8 | 9 | /// A builder for creating hooks 10 | #[derive(Debug, Clone, Default)] 11 | pub struct HooksBuilder { 12 | hooks: Vec>, 13 | } 14 | 15 | impl HooksBuilder { 16 | /// Create a new hooks builder 17 | pub fn new() -> Self { 18 | Self::default() 19 | } 20 | 21 | /// Add a hook to the builder 22 | pub fn with_hook(mut self, hook: F) -> Self 23 | where 24 | F: Hook, 25 | { 26 | self.hooks.push(Arc::new(hook)); 27 | self 28 | } 29 | 30 | /// Build the hooks 31 | pub fn build(self) -> Hooks { 32 | Hooks { hooks: self.hooks } 33 | } 34 | } 35 | 36 | /// Collection of metrics hooks 37 | #[derive(Debug, Clone, Default)] 38 | pub struct Hooks { 39 | hooks: Vec>, 40 | } 41 | 42 | impl Hooks { 43 | /// Create a new hooks builder 44 | pub fn builder() -> HooksBuilder { 45 | HooksBuilder::new() 46 | } 47 | 48 | /// Add a hook 49 | pub fn with_hook(mut self, hook: F) -> Self 50 | where 51 | F: Hook, 52 | { 53 | self.hooks.push(Arc::new(hook)); 54 | self 55 | } 56 | 57 | /// Execute all hooks 58 | pub fn execute_all(&self) { 59 | for hook in &self.hooks { 60 | hook(); 61 | } 62 | } 63 | 64 | /// Returns the number of hooks 65 | pub fn len(&self) -> usize { 66 | self.hooks.len() 67 | } 68 | 69 | /// Returns true if there are no hooks 70 | pub fn is_empty(&self) -> bool { 71 | self.hooks.is_empty() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /crates/crates/metrics/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Metrics and observability for Vertex Swarm 2 | //! 3 | //! This crate provides metrics collection, tracing, and logging for the Vertex Swarm node. 4 | 5 | use std::sync::Arc; 6 | 7 | mod config; 8 | mod hooks; 9 | mod logging; 10 | mod prometheus; 11 | mod recorder; 12 | mod server; 13 | mod tracing; 14 | 15 | pub use config::*; 16 | pub use hooks::*; 17 | pub use logging::*; 18 | pub use prometheus::*; 19 | pub use recorder::*; 20 | pub use server::*; 21 | pub use tracing::*; 22 | 23 | /// Re-export metrics crate for convenience 24 | pub use metrics; 25 | 26 | /// Main entry point for configuring and initializing metrics 27 | #[derive(Debug, Clone)] 28 | pub struct MetricsSystem { 29 | /// Configuration for metrics 30 | config: MetricsConfig, 31 | /// Recorder handle for prometheus 32 | prometheus_recorder: Option>, 33 | /// The metrics server handle 34 | server: Option>, 35 | /// Metrics hooks 36 | hooks: Hooks, 37 | } 38 | 39 | impl MetricsSystem { 40 | /// Create a new metrics system with the given configuration 41 | pub fn new(config: MetricsConfig) -> Self { 42 | Self { 43 | config, 44 | prometheus_recorder: None, 45 | server: None, 46 | hooks: Hooks::default(), 47 | } 48 | } 49 | 50 | /// Build and start the metrics system 51 | pub async fn start(mut self) -> eyre::Result { 52 | // Initialize logging 53 | if self.config.logging.enabled { 54 | initialize_logging(&self.config.logging)?; 55 | } 56 | 57 | // Initialize tracing 58 | if self.config.tracing.enabled { 59 | initialize_tracing(&self.config.tracing)?; 60 | } 61 | 62 | // Initialize metrics 63 | if self.config.prometheus.enabled { 64 | let recorder = install_prometheus_recorder(); 65 | self.prometheus_recorder = Some(Arc::new(recorder)); 66 | 67 | // Start the metrics server if configured 68 | if let Some(addr) = self.config.prometheus.endpoint { 69 | let server = MetricsServer::new( 70 | addr, 71 | recorder.handle().clone(), 72 | self.hooks.clone(), 73 | ); 74 | server.start().await?; 75 | self.server = Some(Arc::new(server)); 76 | } 77 | } 78 | 79 | Ok(self) 80 | } 81 | 82 | /// Add a metrics hook 83 | pub fn with_hook(mut self, hook: F) -> Self 84 | where 85 | F: Fn() + Send + Sync + 'static, 86 | { 87 | self.hooks = self.hooks.with_hook(hook); 88 | self 89 | } 90 | 91 | /// Get the prometheus handle for recording metrics 92 | pub fn prometheus_handle(&self) -> Option<&metrics_exporter_prometheus::PrometheusHandle> { 93 | self.prometheus_recorder.as_ref().map(|r| r.handle()) 94 | } 95 | 96 | /// Shut down the metrics system 97 | pub async fn shutdown(self) -> eyre::Result<()> { 98 | if let Some(server) = self.server { 99 | server.shutdown().await?; 100 | } 101 | Ok(()) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /crates/crates/metrics/src/tracing.rs: -------------------------------------------------------------------------------- 1 | //! Tracing system for Vertex Swarm 2 | 3 | use crate::TracingConfig; 4 | use eyre::Context; 5 | use opentelemetry::{ 6 | runtime::Tokio, 7 | sdk::{ 8 | trace::{self, Sampler}, 9 | Resource, 10 | }, 11 | KeyValue, 12 | }; 13 | use std::str::FromStr; 14 | use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer}; 15 | 16 | /// Initialize the tracing system 17 | pub fn initialize_tracing(config: &TracingConfig) -> eyre::Result<()> { 18 | if !config.enabled { 19 | return Ok(()); 20 | } 21 | 22 | // Create a registry for multiple layers 23 | let registry = tracing_subscriber::registry(); 24 | 25 | // Create an environment filter 26 | let env_filter = EnvFilter::try_from_default_env() 27 | .unwrap_or_else(|_| EnvFilter::from_str("info,vertex=debug").unwrap()); 28 | 29 | // Add the environment filter layer 30 | let registry = registry.with(env_filter); 31 | 32 | // Add the opentelemetry layer based on the configured system 33 | let registry = match config.system { 34 | crate::TracingSystem::Jaeger => { 35 | let tracer = init_jaeger_tracer(config)?; 36 | registry.with(tracing_opentelemetry::layer().with_tracer(tracer)) 37 | } 38 | crate::TracingSystem::Otlp => { 39 | let tracer = init_otlp_tracer(config)?; 40 | registry.with(tracing_opentelemetry::layer().with_tracer(tracer)) 41 | } 42 | }; 43 | 44 | // Initialize the registry 45 | registry.try_init()?; 46 | 47 | Ok(()) 48 | } 49 | 50 | /// Initialize a Jaeger tracer 51 | fn init_jaeger_tracer(config: &TracingConfig) -> eyre::Result { 52 | let endpoint = config 53 | .jaeger_endpoint 54 | .as_deref() 55 | .unwrap_or("http://localhost:14268/api/traces"); 56 | 57 | // Create a new Jaeger exporter pipeline 58 | let tracer = opentelemetry_jaeger::new_agent_pipeline() 59 | .with_endpoint(endpoint) 60 | .with_service_name(&config.service_name) 61 | .with_trace_config( 62 | trace::config() 63 | .with_sampler(Sampler::AlwaysOn) 64 | .with_resource(Resource::new(vec![KeyValue::new( 65 | "service.version", 66 | env!("CARGO_PKG_VERSION"), 67 | )])), 68 | ) 69 | .install_batch(Tokio) 70 | .context("Failed to install Jaeger tracer")?; 71 | 72 | Ok(tracer) 73 | } 74 | 75 | /// Initialize an OTLP tracer 76 | fn init_otlp_tracer(config: &TracingConfig) -> eyre::Result { 77 | let endpoint = config 78 | .otlp_endpoint 79 | .as_deref() 80 | .unwrap_or("http://localhost:4317"); 81 | 82 | // Create a new OTLP exporter pipeline 83 | let tracer = opentelemetry_otlp::new_pipeline() 84 | .tracing() 85 | .with_exporter( 86 | opentelemetry_otlp::new_exporter() 87 | .tonic() 88 | .with_endpoint(endpoint), 89 | ) 90 | .with_trace_config( 91 | trace::config() 92 | .with_sampler(Sampler::AlwaysOn) 93 | .with_resource(Resource::new(vec![ 94 | KeyValue::new("service.name", config.service_name.clone()), 95 | KeyValue::new("service.version", env!("CARGO_PKG_VERSION")), 96 | ])), 97 | ) 98 | .install_batch(Tokio) 99 | .context("Failed to install OTLP tracer")?; 100 | 101 | Ok(tracer) 102 | } 103 | -------------------------------------------------------------------------------- /crates/crates/node-api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vertex-node-api" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "GPL-3.0-or-later" 6 | description = "Node API traits for the Vertex Swarm node" 7 | 8 | [dependencies] 9 | # Internal crates 10 | vertex-primitives = { workspace = true } 11 | vertex-swarm-api = { workspace = true } 12 | vertex-swarmspec = { workspace = true } 13 | 14 | # Async 15 | futures = "0.3" 16 | async-trait = "0.1" 17 | 18 | # Error handling 19 | thiserror = "1.0" 20 | 21 | # Trait implementation 22 | auto_impl = "1.1" 23 | 24 | # Feature-gated dependencies 25 | serde = { version = "1.0", features = ["derive"], optional = true } 26 | 27 | [features] 28 | default = ["std", "serde"] 29 | std = ["vertex-primitives/std", "vertex-swarm-api/std", "vertex-swarmspec/std", "serde?/std"] 30 | serde = ["dep:serde", "vertex-primitives/serde", "vertex-swarm-api/serde", "vertex-swarmspec/serde"] 31 | -------------------------------------------------------------------------------- /crates/crates/node-api/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Node API traits for the Vertex Swarm node 2 | //! 3 | //! This crate defines traits for node components, configuration, and interfaces. 4 | 5 | #![cfg_attr(not(feature = "std"), no_std)] 6 | #![warn(missing_docs)] 7 | 8 | #[cfg(not(feature = "std"))] 9 | extern crate alloc; 10 | 11 | #[cfg(not(feature = "std"))] 12 | use alloc::{boxed::Box, string::String, vec::Vec}; 13 | 14 | pub use async_trait::async_trait; 15 | pub use vertex_primitives::{self, ChunkAddress, Error, PeerId, Result}; 16 | pub use vertex_swarm_api as swarm_api; 17 | pub use vertex_swarmspec as swarmspec; 18 | 19 | /// Node types and components 20 | pub mod node; 21 | pub use node::*; 22 | 23 | /// Node builder and configuration 24 | pub mod builder; 25 | pub use builder::*; 26 | 27 | /// Node task management 28 | pub mod tasks; 29 | pub use tasks::*; 30 | 31 | /// Node events and notifications 32 | pub mod events; 33 | pub use events::*; 34 | 35 | /// Node exit handling 36 | pub mod exit; 37 | pub use exit::*; 38 | 39 | /// Node startup and shutdown 40 | pub mod lifecycle; 41 | pub use lifecycle::*; 42 | -------------------------------------------------------------------------------- /crates/crates/node-api/src/lifecycle.rs: -------------------------------------------------------------------------------- 1 | //! Node startup and shutdown 2 | 3 | use crate::{builder::BuilderContext, FullNodeComponents, NodeTypes}; 4 | use vertex_primitives::Result; 5 | use vertex_swarm_api::{NetworkConfig, StorageConfig}; 6 | use vertex_swarmspec::SwarmSpec; 7 | 8 | #[cfg(not(feature = "std"))] 9 | use alloc::{boxed::Box, string::String, vec::Vec}; 10 | 11 | /// A trait for node lifecycles 12 | #[async_trait] 13 | #[auto_impl::auto_impl(&, Arc)] 14 | pub trait NodeLifecycle: Send + Sync + 'static { 15 | /// Initialize the node 16 | async fn initialize(&self) -> Result<()>; 17 | 18 | /// Start the node 19 | async fn start(&self) -> Result<()>; 20 | 21 | /// Stop the node 22 | async fn stop(&self) -> Result<()>; 23 | 24 | /// Shutdown the node 25 | async fn shutdown(&self) -> Result<()>; 26 | 27 | /// Restart the node 28 | async fn restart(&self) -> Result<()> { 29 | self.stop().await?; 30 | self.start().await 31 | } 32 | 33 | /// Check if the node is running 34 | fn is_running(&self) -> bool; 35 | } 36 | 37 | /// Factory for creating nodes 38 | #[async_trait] 39 | #[auto_impl::auto_impl(&, Arc)] 40 | pub trait NodeFactory: Send + Sync + 'static { 41 | /// The node type this factory creates 42 | type Node: FullNodeComponents; 43 | 44 | /// Create a new node 45 | async fn create_node( 46 | &self, 47 | context: &BuilderContext, 48 | ) -> Result; 49 | } 50 | 51 | /// A launcher for a node 52 | #[async_trait] 53 | #[auto_impl::auto_impl(&, Arc)] 54 | pub trait NodeLauncher: Send + Sync + 'static { 55 | /// The node type this launcher launches 56 | type Node: FullNodeComponents; 57 | 58 | /// Launch a node with the given node factory 59 | async fn launch_node>( 60 | &self, 61 | factory: F, 62 | spec: N::Spec, 63 | network_config: NetworkConfig, 64 | storage_config: StorageConfig, 65 | ) -> Result>; 66 | } 67 | 68 | /// A handle to a launched node 69 | pub struct NodeHandle { 70 | /// The node 71 | pub node: Node, 72 | /// The node's lifecycle 73 | pub lifecycle: Box, 74 | /// Exit future that resolves when the node exits 75 | pub exit_future: crate::exit::ExitFuture, 76 | } 77 | 78 | impl NodeHandle { 79 | /// Create a new node handle 80 | pub fn new( 81 | node: Node, 82 | lifecycle: Box, 83 | exit_future: crate::exit::ExitFuture, 84 | ) -> Self { 85 | Self { 86 | node, 87 | lifecycle, 88 | exit_future, 89 | } 90 | } 91 | 92 | /// Wait for the node to exit 93 | pub async fn wait_for_exit(self) -> Result<()> { 94 | self.exit_future.await 95 | } 96 | 97 | /// Stop the node 98 | pub async fn stop(&self) -> Result<()> { 99 | self.lifecycle.stop().await 100 | } 101 | 102 | /// Restart the node 103 | pub async fn restart(&self) -> Result<()> { 104 | self.lifecycle.restart().await 105 | } 106 | 107 | /// Shutdown the node 108 | pub async fn shutdown(self) -> Result<()> { 109 | self.lifecycle.shutdown().await 110 | } 111 | 112 | /// Check if the node is running 113 | pub fn is_running(&self) -> bool { 114 | self.lifecycle.is_running() 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /crates/crates/node/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vertex-node" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "GPL-3.0-or-later" 6 | description = "Vertex Swarm node executable" 7 | 8 | [dependencies] 9 | # Internal dependencies 10 | vertex-primitives = { workspace = true } 11 | vertex-swarm-api = { workspace = true } 12 | vertex-swarmspec = { workspace = true } 13 | vertex-node-api = { workspace = true } 14 | vertex-storage = { workspace = true } 15 | vertex-network = { workspace = true } 16 | vertex-access = { workspace = true } 17 | vertex-protocol = { workspace = true } 18 | vertex-bandwidth = { workspace = true } 19 | vertex-api = { workspace = true } 20 | vertex-metrics = { workspace = true } 21 | 22 | # CLI 23 | clap = { version = "4.3", features = ["derive", "env"] } 24 | directories = "5.0" 25 | shellexpand = "3.1" 26 | 27 | # Tracing 28 | tracing = "0.1" 29 | tracing-subscriber = { version = "0.3", features = ["env-filter"] } 30 | tracing-appender = "0.2" 31 | 32 | # Async runtime 33 | tokio = { version = "1.29", features = ["full", "tracing"] } 34 | futures = "0.3" 35 | 36 | # Error handling 37 | eyre = "0.6" 38 | color-eyre = "0.6" 39 | 40 | # Misc 41 | once_cell = "1.17" 42 | humantime = "2.1" 43 | serde = { version = "1.0", features = ["derive"] } 44 | serde_json = "1.0" 45 | toml = "0.7" 46 | chrono = "0.4" 47 | rand = "0.8" 48 | hex = "0.4" 49 | 50 | [build-dependencies] 51 | vergen = { version = "7.0", features = ["build", "git", "gitcl"] } 52 | 53 | [[bin]] 54 | name = "vertex" 55 | path = "src/main.rs" 56 | -------------------------------------------------------------------------------- /crates/crates/node/build.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use vergen::EmitBuilder; 3 | 4 | fn main() -> Result<(), Box> { 5 | // Generate build information 6 | EmitBuilder::builder() 7 | .git_describe(true, true, None) 8 | .git_sha(false) 9 | .git_commit_timestamp() 10 | .cargo_features() 11 | .build_timestamp() 12 | .emit()?; 13 | 14 | // Extract Git SHA 15 | let sha = std::env::var("VERGEN_GIT_SHA_SHORT").unwrap_or_else(|_| "unknown".to_string()); 16 | 17 | // Get version with suffix for development builds 18 | let version = env!("CARGO_PKG_VERSION"); 19 | let is_dirty = std::env::var("VERGEN_GIT_DIRTY").unwrap_or_default() == "true"; 20 | let version_suffix = if is_dirty { "-dev" } else { "" }; 21 | 22 | // Set environment variables for the build 23 | println!("cargo:rustc-env=VERTEX_VERSION_SUFFIX={}", version_suffix); 24 | println!("cargo:rustc-env=VERTEX_GIT_SHA={}", sha); 25 | println!( 26 | "cargo:rustc-env=VERTEX_VERSION={}{}", 27 | version, version_suffix 28 | ); 29 | 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /crates/crates/node/src/constants.rs: -------------------------------------------------------------------------------- 1 | //! Constants used throughout the Vertex Swarm node. 2 | 3 | /// Default port for the Swarm network's TCP connections 4 | pub const DEFAULT_P2P_PORT: u16 = 1634; 5 | 6 | /// Default port for the Swarm network's UDP discovery 7 | pub const DEFAULT_DISCOVERY_PORT: u16 = 1634; 8 | 9 | /// Default port for the HTTP API 10 | pub const DEFAULT_HTTP_API_PORT: u16 = 1635; 11 | 12 | /// Default port for the gRPC API 13 | pub const DEFAULT_GRPC_API_PORT: u16 = 1636; 14 | 15 | /// Default port for metrics 16 | pub const DEFAULT_METRICS_PORT: u16 = 1637; 17 | 18 | /// Default maximum number of peers 19 | pub const DEFAULT_MAX_PEERS: usize = 50; 20 | 21 | /// Default maximum number of concurrent requests 22 | pub const DEFAULT_MAX_CONCURRENT_REQUESTS: usize = 100; 23 | 24 | /// Default buffer size for chunk data (4KB) 25 | pub const DEFAULT_CHUNK_SIZE: usize = 4 * 1024; 26 | 27 | /// Default data directory name 28 | pub const DEFAULT_DATA_DIR_NAME: &str = "vertex"; 29 | 30 | /// Default pruning parameters 31 | 32 | /// Default maximum number of chunks to store 33 | pub const DEFAULT_MAX_CHUNKS: usize = 1_000_000; 34 | 35 | /// Default target number of chunks 36 | pub const DEFAULT_TARGET_CHUNKS: usize = 500_000; 37 | 38 | /// Default minimum number of chunks 39 | pub const DEFAULT_MIN_CHUNKS: usize = 100_000; 40 | 41 | /// Default reserve storage percentage 42 | pub const DEFAULT_RESERVE_PERCENTAGE: u8 = 10; 43 | 44 | /// Default daily bandwidth allowance (free tier) in bytes (1MB) 45 | pub const DEFAULT_DAILY_BANDWIDTH_ALLOWANCE: u64 = 1_000_000; 46 | 47 | /// Default payment threshold in bytes (10MB) 48 | pub const DEFAULT_PAYMENT_THRESHOLD: u64 = 10_000_000; 49 | 50 | /// Default payment tolerance in bytes (5MB) 51 | pub const DEFAULT_PAYMENT_TOLERANCE: u64 = 5_000_000; 52 | 53 | /// Default disconnect threshold in bytes (50MB) 54 | pub const DEFAULT_DISCONNECT_THRESHOLD: u64 = 50_000_000; 55 | 56 | /// Default maximum storage size (in GB) 57 | pub const DEFAULT_MAX_STORAGE_SIZE_GB: u64 = 10; 58 | 59 | /// Convert GB to bytes 60 | pub const GB_TO_BYTES: u64 = 1024 * 1024 * 1024; 61 | -------------------------------------------------------------------------------- /crates/crates/node/src/logging.rs: -------------------------------------------------------------------------------- 1 | //! Logging configuration for the Vertex Swarm node. 2 | 3 | use crate::{cli::LogArgs, dirs}; 4 | use eyre::Result; 5 | use std::{ 6 | fs::File, 7 | io::{self, Write}, 8 | path::Path, 9 | }; 10 | use tracing_appender::non_blocking::{NonBlocking, WorkerGuard}; 11 | use tracing_subscriber::{ 12 | fmt::{self, format::FmtSpan}, 13 | prelude::*, 14 | EnvFilter, 15 | }; 16 | 17 | /// Initialize logging based on command line arguments. 18 | pub fn init_logging(args: &LogArgs) -> Result> { 19 | let filter = if args.quiet { 20 | EnvFilter::new("error") 21 | } else { 22 | let level = match args.verbosity { 23 | 0 => "error", 24 | 1 => "warn", 25 | 2 => "info", 26 | 3 => "debug", 27 | _ => "trace", 28 | }; 29 | 30 | let filter_str = match &args.filter { 31 | Some(filter) => format!("{},{}", level, filter), 32 | None => level.to_string(), 33 | }; 34 | 35 | EnvFilter::try_new(filter_str)? 36 | }; 37 | 38 | let mut builder = tracing_subscriber::fmt::Subscriber::builder() 39 | .with_env_filter(filter) 40 | .with_span_events(FmtSpan::CLOSE) 41 | .with_writer(io::stdout); 42 | 43 | if !args.timestamps { 44 | builder = builder.without_time(); 45 | } 46 | 47 | // Set up file logging if enabled 48 | let guard = if args.log_file { 49 | let log_dir = args.log_dir.clone().unwrap_or_else(|| { 50 | dirs::default_logs_dir().unwrap_or_else(|| Path::new("logs").to_path_buf()) 51 | }); 52 | 53 | std::fs::create_dir_all(&log_dir)?; 54 | 55 | let file_appender = tracing_appender::rolling::daily(&log_dir, "vertex.log"); 56 | let (non_blocking, guard) = tracing_appender::non_blocking(file_appender); 57 | 58 | builder = builder.with_writer(non_blocking); 59 | Some(guard) 60 | } else { 61 | None 62 | }; 63 | 64 | // Initialize the subscriber 65 | builder.init(); 66 | 67 | // Log startup banner 68 | if !args.quiet { 69 | log_startup_banner(); 70 | } 71 | 72 | Ok(guard) 73 | } 74 | 75 | /// Log a startup banner with the Vertex Swarm logo and version 76 | fn log_startup_banner() { 77 | let banner = format!( 78 | r#" 79 | _ _ _ 80 | | | | | | | 81 | | | | | ___ _ __| |_ _____ __ 82 | | | | |/ _ \ '__| __/ _ \ \/ / 83 | \ \_/ / __/ | | || __/> < 84 | \___/ \___|_| \__\___/_/\_\ 85 | 86 | Swarm Node v{} 87 | "#, 88 | crate::version::SHORT_VERSION 89 | ); 90 | 91 | println!("{}", banner); 92 | } 93 | 94 | /// Create a logger for a specific file that manages its own file handle 95 | pub fn file_logger(path: impl AsRef) -> Result { 96 | let path = path.as_ref(); 97 | 98 | // Ensure parent directory exists 99 | if let Some(parent) = path.parent() { 100 | std::fs::create_dir_all(parent)?; 101 | } 102 | 103 | let file = File::create(path)?; 104 | Ok(file) 105 | } 106 | 107 | /// Create a non-blocking file logger 108 | pub fn non_blocking_file_logger(path: impl AsRef) -> Result<(NonBlocking, WorkerGuard)> { 109 | let path = path.as_ref(); 110 | 111 | // Ensure parent directory exists 112 | if let Some(parent) = path.parent() { 113 | std::fs::create_dir_all(parent)?; 114 | } 115 | 116 | let file = File::create(path)?; 117 | let (non_blocking, guard) = tracing_appender::non_blocking(file); 118 | Ok((non_blocking, guard)) 119 | } 120 | -------------------------------------------------------------------------------- /crates/crates/node/src/main.rs: -------------------------------------------------------------------------------- 1 | 2 | //! Vertex Swarm node executable 3 | //! 4 | //! This is the main entry point for the Vertex Swarm node. 5 | 6 | mod cli; 7 | mod commands; 8 | mod config; 9 | mod constants; 10 | mod dirs; 11 | mod logging; 12 | mod node; 13 | mod utils; 14 | mod version; 15 | 16 | use crate::cli::Cli; 17 | use clap::Parser; 18 | use color_eyre::eyre; 19 | use tracing::info; 20 | 21 | /// Main entry point for the Vertex Swarm node. 22 | #[tokio::main] 23 | async fn main() -> eyre::Result<()> { 24 | // Setup error handling 25 | color_eyre::install()?; 26 | 27 | // Parse command line arguments 28 | let cli = Cli::parse(); 29 | 30 | // Initialize logging 31 | let _guard = logging::init_logging(&cli.log_args)?; 32 | 33 | // Print version information 34 | if cli.version { 35 | println!("{}", version::LONG_VERSION); 36 | return Ok(()); 37 | } 38 | 39 | info!("Starting Vertex Swarm {}", version::VERSION); 40 | 41 | // Dispatch command 42 | match cli.command { 43 | cli::Commands::Node(args) => { 44 | commands::node::run(args).await?; 45 | } 46 | cli::Commands::Dev(args) => { 47 | commands::dev::run(args).await?; 48 | } 49 | cli::Commands::Info(args) => { 50 | commands::info::run(args).await?; 51 | } 52 | cli::Commands::Config(args) => { 53 | commands::config::run(args).await?; 54 | } 55 | } 56 | 57 | Ok(()) 58 | } 59 | -------------------------------------------------------------------------------- /crates/crates/node/src/utils.rs: -------------------------------------------------------------------------------- 1 | //! Utility functions for the Vertex Swarm node. 2 | 3 | use crate::dirs::parse_path; 4 | use eyre::{eyre, Result}; 5 | use rand::{rngs::OsRng, RngCore}; 6 | use std::{fs, path::Path}; 7 | 8 | /// Generate a new random secret key for P2P identity 9 | pub fn generate_p2p_secret() -> Result<[u8; 32]> { 10 | let mut secret = [0u8; 32]; 11 | OsRng.fill_bytes(&mut secret); 12 | Ok(secret) 13 | } 14 | 15 | /// Generate a new JWT secret for API authentication 16 | pub fn generate_jwt_secret() -> Result<[u8; 32]> { 17 | let mut secret = [0u8; 32]; 18 | OsRng.fill_bytes(&mut secret); 19 | Ok(secret) 20 | } 21 | 22 | /// Load or generate a P2P secret key 23 | pub fn load_or_generate_p2p_secret(path: impl AsRef) -> Result<[u8; 32]> { 24 | let path = path.as_ref(); 25 | 26 | if path.exists() { 27 | // Load existing secret 28 | let secret_hex = fs::read_to_string(path)?; 29 | let secret_hex = secret_hex.trim(); 30 | 31 | if secret_hex.len() != 64 { 32 | return Err(eyre!("Invalid P2P secret key length")); 33 | } 34 | 35 | let mut secret = [0u8; 32]; 36 | hex::decode_to_slice(secret_hex, &mut secret) 37 | .map_err(|_| eyre!("Invalid P2P secret key format"))?; 38 | 39 | Ok(secret) 40 | } else { 41 | // Generate new secret 42 | let secret = generate_p2p_secret()?; 43 | 44 | // Ensure parent directory exists 45 | if let Some(parent) = path.parent() { 46 | fs::create_dir_all(parent)?; 47 | } 48 | 49 | // Save the secret 50 | fs::write(path, hex::encode(&secret))?; 51 | 52 | Ok(secret) 53 | } 54 | } 55 | 56 | /// Load or generate a JWT secret 57 | pub fn load_or_generate_jwt_secret(path: impl AsRef) -> Result<[u8; 32]> { 58 | let path = path.as_ref(); 59 | 60 | if path.exists() { 61 | // Load existing secret 62 | let secret_hex = fs::read_to_string(path)?; 63 | let secret_hex = secret_hex.trim(); 64 | 65 | if secret_hex.len() != 64 { 66 | return Err(eyre!("Invalid JWT secret key length")); 67 | } 68 | 69 | let mut secret = [0u8; 32]; 70 | hex::decode_to_slice(secret_hex, &mut secret) 71 | .map_err(|_| eyre!("Invalid JWT secret key format"))?; 72 | 73 | Ok(secret) 74 | } else { 75 | // Generate new secret 76 | let secret = generate_jwt_secret()?; 77 | 78 | // Ensure parent directory exists 79 | if let Some(parent) = path.parent() { 80 | fs::create_dir_all(parent)?; 81 | } 82 | 83 | // Save the secret 84 | fs::write(path, hex::encode(&secret))?; 85 | 86 | Ok(secret) 87 | } 88 | } 89 | 90 | /// Format a size in bytes to a human-readable string 91 | pub fn format_size(size: u64) -> String { 92 | const KB: u64 = 1024; 93 | const MB: u64 = KB * 1024; 94 | const GB: u64 = MB * 1024; 95 | const TB: u64 = GB * 1024; 96 | 97 | if size >= TB { 98 | format!("{:.2} TB", size as f64 / TB as f64) 99 | } else if size >= GB { 100 | format!("{:.2} GB", size as f64 / GB as f64) 101 | } else if size >= MB { 102 | format!("{:.2} MB", size as f64 / MB as f64) 103 | } else if size >= KB { 104 | format!("{:.2} KB", size as f64 / KB as f64) 105 | } else { 106 | format!("{} bytes", size) 107 | } 108 | } 109 | 110 | /// Format a timestamp as a human-readable date/time 111 | pub fn format_timestamp(timestamp: u64) -> String { 112 | let datetime = chrono::NaiveDateTime::from_timestamp_opt(timestamp as i64, 0) 113 | .unwrap_or_else(|| chrono::NaiveDateTime::from_timestamp_opt(0, 0).unwrap()); 114 | 115 | datetime.format("%Y-%m-%d %H:%M:%S").to_string() 116 | } 117 | -------------------------------------------------------------------------------- /crates/crates/node/src/version.rs: -------------------------------------------------------------------------------- 1 | //! Version information for the Vertex Swarm node. 2 | 3 | /// The version string: semver + git sha 4 | pub const VERSION: &str = concat!(env!("VERTEX_VERSION"), " (", env!("VERTEX_GIT_SHA"), ")"); 5 | 6 | /// The short version information for Vertex. 7 | pub const SHORT_VERSION: &str = env!("VERTEX_VERSION"); 8 | 9 | /// The git commit SHA. 10 | pub const GIT_SHA: &str = env!("VERTEX_GIT_SHA"); 11 | 12 | /// The build timestamp. 13 | pub const BUILD_TIMESTAMP: &str = env!("VERGEN_BUILD_TIMESTAMP"); 14 | 15 | /// The cargo features. 16 | pub const CARGO_FEATURES: &str = env!("VERGEN_CARGO_FEATURES"); 17 | 18 | /// The long version information for Vertex. 19 | /// 20 | /// Example: 21 | /// 22 | /// ```text 23 | /// Version: 0.1.0 24 | /// Commit SHA: defa64b2 25 | /// Build Timestamp: 2023-05-19T01:47:19.815651705Z 26 | /// Build Features: default 27 | /// ``` 28 | pub const LONG_VERSION: &str = concat!( 29 | "Version: ", 30 | env!("VERTEX_VERSION"), 31 | "\n", 32 | "Commit SHA: ", 33 | env!("VERTEX_GIT_SHA"), 34 | "\n", 35 | "Build Timestamp: ", 36 | env!("VERGEN_BUILD_TIMESTAMP"), 37 | "\n", 38 | "Build Features: ", 39 | env!("VERGEN_CARGO_FEATURES") 40 | ); 41 | 42 | /// The user agent string for network communication. 43 | pub const USER_AGENT: &str = concat!("vertex/", env!("VERTEX_VERSION")); 44 | 45 | /// The version information for libp2p identification. 46 | pub const P2P_CLIENT_VERSION: &str = concat!("vertex/v", env!("VERTEX_VERSION")); 47 | -------------------------------------------------------------------------------- /crates/crates/primitives/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vertex-primitives" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "GPL-3.0-or-later" 6 | description = "Core primitive types for the Vertex Swarm node" 7 | 8 | [dependencies] 9 | # Serialization 10 | serde = { version = "1.0", features = ["derive"], optional = true } 11 | # Crypto 12 | sha3 = "0.10" 13 | hex = "0.4" 14 | # Error handling 15 | thiserror = "1.0" 16 | # Additional 17 | auto_impl = "1.1" 18 | 19 | [features] 20 | default = ["std", "serde"] 21 | std = ["serde?/std"] 22 | serde = ["dep:serde"] 23 | -------------------------------------------------------------------------------- /crates/crates/primitives/src/chunk.rs: -------------------------------------------------------------------------------- 1 | //! Chunk-related primitive types 2 | 3 | #[cfg(not(feature = "std"))] 4 | use alloc::vec::Vec; 5 | use core::fmt::Debug; 6 | 7 | /// Possible chunk types in the Swarm network 8 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 9 | pub enum ChunkType { 10 | /// Standard content-addressed chunk (default) 11 | ContentAddressed, 12 | /// Encrypted chunk with custom addressing 13 | Encrypted, 14 | /// Manifest chunk for organizing other chunks 15 | Manifest, 16 | /// Custom chunk type with identifier 17 | Custom(u8), 18 | } 19 | 20 | impl ChunkType { 21 | /// Convert chunk type to byte identifier 22 | pub const fn to_byte(&self) -> u8 { 23 | match self { 24 | Self::ContentAddressed => 0, 25 | Self::Encrypted => 1, 26 | Self::Manifest => 2, 27 | Self::Custom(id) => *id, 28 | } 29 | } 30 | 31 | /// Create chunk type from byte identifier 32 | pub const fn from_byte(byte: u8) -> Self { 33 | match byte { 34 | 0 => Self::ContentAddressed, 35 | 1 => Self::Encrypted, 36 | 2 => Self::Manifest, 37 | id => Self::Custom(id), 38 | } 39 | } 40 | } 41 | 42 | /// A peer identifier in the network 43 | #[derive(Clone, PartialEq, Eq, Hash)] 44 | pub struct PeerId(pub Vec); 45 | 46 | impl PeerId { 47 | /// Creates a new PeerId from raw bytes 48 | pub fn new(bytes: Vec) -> Self { 49 | Self(bytes) 50 | } 51 | 52 | /// Returns the underlying bytes 53 | pub fn as_bytes(&self) -> &[u8] { 54 | &self.0 55 | } 56 | } 57 | 58 | impl Debug for PeerId { 59 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 60 | write!(f, "PeerId({})", hex::encode(&self.0)) 61 | } 62 | } 63 | 64 | /// Protocol ID type 65 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 66 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 67 | pub struct ProtocolId( 68 | #[cfg(not(feature = "std"))] pub alloc::string::String, 69 | #[cfg(feature = "std")] pub String, 70 | ); 71 | 72 | impl ProtocolId { 73 | /// Creates a new ProtocolId 74 | pub fn new(id: impl Into) -> Self { 75 | Self(id.into()) 76 | } 77 | 78 | /// Returns the protocol ID as a string 79 | pub fn as_str(&self) -> &str { 80 | &self.0 81 | } 82 | } 83 | 84 | /// Direction of data transfer 85 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 86 | pub enum Direction { 87 | /// Outgoing data 88 | Outgoing, 89 | /// Incoming data 90 | Incoming, 91 | } 92 | -------------------------------------------------------------------------------- /crates/crates/primitives/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error types for the Vertex Swarm node 2 | 3 | #[cfg(not(feature = "std"))] 4 | use alloc::string::String; 5 | 6 | /// Common error type for all Vertex operations 7 | #[derive(Debug, thiserror::Error)] 8 | pub enum Error { 9 | /// Error related to chunk operations 10 | #[error("Chunk error: {0}")] 11 | Chunk(String), 12 | 13 | /// Error related to storage operations 14 | #[error("Storage error: {0}")] 15 | Storage(String), 16 | 17 | /// Error related to network operations 18 | #[error("Network error: {0}")] 19 | Network(String), 20 | 21 | /// Error related to authentication 22 | #[error("Authentication error: {0}")] 23 | Authentication(String), 24 | 25 | /// Error related to authorization 26 | #[error("Authorization error: {0}")] 27 | Authorization(String), 28 | 29 | /// Error related to accounting 30 | #[error("Accounting error: {0}")] 31 | Accounting(String), 32 | 33 | /// Error when a resource is not found 34 | #[error("Not found: {0}")] 35 | NotFound(String), 36 | 37 | /// Error when a resource already exists 38 | #[error("Already exists: {0}")] 39 | AlreadyExists(String), 40 | 41 | /// Error when an operation is invalid 42 | #[error("Invalid operation: {0}")] 43 | InvalidOperation(String), 44 | 45 | /// Error when an operation times out 46 | #[error("Timeout: {0}")] 47 | Timeout(String), 48 | 49 | /// Error related to IO operations 50 | #[error("IO error: {0}")] 51 | Io(String), 52 | 53 | /// Error related to configuration 54 | #[error("Configuration error: {0}")] 55 | Configuration(String), 56 | 57 | /// Error related to blockchain operations 58 | #[error("Blockchain error: {0}")] 59 | Blockchain(String), 60 | 61 | /// Other errors 62 | #[error("Other error: {0}")] 63 | Other(String), 64 | } 65 | 66 | impl Error { 67 | /// Creates a new chunk error 68 | pub fn chunk(msg: impl Into) -> Self { 69 | Self::Chunk(msg.into()) 70 | } 71 | 72 | /// Creates a new storage error 73 | pub fn storage(msg: impl Into) -> Self { 74 | Self::Storage(msg.into()) 75 | } 76 | 77 | /// Creates a new network error 78 | pub fn network(msg: impl Into) -> Self { 79 | Self::Network(msg.into()) 80 | } 81 | 82 | /// Creates a new authentication error 83 | pub fn authentication(msg: impl Into) -> Self { 84 | Self::Authentication(msg.into()) 85 | } 86 | 87 | /// Creates a new authorization error 88 | pub fn authorization(msg: impl Into) -> Self { 89 | Self::Authorization(msg.into()) 90 | } 91 | 92 | /// Creates a new accounting error 93 | pub fn accounting(msg: impl Into) -> Self { 94 | Self::Accounting(msg.into()) 95 | } 96 | 97 | /// Creates a new not found error 98 | pub fn not_found(msg: impl Into) -> Self { 99 | Self::NotFound(msg.into()) 100 | } 101 | 102 | /// Creates a new already exists error 103 | pub fn already_exists(msg: impl Into) -> Self { 104 | Self::AlreadyExists(msg.into()) 105 | } 106 | 107 | /// Creates a new invalid operation error 108 | pub fn invalid_operation(msg: impl Into) -> Self { 109 | Self::InvalidOperation(msg.into()) 110 | } 111 | 112 | /// Creates a new timeout error 113 | pub fn timeout(msg: impl Into) -> Self { 114 | Self::Timeout(msg.into()) 115 | } 116 | 117 | /// Creates a new IO error 118 | pub fn io(msg: impl Into) -> Self { 119 | Self::Io(msg.into()) 120 | } 121 | 122 | /// Creates a new configuration error 123 | pub fn configuration(msg: impl Into) -> Self { 124 | Self::Configuration(msg.into()) 125 | } 126 | 127 | /// Creates a new blockchain error 128 | pub fn blockchain(msg: impl Into) -> Self { 129 | Self::Blockchain(msg.into()) 130 | } 131 | 132 | /// Creates a new other error 133 | pub fn other(msg: impl Into) -> Self { 134 | Self::Other(msg.into()) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /crates/crates/primitives/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Core primitive types for the Vertex Swarm node 2 | //! 3 | //! This crate defines the basic types used throughout the Vertex Swarm project. 4 | 5 | #![cfg_attr(not(feature = "std"), no_std)] 6 | #![warn(missing_docs)] 7 | 8 | #[cfg(not(feature = "std"))] 9 | extern crate alloc; 10 | 11 | #[cfg(not(feature = "std"))] 12 | use alloc::{boxed::Box, string::String, vec::Vec}; 13 | 14 | /// Error types for the Vertex Swarm node 15 | pub mod error; 16 | pub use error::*; 17 | 18 | /// Chunk-related primitive types 19 | pub mod chunk; 20 | pub use chunk::*; 21 | 22 | /// Network-related primitive types 23 | pub mod network; 24 | pub use network::*; 25 | 26 | /// Utility functions 27 | pub mod utils; 28 | 29 | /// Result type used throughout the Vertex codebase 30 | pub type Result = core::result::Result; 31 | 32 | /// A 32-byte address for chunks in the Swarm network 33 | #[derive(Clone, PartialEq, Eq, Hash)] 34 | pub struct ChunkAddress(pub [u8; 32]); 35 | 36 | impl ChunkAddress { 37 | /// Creates a new ChunkAddress from raw bytes 38 | pub const fn new(bytes: [u8; 32]) -> Self { 39 | Self(bytes) 40 | } 41 | 42 | /// Returns the underlying bytes 43 | pub const fn as_bytes(&self) -> &[u8; 32] { 44 | &self.0 45 | } 46 | 47 | /// Calculate proximity (0-256) between two addresses 48 | /// Returns the number of leading bits that match 49 | pub fn proximity(&self, other: &Self) -> u8 { 50 | // Count leading zeros in XOR distance 51 | let mut proximity = 0; 52 | for i in 0..32 { 53 | let xor = self.0[i] ^ other.0[i]; 54 | if xor == 0 { 55 | proximity += 8; 56 | continue; 57 | } 58 | // Count leading zeros in byte 59 | proximity += xor.leading_zeros() as u8; 60 | break; 61 | } 62 | proximity 63 | } 64 | 65 | /// Check if this address is within a certain proximity of another address 66 | pub fn is_within_proximity(&self, other: &Self, min_proximity: u8) -> bool { 67 | self.proximity(other) >= min_proximity 68 | } 69 | } 70 | 71 | impl core::fmt::Debug for ChunkAddress { 72 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 73 | write!(f, "ChunkAddress({})", hex::encode(&self.0[..8])) 74 | } 75 | } 76 | 77 | impl core::fmt::Display for ChunkAddress { 78 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 79 | write!(f, "{}", hex::encode(&self.0[..8])) 80 | } 81 | } 82 | 83 | #[cfg(feature = "serde")] 84 | impl serde::Serialize for ChunkAddress { 85 | fn serialize(&self, serializer: S) -> core::result::Result 86 | where 87 | S: serde::Serializer, 88 | { 89 | serializer.serialize_str(&hex::encode(&self.0)) 90 | } 91 | } 92 | 93 | #[cfg(feature = "serde")] 94 | impl<'de> serde::Deserialize<'de> for ChunkAddress { 95 | fn deserialize(deserializer: D) -> core::result::Result 96 | where 97 | D: serde::Deserializer<'de>, 98 | { 99 | let s = String::deserialize(deserializer)?; 100 | let bytes = hex::decode(s).map_err(serde::de::Error::custom)?; 101 | if bytes.len() != 32 { 102 | return Err(serde::de::Error::custom( 103 | "ChunkAddress must be exactly 32 bytes", 104 | )); 105 | } 106 | let mut arr = [0u8; 32]; 107 | arr.copy_from_slice(&bytes); 108 | Ok(ChunkAddress(arr)) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /crates/crates/primitives/src/utils.rs: -------------------------------------------------------------------------------- 1 | //! Utility functions for Vertex Swarm 2 | 3 | use crate::ChunkAddress; 4 | use sha3::{Digest, Keccak256}; 5 | 6 | #[cfg(not(feature = "std"))] 7 | use alloc::vec::Vec; 8 | 9 | /// Computes the Keccak256 hash of data 10 | pub fn keccak256(data: &[u8]) -> [u8; 32] { 11 | let mut hasher = Keccak256::new(); 12 | hasher.update(data); 13 | hasher.finalize().into() 14 | } 15 | 16 | /// Computes a chunk address from data using Keccak256 17 | pub fn chunk_address_from_data(data: &[u8]) -> ChunkAddress { 18 | ChunkAddress::new(keccak256(data)) 19 | } 20 | 21 | /// Compute proximity order between two byte arrays (0-256) 22 | pub fn proximity_order(a: &[u8], b: &[u8]) -> u8 { 23 | debug_assert_eq!(a.len(), b.len(), "Arrays must be of equal length"); 24 | 25 | let mut proximity = 0; 26 | for i in 0..a.len() { 27 | let xor = a[i] ^ b[i]; 28 | if xor == 0 { 29 | proximity += 8; 30 | continue; 31 | } 32 | // Count leading zeros in byte 33 | proximity += xor.leading_zeros() as u8; 34 | break; 35 | } 36 | proximity 37 | } 38 | 39 | /// Bytes to hex string 40 | pub fn bytes_to_hex(bytes: &[u8]) -> String { 41 | let mut s = String::with_capacity(bytes.len() * 2); 42 | for byte in bytes { 43 | s.push_str(&format!("{:02x}", byte)); 44 | } 45 | s 46 | } 47 | 48 | /// Hex string to bytes 49 | pub fn hex_to_bytes(hex: &str) -> Result, hex::FromHexError> { 50 | // Strip 0x prefix if present 51 | let hex = hex.strip_prefix("0x").unwrap_or(hex); 52 | hex::decode(hex) 53 | } 54 | 55 | /// Converts a slice of bytes to a fixed-size array 56 | pub fn to_array(slice: &[u8]) -> Option<[u8; N]> { 57 | if slice.len() != N { 58 | return None; 59 | } 60 | 61 | let mut arr = [0u8; N]; 62 | arr.copy_from_slice(slice); 63 | Some(arr) 64 | } 65 | -------------------------------------------------------------------------------- /crates/crates/storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vertex-storage" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "GPL-3.0-or-later" 6 | description = "Storage implementations for Vertex Swarm" 7 | 8 | [dependencies] 9 | # Internal crates 10 | vertex-primitives = { workspace = true } 11 | vertex-swarm-api = { workspace = true } 12 | 13 | # Async 14 | tokio = { version = "1.28", features = ["full"] } 15 | async-trait = "0.1" 16 | 17 | # Serialization 18 | serde = { version = "1.0", features = ["derive"] } 19 | serde_json = "1.0" 20 | bincode = "1.3" 21 | 22 | # Logging 23 | tracing = "0.1" 24 | 25 | # Database/Storage 26 | rocksdb = { version = "0.20", optional = true } 27 | sled = { version = "0.34", optional = true } 28 | 29 | # Misc 30 | auto_impl = "1.1" 31 | dashmap = "5.4" 32 | bytes = "1.4" 33 | thiserror = "1.0" 34 | 35 | [features] 36 | default = ["rocksdb"] 37 | rocksdb = ["dep:rocksdb"] 38 | sled = ["dep:sled"] 39 | -------------------------------------------------------------------------------- /crates/crates/swarm-api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vertex-swarm-api" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "GPL-3.0-or-later" 6 | description = "Core traits and interfaces for the Vertex Swarm node" 7 | 8 | [dependencies] 9 | # Internal crates 10 | vertex-primitives = { workspace = true } 11 | 12 | # Async 13 | futures = "0.3" 14 | async-trait = "0.1" 15 | 16 | # Trait implementation 17 | auto_impl = "1.1" 18 | 19 | # Error handling 20 | thiserror = "1.0" 21 | 22 | # Feature-gated dependencies 23 | serde = { version = "1.0", features = ["derive"], optional = true } 24 | 25 | [features] 26 | default = ["std", "serde"] 27 | std = ["vertex-primitives/std", "serde?/std"] 28 | serde = ["dep:serde", "vertex-primitives/serde"] 29 | -------------------------------------------------------------------------------- /crates/crates/swarm-api/src/access.rs: -------------------------------------------------------------------------------- 1 | //! Traits for access control (Authentication, Authorization, Accounting) 2 | 3 | use crate::{Chunk, Result}; 4 | use vertex_primitives::ChunkAddress; 5 | 6 | #[cfg(not(feature = "std"))] 7 | use alloc::boxed::Box; 8 | use core::fmt::Debug; 9 | 10 | /// Trait for access control credentials 11 | pub trait Credential: Clone + Debug + Send + Sync + 'static {} 12 | 13 | /// Authentication trait for verifying chunk credentials 14 | #[auto_impl::auto_impl(&, Arc)] 15 | pub trait Authenticator: Send + Sync + 'static { 16 | /// The credential type used by this authenticator 17 | type Credential: Credential; 18 | 19 | /// Authenticate a chunk with the given credential 20 | fn authenticate(&self, chunk: &dyn Chunk, credential: &Self::Credential) -> Result<()>; 21 | 22 | /// Authenticate a retrieval request with optional credential 23 | fn authenticate_retrieval( 24 | &self, 25 | address: &ChunkAddress, 26 | credential: Option<&Self::Credential>, 27 | ) -> Result<()>; 28 | } 29 | 30 | /// Authorization trait for determining if chunks can be stored or retrieved 31 | #[auto_impl::auto_impl(&, Arc)] 32 | pub trait Authorizer: Send + Sync + 'static { 33 | /// Authorize a chunk to be stored 34 | fn authorize_storage(&self, chunk: &dyn Chunk) -> Result<()>; 35 | 36 | /// Authorize a chunk to be retrieved 37 | fn authorize_retrieval(&self, address: &ChunkAddress) -> Result<()>; 38 | } 39 | 40 | /// Accounting trait for tracking resource usage 41 | #[auto_impl::auto_impl(&, Arc)] 42 | pub trait Accountant: Send + Sync + 'static { 43 | /// Record that a chunk is being stored 44 | fn record_storage(&self, chunk: &dyn Chunk) -> Result<()>; 45 | 46 | /// Record that a chunk is being retrieved 47 | fn record_retrieval(&self, address: &ChunkAddress) -> Result<()>; 48 | 49 | /// Get available storage capacity 50 | fn available_capacity(&self) -> usize; 51 | 52 | /// Check if storage capacity is available for a chunk of the given size 53 | fn has_capacity_for(&self, size: usize) -> bool; 54 | } 55 | 56 | /// Combined access control for chunks 57 | #[auto_impl::auto_impl(&, Arc)] 58 | pub trait AccessController: Send + Sync + 'static { 59 | /// The credential type used by this controller 60 | type Credential: Credential; 61 | 62 | /// Check if a chunk can be stored with the given credential 63 | fn check_storage_permission( 64 | &self, 65 | chunk: &dyn Chunk, 66 | credential: &Self::Credential, 67 | ) -> Result<()>; 68 | 69 | /// Check if a chunk can be retrieved 70 | fn check_retrieval_permission( 71 | &self, 72 | address: &ChunkAddress, 73 | credential: Option<&Self::Credential>, 74 | ) -> Result<()>; 75 | 76 | /// Record that a chunk has been stored 77 | fn record_storage(&self, chunk: &dyn Chunk) -> Result<()>; 78 | 79 | /// Record that a chunk has been retrieved 80 | fn record_retrieval(&self, address: &ChunkAddress) -> Result<()>; 81 | } 82 | -------------------------------------------------------------------------------- /crates/crates/swarm-api/src/chunk.rs: -------------------------------------------------------------------------------- 1 | //! Chunk-related traits and types 2 | 3 | use crate::Result; 4 | use core::fmt::Debug; 5 | use vertex_primitives::{ChunkAddress, ChunkType}; 6 | 7 | #[cfg(not(feature = "std"))] 8 | use alloc::{boxed::Box, vec::Vec}; 9 | 10 | /// Core chunk trait that all chunk implementations must satisfy 11 | #[auto_impl::auto_impl(&, Arc)] 12 | pub trait Chunk: Send + Sync + Debug + 'static { 13 | /// Returns the address of the chunk 14 | fn address(&self) -> ChunkAddress; 15 | 16 | /// Returns the data contained in the chunk 17 | fn data(&self) -> &[u8]; 18 | 19 | /// Returns the size of the chunk in bytes 20 | fn size(&self) -> usize { 21 | self.data().len() 22 | } 23 | 24 | /// Returns the chunk type 25 | fn chunk_type(&self) -> ChunkType; 26 | 27 | /// Verifies the integrity of the chunk 28 | fn verify_integrity(&self) -> bool; 29 | 30 | /// Clone this chunk as a boxed trait object 31 | fn clone_box(&self) -> Box; 32 | } 33 | 34 | /// Trait for creating new chunks 35 | #[auto_impl::auto_impl(&, Arc)] 36 | pub trait ChunkFactory: Send + Sync + 'static { 37 | /// Create a new chunk from data 38 | fn create(&self, data: Vec) -> Result>; 39 | 40 | /// Create a chunk with a known address (caution: may not verify integrity) 41 | fn create_with_address(&self, address: ChunkAddress, data: Vec) -> Result>; 42 | 43 | /// Returns the type of chunks this factory creates 44 | fn chunk_type(&self) -> ChunkType; 45 | } 46 | 47 | /// Trait for computing chunk addresses 48 | #[auto_impl::auto_impl(&, Arc)] 49 | pub trait AddressFunction: Send + Sync + 'static { 50 | /// Calculate the address for the given data 51 | fn address_of(&self, data: &[u8]) -> ChunkAddress; 52 | 53 | /// Get the chunk type this address function is for 54 | fn chunk_type(&self) -> ChunkType; 55 | } 56 | -------------------------------------------------------------------------------- /crates/crates/swarm-api/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Core traits and interfaces for the Vertex Swarm node 2 | //! 3 | //! This crate defines the fundamental traits that make up the Vertex Swarm API. 4 | //! It provides the contract for component interactions without specifying implementations. 5 | 6 | #![cfg_attr(not(feature = "std"), no_std)] 7 | #![warn(missing_docs)] 8 | 9 | #[cfg(not(feature = "std"))] 10 | extern crate alloc; 11 | 12 | #[cfg(not(feature = "std"))] 13 | use alloc::{boxed::Box, string::String, vec::Vec}; 14 | 15 | pub use async_trait::async_trait; 16 | pub use vertex_primitives::{self, ChunkAddress, Error, PeerId, Result}; 17 | 18 | /// Chunk-related traits and types 19 | pub mod chunk; 20 | pub use chunk::*; 21 | 22 | /// Access control traits (authentication, authorization, accounting) 23 | pub mod access; 24 | pub use access::*; 25 | 26 | /// Storage-related traits 27 | pub mod storage; 28 | pub use storage::*; 29 | 30 | /// Network-related traits 31 | pub mod network; 32 | pub use network::*; 33 | 34 | /// Node-related traits 35 | pub mod node; 36 | pub use node::*; 37 | 38 | /// Bandwidth management traits 39 | pub mod bandwidth; 40 | pub use bandwidth::*; 41 | 42 | /// Protocol-related traits 43 | pub mod protocol; 44 | pub use protocol::*; 45 | -------------------------------------------------------------------------------- /crates/crates/swarm-api/src/network.rs: -------------------------------------------------------------------------------- 1 | //! Network-related traits 2 | 3 | use crate::{Chunk, Credential, Result}; 4 | use vertex_primitives::{ChunkAddress, NetworkStatus, PeerId}; 5 | 6 | #[cfg(not(feature = "std"))] 7 | use alloc::{boxed::Box, string::String, vec::Vec}; 8 | use core::fmt::Debug; 9 | 10 | /// Network configuration 11 | #[derive(Debug, Clone)] 12 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 13 | pub struct NetworkConfig { 14 | /// Local listening address 15 | pub listen_addr: String, 16 | /// Bootnodes to connect to 17 | pub bootnodes: Vec, 18 | /// Network ID 19 | pub network_id: u64, 20 | /// Maximum number of peers 21 | pub max_peers: usize, 22 | /// Whether to enable peer discovery 23 | pub enable_discovery: bool, 24 | /// Custom node identity 25 | pub node_identity: Option, 26 | /// Bootstrap node mode (will not participate in storage) 27 | pub bootstrap_node: bool, 28 | /// Maximum number of connection attempts 29 | pub max_connection_attempts: usize, 30 | /// Connection timeout in seconds 31 | pub connection_timeout: u64, 32 | } 33 | 34 | impl Default for NetworkConfig { 35 | fn default() -> Self { 36 | Self { 37 | listen_addr: "/ip4/0.0.0.0/tcp/1634".into(), 38 | bootnodes: Vec::new(), 39 | network_id: 1, // Default to mainnet 40 | max_peers: 50, 41 | enable_discovery: true, 42 | node_identity: None, 43 | bootstrap_node: false, 44 | max_connection_attempts: 3, 45 | connection_timeout: 30, 46 | } 47 | } 48 | } 49 | 50 | /// Core trait for network operations 51 | #[async_trait] 52 | #[auto_impl::auto_impl(&, Arc)] 53 | pub trait NetworkClient: Send + Sync + 'static { 54 | /// The credential type used by this client 55 | type Credential: Credential; 56 | 57 | /// Retrieve a chunk from the network 58 | async fn retrieve( 59 | &self, 60 | address: &ChunkAddress, 61 | credential: Option<&Self::Credential>, 62 | ) -> Result>; 63 | 64 | /// Store a chunk in the network 65 | async fn store( 66 | &self, 67 | chunk: Box, 68 | credential: &Self::Credential, 69 | ) -> Result<()>; 70 | 71 | /// Connect to the Swarm network 72 | async fn connect(&self) -> Result<()>; 73 | 74 | /// Disconnect from the Swarm network 75 | async fn disconnect(&self) -> Result<()>; 76 | 77 | /// Get current network status 78 | fn status(&self) -> NetworkStatus; 79 | 80 | /// List connected peers 81 | fn connected_peers(&self) -> Vec; 82 | 83 | /// Find closest peers to an address 84 | async fn find_closest( 85 | &self, 86 | address: &ChunkAddress, 87 | limit: usize, 88 | ) -> Result>; 89 | } 90 | 91 | /// Factory for creating network client implementations 92 | #[auto_impl::auto_impl(&, Arc)] 93 | pub trait NetworkClientFactory: Send + Sync + 'static { 94 | /// Create a new network client with the given configuration 95 | fn create_client(&self, config: &NetworkConfig) -> Result>>; 96 | 97 | /// The credential type used by created clients 98 | type Credential: Credential; 99 | } 100 | -------------------------------------------------------------------------------- /crates/crates/swarm-api/src/protocol.rs: -------------------------------------------------------------------------------- 1 | //! Protocol-related traits 2 | 3 | use crate::{Chunk, Result}; 4 | use vertex_primitives::{ChunkAddress, PeerId, ProtocolId}; 5 | 6 | #[cfg(not(feature = "std"))] 7 | use alloc::{boxed::Box, string::String, vec::Vec}; 8 | 9 | /// Core trait for protocol handlers 10 | #[auto_impl::auto_impl(&, Arc)] 11 | pub trait ProtocolHandler: Send + Sync + 'static { 12 | /// The protocol ID 13 | fn protocol_id(&self) -> ProtocolId; 14 | 15 | /// Get the protocol name 16 | fn protocol_name(&self) -> &str; 17 | 18 | /// Get the protocol version 19 | fn protocol_version(&self) -> &str; 20 | } 21 | 22 | /// Handler for retrieval protocol 23 | #[async_trait] 24 | #[auto_impl::auto_impl(&, Arc)] 25 | pub trait RetrievalProtocol: ProtocolHandler { 26 | /// Retrieve a chunk from a peer 27 | async fn retrieve_from( 28 | &self, 29 | peer: &PeerId, 30 | address: &ChunkAddress, 31 | ) -> Result>; 32 | 33 | /// Handle a retrieval request from a peer 34 | async fn handle_retrieval_request( 35 | &self, 36 | peer: &PeerId, 37 | address: &ChunkAddress, 38 | ) -> Result>; 39 | } 40 | 41 | /// Handler for push sync protocol 42 | #[async_trait] 43 | #[auto_impl::auto_impl(&, Arc)] 44 | pub trait PushSyncProtocol: ProtocolHandler { 45 | /// Push a chunk to a peer 46 | async fn push_to( 47 | &self, 48 | peer: &PeerId, 49 | chunk: Box, 50 | ) -> Result<()>; 51 | 52 | /// Handle a push request from a peer 53 | async fn handle_push_request( 54 | &self, 55 | peer: &PeerId, 56 | chunk: Box, 57 | ) -> Result<()>; 58 | } 59 | 60 | /// Handler for pull sync protocol 61 | #[async_trait] 62 | #[auto_impl::auto_impl(&, Arc)] 63 | pub trait PullSyncProtocol: ProtocolHandler { 64 | /// Sync a batch of chunks from a peer 65 | async fn pull_from( 66 | &self, 67 | peer: &PeerId, 68 | start: &ChunkAddress, 69 | limit: usize, 70 | ) -> Result>>; 71 | 72 | /// Handle a pull request from a peer 73 | async fn handle_pull_request( 74 | &self, 75 | peer: &PeerId, 76 | start: &ChunkAddress, 77 | limit: usize, 78 | ) -> Result>>; 79 | } 80 | 81 | /// Factory for creating protocol handlers 82 | #[auto_impl::auto_impl(&, Arc)] 83 | pub trait ProtocolFactory: Send + Sync + 'static { 84 | /// Create a retrieval protocol handler 85 | fn create_retrieval_protocol(&self) -> Box; 86 | 87 | /// Create a push sync protocol handler 88 | fn create_push_protocol(&self) -> Box; 89 | 90 | /// Create a pull sync protocol handler 91 | fn create_pull_protocol(&self) -> Box; 92 | } 93 | -------------------------------------------------------------------------------- /crates/crates/swarm-api/src/storage.rs: -------------------------------------------------------------------------------- 1 | //! Storage-related traits 2 | 3 | use crate::{Chunk, Credential, Result}; 4 | use vertex_primitives::ChunkAddress; 5 | 6 | #[cfg(not(feature = "std"))] 7 | use alloc::{boxed::Box, string::String, vec::Vec}; 8 | use core::fmt::Debug; 9 | 10 | /// Storage statistics 11 | #[derive(Debug, Clone, Default)] 12 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 13 | pub struct StorageStats { 14 | /// Total number of chunks stored 15 | pub total_chunks: usize, 16 | /// Total storage space used in bytes 17 | pub used_space: u64, 18 | /// Total storage space available in bytes 19 | pub available_space: u64, 20 | /// Storage utilization percentage (0-100) 21 | pub utilization_percent: f32, 22 | } 23 | 24 | /// Core storage trait for chunk persistence 25 | #[auto_impl::auto_impl(&, Arc)] 26 | pub trait ChunkStore: Send + Sync + 'static { 27 | /// Store a chunk with its associated credential 28 | fn put(&self, chunk: Box, credential: &dyn Credential) -> Result<()>; 29 | 30 | /// Retrieve a chunk by its address 31 | fn get(&self, address: &ChunkAddress) -> Result>>; 32 | 33 | /// Check if a chunk exists in the store 34 | fn contains(&self, address: &ChunkAddress) -> Result; 35 | 36 | /// Delete a chunk from the store 37 | fn delete(&self, address: &ChunkAddress) -> Result<()>; 38 | 39 | /// Return the number of chunks in the store 40 | fn len(&self) -> Result; 41 | 42 | /// Check if the store is empty 43 | fn is_empty(&self) -> Result { 44 | Ok(self.len()? == 0) 45 | } 46 | 47 | /// Get statistics about the store 48 | fn stats(&self) -> Result; 49 | 50 | /// Iterate over all chunks in the store 51 | fn iter(&self) -> Box>> + '_>; 52 | } 53 | 54 | /// Storage configuration 55 | #[derive(Debug, Clone)] 56 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 57 | pub struct StorageConfig { 58 | /// Root directory for storage 59 | pub root_dir: String, 60 | /// Maximum storage space in bytes 61 | pub max_space: u64, 62 | /// Target storage space in bytes 63 | pub target_space: u64, 64 | /// Minimum chunk size in bytes 65 | pub min_chunk_size: usize, 66 | /// Maximum chunk size in bytes 67 | pub max_chunk_size: usize, 68 | /// Whether to validate chunks on read 69 | pub validate_on_read: bool, 70 | /// Cache capacity in number of chunks 71 | pub cache_capacity: usize, 72 | /// Whether to persist storage metadata 73 | pub persist_metadata: bool, 74 | } 75 | 76 | impl Default for StorageConfig { 77 | fn default() -> Self { 78 | Self { 79 | root_dir: "./storage".into(), 80 | max_space: 1024 * 1024 * 1024, // 1GB 81 | target_space: 512 * 1024 * 1024, // 512MB 82 | min_chunk_size: 1, 83 | max_chunk_size: 4 * 1024 * 1024, // 4MB 84 | validate_on_read: true, 85 | cache_capacity: 1000, 86 | persist_metadata: true, 87 | } 88 | } 89 | } 90 | 91 | /// Factory for creating storage implementations 92 | #[auto_impl::auto_impl(&, Arc)] 93 | pub trait ChunkStoreFactory: Send + Sync + 'static { 94 | /// Create a new chunk store with the given configuration 95 | fn create_store(&self, config: &StorageConfig) -> Result>; 96 | } 97 | -------------------------------------------------------------------------------- /crates/crates/swarm-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vertex-swarm-core" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "GPL-3.0-or-later" 6 | description = "Core implementation of Swarm node functionality" 7 | 8 | [dependencies] 9 | # Internal crates 10 | vertex-primitives = { workspace = true } 11 | vertex-swarm-api = { workspace = true } 12 | vertex-swarmspec = { workspace = true } 13 | vertex-storage = { workspace = true, optional = true } 14 | vertex-network = { workspace = true, optional = true } 15 | vertex-access = { workspace = true, optional = true } 16 | vertex-protocol = { workspace = true, optional = true } 17 | 18 | # Async 19 | tokio = { version = "1.28", features = ["full"] } 20 | futures = "0.3" 21 | 22 | # Logging and metrics 23 | tracing = "0.1" 24 | 25 | # Error handling 26 | thiserror = "1.0" 27 | 28 | # Misc 29 | async-trait = "0.1" 30 | auto_impl = "1.1" 31 | bytes = "1.4" 32 | dashmap = "5.4" 33 | once_cell = "1.17" 34 | 35 | [features] 36 | default = ["full"] 37 | full = ["storage", "network", "access", "protocol"] 38 | storage = ["dep:vertex-storage"] 39 | network = ["dep:vertex-network"] 40 | access = ["dep:vertex-access"] 41 | protocol = ["dep:vertex-protocol"] 42 | -------------------------------------------------------------------------------- /crates/crates/swarm-core/src/factory.rs: -------------------------------------------------------------------------------- 1 | //! Factory for creating Swarm node instances 2 | 3 | use std::sync::Arc; 4 | 5 | use tracing::info; 6 | use vertex_primitives::Result; 7 | use vertex_swarm_api::{ 8 | node::{NodeConfig, NodeMode, SwarmBaseNode, SwarmFullNode, SwarmIncentivizedNode}, 9 | }; 10 | use vertex_swarmspec::SwarmSpec; 11 | 12 | #[cfg(feature = "access")] 13 | use vertex_access::PostageStampCredential; 14 | 15 | use crate::{ 16 | SwarmNode, 17 | LightNode, 18 | FullNode, 19 | IncentivizedNode, 20 | }; 21 | 22 | /// Factory for creating Swarm node instances 23 | #[derive(Clone)] 24 | pub struct SwarmNodeFactory { 25 | /// Default network specification to use 26 | default_spec: Arc, 27 | } 28 | 29 | impl SwarmNodeFactory { 30 | /// Create a new node factory with default network specification 31 | pub fn new(default_spec: Arc) -> Self { 32 | Self { default_spec } 33 | } 34 | 35 | /// Create a node with the given configuration 36 | pub async fn create_node(&self, config: NodeConfig) -> Result>> { 37 | match config.mode { 38 | NodeMode::Light => self.create_light_node(config).await, 39 | NodeMode::Full => self.create_full_node(config).await, 40 | NodeMode::Incentivized => self.create_incentivized_node(config).await, 41 | } 42 | } 43 | 44 | /// Create a light node 45 | pub async fn create_light_node(&self, config: NodeConfig) -> Result>> { 46 | info!("Creating light node"); 47 | 48 | #[cfg(all(feature = "access", feature = "network"))] 49 | { 50 | let node = SwarmNode::::new(config, self.default_spec.clone()).await?; 51 | let light_node = LightNode { 52 | inner: Arc::new(node), 53 | }; 54 | Ok(Box::new(light_node)) 55 | } 56 | 57 | #[cfg(not(all(feature = "access", feature = "network")))] 58 | { 59 | Err(vertex_primitives::Error::other( 60 | "Cannot create light node: required features 'access' and 'network' are not enabled" 61 | )) 62 | } 63 | } 64 | 65 | /// Create a full node 66 | pub async fn create_full_node(&self, config: NodeConfig) -> Result>> { 67 | info!("Creating full node"); 68 | 69 | #[cfg(all(feature = "access", feature = "network", feature = "storage"))] 70 | { 71 | let node = SwarmNode::::new(config, self.default_spec.clone()).await?; 72 | let full_node = FullNode { 73 | inner: Arc::new(node), 74 | }; 75 | Ok(Box::new(full_node)) 76 | } 77 | 78 | #[cfg(not(all(feature = "access", feature = "network", feature = "storage")))] 79 | { 80 | Err(vertex_primitives::Error::other( 81 | "Cannot create full node: required features 'access', 'network', and 'storage' are not enabled" 82 | )) 83 | } 84 | } 85 | 86 | /// Create an incentivized node 87 | pub async fn create_incentivized_node(&self, config: NodeConfig) -> Result>> { 88 | info!("Creating incentivized node"); 89 | 90 | #[cfg(all(feature = "access", feature = "network", feature = "storage"))] 91 | { 92 | let node = SwarmNode::::new(config, self.default_spec.clone()).await?; 93 | let incentivized_node = IncentivizedNode { 94 | inner: Arc::new(node), 95 | }; 96 | Ok(Box::new(incentivized_node)) 97 | } 98 | 99 | #[cfg(not(all(feature = "access", feature = "network", feature = "storage")))] 100 | { 101 | Err(vertex_primitives::Error::other( 102 | "Cannot create incentivized node: required features 'access', 'network', and 'storage' are not enabled" 103 | )) 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /crates/crates/swarmspec/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vertex-swarmspec" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "GPL-3.0-or-later" 6 | description = "Swarm network specification for Vertex" 7 | 8 | [dependencies] 9 | # Internal crates 10 | vertex-primitives = { workspace = true } 11 | 12 | # Network 13 | libp2p = { version = "0.52", features = ["identify", "kad", "ping", "noise"] } 14 | 15 | # Serialization 16 | serde = { version = "1.0", features = ["derive"], optional = true } 17 | 18 | # Error handling 19 | thiserror = "1.0" 20 | 21 | # Misc 22 | once_cell = "1.17" 23 | tracing = "0.1" 24 | auto_impl = "1.1" 25 | 26 | [features] 27 | default = ["std", "serde"] 28 | std = ["vertex-primitives/std", "serde?/std"] 29 | serde = ["dep:serde", "vertex-primitives/serde"] 30 | -------------------------------------------------------------------------------- /crates/crates/swarmspec/src/constants.rs: -------------------------------------------------------------------------------- 1 | //! Constants used throughout the Swarm network specification 2 | 3 | use crate::types::{StorageContracts, Token}; 4 | use vertex_primitives::Address; 5 | 6 | /// Mainnet constants 7 | pub mod mainnet { 8 | use super::*; 9 | 10 | /// Swarm mainnet network ID 11 | pub const NETWORK_ID: u64 = 1; 12 | 13 | /// Swarm mainnet network name 14 | pub const NETWORK_NAME: &str = "mainnet"; 15 | 16 | /// Swarm token on mainnet (BZZ) 17 | pub const TOKEN: Token = Token { 18 | address: Address::new([ 19 | 0x2a, 0xc3, 0xc1, 0xd3, 0xe2, 0x4b, 0x45, 0xc6, 0xc3, 0x10, 0x53, 0x4b, 0xc2, 0xdd, 20 | 0x84, 0xb5, 0xed, 0x57, 0x63, 0x35, 21 | ]), 22 | name: "Swarm", 23 | symbol: "BZZ", 24 | decimals: 16, 25 | }; 26 | 27 | /// Storage contracts for mainnet 28 | pub const STORAGE_CONTRACTS: StorageContracts = StorageContracts { 29 | postage: Address::new([ 30 | 0x5b, 0x53, 0xf7, 0xa1, 0x97, 0x5e, 0xb2, 0x12, 0xd4, 0xb2, 0x0b, 0x7c, 0xdd, 0x44, 31 | 0x3b, 0xaa, 0x18, 0x9a, 0xf7, 0xc9, 32 | ]), 33 | redistribution: Address::new([ 34 | 0xeb, 0x21, 0x0c, 0x2e, 0x16, 0x6f, 0x61, 0xb3, 0xfd, 0x32, 0x24, 0x6d, 0x53, 0x89, 35 | 0x3f, 0x8b, 0x9d, 0x2a, 0x62, 0x4c, 36 | ]), 37 | staking: Some(Address::new([ 38 | 0x0c, 0x6a, 0xa1, 0x97, 0x27, 0x14, 0x66, 0xf0, 0xaf, 0xe3, 0x81, 0x8c, 0xa0, 0x3a, 39 | 0xc4, 0x7d, 0x8f, 0x5c, 0x2f, 0x8a, 40 | ])), 41 | }; 42 | } 43 | 44 | /// Testnet (Sepolia) constants 45 | pub mod testnet { 46 | use super::*; 47 | 48 | /// Swarm testnet network ID 49 | pub const NETWORK_ID: u64 = 10; 50 | 51 | /// Swarm testnet network name 52 | pub const NETWORK_NAME: &str = "testnet"; 53 | 54 | /// Swarm token on testnet (tBZZ) 55 | pub const TOKEN: Token = Token { 56 | address: Address::new([ 57 | 0x6e, 0x01, 0xee, 0x61, 0x83, 0x72, 0x1a, 0xe9, 0xa0, 0x06, 0xfd, 0x49, 0x06, 0x97, 58 | 0x0c, 0x15, 0x83, 0x86, 0x37, 0x65, 59 | ]), 60 | name: "Test Swarm", 61 | symbol: "tBZZ", 62 | decimals: 16, 63 | }; 64 | 65 | /// Storage contracts for testnet 66 | pub const STORAGE_CONTRACTS: StorageContracts = StorageContracts { 67 | postage: Address::new([ 68 | 0x62, 0x1c, 0x2e, 0x0f, 0xa5, 0xed, 0x48, 0x8c, 0x71, 0x24, 0xeb, 0x55, 0xcc, 0x7e, 69 | 0xb3, 0xaf, 0x75, 0xd0, 0xd9, 0xe8, 70 | ]), 71 | redistribution: Address::new([ 72 | 0xfb, 0x6c, 0x7d, 0x33, 0xbe, 0x1f, 0xb1, 0x2f, 0x4c, 0x5d, 0xa7, 0x1d, 0xf7, 0xc9, 73 | 0xd5, 0xc2, 0x29, 0x70, 0xba, 0x7a, 74 | ]), 75 | staking: Some(Address::new([ 76 | 0x6f, 0x25, 0x2d, 0xd6, 0xf3, 0x40, 0xf6, 0xc6, 0xd2, 0xf6, 0xee, 0x89, 0x54, 0xb0, 77 | 0x11, 0xdd, 0x5a, 0xba, 0x43, 0x50, 78 | ])), 79 | }; 80 | } 81 | 82 | /// Default values for development networks 83 | pub mod dev { 84 | use super::*; 85 | 86 | /// Default network name for development 87 | pub const NETWORK_NAME: &str = "dev"; 88 | 89 | /// Default Swarm token for development 90 | pub const TOKEN: Token = Token { 91 | address: Address::ZERO, 92 | name: "Dev Swarm", 93 | symbol: "dBZZ", 94 | decimals: 16, 95 | }; 96 | 97 | /// Default storage contracts for development 98 | pub const STORAGE_CONTRACTS: StorageContracts = StorageContracts { 99 | postage: Address::ZERO, 100 | redistribution: Address::ZERO, 101 | staking: Some(Address::ZERO), 102 | }; 103 | } 104 | -------------------------------------------------------------------------------- /crates/crates/swarmspec/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Swarm network specification for the Vertex node 2 | //! 3 | //! This crate defines the specifications for a Swarm network, 4 | //! including chain identifiers, protocol parameters, and fork schedules. 5 | 6 | #![cfg_attr(not(feature = "std"), no_std)] 7 | 8 | extern crate alloc; 9 | 10 | mod constants; 11 | mod forks; 12 | mod network; 13 | mod types; 14 | 15 | pub use constants::*; 16 | pub use forks::*; 17 | pub use network::*; 18 | pub use types::*; 19 | 20 | use alloc::sync::Arc; 21 | use core::sync::atomic::{AtomicU64, Ordering}; 22 | use vertex_primitives::network::Swarm; 23 | 24 | /// A counter for generating unique network IDs for development/testing 25 | static DEV_NETWORK_ID_COUNTER: AtomicU64 = AtomicU64::new(1337); 26 | 27 | /// Generate a unique network ID for development purposes 28 | /// 29 | /// This ensures that development/test networks don't clash with each other 30 | pub fn generate_dev_network_id() -> u64 { 31 | DEV_NETWORK_ID_COUNTER.fetch_add(1, Ordering::SeqCst) 32 | } 33 | 34 | /// The network specification trait that all Swarm network implementations 35 | /// must satisfy. 36 | #[auto_impl::auto_impl(&, Arc)] 37 | pub trait SwarmSpec: Send + Sync + 'static { 38 | /// Returns the corresponding Swarm network identifier 39 | fn swarm(&self) -> Swarm; 40 | 41 | /// Returns the network ID for the Swarm network 42 | fn network_id(&self) -> u64; 43 | 44 | /// Returns the Swarm network name (like "mainnet", "testnet", etc.) 45 | fn network_name(&self) -> &str; 46 | 47 | /// Returns the bootnodes for the network 48 | fn bootnodes(&self) -> &[Multiaddr]; 49 | 50 | /// Returns the fork activation status for a given Swarm hardfork at a timestamp 51 | fn is_fork_active_at_timestamp(&self, fork: SwarmHardfork, timestamp: u64) -> bool; 52 | 53 | /// Returns whether this is the mainnet Swarm 54 | fn is_mainnet(&self) -> bool { 55 | self.network_id() == 1 56 | } 57 | 58 | /// Returns whether this is a testnet Swarm 59 | fn is_testnet(&self) -> bool { 60 | self.network_id() == 10 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /crates/crates/swarmspec/src/types.rs: -------------------------------------------------------------------------------- 1 | //! Common types used in Swarm network specification 2 | 3 | use core::fmt::Debug; 4 | use libp2p::Multiaddr; 5 | use vertex_primitives::Address; 6 | 7 | #[cfg(feature = "serde")] 8 | use serde::{Deserialize, Serialize}; 9 | 10 | /// Swarm token (BZZ) contract details 11 | #[derive(Debug, Clone, PartialEq, Eq)] 12 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 13 | pub struct Token { 14 | /// Contract address 15 | pub address: Address, 16 | /// Token name 17 | pub name: &'static str, 18 | /// Token symbol 19 | pub symbol: &'static str, 20 | /// Decimal places 21 | pub decimals: u8, 22 | } 23 | 24 | /// Storage contract addresses 25 | #[derive(Debug, Clone, PartialEq, Eq)] 26 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 27 | pub struct StorageContracts { 28 | /// Postage stamp contract address 29 | pub postage: Address, 30 | /// Redistribution contract address 31 | pub redistribution: Address, 32 | /// Optional staking contract address 33 | pub staking: Option
, 34 | } 35 | 36 | /// Configuration for pseudosettle (free bandwidth allocation) 37 | #[derive(Debug, Clone, PartialEq, Eq)] 38 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 39 | pub struct PseudosettleConfig { 40 | /// Daily free bandwidth allowance in bytes 41 | pub daily_allowance_bytes: u64, 42 | /// Threshold after which payment is required (bytes) 43 | pub payment_threshold: u64, 44 | /// Payment tolerance before disconnection (bytes) 45 | pub payment_tolerance: u64, 46 | /// Disconnect threshold (bytes) 47 | pub disconnect_threshold: u64, 48 | } 49 | 50 | impl Default for PseudosettleConfig { 51 | fn default() -> Self { 52 | Self { 53 | daily_allowance_bytes: 1_000_000, // 1MB per day free 54 | payment_threshold: 10_000_000, // 10MB 55 | payment_tolerance: 5_000_000, // 5MB 56 | disconnect_threshold: 50_000_000, // 50MB 57 | } 58 | } 59 | } 60 | 61 | /// Configuration for node storage parameters 62 | #[derive(Debug, Clone, PartialEq, Eq)] 63 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 64 | pub struct Storage { 65 | /// Smart contract addresses for storage incentives 66 | pub contracts: StorageContracts, 67 | /// Maximum number of chunks the node will store 68 | pub max_chunks: u64, 69 | /// Target number of chunks for optimal operation 70 | pub target_chunks: u64, 71 | /// Minimum number of chunks before node considers scaling down 72 | pub min_chunks: u64, 73 | /// Reserve storage percentage (0-100) before starting to evict content 74 | pub reserve_percentage: u8, 75 | /// Chunk size in bytes 76 | pub chunk_size: u64, 77 | /// Time (in seconds) to wait before a node tries to scale up the neighborhood 78 | pub scale_up_interval: u64, 79 | /// Time (in seconds) to wait before a node tries to scale down the neighborhood 80 | pub scale_down_interval: u64, 81 | } 82 | 83 | impl Default for Storage { 84 | fn default() -> Self { 85 | Self { 86 | contracts: StorageContracts { 87 | postage: Address::ZERO, 88 | redistribution: Address::ZERO, 89 | staking: Some(Address::ZERO), 90 | }, 91 | max_chunks: 1_000_000, // 1 million chunks 92 | target_chunks: 500_000, // 500k chunks 93 | min_chunks: 100_000, // 100k chunks 94 | reserve_percentage: 10, // 10% reserve 95 | chunk_size: 4096, // 4KB chunks 96 | scale_up_interval: 86400, // 24 hours 97 | scale_down_interval: 86400, // 24 hours 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /crates/file/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "file" 3 | version.workspace = true 4 | edition.workspace = true 5 | rust-version.workspace = true 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | 10 | [dependencies] 11 | 12 | [lints] 13 | workspace = true 14 | -------------------------------------------------------------------------------- /crates/file/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use super::*; 4 | } 5 | -------------------------------------------------------------------------------- /crates/file/src/span.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone)] 2 | pub struct Span { 3 | value: u64, 4 | } 5 | 6 | impl Span { 7 | pub fn new(value: u64) -> Span { 8 | match value { 9 | 0 => { 10 | panic!("invalid length for span: {}", value); 11 | } 12 | _ => Span { value }, 13 | } 14 | } 15 | 16 | pub fn value(&self) -> u64 { 17 | self.value 18 | } 19 | 20 | pub fn to_bytes(&self) -> [u8; 8] { 21 | self.value.to_le_bytes() 22 | } 23 | } 24 | 25 | #[cfg(test)] 26 | mod tests { 27 | use super::*; 28 | 29 | #[test] 30 | fn exact_span_size() { 31 | let span = Span::new(4096); 32 | assert_eq!(u64::from_le_bytes(span.to_bytes()), 4096); 33 | } 34 | 35 | #[test] 36 | fn max_span_size() { 37 | const MAX_SPAN_SIZE: u64 = (2 ^ 32) - 1; 38 | let span = Span::new(MAX_SPAN_SIZE); 39 | assert_eq!(u64::from_le_bytes(span.to_bytes()), MAX_SPAN_SIZE); 40 | } 41 | 42 | #[test] 43 | fn one_span_size() { 44 | let span = Span::new(1); 45 | assert_eq!(u64::from_le_bytes(span.to_bytes()), 1); 46 | } 47 | 48 | #[test] 49 | #[should_panic(expected = "invalid length for span")] 50 | fn invalid_span() { 51 | let _span = Span::new(0); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /crates/logging/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "logging" 3 | version.workspace = true 4 | edition.workspace = true 5 | homepage.workspace = true 6 | repository.workspace = true 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | atty = "0.2.14" 12 | clap = { workspace = true } 13 | time = { version = "0.3.21", features = ["macros"] } 14 | tracing = { workspace = true } 15 | tracing-subscriber = { version = "0.3.17", features = ["env-filter", "fmt", "time"] } 16 | -------------------------------------------------------------------------------- /crates/logging/src/lib.rs: -------------------------------------------------------------------------------- 1 | use { 2 | std::{panic::PanicInfo, sync::Once}, 3 | time::macros::format_description, 4 | tracing::level_filters::LevelFilter, 5 | tracing_subscriber::fmt::{time::UtcTime, writer::MakeWriterExt as _}, 6 | }; 7 | 8 | /// Initializes tracing setup that is shared between the binaries. 9 | /// `env_filter` has similar syntax to env_logger. It is documented at 10 | /// https://docs.rs/tracing-subscriber/0.2.15/tracing_subscriber/filter/struct.EnvFilter.html 11 | pub fn initialize(env_filter: &str, stderr_threshold: LevelFilter) { 12 | set_tracing_subscriber(env_filter, stderr_threshold); 13 | std::panic::set_hook(Box::new(tracing_panic_hook)); 14 | } 15 | 16 | /// Like [`initialize`], but can be called multiple times in a row. Later calls 17 | /// are ignored. 18 | /// 19 | /// Useful for tests. 20 | pub fn initialize_reentrant(env_filter: &str) { 21 | // The tracing subscriber below is global object so initializing it again in the 22 | // same process by a different thread would fail. 23 | static ONCE: Once = Once::new(); 24 | ONCE.call_once(|| set_tracing_subscriber(env_filter, LevelFilter::ERROR)); 25 | } 26 | 27 | fn set_tracing_subscriber(env_filter: &str, stderr_threshold: LevelFilter) { 28 | // This is what kibana uses to separate multi line log messages. 29 | let subscriber_builder = tracing_subscriber::fmt::fmt() 30 | .with_timer(UtcTime::new(format_description!( 31 | "[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3]Z" 32 | ))) 33 | .with_env_filter(env_filter) 34 | .with_ansi(atty::is(atty::Stream::Stdout)); 35 | match stderr_threshold.into_level() { 36 | Some(threshold) => subscriber_builder 37 | .with_writer( 38 | std::io::stderr 39 | .with_max_level(threshold) 40 | .or_else(std::io::stdout), 41 | ) 42 | .init(), 43 | None => subscriber_builder.init(), 44 | } 45 | } 46 | 47 | /// Panic hook that prints roughly the same message as the default panic hook 48 | /// but uses tracing:error instead of stderr. 49 | /// 50 | /// Useful when we want panic messages to have the proper log format for Kibana. 51 | fn tracing_panic_hook(panic: &PanicInfo) { 52 | let thread = std::thread::current(); 53 | let name = thread.name().unwrap_or(""); 54 | let backtrace = std::backtrace::Backtrace::force_capture(); 55 | tracing::error!("thread '{name}' {panic}\nstack backtrace:\n{backtrace}"); 56 | } 57 | 58 | #[macro_export] 59 | macro_rules! logging_args_with_default_filter { 60 | ($struct_name:ident ,$default_filter:literal) => { 61 | #[derive(clap::Parser)] 62 | pub struct $struct_name { 63 | #[clap(long, env, default_value = $default_filter)] 64 | pub log_filter: String, 65 | 66 | #[clap(long, env, default_value = "error")] 67 | pub log_stderr_threshold: LevelFilter, 68 | } 69 | }; 70 | } 71 | 72 | logging_args_with_default_filter!(LoggingArguments, "warn,waku_bridge=trace"); 73 | 74 | #[derive(clap::Parser)] 75 | #[group(skip)] 76 | pub struct Arguments { 77 | #[clap(flatten)] 78 | pub logging: LoggingArguments, 79 | } 80 | -------------------------------------------------------------------------------- /crates/manifest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "manifest" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | serde = { workspace = true } 10 | serde_json = { workspace = true } 11 | serde_with = { workspace = true } 12 | hex = { workspace = true } 13 | const_format = "0.2.26" 14 | rand = { workspace = true } 15 | tokio = { workspace = true } 16 | reqwest = { workspace = true } 17 | futures = { workspace = true } 18 | async-recursion = { workspace = true } 19 | tiny-keccak = { workspace = true } 20 | async-trait = { workspace = true } 21 | # bee_api = { path = "../bee_api" } 22 | bee_api = { git = "https://github.com/rndlabs/bee-api-rs.git" } 23 | thiserror = { workspace = true } 24 | lru = "0.7.8" 25 | 26 | [dev-dependencies] 27 | test-case = "2.2.1" -------------------------------------------------------------------------------- /crates/manifest/README.md: -------------------------------------------------------------------------------- 1 | # Mantaray Manifests 2 | 3 | This library is a `rust` port of the mantaray manifest implementation from [Bee](https://github.com/ethersphere/bee). 4 | 5 | # Todo 6 | 7 | - [ ] Walker function (only currently does a simple walk to retrieve nodes, no parsing). 8 | 9 | # Contributions 10 | 11 | The algorithms used in this implementation are from The Swarm Authors. Advice on manifest generation was provided by @ldeffenb. -------------------------------------------------------------------------------- /crates/manifest/src/stringer.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use crate::node::Node; 4 | 5 | // const TOP: &str = "─"; 6 | const TOP_MID: &str = "┬"; 7 | // const TOP_LEFT: &str = "┌"; 8 | const TOP_RIGHT: &str = "┐"; 9 | const BOTTOM: &str = "─"; 10 | // const BOTTOM_MID: &str = "┴"; 11 | const BOTTOM_LEFT: &str = "└"; 12 | // const BOTTOM_RIGHT: &str = "┘"; 13 | // const LEFT: &str = "│"; 14 | const LEFT_MID: &str = "├"; 15 | const MID: &str = "─"; 16 | // const MID_MID: &str = "┼"; 17 | // const RIGHT: &str = "│"; 18 | // const RIGHT_MID: &str = "┤"; 19 | const MIDDLE: &str = "│"; 20 | 21 | impl Display for Node { 22 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 23 | let mut buf = String::new(); 24 | 25 | buf.push_str(BOTTOM_LEFT); 26 | buf.push_str(BOTTOM); 27 | buf.push_str(TOP_RIGHT); 28 | buf.push('\n'); 29 | 30 | to_string_with_prefix(self, " ", &mut buf); 31 | 32 | write!(f, "{}", buf) 33 | } 34 | } 35 | 36 | fn to_string_with_prefix(n: &Node, prefix: &str, buf: &mut String) { 37 | buf.push_str(prefix); 38 | buf.push_str(LEFT_MID); 39 | buf.push_str(format!("r: '{}'\n", hex::encode(n.ref_.clone())).as_str()); 40 | buf.push_str(prefix); 41 | buf.push_str(LEFT_MID); 42 | buf.push_str(format!("t: '{}'", n.node_type).as_str()); 43 | buf.push_str(" ["); 44 | if n.is_value_type() { 45 | buf.push_str(" Value"); 46 | } 47 | if n.is_edge_type() { 48 | buf.push_str(" Edge"); 49 | } 50 | if n.is_with_path_separator_type() { 51 | buf.push_str(" PathSeparator"); 52 | } 53 | buf.push_str(" ]"); 54 | buf.push('\n'); 55 | buf.push_str(prefix); 56 | if !n.forks.is_empty() || !n.metadata.is_empty() { 57 | buf.push_str(LEFT_MID); 58 | } else { 59 | buf.push_str(BOTTOM_LEFT); 60 | } 61 | buf.push_str(format!("e: '{}'\n", hex::encode(n.entry.clone())).as_str()); 62 | if !n.metadata.is_empty() { 63 | buf.push_str(prefix); 64 | if !n.forks.is_empty() { 65 | buf.push_str(LEFT_MID); 66 | } else { 67 | buf.push_str(BOTTOM_LEFT); 68 | } 69 | buf.push_str(format!("m: '{}'\n", serde_json::to_string(&n.metadata).unwrap()).as_str()); 70 | } 71 | 72 | // get the keys of the forks and sort them. 73 | let mut keys: Vec<&u8> = n.forks.keys().collect(); 74 | keys.sort(); 75 | 76 | for (i, key) in keys.iter().enumerate() { 77 | let f = n.forks.get(key).unwrap(); 78 | let is_last = i == keys.len() - 1; 79 | 80 | buf.push_str(prefix); 81 | if is_last { 82 | buf.push_str(LEFT_MID); 83 | } else { 84 | buf.push_str(BOTTOM_LEFT); 85 | } 86 | buf.push_str(MID); 87 | buf.push_str(format!("[{}]", key).as_str()); 88 | buf.push_str(MID); 89 | buf.push_str(TOP_MID); 90 | buf.push_str(MID); 91 | buf.push_str(format!("`{}`\n", String::from_utf8(f.prefix.clone()).unwrap()).as_str()); 92 | let mut new_prefix = String::from(prefix); 93 | if is_last { 94 | // add MIDDLE to the new prefix 95 | new_prefix.push_str(MIDDLE); 96 | } else { 97 | new_prefix.push(' '); 98 | } 99 | new_prefix.push_str(" "); 100 | to_string_with_prefix(&f.node, &new_prefix, buf); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /crates/manifest/src/walker.rs: -------------------------------------------------------------------------------- 1 | use async_recursion::async_recursion; 2 | 3 | use crate::{persist::DynLoaderSaver, Node, Result}; 4 | 5 | #[async_recursion] 6 | pub async fn walk_node(path: Vec, l: &mut Option, n: &mut Node) -> Result<()> { 7 | if n.forks.is_empty() { 8 | n.load(l).await?; 9 | } 10 | 11 | // err := walkNodeFnCopyBytes(ctx, path, n, nil, walkFn) 12 | // if err != nil { 13 | // return err 14 | // } 15 | 16 | for (_, v) in n.forks.iter_mut() { 17 | let mut next_path = path.clone(); 18 | next_path.extend_from_slice(&v.prefix); 19 | 20 | walk_node(next_path, l, &mut v.node).await? 21 | } 22 | 23 | Ok(()) 24 | } 25 | -------------------------------------------------------------------------------- /crates/network-primitives-traits/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vertex-network-primitives-traits" 3 | version.workspace = true 4 | edition.workspace = true 5 | rust-version.workspace = true 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | 10 | [lints] 11 | workspace = true 12 | 13 | [dependencies] 14 | ## nectar 15 | nectar-primitives.workspace = true 16 | 17 | ## alloy 18 | alloy-primitives.workspace = true 19 | alloy-signer.workspace = true 20 | 21 | ## libp2p 22 | libp2p.workspace = true 23 | 24 | thiserror.workspace = true 25 | once_cell.workspace = true 26 | 27 | [features] 28 | default = ["std"] 29 | std = ["once_cell/std"] 30 | -------------------------------------------------------------------------------- /crates/network-primitives-traits/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Traits and utilities for network primitives in the Ethereum Swarm P2P network. 2 | //! 3 | //! This crate provides the core traits and types needed for node addressing and 4 | //! identification in the network. It defines the [`NodeAddress`] trait and related 5 | //! functionality for managing node addresses across different network configurations. 6 | 7 | use alloy_primitives::{Address, B256, Keccak256, Signature}; 8 | use libp2p::Multiaddr; 9 | use nectar_primitives::SwarmAddress; 10 | use thiserror::Error; 11 | 12 | mod sync; 13 | pub use sync::*; 14 | 15 | /// Errors that can occur when working with node addresses. 16 | #[derive(Error, Debug)] 17 | pub enum NodeAddressError { 18 | /// Wrapper for signature validation errors from the alloy crate 19 | #[error("Invalid signature: {0}")] 20 | InvalidSignature(#[from] alloy_primitives::SignatureError), 21 | 22 | /// Wrapper for signer-related errors from the alloy crate 23 | #[error("Signer error: {0}")] 24 | SignerError(#[from] alloy_signer::Error), 25 | 26 | /// Indicates that the calculated overlay address doesn't match the expected value 27 | #[error("Invalid overlay address")] 28 | InvalidOverlay, 29 | } 30 | 31 | /// Defines the interface for node addresses in the network. 32 | /// 33 | /// A node address consists of multiple components that together uniquely identify 34 | /// a node in the network: 35 | /// - An overlay address (derived from chain address and nonce) 36 | /// - An underlay address (physical network address) 37 | /// - A chain address (Ethereum address) 38 | /// - A nonce value 39 | /// - A signature proving ownership 40 | /// 41 | /// The generic parameter `N` represents the network ID. 42 | pub trait NodeAddress { 43 | /// Calculates the overlay address for this node. 44 | /// 45 | /// The overlay address is derived from the chain address, network ID, and nonce 46 | /// using the Keccak256 hash function. 47 | fn overlay_address(&self) -> SwarmAddress { 48 | calculate_overlay_address::(self.chain_address(), self.nonce()) 49 | } 50 | 51 | /// Returns the underlay address (physical network address) of the node. 52 | fn underlay_address(&self) -> &Multiaddr; 53 | 54 | /// Returns the chain address (Ethereum address) of the node. 55 | fn chain_address(&self) -> &Address; 56 | 57 | /// Returns the nonce used in address generation. 58 | fn nonce(&self) -> &B256; 59 | 60 | /// Returns the signature proving ownership of the address. 61 | /// 62 | /// # Errors 63 | /// Returns a [`NodeAddressError`] if the signature is invalid or unavailable. 64 | fn signature(&self) -> Result<&Signature, NodeAddressError>; 65 | } 66 | 67 | /// Calculates the overlay address for a node given its chain address and nonce. 68 | /// 69 | /// The overlay address is a Keccak256 hash of the concatenation of: 70 | /// - The chain address 71 | /// - The network ID (in little-endian bytes) 72 | /// - The nonce 73 | /// 74 | /// # Parameters 75 | /// * `chain_address` - The Ethereum address of the node 76 | /// * `nonce` - A unique nonce value 77 | /// 78 | /// # Type Parameters 79 | /// * `N` - The network ID 80 | pub fn calculate_overlay_address( 81 | chain_address: &Address, 82 | nonce: &B256, 83 | ) -> SwarmAddress { 84 | let mut hasher = Keccak256::new(); 85 | hasher.update(chain_address); 86 | hasher.update(N.to_le_bytes()); 87 | hasher.update(nonce); 88 | hasher.finalize().into() 89 | } 90 | -------------------------------------------------------------------------------- /crates/network-primitives-traits/src/sync.rs: -------------------------------------------------------------------------------- 1 | //! Lock synchronization primitives 2 | 3 | use once_cell as _; 4 | 5 | #[cfg(not(feature = "std"))] 6 | pub use once_cell::sync::{Lazy as LazyLock, OnceCell as OnceLock}; 7 | 8 | #[cfg(feature = "std")] 9 | pub use std::sync::{LazyLock, OnceLock}; 10 | -------------------------------------------------------------------------------- /crates/network-primitives/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vertex-network-primitives" 3 | version.workspace = true 4 | edition.workspace = true 5 | rust-version.workspace = true 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | 10 | [dependencies] 11 | ## nectar primitives 12 | nectar-primitives.workspace = true 13 | 14 | ## vertex primitives 15 | vertex-network-primitives-traits.workspace = true 16 | 17 | alloy-primitives = { workspace = true, features = ["arbitrary"] } 18 | alloy-signer.workspace = true 19 | alloy-signer-local.workspace = true 20 | alloy-chains.workspace = true 21 | 22 | libp2p.workspace = true 23 | arbitrary = { workspace = true, features = ["derive"] } 24 | proptest.workspace = true 25 | thiserror.workspace = true 26 | 27 | bytes.workspace = true 28 | num_enum = { version = "0.7", default-features = false } 29 | strum = { version = "0.27", default-features = false, features = ["derive"] } 30 | 31 | [dev-dependencies] 32 | arbitrary.workspace = true 33 | proptest.workspace = true 34 | proptest-arbitrary-interop.workspace = true 35 | rand.workspace = true 36 | 37 | [lints] 38 | workspace = true 39 | -------------------------------------------------------------------------------- /crates/network/codec/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vertex-network-codec" 3 | version.workspace = true 4 | edition.workspace = true 5 | rust-version.workspace = true 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | 10 | [lints] 11 | workspace = true 12 | 13 | [dependencies] 14 | asynchronous-codec.workspace = true 15 | bytes.workspace = true 16 | quick-protobuf-codec.workspace = true 17 | quick-protobuf.workspace = true 18 | -------------------------------------------------------------------------------- /crates/network/codec/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use bytes::BytesMut; 4 | 5 | pub struct ProtocolCodec( 6 | quick_protobuf_codec::Codec, 7 | std::marker::PhantomData<(Protocol, E)>, 8 | ); 9 | 10 | impl ProtocolCodec { 11 | pub fn new(max_packet_size: usize) -> Self { 12 | Self( 13 | quick_protobuf_codec::Codec::new(max_packet_size), 14 | PhantomData, 15 | ) 16 | } 17 | } 18 | 19 | impl asynchronous_codec::Encoder for ProtocolCodec 20 | where 21 | Proto: quick_protobuf::MessageWrite, 22 | Protocol: Into, 23 | quick_protobuf_codec::Error: Into, 24 | E: From, 25 | { 26 | type Item<'a> = Protocol; 27 | type Error = E; 28 | 29 | fn encode(&mut self, item: Self::Item<'_>, dst: &mut BytesMut) -> Result<(), Self::Error> { 30 | self.0.encode(item.into(), dst).map_err(Into::into) 31 | } 32 | } 33 | 34 | impl asynchronous_codec::Decoder for ProtocolCodec 35 | where 36 | Proto: for<'a> quick_protobuf::MessageRead<'a>, 37 | Protocol: TryFrom, 38 | PE: Into, 39 | quick_protobuf_codec::Error: Into, 40 | E: From, 41 | { 42 | type Item = Protocol; 43 | type Error = E; 44 | 45 | fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { 46 | match self.0.decode(src).map_err(Into::into)? { 47 | Some(proto) => match Protocol::try_from(proto) { 48 | Ok(protocol) => Ok(Some(protocol)), 49 | Err(e) => Err(e.into()), 50 | }, 51 | None => Ok(None), 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /crates/network/handshake/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vertex-network-handshake" 3 | version.workspace = true 4 | edition.workspace = true 5 | rust-version.workspace = true 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | build = "build.rs" 10 | 11 | [dependencies] 12 | ## nectar 13 | nectar-primitives.workspace = true 14 | 15 | ## vertex 16 | vertex-node-core.workspace = true 17 | vertex-network-codec.workspace = true 18 | vertex-network-primitives.workspace = true 19 | vertex-network-primitives-traits.workspace = true 20 | 21 | ## alloy 22 | alloy-primitives.workspace = true 23 | alloy-signer.workspace = true 24 | alloy-signer-local.workspace = true 25 | 26 | ## async 27 | futures.workspace = true 28 | tokio.workspace = true 29 | 30 | ## p2p 31 | asynchronous-codec.workspace = true 32 | quick-protobuf.workspace = true 33 | quick-protobuf-codec.workspace = true 34 | libp2p.workspace = true 35 | 36 | ## misc 37 | bytes.workspace = true 38 | thiserror.workspace = true 39 | 40 | ## tracing 41 | tracing.workspace = true 42 | 43 | arbitrary = { workspace = true, features = ["derive"] } 44 | proptest.workspace = true 45 | 46 | [lints] 47 | workspace = true 48 | 49 | [dev-dependencies] 50 | libp2p-swarm = { workspace = true, features = ["macros"] } 51 | libp2p-swarm-test.workspace = true 52 | tokio = { workspace = true, features = ["rt", "macros"] } 53 | tracing-subscriber.workspace = true 54 | quickcheck.workspace = true 55 | 56 | arbitrary.workspace = true 57 | proptest.workspace = true 58 | proptest-arbitrary-interop.workspace = true 59 | rand.workspace = true 60 | 61 | [build-dependencies] 62 | pb-rs.workspace = true 63 | walkdir.workspace = true 64 | -------------------------------------------------------------------------------- /crates/network/handshake/build.rs: -------------------------------------------------------------------------------- 1 | use pb_rs::{types::FileDescriptor, ConfigBuilder}; 2 | use std::path::{Path, PathBuf}; 3 | use walkdir::WalkDir; 4 | 5 | fn main() { 6 | let out_dir = std::env::var("OUT_DIR").unwrap(); 7 | let out_dir = Path::new(&out_dir).join("proto"); 8 | 9 | let in_dir = PathBuf::from(::std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("proto"); 10 | // Re-run this build.rs if the protos dir changes (i.e. a new file is added) 11 | println!("cargo:rerun-if-changed={}", in_dir.to_str().unwrap()); 12 | 13 | // Find all *.proto files in the `in_dir` and add them to the list of files 14 | let mut protos = Vec::new(); 15 | let proto_ext = Some(Path::new("proto").as_os_str()); 16 | for entry in WalkDir::new(&in_dir) { 17 | let path = entry.unwrap().into_path(); 18 | if path.extension() == proto_ext { 19 | // Re-run this build.rs if any of the files in the protos dir change 20 | println!("cargo:rerun-if-changed={}", path.to_str().unwrap()); 21 | protos.push(path); 22 | } 23 | } 24 | 25 | // Delete all old generated files before re-generating new ones 26 | if out_dir.exists() { 27 | std::fs::remove_dir_all(&out_dir).unwrap(); 28 | } 29 | std::fs::DirBuilder::new().create(&out_dir).unwrap(); 30 | let config_builder = ConfigBuilder::new(&protos, None, Some(&out_dir), &[in_dir]).unwrap(); 31 | FileDescriptor::run(&config_builder.dont_use_cow(true).build()).unwrap() 32 | } 33 | -------------------------------------------------------------------------------- /crates/network/handshake/proto/handshake.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Swarm Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | syntax = "proto3"; 6 | 7 | package handshake; 8 | 9 | message Syn { 10 | bytes observed_underlay = 1; 11 | }; 12 | 13 | message Ack { 14 | BzzAddress address = 1; 15 | uint64 network_id = 2; 16 | bool full_node = 3; 17 | bytes nonce = 4; 18 | string welcome_message = 99; 19 | }; 20 | 21 | message SynAck { 22 | Syn syn = 1; 23 | Ack ack = 2; 24 | }; 25 | 26 | message BzzAddress { 27 | bytes underlay = 1; 28 | bytes signature = 2; 29 | bytes overlay = 3; 30 | }; 31 | -------------------------------------------------------------------------------- /crates/network/handshake/src/behaviour.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::VecDeque, 3 | sync::Arc, 4 | task::{Context, Poll}, 5 | }; 6 | 7 | use libp2p::{ 8 | core::{transport::PortUse, Endpoint}, 9 | swarm::{ 10 | ConnectionDenied, ConnectionId, FromSwarm, NetworkBehaviour, NotifyHandler, 11 | THandlerInEvent, ToSwarm, 12 | }, 13 | Multiaddr, PeerId, 14 | }; 15 | use vertex_node_core::args::NodeCommand; 16 | 17 | use crate::{HandshakeCommand, HandshakeEvent, HandshakeHandler}; 18 | 19 | pub struct HandshakeBehaviour { 20 | config: Arc, 21 | events: VecDeque, HandshakeCommand>>, 22 | } 23 | 24 | impl HandshakeBehaviour { 25 | pub fn new(config: Arc) -> Self { 26 | Self { 27 | config, 28 | events: VecDeque::new(), 29 | } 30 | } 31 | } 32 | 33 | impl NetworkBehaviour for HandshakeBehaviour { 34 | type ConnectionHandler = HandshakeHandler; 35 | type ToSwarm = HandshakeEvent; 36 | 37 | fn on_swarm_event(&mut self, event: FromSwarm) { 38 | match event { 39 | FromSwarm::ConnectionEstablished(connection) => { 40 | // Start handshake for outbound connections. 41 | if connection.endpoint.is_dialer() { 42 | self.events.push_back(ToSwarm::NotifyHandler { 43 | peer_id: connection.peer_id, 44 | handler: NotifyHandler::One(connection.connection_id), 45 | event: HandshakeCommand::StartHandshake, 46 | }); 47 | } 48 | } 49 | _ => {} 50 | } 51 | } 52 | 53 | fn on_connection_handler_event( 54 | &mut self, 55 | _: PeerId, 56 | _: ConnectionId, 57 | event: HandshakeEvent, 58 | ) { 59 | match event { 60 | HandshakeEvent::Completed(info) => { 61 | self.events 62 | .push_back(ToSwarm::GenerateEvent(HandshakeEvent::Completed(info))); 63 | } 64 | HandshakeEvent::Failed(error) => { 65 | self.events 66 | .push_back(ToSwarm::GenerateEvent(HandshakeEvent::Failed(error))); 67 | } 68 | } 69 | } 70 | 71 | fn poll(&mut self, _: &mut Context<'_>) -> Poll>> { 72 | if let Some(event) = self.events.pop_front() { 73 | return Poll::Ready(event); 74 | } 75 | Poll::Pending 76 | } 77 | 78 | fn handle_established_inbound_connection( 79 | &mut self, 80 | _: ConnectionId, 81 | peer: PeerId, 82 | _: &Multiaddr, 83 | remote_addr: &Multiaddr, 84 | ) -> Result, ConnectionDenied> { 85 | Ok(HandshakeHandler::new( 86 | self.config.clone(), 87 | peer, 88 | remote_addr, 89 | )) 90 | } 91 | 92 | fn handle_established_outbound_connection( 93 | &mut self, 94 | _: ConnectionId, 95 | peer: PeerId, 96 | addr: &Multiaddr, 97 | _: Endpoint, 98 | _: PortUse, 99 | ) -> Result, ConnectionDenied> { 100 | Ok(HandshakeHandler::new(self.config.clone(), peer, addr)) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /crates/network/handshake/src/codec/error.rs: -------------------------------------------------------------------------------- 1 | use libp2p::multiaddr; 2 | use thiserror::Error; 3 | use vertex_network_primitives_traits::NodeAddressError; 4 | 5 | #[derive(Debug, Error)] 6 | pub enum CodecError { 7 | #[error("Network ID mismatch")] 8 | NetworkIDMismatch, 9 | 10 | #[error("Missing field: {0}")] 11 | MissingField(&'static str), 12 | 13 | #[error("Maximum {0} field length exceeded limit {1}, received {2}")] 14 | FieldLengthLimitExceeded(&'static str, usize, usize), 15 | 16 | #[error("Invalid data conversion: {0}")] 17 | InvalidData(#[from] std::array::TryFromSliceError), 18 | 19 | #[error("Invalid Multiaddr: {0}")] 20 | InvalidMultiaddr(#[from] multiaddr::Error), 21 | 22 | #[error("Invalid signature: {0}")] 23 | InvalidSignature(#[from] alloy_primitives::SignatureError), 24 | 25 | #[error("Invalid node address: {0}")] 26 | InvalidNodeAddress(#[from] NodeAddressError), 27 | 28 | #[error("Protocol error: {0}")] 29 | Protocol(String), 30 | 31 | #[error("IO error: {0}")] 32 | Io(#[from] std::io::Error), 33 | } 34 | 35 | impl From for CodecError { 36 | fn from(error: quick_protobuf_codec::Error) -> Self { 37 | CodecError::Protocol(error.to_string()) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /crates/network/handshake/src/codec/mod.rs: -------------------------------------------------------------------------------- 1 | use vertex_network_codec::ProtocolCodec; 2 | 3 | mod ack; 4 | mod error; 5 | mod syn; 6 | mod synack; 7 | pub use ack::Ack; 8 | pub use syn::Syn; 9 | pub use synack::SynAck; 10 | 11 | pub use error::CodecError; 12 | 13 | pub type AckCodec = ProtocolCodec, CodecError>; 14 | pub type SynCodec = ProtocolCodec, CodecError>; 15 | pub type SynAckCodec = 16 | ProtocolCodec, CodecError>; 17 | -------------------------------------------------------------------------------- /crates/network/handshake/src/codec/syn.rs: -------------------------------------------------------------------------------- 1 | use libp2p::Multiaddr; 2 | use vertex_network_primitives::arbitrary_multiaddr; 3 | 4 | use super::CodecError; 5 | 6 | #[derive(Debug, Clone, PartialEq)] 7 | pub struct Syn { 8 | observed_underlay: Multiaddr, 9 | } 10 | 11 | impl Syn { 12 | pub fn new(observed_underlay: Multiaddr) -> Self { 13 | Self { observed_underlay } 14 | } 15 | 16 | pub fn observed_underlay(&self) -> &Multiaddr { 17 | &self.observed_underlay 18 | } 19 | } 20 | 21 | impl TryFrom for Syn { 22 | type Error = CodecError; 23 | 24 | fn try_from(value: crate::proto::handshake::Syn) -> Result { 25 | Ok(Self::new(Multiaddr::try_from(value.observed_underlay)?)) 26 | } 27 | } 28 | 29 | impl Into for Syn { 30 | fn into(self) -> crate::proto::handshake::Syn { 31 | crate::proto::handshake::Syn { 32 | observed_underlay: self.observed_underlay.to_vec(), 33 | } 34 | } 35 | } 36 | 37 | impl<'a, const N: u64> arbitrary::Arbitrary<'a> for Syn { 38 | fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { 39 | let observed_underlay = arbitrary_multiaddr(u)?; 40 | Ok(Self::new(observed_underlay)) 41 | } 42 | } 43 | 44 | #[cfg(test)] 45 | mod tests { 46 | use super::*; 47 | 48 | use proptest::prelude::*; 49 | use proptest_arbitrary_interop::arb; 50 | 51 | const TEST_NETWORK_ID: u64 = 1234567890; 52 | 53 | // Helper function to create a valid Syn instance 54 | fn create_test_syn() -> Syn<1> { 55 | Syn::new(Multiaddr::try_from("/ip4/127.0.0.1/tcp/1234").unwrap()) 56 | } 57 | 58 | proptest! { 59 | #[test] 60 | fn test_syn_proto_roundtrip( 61 | syn in arb::>() 62 | ) { 63 | // Convert Syn to proto 64 | let proto_syn: crate::proto::handshake::Syn = syn.clone().into(); 65 | 66 | // Convert proto back to Syn 67 | let recovered_syn = Syn::::try_from(proto_syn); 68 | 69 | prop_assert!(recovered_syn.is_ok()); 70 | let recovered_syn = recovered_syn.unwrap(); 71 | 72 | // Verify equality 73 | prop_assert_eq!(&syn, &recovered_syn); 74 | 75 | // Verify fields using accessors 76 | prop_assert_eq!(syn.observed_underlay(), recovered_syn.observed_underlay()); 77 | } 78 | } 79 | 80 | #[test] 81 | fn test_syn_err_on_malformed_proto() { 82 | let mut proto_syn: crate::proto::handshake::Syn = create_test_syn().into(); 83 | proto_syn.observed_underlay = vec![0x01, 0x02, 0x03]; 84 | 85 | let result = Syn::::try_from(proto_syn); 86 | assert!(result.is_err()); 87 | assert!(matches!( 88 | result.unwrap_err(), 89 | CodecError::InvalidMultiaddr(_) 90 | )) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /crates/network/handshake/src/error.rs: -------------------------------------------------------------------------------- 1 | use crate::codec; 2 | 3 | #[derive(Debug, thiserror::Error)] 4 | pub enum HandshakeError { 5 | #[error("Picker rejection")] 6 | PickerRejection, 7 | #[error("Timeout")] 8 | Timeout, 9 | #[error("Codec error: {0}")] 10 | Codec(#[from] codec::CodecError), 11 | #[error("Protocol error: {0}")] 12 | Protocol(String), 13 | #[error("Stream error: {0}")] 14 | Stream(#[from] std::io::Error), 15 | #[error("Connection closed")] 16 | ConnectionClosed, 17 | #[error("Missing data")] 18 | MissingData, 19 | } 20 | 21 | impl From> for HandshakeError { 22 | fn from(opt: Option) -> Self { 23 | match opt { 24 | None => HandshakeError::ConnectionClosed, 25 | Some(_) => unreachable!("Should not convert Some variant"), 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /crates/network/handshake/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use libp2p::{swarm::ConnectionId, PeerId}; 4 | 5 | mod proto { 6 | include!(concat!(env!("OUT_DIR"), "/proto/mod.rs")); 7 | } 8 | mod error; 9 | pub use error::HandshakeError; 10 | mod behaviour; 11 | pub use behaviour::HandshakeBehaviour; 12 | mod protocol; 13 | pub use protocol::HandshakeProtocol; 14 | mod handler; 15 | pub use handler::HandshakeHandler; 16 | mod codec; 17 | pub use codec::*; 18 | 19 | const PROTOCOL: &str = "/swarm/handshake/13.0.0/handshake"; 20 | const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(15); 21 | const MAX_WELCOME_MESSAGE_CHARS: usize = 140; 22 | 23 | #[derive(Debug, Clone)] 24 | pub struct HandshakeInfo { 25 | pub peer_id: PeerId, 26 | pub ack: Ack, 27 | } 28 | 29 | #[derive(Debug, Clone)] 30 | pub struct PeerState { 31 | pub info: HandshakeInfo, 32 | pub connections: Vec, 33 | } 34 | 35 | #[derive(Debug)] 36 | pub enum HandshakeEvent { 37 | Completed(HandshakeInfo), 38 | Failed(HandshakeError), 39 | } 40 | 41 | #[derive(Debug, Clone, PartialEq, Eq)] 42 | enum HandshakeState { 43 | Idle, 44 | Handshaking, 45 | Completed, 46 | Failed, 47 | } 48 | 49 | #[derive(Debug)] 50 | pub enum HandshakeCommand { 51 | StartHandshake, 52 | } 53 | -------------------------------------------------------------------------------- /crates/network/headers/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vertex-network-headers" 3 | version.workspace = true 4 | edition.workspace = true 5 | rust-version.workspace = true 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | build = "build.rs" 10 | 11 | [lints] 12 | workspace = true 13 | 14 | [dependencies] 15 | ## vertex 16 | vertex-network-codec.workspace = true 17 | 18 | ## async 19 | futures.workspace = true 20 | 21 | ## p2p 22 | asynchronous-codec.workspace = true 23 | quick-protobuf.workspace = true 24 | quick-protobuf-codec.workspace = true 25 | libp2p.workspace = true 26 | 27 | ## misc 28 | bytes.workspace = true 29 | thiserror.workspace = true 30 | 31 | ## tracing 32 | tracing.workspace = true 33 | 34 | [build-dependencies] 35 | pb-rs.workspace = true 36 | walkdir.workspace = true 37 | -------------------------------------------------------------------------------- /crates/network/headers/build.rs: -------------------------------------------------------------------------------- 1 | use pb_rs::{types::FileDescriptor, ConfigBuilder}; 2 | use std::path::{Path, PathBuf}; 3 | use walkdir::WalkDir; 4 | 5 | fn main() { 6 | let out_dir = std::env::var("OUT_DIR").unwrap(); 7 | let out_dir = Path::new(&out_dir).join("proto"); 8 | 9 | let in_dir = PathBuf::from(::std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("proto"); 10 | // Re-run this build.rs if the protos dir changes (i.e. a new file is added) 11 | println!("cargo:rerun-if-changed={}", in_dir.to_str().unwrap()); 12 | 13 | // Find all *.proto files in the `in_dir` and add them to the list of files 14 | let mut protos = Vec::new(); 15 | let proto_ext = Some(Path::new("proto").as_os_str()); 16 | for entry in WalkDir::new(&in_dir) { 17 | let path = entry.unwrap().into_path(); 18 | if path.extension() == proto_ext { 19 | // Re-run this build.rs if any of the files in the protos dir change 20 | println!("cargo:rerun-if-changed={}", path.to_str().unwrap()); 21 | protos.push(path); 22 | } 23 | } 24 | 25 | // Delete all old generated files before re-generating new ones 26 | if out_dir.exists() { 27 | std::fs::remove_dir_all(&out_dir).unwrap(); 28 | } 29 | std::fs::DirBuilder::new().create(&out_dir).unwrap(); 30 | let config_builder = ConfigBuilder::new(&protos, None, Some(&out_dir), &[in_dir]).unwrap(); 31 | FileDescriptor::run(&config_builder.dont_use_cow(true).build()).unwrap() 32 | } 33 | -------------------------------------------------------------------------------- /crates/network/headers/proto/headers.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Swarm Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | syntax = "proto3"; 6 | 7 | package headers; 8 | 9 | message Headers { 10 | repeated Header headers = 1; 11 | } 12 | 13 | message Header { 14 | string key = 1; 15 | bytes value = 2; 16 | } 17 | -------------------------------------------------------------------------------- /crates/network/headers/src/codec.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use vertex_network_codec::ProtocolCodec; 4 | 5 | use bytes::Bytes; 6 | 7 | pub(crate) type HeadersCodec = ProtocolCodec; 8 | 9 | #[derive(Debug, thiserror::Error)] 10 | pub enum CodecError { 11 | #[error("Protocol error: {0}")] 12 | Protocol(String), 13 | #[error("IO error: {0}")] 14 | Io(#[from] std::io::Error), 15 | } 16 | 17 | impl From for CodecError { 18 | fn from(error: quick_protobuf_codec::Error) -> Self { 19 | CodecError::Protocol(error.to_string()) 20 | } 21 | } 22 | 23 | #[derive(Debug, Clone, PartialEq)] 24 | pub(crate) struct Headers { 25 | inner: HashMap, 26 | } 27 | 28 | impl Headers { 29 | pub(crate) fn new(inner: HashMap) -> Self { 30 | Headers { inner } 31 | } 32 | } 33 | 34 | impl Headers { 35 | pub(crate) fn into_inner(self) -> HashMap { 36 | self.inner 37 | } 38 | } 39 | 40 | impl TryFrom for Headers { 41 | type Error = CodecError; 42 | 43 | fn try_from(value: crate::proto::headers::Headers) -> Result { 44 | Ok(Headers { 45 | inner: value 46 | .headers 47 | .into_iter() 48 | .map(|v| (v.key, Bytes::from(v.value))) 49 | .collect(), 50 | }) 51 | } 52 | } 53 | 54 | impl Into for Headers { 55 | fn into(self) -> crate::proto::headers::Headers { 56 | crate::proto::headers::Headers { 57 | headers: self 58 | .inner 59 | .into_iter() 60 | .map(|(k, v)| crate::proto::headers::Header { 61 | key: k, 62 | value: v.into(), 63 | }) 64 | .collect(), 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /crates/network/headers/src/instrumented.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | use futures::AsyncRead; 3 | use futures::AsyncWrite; 4 | use libp2p::Stream; 5 | use std::collections::HashMap; 6 | use std::{ 7 | pin::Pin, 8 | task::{Context, Poll}, 9 | }; 10 | use tracing::Span; 11 | 12 | /// A wrapper around a libp2p Stream that maintains tracing instrumentation 13 | /// with protocol headers as span fields. 14 | pub struct InstrumentedStream { 15 | /// The underlying network stream 16 | stream: Stream, 17 | /// Protocol headers exchanged during upgrade 18 | headers: HashMap, 19 | /// Keeps the span entered for the lifetime of the stream 20 | _span_guard: tracing::span::EnteredSpan, 21 | } 22 | 23 | impl InstrumentedStream { 24 | /// Create a new instrumented stream with automatic span management 25 | pub fn new(stream: Stream, headers: HashMap, span: Span) -> Self { 26 | Self { 27 | stream, 28 | headers, 29 | _span_guard: span.entered(), 30 | } 31 | } 32 | 33 | /// Get a reference to the protocol headers 34 | pub fn headers(&self) -> &HashMap { 35 | &self.headers 36 | } 37 | 38 | /// Get a specific header value 39 | pub fn get_header(&self, key: &str) -> Option<&Bytes> { 40 | self.headers.get(key) 41 | } 42 | 43 | /// Get the underlying stream and headers, consuming this wrapper 44 | pub fn into_inner(self) -> (Stream, HashMap) { 45 | (self.stream, self.headers) 46 | } 47 | } 48 | 49 | impl AsyncRead for InstrumentedStream { 50 | fn poll_read( 51 | mut self: Pin<&mut Self>, 52 | cx: &mut Context<'_>, 53 | buf: &mut [u8], 54 | ) -> Poll> { 55 | Pin::new(&mut self.stream).poll_read(cx, buf) 56 | } 57 | } 58 | 59 | impl AsyncWrite for InstrumentedStream { 60 | fn poll_write( 61 | mut self: Pin<&mut Self>, 62 | cx: &mut Context<'_>, 63 | buf: &[u8], 64 | ) -> Poll> { 65 | Pin::new(&mut self.stream).poll_write(cx, buf) 66 | } 67 | 68 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 69 | Pin::new(&mut self.stream).poll_flush(cx) 70 | } 71 | 72 | fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 73 | Pin::new(&mut self.stream).poll_close(cx) 74 | } 75 | } 76 | 77 | // Add AsRef/AsMut implementations for easier access to the underlying stream 78 | impl AsRef for InstrumentedStream { 79 | fn as_ref(&self) -> &Stream { 80 | &self.stream 81 | } 82 | } 83 | 84 | impl AsMut for InstrumentedStream { 85 | fn as_mut(&mut self) -> &mut Stream { 86 | &mut self.stream 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /crates/network/pricing/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vertex-network-pricing" 3 | version.workspace = true 4 | edition.workspace = true 5 | rust-version.workspace = true 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | build = "build.rs" 10 | 11 | [lints] 12 | workspace = true 13 | 14 | [dependencies] 15 | ## vertex 16 | vertex-network-codec.workspace = true 17 | 18 | ## async 19 | futures.workspace = true 20 | 21 | ## p2p 22 | asynchronous-codec.workspace = true 23 | quick-protobuf.workspace = true 24 | quick-protobuf-codec.workspace = true 25 | libp2p.workspace = true 26 | 27 | ## misc 28 | bytes.workspace = true 29 | thiserror.workspace = true 30 | 31 | ## tracing 32 | tracing.workspace = true 33 | 34 | [build-dependencies] 35 | pb-rs.workspace = true 36 | walkdir.workspace = true 37 | -------------------------------------------------------------------------------- /crates/network/pricing/build.rs: -------------------------------------------------------------------------------- 1 | use pb_rs::{types::FileDescriptor, ConfigBuilder}; 2 | use std::path::{Path, PathBuf}; 3 | use walkdir::WalkDir; 4 | 5 | fn main() { 6 | let out_dir = std::env::var("OUT_DIR").unwrap(); 7 | let out_dir = Path::new(&out_dir).join("proto"); 8 | 9 | let in_dir = PathBuf::from(::std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("proto"); 10 | // Re-run this build.rs if the protos dir changes (i.e. a new file is added) 11 | println!("cargo:rerun-if-changed={}", in_dir.to_str().unwrap()); 12 | 13 | // Find all *.proto files in the `in_dir` and add them to the list of files 14 | let mut protos = Vec::new(); 15 | let proto_ext = Some(Path::new("proto").as_os_str()); 16 | for entry in WalkDir::new(&in_dir) { 17 | let path = entry.unwrap().into_path(); 18 | if path.extension() == proto_ext { 19 | // Re-run this build.rs if any of the files in the protos dir change 20 | println!("cargo:rerun-if-changed={}", path.to_str().unwrap()); 21 | protos.push(path); 22 | } 23 | } 24 | 25 | // Delete all old generated files before re-generating new ones 26 | if out_dir.exists() { 27 | std::fs::remove_dir_all(&out_dir).unwrap(); 28 | } 29 | std::fs::DirBuilder::new().create(&out_dir).unwrap(); 30 | let config_builder = ConfigBuilder::new(&protos, None, Some(&out_dir), &[in_dir]).unwrap(); 31 | FileDescriptor::run(&config_builder.dont_use_cow(true).build()).unwrap() 32 | } 33 | -------------------------------------------------------------------------------- /crates/network/pricing/proto/pricing.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Swarm Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | syntax = "proto3"; 6 | 7 | package pricing; 8 | 9 | option go_package = "pb"; 10 | 11 | message AnnouncePaymentThreshold { 12 | bytes payment_threshold = 1; 13 | } 14 | -------------------------------------------------------------------------------- /crates/network/pricing/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod proto { 2 | include!(concat!(env!("OUT_DIR"), "/proto/mod.rs")); 3 | } 4 | -------------------------------------------------------------------------------- /crates/node/api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vertex-node-api" 3 | version.workspace = true 4 | edition.workspace = true 5 | rust-version.workspace = true 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | 10 | [dependencies] 11 | # Internal dependencies 12 | vertex-primitives = { workspace = true } 13 | vertex-swarm-api = { workspace = true } 14 | vertex-swarmspec = { workspace = true } 15 | 16 | # External dependencies 17 | auto_impl = "1.1" 18 | async-trait = "0.1" 19 | futures = { workspace = true } 20 | alloy-primitives = { workspace = true } 21 | 22 | [features] 23 | default = ["std"] 24 | std = [ 25 | "vertex-primitives/std", 26 | "vertex-swarm-api/std" 27 | ] 28 | -------------------------------------------------------------------------------- /crates/node/api/src/config.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nxm-rs/vertex/380b1bdf12495d9b0053c7d0057e05d4a35ea9e9/crates/node/api/src/config.rs -------------------------------------------------------------------------------- /crates/node/api/src/types.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nxm-rs/vertex/380b1bdf12495d9b0053c7d0057e05d4a35ea9e9/crates/node/api/src/types.rs -------------------------------------------------------------------------------- /crates/node/cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vertex-node-cli" 3 | version.workspace = true 4 | edition.workspace = true 5 | rust-version.workspace = true 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | 10 | [lints] 11 | workspace = true 12 | 13 | [dependencies] 14 | ## vertex 15 | vertex-node-core.workspace = true 16 | 17 | clap = { workspace = true, features = ["derive"] } 18 | -------------------------------------------------------------------------------- /crates/node/cli/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn add(left: u64, right: u64) -> u64 { 2 | left + right 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use super::*; 8 | 9 | #[test] 10 | fn it_works() { 11 | let result = add(2, 2); 12 | assert_eq!(result, 4); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /crates/node/core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vertex-node-core" 3 | version.workspace = true 4 | edition.workspace = true 5 | rust-version.workspace = true 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | build = "build.rs" 10 | 11 | [lints] 12 | workspace = true 13 | 14 | [dependencies] 15 | alloy-primitives.workspace = true 16 | alloy-signer.workspace = true 17 | alloy-signer-local.workspace = true 18 | bytes.workspace = true 19 | clap = { workspace = true, features = ["derive", "env"] } 20 | eyre.workspace = true 21 | libp2p.workspace = true 22 | 23 | [build-dependencies] 24 | vergen-gitcl = { workspace = true, features = ["build", "cargo", "emit_and_set"] } 25 | -------------------------------------------------------------------------------- /crates/node/core/build.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs)] 2 | 3 | use std::{env, error::Error}; 4 | 5 | use vergen_gitcl::{BuildBuilder, CargoBuilder, Emitter, GitclBuilder}; 6 | 7 | fn main() -> Result<(), Box> { 8 | let build = BuildBuilder::all_build()?; 9 | let cargo = CargoBuilder::all_cargo()?; 10 | let gitcl = GitclBuilder::all_git()?; 11 | 12 | // Emit the instructions 13 | Emitter::default() 14 | .add_instructions(&build)? 15 | .add_instructions(&cargo)? 16 | .add_instructions(&gitcl)? 17 | .emit_and_set()?; 18 | 19 | let sha = env::var("VERGEN_GIT_SHA")?; 20 | let sha_short = &sha[0..7]; 21 | 22 | let is_dirty = env::var("VERGEN_GIT_DIRTY")? == "true"; 23 | // > git describe --always --tags 24 | // if not on a tag: v0.2.0-beta.3-82-g1939939b 25 | // if on a tag: v0.2.0-beta.3 26 | let not_on_tag = env::var("VERGEN_GIT_DESCRIBE")?.ends_with(&format!("-g{sha_short}")); 27 | let version_suffix = if is_dirty || not_on_tag { "-dev" } else { "" }; 28 | println!("cargo:rustc-env=VERTEX_VERSION_SUFFIX={}", version_suffix); 29 | 30 | // Set short SHA 31 | println!("cargo:rustc-env=VERGEN_GIT_SHA_SHORT={}", &sha[..8]); 32 | 33 | // Set the build profile 34 | let out_dir = env::var("OUT_DIR").unwrap(); 35 | let profile = out_dir.rsplit(std::path::MAIN_SEPARATOR).nth(3).unwrap(); 36 | println!("cargo:rustc-env=VERTEX_BUILD_PROFILE={profile}"); 37 | 38 | // Set formatted version strings 39 | let pkg_version = env!("CARGO_PKG_VERSION"); 40 | 41 | // The short version information for vertex. 42 | // - The latest version from Cargo.toml 43 | // - The short SHA of the latest commit. 44 | // Example: 0.1.0 (defa64b2) 45 | println!("cargo:rustc-env=VERTEX_SHORT_VERSION={pkg_version}{version_suffix} ({sha_short})"); 46 | 47 | // LONG_VERSION 48 | // The long version information for vertex. 49 | // 50 | // - The latest version from Cargo.toml + version suffix (if any) 51 | // - The full SHA of the latest commit 52 | // - The build datetime 53 | // - The build features 54 | // - The build profile 55 | // 56 | // Example: 57 | // 58 | // ```text 59 | // Version: 0.1.0 60 | // Commit SHA: defa64b2 61 | // Build Timestamp: 2023-05-19T01:47:19.815651705Z 62 | // Build Features: jemalloc 63 | // Build Profile: maxperf 64 | // ``` 65 | println!("cargo:rustc-env=VERTEX_LONG_VERSION_0=Version: {pkg_version}{version_suffix}"); 66 | println!("cargo:rustc-env=VERTEX_LONG_VERSION_1=Commit SHA: {sha}"); 67 | println!( 68 | "cargo:rustc-env=VERTEX_LONG_VERSION_2=Build Timestamp: {}", 69 | env::var("VERGEN_BUILD_TIMESTAMP")? 70 | ); 71 | println!( 72 | "cargo:rustc-env=VERTEX_LONG_VERSION_3=Build Features: {}", 73 | env::var("VERGEN_CARGO_FEATURES")? 74 | ); 75 | println!("cargo:rustc-env=VERTEX_LONG_VERSION_4=Build Profile: {profile}"); 76 | 77 | // The version information for vertex formatted for P2P (libp2p). 78 | // - The latest version from Cargo.toml 79 | // - The target triple 80 | // 81 | // Example: vertex/v0.1.0-alpha.1-428a6dc2f/aarch64-apple-darwin 82 | println!( 83 | "cargo:rustc-env=VERTEX_P2P_CLIENT_VERSION={}", 84 | format_args!( 85 | "vertex/v{pkg_version}-{sha_short}/{}", 86 | env::var("VERGEN_CARGO_TARGET_TRIPLE")? 87 | ) 88 | ); 89 | 90 | Ok(()) 91 | } 92 | -------------------------------------------------------------------------------- /crates/node/core/src/args/mod.rs: -------------------------------------------------------------------------------- 1 | /// NeighbourhoodArgs struct for configuring the node's neighbourhood 2 | mod neighbourhood; 3 | use clap::Parser; 4 | pub use neighbourhood::NeighbourhoodArgs; 5 | 6 | /// NetworkArgs struct for configuring the network 7 | mod network; 8 | pub use network::NetworkArgs; 9 | 10 | /// WalletArgs struct for configuring the wallet 11 | mod wallet; 12 | pub use wallet::WalletArgs; 13 | 14 | #[derive(Debug, Parser)] 15 | pub struct NodeCommand { 16 | /// All network related arguments 17 | #[command(flatten)] 18 | pub network: NetworkArgs, 19 | 20 | /// All wallet related arguments 21 | #[command(flatten)] 22 | pub wallet: WalletArgs, 23 | 24 | /// All neighbourhood related arguments 25 | #[command(flatten)] 26 | pub neighbourhood: NeighbourhoodArgs, 27 | } 28 | -------------------------------------------------------------------------------- /crates/node/core/src/args/neighbourhood.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use alloy_primitives::B256; 4 | use clap::Args; 5 | 6 | const DEFAULT_NONCE: &str = "0x0000000000000000000000000000000000000000000000000000000000000000"; 7 | 8 | /// Parameters for configuring the neighbourhood 9 | #[derive(Debug, Clone, Args, PartialEq, Eq)] 10 | #[command(next_help_heading = "Identity Configuration")] 11 | pub struct NeighbourhoodArgs { 12 | /// Whether the node is to advertise itself as a full node. 13 | #[arg(long, value_name = "BOOL", default_value_t = false)] 14 | pub full: bool, 15 | 16 | /// The `nonce` for determining the node address. 17 | #[arg(long, value_name = "NONCE", value_parser = B256::from_str, default_value = DEFAULT_NONCE)] 18 | pub nonce: B256, 19 | } 20 | -------------------------------------------------------------------------------- /crates/node/core/src/args/network.rs: -------------------------------------------------------------------------------- 1 | use clap::{arg, command, Args}; 2 | use libp2p::Multiaddr; 3 | 4 | use crate::version::P2P_CLIENT_VERSION; 5 | 6 | /// Parameters for configuring the network 7 | #[derive(Debug, Clone, Args, PartialEq, Eq)] 8 | #[command(next_help_heading = "Networking")] 9 | pub struct NetworkArgs { 10 | /// Custom node identity 11 | #[arg(long, value_name = "IDENTITY", default_value = P2P_CLIENT_VERSION)] 12 | pub identity: String, 13 | 14 | /// Comma separated multiaddrs of trusted peers for P2P connections. 15 | /// 16 | /// --trusted-peers /ip4/123.123.123.123/tcp/1234/p2p/PeerID 17 | #[arg(long, value_delimiter = ',')] 18 | pub trusted_peers: Vec, 19 | 20 | /// Connect to or accept from trusted peers only 21 | #[arg(long, default_value_t = false)] 22 | pub trusted_only: bool, 23 | 24 | /// Comma separated Multiaddr URLs for P2P discovery bootstrap. 25 | /// 26 | /// --bootstrap-peers /ip4/123.123.123.123/tcp/1234/p2p/PeerID 27 | #[arg(long, value_delimiter = ',')] 28 | pub bootnodes: Vec, 29 | 30 | /// Welcome message shown to peers on connection 31 | #[arg( 32 | long, 33 | value_name = "WELCOME_MESSAGE", 34 | default_value = "Vertex into the Swarm!" 35 | )] 36 | pub welcome_message: String, 37 | } 38 | -------------------------------------------------------------------------------- /crates/node/core/src/args/wallet.rs: -------------------------------------------------------------------------------- 1 | use alloy_signer::k256::ecdsa::SigningKey; 2 | use alloy_signer_local::{LocalSigner, PrivateKeySigner}; 3 | use clap::{command, Args}; 4 | use std::{path::PathBuf, str::FromStr, sync::Arc}; 5 | 6 | #[derive(Debug, Clone, Args, PartialEq, Eq)] 7 | #[command(next_help_heading = "Wallet Configuration")] 8 | pub struct WalletArgs { 9 | /// The path to the JSON keystore file 10 | #[arg( 11 | long, 12 | value_name = "PATH", 13 | requires = "password", 14 | group = "wallet_config" 15 | )] 16 | pub keystore_file: Option, 17 | 18 | /// The password to unlock the keystore file 19 | #[arg( 20 | long, 21 | value_name = "PASSWORD", 22 | requires = "keystore_file", 23 | group = "wallet_config" 24 | )] 25 | pub password: Option, 26 | 27 | /// The raw private key to use for the wallet as a hex string 28 | #[arg(long, value_name = "PRIVATE_KEY", group = "wallet_config")] 29 | pub private_key: Option, 30 | } 31 | 32 | impl WalletArgs { 33 | /// Returns the signer in an Arc 34 | pub fn signer(&self) -> Arc> { 35 | match (self.keystore_file.as_ref(), self.private_key.as_ref()) { 36 | (Some(keystore_file), None) => { 37 | // We can safely unwrap password here because of the requires attribute 38 | Arc::new( 39 | LocalSigner::decrypt_keystore(keystore_file, self.password.as_ref().unwrap()) 40 | .unwrap(), 41 | ) 42 | } 43 | (None, Some(private_key)) => Arc::new(PrivateKeySigner::from_str(private_key).unwrap()), 44 | _ => Arc::new(PrivateKeySigner::random()), 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /crates/node/core/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod args; 2 | pub mod node_config; 3 | pub mod version; 4 | -------------------------------------------------------------------------------- /crates/node/core/src/node_config.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub struct NodeConfig {} 3 | -------------------------------------------------------------------------------- /crates/node/core/src/version.rs: -------------------------------------------------------------------------------- 1 | //! Version information for vertex. 2 | use bytes::Bytes; 3 | 4 | /// The human readable name of the client 5 | pub const NAME_CLIENT: &str = "Vertex"; 6 | 7 | /// The latest version from Cargo.toml. 8 | pub const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); 9 | 10 | /// The full SHA of the latest commit. 11 | pub const VERGEN_GIT_SHA_LONG: &str = env!("VERGEN_GIT_SHA"); 12 | 13 | /// The 8 character short SHA of the latest commit. 14 | pub const VERGEN_GIT_SHA: &str = env!("VERGEN_GIT_SHA_SHORT"); 15 | 16 | /// The build timestamp. 17 | pub const VERGEN_BUILD_TIMESTAMP: &str = env!("VERGEN_BUILD_TIMESTAMP"); 18 | 19 | /// The target triple. 20 | pub const VERGEN_CARGO_TARGET_TRIPLE: &str = env!("VERGEN_CARGO_TARGET_TRIPLE"); 21 | 22 | /// The build features. 23 | pub const VERGEN_CARGO_FEATURES: &str = env!("VERGEN_CARGO_FEATURES"); 24 | 25 | /// The short version information for vertex. 26 | pub const SHORT_VERSION: &str = env!("VERTEX_SHORT_VERSION"); 27 | 28 | /// The long version information for vertex. 29 | pub const LONG_VERSION: &str = concat!( 30 | env!("VERTEX_LONG_VERSION_0"), 31 | "\n", 32 | env!("VERTEX_LONG_VERSION_1"), 33 | "\n", 34 | env!("VERTEX_LONG_VERSION_2"), 35 | "\n", 36 | env!("VERTEX_LONG_VERSION_3"), 37 | "\n", 38 | env!("VERTEX_LONG_VERSION_4") 39 | ); 40 | 41 | /// The build profile name. 42 | pub const BUILD_PROFILE_NAME: &str = env!("VERTEX_BUILD_PROFILE"); 43 | 44 | /// The version information for vertex formatted for P2P (devp2p). 45 | /// 46 | /// - The latest version from Cargo.toml 47 | /// - The target triple 48 | /// 49 | /// # Example 50 | /// 51 | /// ```text 52 | /// vertex/v{major}.{minor}.{patch}-{sha1}/{target} 53 | /// ``` 54 | /// e.g.: `vertex/v0.1.0-alpha.1-428a6dc2f/aarch64-apple-darwin` 55 | pub const P2P_CLIENT_VERSION: &str = env!("VERTEX_P2P_CLIENT_VERSION"); 56 | 57 | /// The default extra data used for payload building. 58 | /// 59 | /// - The latest version from Cargo.toml 60 | /// - The OS identifier 61 | /// 62 | /// # Example 63 | /// 64 | /// ```text 65 | /// vertex/v{major}.{minor}.{patch}/{OS} 66 | /// ``` 67 | pub fn default_extra_data() -> String { 68 | format!( 69 | "vertex/v{}/{}", 70 | env!("CARGO_PKG_VERSION"), 71 | std::env::consts::OS 72 | ) 73 | } 74 | 75 | /// The default extra data in bytes. 76 | /// See [`default_extra_data`]. 77 | pub fn default_extra_data_bytes() -> Bytes { 78 | Bytes::from(default_extra_data().as_bytes().to_vec()) 79 | } 80 | 81 | #[cfg(test)] 82 | mod tests { 83 | use super::*; 84 | 85 | #[test] 86 | fn assert_extra_data_less_32bytes() { 87 | let extra_data = default_extra_data(); 88 | assert!( 89 | extra_data.len() <= 32, 90 | "extra data must be less than 32 bytes: {extra_data}" 91 | ) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /crates/postage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "postage" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | bmt = { path = "../bmt" } 10 | chrono = { workspace = true } 11 | ethers-core = { workspace = true } 12 | ethers-signers = { workspace = true } 13 | hex = { workspace = true } 14 | once_cell = { workspace = true } 15 | lazy_static = "1.4.0" 16 | tiny-keccak = { workspace = true } 17 | tracing = { workspace = true } 18 | thiserror = { workspace = true } 19 | serde = { workspace = true } 20 | serde_json = { workspace = true } 21 | tokio = { workspace = true } -------------------------------------------------------------------------------- /crates/postage/src/batch.rs: -------------------------------------------------------------------------------- 1 | use ethers_core::types::Address; 2 | use serde::{Deserialize, Serialize}; 3 | use std::collections::HashMap; 4 | use std::sync::{Arc, Mutex}; 5 | use thiserror::Error; 6 | use tracing::error; 7 | 8 | use crate::stamp::{MarshalledStamp, Stamp, StampError, StampValidator, ValidateStamp}; 9 | use bmt::chunk::Chunk; 10 | 11 | pub type BatchId = [u8; 32]; 12 | 13 | #[derive(Debug, Clone, Serialize, Deserialize)] 14 | pub struct Batch { 15 | pub id: BatchId, // the batch id 16 | value: u128, // normalised balance of the batch 17 | pub block_created: Option, // block number the batch was created 18 | pub(crate) owner: Address, // owner of the batch 19 | pub depth: u32, // depth of the batch 20 | pub bucket_depth: u32, // depth of the bucket 21 | pub immutable: bool, // whether the batch is immutable 22 | } 23 | 24 | impl Batch { 25 | pub fn new( 26 | id: BatchId, 27 | value: u128, 28 | start: Option, 29 | owner: Address, 30 | depth: u32, 31 | bucket_depth: u32, 32 | immutable: bool, 33 | ) -> Self { 34 | Self { 35 | id, 36 | value, 37 | block_created: start, 38 | owner, 39 | depth, 40 | bucket_depth, 41 | immutable, 42 | } 43 | } 44 | 45 | pub fn id(&self) -> BatchId { 46 | self.id 47 | } 48 | 49 | pub fn owner(&self) -> Address { 50 | self.owner 51 | } 52 | 53 | pub fn depth(&self) -> u32 { 54 | self.depth 55 | } 56 | 57 | pub fn bucket_depth(&self) -> u32 { 58 | self.bucket_depth 59 | } 60 | } 61 | 62 | /// An error involving the batch store 63 | #[derive(Debug, Error)] 64 | pub enum BatchStoreError { 65 | /// When a batch is not found 66 | #[error("Batch not found")] 67 | BatchNotFound(BatchId), 68 | } 69 | 70 | pub(crate) struct Store { 71 | pub batches: Arc>>, 72 | } 73 | 74 | impl Store { 75 | pub fn new() -> Self { 76 | Self { 77 | batches: Arc::new(Mutex::new(HashMap::new())), 78 | } 79 | } 80 | 81 | pub fn load(batches: HashMap) -> Self { 82 | Self { 83 | batches: Arc::new(Mutex::new(batches)), 84 | } 85 | } 86 | 87 | pub fn get(&self, id: BatchId) -> Option { 88 | self.batches.lock().unwrap().get(&id).cloned() 89 | } 90 | 91 | pub fn insert(&self, batch: Batch) { 92 | self.batches.lock().unwrap().insert(batch.id, batch); 93 | } 94 | 95 | pub fn exists(&self, id: BatchId) -> bool { 96 | self.batches.lock().unwrap().contains_key(&id) 97 | } 98 | } 99 | 100 | impl StampValidator for Store { 101 | fn validate_stamp<'a>(&'a self) -> ValidateStamp<'a> { 102 | let store = self.clone(); 103 | Box::new(move |chunk: &mut Chunk, stamp: MarshalledStamp| { 104 | let stamp = Stamp::from(stamp); 105 | match store.get(stamp.batch()) { 106 | Some(batch) => { 107 | match stamp.valid(chunk, batch.owner, batch.depth, batch.bucket_depth) { 108 | Ok(_valid) => { 109 | chunk.add_stamp(stamp.into()); 110 | Ok(()) 111 | } 112 | Err(e) => Err(e), 113 | } 114 | } 115 | None => { 116 | error!("Batch not found: {:?}", stamp.batch()); 117 | Err(StampError::BatchNotFound(stamp.batch())) 118 | } 119 | } 120 | }) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /crates/postage/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(async_closure)] 2 | #![feature(associated_type_bounds)] 3 | 4 | pub mod batch; 5 | pub mod pat; 6 | pub mod stamp; 7 | -------------------------------------------------------------------------------- /crates/storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vertex-storage" 3 | version.workspace = true 4 | edition.workspace = true 5 | rust-version.workspace = true 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | 10 | [dependencies] 11 | bytes.workspace = true 12 | serde = { workspace = true, default-features = false } 13 | thiserror.workspace = true 14 | 15 | [lints] 16 | workspace = true 17 | -------------------------------------------------------------------------------- /crates/swarm-api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vertex-swarm-api" 3 | version.workspace = true 4 | edition.workspace = true 5 | rust-version.workspace = true 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | 10 | [dependencies] 11 | # Internal dependencies 12 | vertex-primitives = { workspace = true } 13 | 14 | # External dependencies 15 | auto_impl = "1.1" 16 | futures = { workspace = true } 17 | thiserror = { workspace = true } 18 | async-trait = "0.1" 19 | alloy-primitives = { workspace = true } 20 | 21 | [features] 22 | default = ["std"] 23 | std = ["vertex-primitives/std"] 24 | -------------------------------------------------------------------------------- /crates/swarm-api/src/access.rs: -------------------------------------------------------------------------------- 1 | //! Access control traits (authentication, authorization, accounting) 2 | //! 3 | //! This module defines the traits for controlling access to chunks in the Swarm network. 4 | 5 | use alloc::boxed::Box; 6 | use async_trait::async_trait; 7 | use core::fmt::Debug; 8 | use vertex_primitives::{ChunkAddress, Result}; 9 | 10 | use crate::chunk::Chunk; 11 | 12 | /// Trait for access control credentials 13 | pub trait Credential: Clone + Debug + Send + Sync + 'static {} 14 | 15 | /// Authentication trait for verifying chunk credentials 16 | #[auto_impl::auto_impl(&, Arc)] 17 | pub trait Authenticator: Send + Sync + 'static { 18 | /// The credential type used by this authenticator 19 | type Credential: Credential; 20 | 21 | /// Authenticate a chunk with the given credential 22 | fn authenticate(&self, chunk: &dyn Chunk, credential: &Self::Credential) -> Result<()>; 23 | 24 | /// Authenticate a retrieval request with optional credential 25 | fn authenticate_retrieval( 26 | &self, 27 | address: &ChunkAddress, 28 | credential: Option<&Self::Credential>, 29 | ) -> Result<()>; 30 | } 31 | 32 | /// Authorization trait for determining if chunks can be stored or retrieved 33 | #[auto_impl::auto_impl(&, Arc)] 34 | pub trait Authorizer: Send + Sync + 'static { 35 | /// Authorize a chunk to be stored 36 | fn authorize_storage(&self, chunk: &dyn Chunk) -> Result<()>; 37 | 38 | /// Authorize a chunk to be retrieved 39 | fn authorize_retrieval(&self, address: &ChunkAddress) -> Result<()>; 40 | } 41 | 42 | /// Accounting trait for tracking resource usage 43 | #[auto_impl::auto_impl(&, Arc)] 44 | pub trait Accountant: Send + Sync + 'static { 45 | /// Record that a chunk is being stored 46 | fn record_storage(&self, chunk: &dyn Chunk) -> Result<()>; 47 | 48 | /// Record that a chunk is being retrieved 49 | fn record_retrieval(&self, address: &ChunkAddress) -> Result<()>; 50 | 51 | /// Get available storage capacity 52 | fn available_capacity(&self) -> usize; 53 | 54 | /// Check if storage capacity is available for a chunk of the given size 55 | fn has_capacity_for(&self, size: usize) -> bool; 56 | } 57 | 58 | /// Combined access control for chunks 59 | #[auto_impl::auto_impl(&, Arc)] 60 | pub trait AccessController: Send + Sync + 'static { 61 | /// The credential type used by this controller 62 | type Credential: Credential; 63 | 64 | /// Check if a chunk can be stored with the given credential 65 | fn check_storage_permission( 66 | &self, 67 | chunk: &dyn Chunk, 68 | credential: &Self::Credential, 69 | ) -> Result<()>; 70 | 71 | /// Check if a chunk can be retrieved 72 | fn check_retrieval_permission( 73 | &self, 74 | address: &ChunkAddress, 75 | credential: Option<&Self::Credential>, 76 | ) -> Result<()>; 77 | 78 | /// Record that a chunk has been stored 79 | fn record_storage(&self, chunk: &dyn Chunk) -> Result<()>; 80 | 81 | /// Record that a chunk has been retrieved 82 | fn record_retrieval(&self, address: &ChunkAddress) -> Result<()>; 83 | } 84 | 85 | /// Factory for creating access controllers 86 | #[auto_impl::auto_impl(&, Arc)] 87 | pub trait AccessControllerFactory: Send + Sync + 'static { 88 | /// The type of access controller this factory creates 89 | type Controller: AccessController; 90 | 91 | /// Create a new access controller 92 | fn create_controller(&self, config: &AccessConfig) -> Result; 93 | } 94 | 95 | /// Configuration for access control 96 | #[derive(Debug, Clone)] 97 | pub struct AccessConfig { 98 | /// Maximum chunk size allowed 99 | pub max_chunk_size: usize, 100 | /// Maximum storage capacity 101 | pub max_capacity: u64, 102 | /// Whether to enforce strict authentication 103 | pub strict_authentication: bool, 104 | /// Authentication parameters 105 | pub auth_params: Vec<(String, String)>, 106 | } 107 | -------------------------------------------------------------------------------- /crates/swarm-api/src/chunk.rs: -------------------------------------------------------------------------------- 1 | //! Chunk-related traits and types 2 | //! 3 | //! This module defines the core abstraction for data in the Swarm network. 4 | 5 | use alloc::{boxed::Box, vec::Vec}; 6 | use core::fmt::Debug; 7 | use vertex_primitives::{ChunkAddress, Result}; 8 | 9 | /// Chunk type identifier 10 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 11 | pub enum ChunkType { 12 | /// Content-addressed chunk (default) 13 | ContentAddressed, 14 | /// Encrypted chunk 15 | Encrypted, 16 | /// Manifest chunk for organizing other chunks 17 | Manifest, 18 | /// Custom chunk type with identifier 19 | Custom(u8), 20 | } 21 | 22 | /// Core trait that all chunk types must implement 23 | #[auto_impl::auto_impl(&, Box)] 24 | pub trait Chunk: Send + Sync + Debug + 'static { 25 | /// Returns the address of the chunk 26 | fn address(&self) -> ChunkAddress; 27 | 28 | /// Returns the data contained in the chunk 29 | fn data(&self) -> &[u8]; 30 | 31 | /// Returns the size of the chunk in bytes 32 | fn size(&self) -> usize { 33 | self.data().len() 34 | } 35 | 36 | /// Returns the chunk type 37 | fn chunk_type(&self) -> ChunkType; 38 | 39 | /// Verifies the integrity of the chunk 40 | fn verify_integrity(&self) -> bool; 41 | 42 | /// Clone this chunk as a boxed trait object 43 | fn clone_box(&self) -> Box; 44 | } 45 | 46 | /// Trait for creating new chunks 47 | #[auto_impl::auto_impl(&, Arc)] 48 | pub trait ChunkFactory: Send + Sync + 'static { 49 | /// Create a new chunk from data 50 | fn create(&self, data: Vec) -> Result>; 51 | 52 | /// Create a chunk with a known address 53 | fn create_with_address(&self, address: ChunkAddress, data: Vec) -> Result>; 54 | 55 | /// Returns the type of chunks this factory creates 56 | fn chunk_type(&self) -> ChunkType; 57 | } 58 | 59 | /// A collection of chunk factories for different chunk types 60 | #[auto_impl::auto_impl(&, Arc)] 61 | pub trait ChunkRegistry: Send + Sync + 'static { 62 | /// Register a new chunk factory 63 | fn register_factory(&mut self, factory: Box); 64 | 65 | /// Get a factory for a specific chunk type 66 | fn factory_for(&self, chunk_type: ChunkType) -> Option<&dyn ChunkFactory>; 67 | 68 | /// Get the default factory 69 | fn default_factory(&self) -> &dyn ChunkFactory; 70 | 71 | /// Create a new chunk using the appropriate factory 72 | fn create_chunk(&self, data: Vec, chunk_type: Option) -> Result>; 73 | } 74 | -------------------------------------------------------------------------------- /crates/swarm-api/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Core API traits for the Vertex Swarm node 2 | //! 3 | //! This crate defines the fundamental traits and interfaces that all Vertex 4 | //! Swarm implementations must satisfy. 5 | 6 | #![cfg_attr(not(feature = "std"), no_std)] 7 | #![warn(missing_docs)] 8 | 9 | extern crate alloc; 10 | 11 | use async_trait::async_trait; 12 | use vertex_primitives::{ChunkAddress, Error, Result}; 13 | 14 | /// Chunk-related traits 15 | pub mod chunk; 16 | pub use chunk::*; 17 | 18 | /// Access control traits (authentication, authorization, accounting) 19 | pub mod access; 20 | pub use access::*; 21 | 22 | /// Storage-related traits 23 | pub mod storage; 24 | pub use storage::*; 25 | 26 | /// Network-related traits 27 | pub mod network; 28 | pub use network::*; 29 | 30 | /// Node type traits 31 | pub mod node; 32 | pub use node::*; 33 | 34 | /// Bandwidth management traits 35 | pub mod bandwidth; 36 | pub use bandwidth::*; 37 | 38 | /// Protocol-related traits 39 | pub mod protocol; 40 | pub use protocol::*; 41 | 42 | /// Common types and structures 43 | pub mod types; 44 | pub use types::*; 45 | 46 | // Re-export common primitives for convenience 47 | pub use vertex_primitives::{Address, ChunkAddress, Error, PeerId, Result, B256, U256}; 48 | -------------------------------------------------------------------------------- /crates/swarm-forks/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vertex-swarm-forks" 3 | version.workspace = true 4 | edition.workspace = true 5 | rust-version.workspace = true 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | description = "Swarm fork types used in vertex" 10 | 11 | [lints] 12 | workspace = true 13 | 14 | [dependencies] 15 | # vertex 16 | vertex-network-primitives.workspace = true 17 | 18 | # ethereum 19 | alloy-eip2124.workspace = true 20 | alloy-primitives.workspace = true 21 | 22 | # misc 23 | serde = { workspace = true, features = ["derive"], optional = true } 24 | dyn-clone.workspace = true 25 | once_cell.workspace = true 26 | rustc-hash = { workspace = true, optional = true } 27 | 28 | # arbitrary 29 | arbitrary = { workspace = true, features = ["derive"], optional = true } 30 | auto_impl.workspace = true 31 | 32 | [dev-dependencies] 33 | arbitrary = { workspace = true, features = ["derive"] } 34 | 35 | [features] 36 | default = ["std", "serde", "rustc-hash"] 37 | arbitrary = [ 38 | "dep:arbitrary", 39 | "alloy-primitives/arbitrary", 40 | "alloy-eip2124/arbitrary", 41 | ] 42 | serde = [ 43 | "dep:serde", 44 | "alloy-primitives/serde", 45 | "alloy-eip2124/serde", 46 | ] 47 | std = [ 48 | "alloy-primitives/std", 49 | "rustc-hash/std", 50 | "serde?/std", 51 | "alloy-eip2124/std", 52 | "once_cell/std", 53 | ] 54 | rustc-hash = ["dep:rustc-hash"] 55 | -------------------------------------------------------------------------------- /crates/swarm-forks/src/display.rs: -------------------------------------------------------------------------------- 1 | use crate::{hardforks::Hardforks, ForkCondition}; 2 | use alloc::{ 3 | format, 4 | string::{String, ToString}, 5 | vec::Vec, 6 | }; 7 | 8 | /// A container to pretty-print a hardfork. 9 | /// 10 | /// The fork is formatted as: `{name} <({eip})> @{condition}` 11 | /// where the EIP part is optional. 12 | #[derive(Debug)] 13 | struct DisplayFork { 14 | /// The name of the hardfork (e.g. Frontier) 15 | name: String, 16 | /// The fork condition (timestamp) 17 | activated_at: ForkCondition, 18 | /// An optional EIP (e.g. `EIP-1`). 19 | eip: Option, 20 | } 21 | 22 | impl core::fmt::Display for DisplayFork { 23 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 24 | let name_with_eip = if let Some(eip) = &self.eip { 25 | format!("{} ({})", self.name, eip) 26 | } else { 27 | self.name.clone() 28 | }; 29 | 30 | match self.activated_at { 31 | ForkCondition::Block(at) | ForkCondition::Timestamp(at) => { 32 | write!(f, "{name_with_eip:32} @{at}")?; 33 | } 34 | ForkCondition::Never => unreachable!(), 35 | } 36 | 37 | Ok(()) 38 | } 39 | } 40 | 41 | /// A container for pretty-printing a list of hardforks. 42 | /// 43 | /// An example of the output: 44 | /// 45 | /// ```text 46 | /// Hard forks (timestamp based): 47 | /// - Frontier @1631112000 48 | /// - FutureHardfork @1799999999 49 | /// ``` 50 | #[derive(Debug)] 51 | pub struct DisplayHardforks { 52 | /// A list of hardforks 53 | hardforks: Vec, 54 | } 55 | 56 | impl core::fmt::Display for DisplayHardforks { 57 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 58 | writeln!(f, "Hard forks (timestamp based):")?; 59 | for fork in &self.hardforks { 60 | writeln!(f, "- {fork}")?; 61 | } 62 | Ok(()) 63 | } 64 | } 65 | 66 | impl DisplayHardforks { 67 | /// Creates a new [`DisplayHardforks`] from an iterator of hardforks. 68 | pub fn new(hardforks: &H) -> Self { 69 | let mut hardforks_vec = Vec::new(); 70 | 71 | for (fork, condition) in hardforks.forks_iter() { 72 | if condition != ForkCondition::Never { 73 | let display_fork = DisplayFork { 74 | name: fork.name().to_string(), 75 | activated_at: condition, 76 | eip: None, 77 | }; 78 | hardforks_vec.push(display_fork); 79 | } 80 | } 81 | 82 | Self { 83 | hardforks: hardforks_vec, 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /crates/swarm-forks/src/hardfork/dev.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec; 2 | 3 | use once_cell as _; 4 | #[cfg(not(feature = "std"))] 5 | use once_cell::sync::Lazy as LazyLock; 6 | #[cfg(feature = "std")] 7 | use std::sync::LazyLock; 8 | 9 | use crate::{ForkCondition, Hardfork, SwarmHardfork, SwarmHardforks}; 10 | 11 | /// Dev hardforks - all active at timestamp 0 for development purposes 12 | pub static DEV_HARDFORKS: LazyLock = LazyLock::new(|| { 13 | SwarmHardforks::new(vec![( 14 | SwarmHardfork::Frontier.boxed(), 15 | ForkCondition::Timestamp(0), 16 | )]) 17 | }); 18 | -------------------------------------------------------------------------------- /crates/swarm-forks/src/hardfork/macros.rs: -------------------------------------------------------------------------------- 1 | /// Macro that defines different variants of a swarm specific enum. See [`crate::Hardfork`] as an 2 | /// example. 3 | #[macro_export] 4 | macro_rules! hardfork { 5 | ($(#[$enum_meta:meta])* $enum:ident { $( $(#[$meta:meta])* $variant:ident ),* $(,)? }) => { 6 | $(#[$enum_meta])* 7 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 8 | #[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] 9 | pub enum $enum { 10 | $( $(#[$meta])* $variant ),* 11 | } 12 | 13 | impl $enum { 14 | /// Returns variant as `str`. 15 | pub const fn name(&self) -> &'static str { 16 | match self { 17 | $( $enum::$variant => stringify!($variant), )* 18 | } 19 | } 20 | } 21 | 22 | impl FromStr for $enum { 23 | type Err = String; 24 | 25 | fn from_str(s: &str) -> Result { 26 | match s.to_lowercase().as_str() { 27 | $( 28 | s if s == stringify!($variant).to_lowercase() => Ok($enum::$variant), 29 | )* 30 | _ => return Err(format!("Unknown hardfork: {s}")), 31 | } 32 | } 33 | } 34 | 35 | impl Hardfork for $enum { 36 | fn name(&self) -> &'static str { 37 | self.name() 38 | } 39 | } 40 | 41 | impl Display for $enum { 42 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 43 | write!(f, "{self:?}") 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /crates/swarm-forks/src/hardfork/mod.rs: -------------------------------------------------------------------------------- 1 | mod macros; 2 | 3 | mod swarm; 4 | pub use swarm::SwarmHardfork; 5 | 6 | mod dev; 7 | pub use dev::DEV_HARDFORKS; 8 | 9 | use alloc::boxed::Box; 10 | use core::{ 11 | any::Any, 12 | hash::{Hash, Hasher}, 13 | }; 14 | use dyn_clone::DynClone; 15 | 16 | /// Generic hardfork trait. 17 | /// 18 | /// This trait is implemented by all hardfork types and provides a common 19 | /// interface for working with hardforks. 20 | #[auto_impl::auto_impl(&, Box)] 21 | pub trait Hardfork: Any + DynClone + Send + Sync + 'static { 22 | /// Returns the fork name. 23 | fn name(&self) -> &'static str; 24 | 25 | /// Returns boxed value. 26 | fn boxed(&self) -> Box { 27 | Box::new(self) 28 | } 29 | } 30 | 31 | dyn_clone::clone_trait_object!(Hardfork); 32 | 33 | impl core::fmt::Debug for dyn Hardfork + 'static { 34 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 35 | f.debug_struct(self.name()).finish() 36 | } 37 | } 38 | 39 | impl PartialEq for dyn Hardfork + 'static { 40 | fn eq(&self, other: &Self) -> bool { 41 | self.name() == other.name() 42 | } 43 | } 44 | 45 | impl Eq for dyn Hardfork + 'static {} 46 | 47 | impl Hash for dyn Hardfork + 'static { 48 | fn hash(&self, state: &mut H) { 49 | self.name().hash(state) 50 | } 51 | } 52 | 53 | #[cfg(test)] 54 | mod tests { 55 | use super::*; 56 | use std::str::FromStr; 57 | 58 | #[test] 59 | fn check_hardfork_from_str() { 60 | let hardfork_str = ["frOntier"]; 61 | let expected_hardforks = [SwarmHardfork::Frontier]; 62 | 63 | let hardforks: Vec = hardfork_str 64 | .iter() 65 | .map(|h| SwarmHardfork::from_str(h).unwrap()) 66 | .collect(); 67 | 68 | assert_eq!(hardforks, expected_hardforks); 69 | } 70 | 71 | #[test] 72 | fn check_nonexistent_hardfork_from_str() { 73 | assert!(SwarmHardfork::from_str("not a hardfork").is_err()); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /crates/swarm-forks/src/hardfork/swarm.rs: -------------------------------------------------------------------------------- 1 | use crate::{hardfork, ForkCondition, Hardfork, SwarmHardforks}; 2 | use alloc::{boxed::Box, format, string::String}; 3 | use core::{ 4 | fmt, 5 | fmt::{Display, Formatter}, 6 | str::FromStr, 7 | }; 8 | #[cfg(feature = "serde")] 9 | use serde::{Deserialize, Serialize}; 10 | use vertex_network_primitives::{NamedSwarm, Swarm, SwarmKind}; 11 | 12 | hardfork!( 13 | /// The name of a Swarm hardfork. 14 | SwarmHardfork { 15 | /// Frontier: The initial network launch. 16 | Frontier 17 | } 18 | ); 19 | 20 | impl SwarmHardfork { 21 | /// Mainnet timestamp for the hardfork activation (June 9, 2021 16:19:47 UTC) 22 | pub const MAINNET_GENESIS_TIMESTAMP: u64 = 1623255587; 23 | 24 | /// Testnet timestamp for the hardfork activation (June 9, 2021 16:19:47 UTC) 25 | pub const TESTNET_GENESIS_TIMESTAMP: u64 = 1623255587; 26 | 27 | /// Retrieves the activation timestamp for the specified hardfork on the given swarm. 28 | pub fn activation_timestamp(&self, swarm: Swarm) -> Option { 29 | match swarm.kind() { 30 | SwarmKind::Named(named) => match named { 31 | NamedSwarm::Mainnet => self.mainnet_activation_timestamp(), 32 | NamedSwarm::Testnet => self.testnet_activation_timestamp(), 33 | NamedSwarm::Dev => Some(0), 34 | _ => None, 35 | }, 36 | SwarmKind::Id(_) => None, 37 | } 38 | } 39 | 40 | /// Retrieves the activation timestamp for the specified hardfork on the mainnet. 41 | pub const fn mainnet_activation_timestamp(&self) -> Option { 42 | match self { 43 | Self::Frontier => Some(Self::MAINNET_GENESIS_TIMESTAMP), 44 | // Add additional hardforks here as they are defined 45 | } 46 | } 47 | 48 | /// Retrieves the activation timestamp for the specified hardfork on the testnet. 49 | pub const fn testnet_activation_timestamp(&self) -> Option { 50 | match self { 51 | Self::Frontier => Some(Self::TESTNET_GENESIS_TIMESTAMP), 52 | // Add additional hardforks here as they are defined 53 | } 54 | } 55 | 56 | /// Mainnet list of hardforks. 57 | pub const fn mainnet() -> [(Self, ForkCondition); 1] { 58 | [( 59 | Self::Frontier, 60 | ForkCondition::Timestamp(Self::MAINNET_GENESIS_TIMESTAMP), 61 | )] 62 | } 63 | 64 | /// Testnet list of hardforks. 65 | pub const fn testnet() -> [(Self, ForkCondition); 1] { 66 | [( 67 | Self::Frontier, 68 | ForkCondition::Timestamp(Self::TESTNET_GENESIS_TIMESTAMP), 69 | )] 70 | } 71 | } 72 | 73 | impl From<[(SwarmHardfork, ForkCondition); N]> for SwarmHardforks { 74 | fn from(list: [(SwarmHardfork, ForkCondition); N]) -> Self { 75 | Self::new( 76 | list.into_iter() 77 | .map(|(fork, cond)| (Box::new(fork) as Box, cond)) 78 | .collect(), 79 | ) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /crates/swarm-forks/src/hardforks/swarm.rs: -------------------------------------------------------------------------------- 1 | use crate::{ForkCondition, SwarmHardfork}; 2 | 3 | /// Helper methods for Swarm forks. 4 | /// 5 | /// This trait provides convenience methods for checking the activation status 6 | /// of various hardforks. 7 | #[auto_impl::auto_impl(&, Arc)] 8 | pub trait SwarmHardforksTrait: Clone { 9 | /// Retrieves [`ForkCondition`] by an [`SwarmHardfork`]. If `fork` is not present, returns 10 | /// [`ForkCondition::Never`]. 11 | fn swarm_fork_activation(&self, fork: SwarmHardfork) -> ForkCondition; 12 | 13 | /// Convenience method to check if an [`SwarmHardfork`] is active at a given timestamp. 14 | fn is_swarm_fork_active_at_timestamp(&self, fork: SwarmHardfork, timestamp: u64) -> bool { 15 | self.swarm_fork_activation(fork) 16 | .active_at_timestamp(timestamp) 17 | } 18 | 19 | /// Convenience method to check if an [`SwarmHardfork`] is active at a given block number. 20 | fn is_swarm_fork_active_at_block(&self, fork: SwarmHardfork, block_number: u64) -> bool { 21 | self.swarm_fork_activation(fork) 22 | .active_at_block(block_number) 23 | } 24 | 25 | /// Convenience method to check if [`SwarmHardfork::Frontier`] is active at a given timestamp. 26 | fn is_frontier_active_at_timestamp(&self, timestamp: u64) -> bool { 27 | self.is_swarm_fork_active_at_timestamp(SwarmHardfork::Frontier, timestamp) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /crates/swarm-forks/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Swarm fork types for the network. 2 | //! 3 | //! This crate contains network fork types and helper functions for managing 4 | //! hardforks in a timestamp-based activation model. 5 | //! 6 | //! ## Feature Flags 7 | //! 8 | //! - `arbitrary`: Adds `arbitrary` support for primitive types. 9 | //! - `serde`: Adds serialization/deserialization capabilities. 10 | //! - `std`: Uses standard library (default feature). 11 | //! - `rustc-hash`: Uses rustc's hash implementation (default feature). 12 | 13 | #![cfg_attr(not(test), warn(unused_crate_dependencies))] 14 | #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] 15 | #![cfg_attr(not(feature = "std"), no_std)] 16 | 17 | extern crate alloc; 18 | 19 | /// Re-exported EIP-2124 forkid types for network compatibility. 20 | pub use alloy_eip2124::*; 21 | 22 | mod display; 23 | mod forkcondition; 24 | mod hardfork; 25 | mod hardforks; 26 | 27 | pub use hardfork::{Hardfork, SwarmHardfork, DEV_HARDFORKS}; 28 | 29 | pub use display::DisplayHardforks; 30 | pub use forkcondition::ForkCondition; 31 | pub use hardforks::*; 32 | 33 | #[cfg(any(test, feature = "arbitrary"))] 34 | pub use arbitrary; 35 | -------------------------------------------------------------------------------- /crates/swarmspec/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vertex-swarmspec" 3 | version.workspace = true 4 | edition.workspace = true 5 | rust-version.workspace = true 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | 10 | [lints] 11 | workspace = true 12 | 13 | [dependencies] 14 | # ethereum 15 | alloy-eip2124.workspace = true 16 | alloy-chains = { workspace = true, features = ["serde"] } 17 | alloy-primitives = { workspace = true, features = ["serde"] } 18 | 19 | # swarm forks 20 | vertex-swarm-forks.workspace = true 21 | 22 | # networking 23 | libp2p.workspace = true 24 | vertex-network-primitives.workspace = true 25 | vertex-network-primitives-traits.workspace = true 26 | 27 | # misc 28 | auto_impl.workspace = true 29 | serde = { workspace = true, features = ["derive"] } 30 | 31 | [dev-dependencies] 32 | # Test utilities 33 | proptest = { workspace = true } 34 | arbitrary = { workspace = true } 35 | 36 | [features] 37 | default = ["std"] 38 | std = [ 39 | "alloy-chains/std", 40 | "alloy-primitives/std", 41 | "vertex-swarm-forks/std", 42 | "serde/std", 43 | ] 44 | arbitrary = [ 45 | "alloy-chains/arbitrary", 46 | "alloy-primitives/arbitrary", 47 | "vertex-swarm-forks/arbitrary", 48 | ] 49 | test-utils = [] 50 | -------------------------------------------------------------------------------- /crates/swarmspec/src/api.rs: -------------------------------------------------------------------------------- 1 | //! API for interacting with Swarm network specifications 2 | 3 | use crate::{LightClient, NetworkSpec, Storage, Token}; 4 | use alloc::vec::Vec; 5 | use alloy_chains::Chain; 6 | use core::fmt::Debug; 7 | use libp2p::Multiaddr; 8 | use vertex_network_primitives::Swarm; 9 | use vertex_swarm_forks::{ForkCondition, SwarmHardfork}; 10 | 11 | /// Trait representing type configuring a Swarm network specification 12 | #[auto_impl::auto_impl(&, Arc)] 13 | pub trait SwarmSpec: Send + Sync + Unpin + Debug { 14 | /// Returns the corresponding Swarm network 15 | fn swarm(&self) -> Swarm; 16 | 17 | /// Returns the [`Chain`] object this spec targets 18 | fn chain(&self) -> Chain; 19 | 20 | /// Returns the network ID for the Swarm network 21 | fn network_id(&self) -> u64; 22 | 23 | /// Returns the Swarm network name (like "mainnet", "testnet", etc.) 24 | fn network_name(&self) -> &str; 25 | 26 | /// Returns the bootnodes for the network 27 | fn bootnodes(&self) -> Vec; 28 | 29 | /// Returns the storage configuration 30 | fn storage(&self) -> &Storage; 31 | 32 | /// Returns the bandwidth incentives configuration 33 | fn bandwidth(&self) -> &LightClient; 34 | 35 | /// Returns the Swarm token details 36 | fn token(&self) -> &Token; 37 | 38 | /// Returns the fork activation status for a given Swarm hardfork at a timestamp 39 | fn is_fork_active_at_timestamp(&self, fork: SwarmHardfork, timestamp: u64) -> bool; 40 | 41 | /// Returns whether this is the mainnet Swarm 42 | fn is_mainnet(&self) -> bool { 43 | self.network_id() == 1 44 | } 45 | 46 | /// Returns whether this is a testnet Swarm 47 | fn is_testnet(&self) -> bool { 48 | self.network_id() == 10 49 | } 50 | } 51 | 52 | impl SwarmSpec for NetworkSpec { 53 | fn swarm(&self) -> Swarm { 54 | match self.network_id { 55 | 1 => vertex_network_primitives::NamedSwarm::Mainnet.into(), 56 | 10 => vertex_network_primitives::NamedSwarm::Testnet.into(), 57 | _ => Swarm::from_id(self.network_id), 58 | } 59 | } 60 | 61 | fn chain(&self) -> Chain { 62 | self.chain 63 | } 64 | 65 | fn network_id(&self) -> u64 { 66 | self.network_id 67 | } 68 | 69 | fn network_name(&self) -> &str { 70 | &self.network_name 71 | } 72 | 73 | fn bootnodes(&self) -> Vec { 74 | self.bootnodes.clone() 75 | } 76 | 77 | fn storage(&self) -> &Storage { 78 | &self.storage 79 | } 80 | 81 | fn bandwidth(&self) -> &LightClient { 82 | &self.light_client 83 | } 84 | 85 | fn token(&self) -> &Token { 86 | &self.token 87 | } 88 | 89 | fn is_fork_active_at_timestamp(&self, fork: SwarmHardfork, timestamp: u64) -> bool { 90 | match self.hardforks.get(fork) { 91 | Some(ForkCondition::Timestamp(activation_time)) => timestamp >= activation_time, 92 | _ => false, 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /crates/swarmspec/src/constants.rs: -------------------------------------------------------------------------------- 1 | //! Constants used throughout the Swarm network specification 2 | 3 | use crate::{StorageContracts, Token}; 4 | use alloy_primitives::address; 5 | 6 | /// Mainnet constants 7 | pub mod mainnet { 8 | use super::*; 9 | 10 | /// Swarm mainnet network ID 11 | pub const NETWORK_ID: u64 = 1; 12 | 13 | /// Swarm mainnet network name 14 | pub const NETWORK_NAME: &str = "mainnet"; 15 | 16 | /// Swarm token on mainnet (BZZ) 17 | pub const TOKEN: Token = Token { 18 | address: address!("2ac3c1d3e24b45c6c310534bc2dd84b5ed576335"), 19 | name: "Swarm", 20 | symbol: "BZZ", 21 | decimals: 16, 22 | }; 23 | 24 | /// Storage contracts for mainnet 25 | pub const STORAGE_CONTRACTS: StorageContracts = StorageContracts { 26 | postage: address!("5b53f7a1975eb212d4b20b7cdd443baa189af7c9"), 27 | redistribution: address!("eb210c2e166f61b3fd32246d53893f8b9d2a624c"), 28 | staking: Some(address!("0c6aa197271466f0afe3818ca03ac47d8f5c2f8a")), 29 | }; 30 | } 31 | 32 | /// Testnet (Sepolia) constants 33 | pub mod testnet { 34 | use super::*; 35 | 36 | /// Swarm testnet network ID 37 | pub const NETWORK_ID: u64 = 10; 38 | 39 | /// Swarm testnet network name 40 | pub const NETWORK_NAME: &str = "testnet"; 41 | 42 | /// Swarm token on testnet (tBZZ) 43 | pub const TOKEN: Token = Token { 44 | address: address!("6e01ee6183721ae9a006fd4906970c1583863765"), 45 | name: "Test Swarm", 46 | symbol: "tBZZ", 47 | decimals: 16, 48 | }; 49 | 50 | /// Storage contracts for testnet 51 | pub const STORAGE_CONTRACTS: StorageContracts = StorageContracts { 52 | postage: address!("621c2e0fa5ed488c7124eb55cc7eb3af75d0d9e8"), 53 | redistribution: address!("fb6c7d33be1fb12f4c5da71df7c9d5c22970ba7a"), 54 | staking: Some(address!("6f252dd6f340f6c6d2f6ee8954b011dd5aba4350")), 55 | }; 56 | } 57 | 58 | /// Default values for development networks 59 | pub mod dev { 60 | use super::*; 61 | use alloy_primitives::Address; 62 | 63 | /// Default network name for development 64 | pub const NETWORK_NAME: &str = "dev"; 65 | 66 | /// Default Swarm token for development 67 | pub const TOKEN: Token = Token { 68 | address: Address::ZERO, 69 | name: "Dev Swarm", 70 | symbol: "dBZZ", 71 | decimals: 16, 72 | }; 73 | 74 | /// Default storage contracts for development 75 | pub const STORAGE_CONTRACTS: StorageContracts = StorageContracts { 76 | postage: Address::ZERO, 77 | redistribution: Address::ZERO, 78 | staking: Some(Address::ZERO), 79 | }; 80 | } 81 | -------------------------------------------------------------------------------- /crates/swarmspec/src/contracts.rs: -------------------------------------------------------------------------------- 1 | //! Smart contract definitions for Swarm 2 | 3 | use alloy_primitives::{Address, B256}; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | /// Trait for contract addresses on chain 7 | pub trait ContractAddress { 8 | /// Returns the contract's deployed address 9 | fn address(&self) -> Address; 10 | 11 | /// Returns the block number when the contract was deployed 12 | fn deployment_block(&self) -> u64; 13 | 14 | /// Returns the transaction hash of the deployment transaction 15 | fn deployment_tx(&self) -> B256; 16 | } 17 | 18 | /// Swarm token (BZZ) contract details 19 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 20 | pub struct SwarmToken { 21 | /// Contract address 22 | pub address: Address, 23 | /// Token name 24 | pub name: &'static str, 25 | /// Token symbol 26 | pub symbol: &'static str, 27 | /// Decimal places 28 | pub decimals: u8, 29 | } 30 | 31 | impl ContractAddress for SwarmToken { 32 | fn address(&self) -> Address { 33 | self.address 34 | } 35 | 36 | fn deployment_block(&self) -> u64 { 37 | 0 // Placeholder, actual deployment block would be set in implementation 38 | } 39 | 40 | fn deployment_tx(&self) -> B256 { 41 | B256::ZERO // Placeholder 42 | } 43 | } 44 | 45 | /// Postage stamp contract details 46 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 47 | pub struct PostageContract { 48 | /// Contract address 49 | pub address: Address, 50 | /// Deployment block number 51 | pub deployment_block: u64, 52 | /// Deployment transaction hash 53 | pub deployment_tx: B256, 54 | } 55 | 56 | impl ContractAddress for PostageContract { 57 | fn address(&self) -> Address { 58 | self.address 59 | } 60 | 61 | fn deployment_block(&self) -> u64 { 62 | self.deployment_block 63 | } 64 | 65 | fn deployment_tx(&self) -> B256 { 66 | self.deployment_tx 67 | } 68 | } 69 | 70 | /// Redistribution contract details 71 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 72 | pub struct RedistributionContract { 73 | /// Contract address 74 | pub address: Address, 75 | /// Deployment block number 76 | pub deployment_block: u64, 77 | /// Deployment transaction hash 78 | pub deployment_tx: B256, 79 | } 80 | 81 | impl ContractAddress for RedistributionContract { 82 | fn address(&self) -> Address { 83 | self.address 84 | } 85 | 86 | fn deployment_block(&self) -> u64 { 87 | self.deployment_block 88 | } 89 | 90 | fn deployment_tx(&self) -> B256 { 91 | self.deployment_tx 92 | } 93 | } 94 | 95 | /// Staking contract details 96 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 97 | pub struct StakingContract { 98 | /// Contract address 99 | pub address: Address, 100 | /// Deployment block number 101 | pub deployment_block: u64, 102 | /// Deployment transaction hash 103 | pub deployment_tx: B256, 104 | } 105 | 106 | impl ContractAddress for StakingContract { 107 | fn address(&self) -> Address { 108 | self.address 109 | } 110 | 111 | fn deployment_block(&self) -> u64 { 112 | self.deployment_block 113 | } 114 | 115 | fn deployment_tx(&self) -> B256 { 116 | self.deployment_tx 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /crates/swarmspec/src/info.rs: -------------------------------------------------------------------------------- 1 | //! Current info about the Swarm network state 2 | 3 | use alloy_primitives::{BlockNumber, B256}; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | /// Current status of the Swarm network. 7 | #[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] 8 | pub struct SwarmInfo { 9 | /// The block hash of the highest fully synced block. 10 | pub best_hash: B256, 11 | /// The block number of the highest fully synced block. 12 | pub best_number: BlockNumber, 13 | /// The current network size estimation 14 | pub network_size: u64, 15 | /// Current global postage price per chunk 16 | pub global_postage_price: u64, 17 | /// Neighborhood radius (radius of proximity) 18 | pub neighborhood_radius: u8, 19 | /// Connected peer count 20 | pub connected_peers: u16, 21 | } 22 | -------------------------------------------------------------------------------- /crates/swarmspec/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The spec of a Swarm network 2 | //! 3 | //! This crate defines the specifications and configurations for a Swarm network, 4 | //! including network identification, smart contracts, storage configuration, 5 | //! and incentives settings. 6 | 7 | #![cfg_attr(not(test), warn(unused_crate_dependencies))] 8 | #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] 9 | #![cfg_attr(not(feature = "std"), no_std)] 10 | 11 | extern crate alloc; 12 | 13 | mod api; 14 | mod constants; 15 | mod info; 16 | mod lightclient; 17 | mod spec; 18 | mod storage; 19 | mod token; 20 | 21 | pub use alloy_chains::{Chain, NamedChain}; 22 | pub use vertex_swarm_forks::*; 23 | 24 | pub use api::SwarmSpec; 25 | pub use constants::*; 26 | pub use info::SwarmInfo; 27 | pub use lightclient::{LightClient, PseudosettleConfig, SettlementConfig}; 28 | pub use spec::{NetworkSpec, SwarmSpecBuilder, SwarmSpecProvider, DEV, MAINNET, TESTNET}; 29 | pub use storage::{Storage, StorageContracts}; 30 | pub use token::Token; 31 | 32 | use core::sync::atomic::{AtomicU64, Ordering}; 33 | use vertex_network_primitives_traits::OnceLock; 34 | 35 | /// A counter for generating unique network IDs for development/testing 36 | static DEV_NETWORK_ID_COUNTER: AtomicU64 = AtomicU64::new(1337); 37 | 38 | /// Generate a unique network ID for development purposes 39 | /// 40 | /// This ensures that development/test networks don't clash with each other 41 | pub fn generate_dev_network_id() -> u64 { 42 | DEV_NETWORK_ID_COUNTER.fetch_add(1, Ordering::SeqCst) 43 | } 44 | 45 | /// Simple utility to create a thread-safe sync cell with a value set. 46 | pub fn once_cell_set(value: T) -> OnceLock { 47 | let once = OnceLock::new(); 48 | let _ = once.set(value); 49 | once 50 | } 51 | -------------------------------------------------------------------------------- /crates/swarmspec/src/lightclient.rs: -------------------------------------------------------------------------------- 1 | //! Incentives configuration for Swarm network 2 | 3 | use alloy_primitives::U256; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | /// Configuration for pseudosettle (free bandwidth allocation) 7 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 8 | pub struct PseudosettleConfig { 9 | /// Daily free bandwidth allowance in bytes 10 | pub daily_allowance_bytes: u64, 11 | /// Threshold after which payment is required (bytes) 12 | pub payment_threshold: u64, 13 | /// Payment tolerance before disconnection (bytes) 14 | pub payment_tolerance: u64, 15 | /// Disconnect threshold (bytes) 16 | pub disconnect_threshold: u64, 17 | } 18 | 19 | impl Default for PseudosettleConfig { 20 | fn default() -> Self { 21 | Self { 22 | daily_allowance_bytes: 1_000_000, // 1MB per day free 23 | payment_threshold: 10_000_000, // 10MB 24 | payment_tolerance: 5_000_000, // 5MB 25 | disconnect_threshold: 50_000_000, // 50MB 26 | } 27 | } 28 | } 29 | 30 | /// Configuration for settlement (payment channel) 31 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 32 | pub struct SettlementConfig { 33 | /// Minimum deposit amount in token base units 34 | pub min_deposit: U256, 35 | /// Minimum settlement threshold in token base units 36 | pub min_settlement_threshold: U256, 37 | /// Maximum settlement time in seconds 38 | pub settlement_timeout: u64, 39 | /// Whether to enforce settlement timeouts 40 | pub enforce_timeouts: bool, 41 | } 42 | 43 | impl Default for SettlementConfig { 44 | fn default() -> Self { 45 | Self { 46 | min_deposit: U256::from(1000), // 1000 base units 47 | min_settlement_threshold: U256::from(100), // 100 base units 48 | settlement_timeout: 86400, // 24 hours 49 | enforce_timeouts: true, 50 | } 51 | } 52 | } 53 | 54 | /// Configuration for bandwidth incentives 55 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 56 | pub struct LightClient { 57 | /// Pseudosettle configuration for free bandwidth allocation 58 | pub pseudosettle: PseudosettleConfig, 59 | /// Settlement configuration for payment channels 60 | pub settlement: SettlementConfig, 61 | /// Minimum price per byte in wei 62 | pub min_price_per_byte: U256, 63 | /// Maximum price per byte in wei 64 | pub max_price_per_byte: U256, 65 | /// Default price per byte in wei 66 | pub default_price_per_byte: U256, 67 | /// Whether bandwidth incentives are enabled 68 | pub enabled: bool, 69 | } 70 | 71 | impl Default for LightClient { 72 | fn default() -> Self { 73 | Self { 74 | pseudosettle: PseudosettleConfig::default(), 75 | settlement: SettlementConfig::default(), 76 | min_price_per_byte: U256::from(1), // 1 wei per byte 77 | max_price_per_byte: U256::from(1000), // 1000 wei per byte 78 | default_price_per_byte: U256::from(10), // 10 wei per byte 79 | enabled: true, 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /crates/swarmspec/src/storage.rs: -------------------------------------------------------------------------------- 1 | //! Storage configuration for Swarm network 2 | 3 | use alloy_primitives::Address; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | /// Contract addresses for storage incentives system 7 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 8 | pub struct StorageContracts { 9 | /// Postage stamp contract address 10 | pub postage: Address, 11 | /// Redistribution contract address 12 | pub redistribution: Address, 13 | /// Optional staking contract address 14 | pub staking: Option
, 15 | } 16 | 17 | impl Default for StorageContracts { 18 | fn default() -> Self { 19 | Self { 20 | postage: Address::ZERO, 21 | redistribution: Address::ZERO, 22 | staking: Some(Address::ZERO), 23 | } 24 | } 25 | } 26 | 27 | /// Configuration for node storage parameters 28 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 29 | pub struct Storage { 30 | /// Smart contract addresses for storage incentives 31 | pub contracts: StorageContracts, 32 | /// Maximum number of chunks the node will store 33 | pub max_chunks: u64, 34 | /// Target number of chunks for optimal operation 35 | pub target_chunks: u64, 36 | /// Minimum number of chunks before node considers scaling down 37 | pub min_chunks: u64, 38 | /// Reserve storage percentage (0-100) before starting to evict content 39 | pub reserve_percentage: u8, 40 | /// Chunk size in bytes 41 | pub chunk_size: u64, 42 | /// Time (in seconds) to wait before a node tries to scale up the neighborhood 43 | pub scale_up_interval: u64, 44 | /// Time (in seconds) to wait before a node tries to scale down the neighborhood 45 | pub scale_down_interval: u64, 46 | } 47 | 48 | impl Default for Storage { 49 | fn default() -> Self { 50 | Self { 51 | contracts: StorageContracts::default(), 52 | max_chunks: 1_000_000, // 1 million chunks 53 | target_chunks: 500_000, // 500k chunks 54 | min_chunks: 100_000, // 100k chunks 55 | reserve_percentage: 10, // 10% reserve 56 | chunk_size: 4096, // 4KB chunks 57 | scale_up_interval: 86400, // 24 hours 58 | scale_down_interval: 86400, // 24 hours 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /crates/swarmspec/src/token.rs: -------------------------------------------------------------------------------- 1 | //! Definition of the Swarm token 2 | 3 | use alloy_primitives::Address; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | /// Swarm token (BZZ) details 7 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 8 | pub struct Token { 9 | /// Contract address 10 | pub address: Address, 11 | /// Token name 12 | pub name: &'static str, 13 | /// Token symbol 14 | pub symbol: &'static str, 15 | /// Decimal places 16 | pub decimals: u8, 17 | } 18 | 19 | impl Default for Token { 20 | fn default() -> Self { 21 | Self { 22 | address: Address::ZERO, 23 | name: "Dev Swarm", 24 | symbol: "dBZZ", 25 | decimals: 16, 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /crates/vertex-primitives/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vertex-primitives" 3 | version.workspace = true 4 | edition.workspace = true 5 | rust-version.workspace = true 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | 10 | [dependencies] 11 | # External dependencies 12 | thiserror = { workspace = true } 13 | serde = { workspace = true, features = ["derive"], optional = true } 14 | hex = { workspace = true } 15 | alloy-primitives = { workspace = true } 16 | auto_impl = "1.1" 17 | 18 | [features] 19 | default = ["std", "serde"] 20 | std = ["alloy-primitives/std", "serde?/std"] 21 | serde = ["dep:serde", "alloy-primitives/serde"] 22 | -------------------------------------------------------------------------------- /crates/vertex-primitives/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error types for the Vertex Swarm node 2 | 3 | use alloc::string::String; 4 | 5 | /// Common error type for all Vertex operations 6 | #[derive(Debug, thiserror::Error)] 7 | pub enum Error { 8 | /// Error related to chunk operations 9 | #[error("Chunk error: {0}")] 10 | Chunk(String), 11 | 12 | /// Error related to storage operations 13 | #[error("Storage error: {0}")] 14 | Storage(String), 15 | 16 | /// Error related to network operations 17 | #[error("Network error: {0}")] 18 | Network(String), 19 | 20 | /// Error related to authentication 21 | #[error("Authentication error: {0}")] 22 | Authentication(String), 23 | 24 | /// Error related to authorization 25 | #[error("Authorization error: {0}")] 26 | Authorization(String), 27 | 28 | /// Error related to accounting 29 | #[error("Accounting error: {0}")] 30 | Accounting(String), 31 | 32 | /// Error when a resource is not found 33 | #[error("Not found: {0}")] 34 | NotFound(String), 35 | 36 | /// Error when a resource already exists 37 | #[error("Already exists: {0}")] 38 | AlreadyExists(String), 39 | 40 | /// Error when an operation is invalid 41 | #[error("Invalid operation: {0}")] 42 | InvalidOperation(String), 43 | 44 | /// Error when an operation times out 45 | #[error("Timeout: {0}")] 46 | Timeout(String), 47 | 48 | /// Error related to IO operations 49 | #[error("IO error: {0}")] 50 | Io(String), 51 | 52 | /// Error related to configuration 53 | #[error("Configuration error: {0}")] 54 | Configuration(String), 55 | 56 | /// Error related to blockchain operations 57 | #[error("Blockchain error: {0}")] 58 | Blockchain(String), 59 | 60 | /// Other errors 61 | #[error("Other error: {0}")] 62 | Other(String), 63 | } 64 | 65 | impl Error { 66 | /// Creates a new chunk error 67 | pub fn chunk(msg: impl Into) -> Self { 68 | Self::Chunk(msg.into()) 69 | } 70 | 71 | /// Creates a new storage error 72 | pub fn storage(msg: impl Into) -> Self { 73 | Self::Storage(msg.into()) 74 | } 75 | 76 | /// Creates a new network error 77 | pub fn network(msg: impl Into) -> Self { 78 | Self::Network(msg.into()) 79 | } 80 | 81 | /// Creates a new authentication error 82 | pub fn authentication(msg: impl Into) -> Self { 83 | Self::Authentication(msg.into()) 84 | } 85 | 86 | /// Creates a new authorization error 87 | pub fn authorization(msg: impl Into) -> Self { 88 | Self::Authorization(msg.into()) 89 | } 90 | 91 | /// Creates a new accounting error 92 | pub fn accounting(msg: impl Into) -> Self { 93 | Self::Accounting(msg.into()) 94 | } 95 | 96 | /// Creates a new not found error 97 | pub fn not_found(msg: impl Into) -> Self { 98 | Self::NotFound(msg.into()) 99 | } 100 | 101 | /// Creates a new already exists error 102 | pub fn already_exists(msg: impl Into) -> Self { 103 | Self::AlreadyExists(msg.into()) 104 | } 105 | 106 | /// Creates a new invalid operation error 107 | pub fn invalid_operation(msg: impl Into) -> Self { 108 | Self::InvalidOperation(msg.into()) 109 | } 110 | 111 | /// Creates a new timeout error 112 | pub fn timeout(msg: impl Into) -> Self { 113 | Self::Timeout(msg.into()) 114 | } 115 | 116 | /// Creates a new IO error 117 | pub fn io(msg: impl Into) -> Self { 118 | Self::Io(msg.into()) 119 | } 120 | 121 | /// Creates a new other error 122 | pub fn other(msg: impl Into) -> Self { 123 | Self::Other(msg.into()) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /crates/vertex-primitives/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Core primitive types for the Vertex Swarm node 2 | //! 3 | //! This crate defines the basic types used throughout the Vertex Swarm project. 4 | 5 | #![cfg_attr(not(feature = "std"), no_std)] 6 | #![warn(missing_docs)] 7 | 8 | extern crate alloc; 9 | 10 | use alloc::{boxed::Box, string::String, vec::Vec}; 11 | use core::fmt::{self, Debug, Display, Formatter}; 12 | 13 | /// Common error type for all Vertex operations 14 | pub mod error; 15 | pub use error::*; 16 | 17 | /// Chunk-related primitives 18 | pub mod chunk; 19 | pub use chunk::*; 20 | 21 | /// Network-related primitives 22 | pub mod network; 23 | pub use network::*; 24 | 25 | /// Ethereum-related primitives 26 | pub mod eth; 27 | pub use eth::*; 28 | 29 | /// Common traits used across the codebase 30 | pub mod traits; 31 | pub use traits::*; 32 | 33 | /// Re-exports from alloy-primitives to ensure consistent usage 34 | pub use alloy_primitives::{keccak256, Address, B256, U256}; 35 | 36 | /// Result type used throughout the Vertex codebase 37 | pub type Result = core::result::Result; 38 | 39 | /// A 32-byte address for chunks in the Swarm network 40 | #[derive(Clone, PartialEq, Eq, Hash)] 41 | pub struct ChunkAddress(pub [u8; 32]); 42 | 43 | impl ChunkAddress { 44 | /// Creates a new ChunkAddress from raw bytes 45 | pub const fn new(bytes: [u8; 32]) -> Self { 46 | Self(bytes) 47 | } 48 | 49 | /// Creates a ChunkAddress from a B256 50 | pub fn from_b256(b256: B256) -> Self { 51 | Self(b256.0) 52 | } 53 | 54 | /// Returns the underlying bytes 55 | pub const fn as_bytes(&self) -> &[u8; 32] { 56 | &self.0 57 | } 58 | 59 | /// Calculate proximity (0-256) between two addresses 60 | pub fn proximity(&self, other: &Self) -> u8 { 61 | // Count leading zeros in XOR distance 62 | let mut proximity = 0; 63 | for i in 0..32 { 64 | let xor = self.0[i] ^ other.0[i]; 65 | if xor == 0 { 66 | proximity += 8; 67 | continue; 68 | } 69 | // Count leading zeros in byte 70 | proximity += xor.leading_zeros() as u8; 71 | break; 72 | } 73 | proximity 74 | } 75 | 76 | /// Check if this address is within a certain proximity of another address 77 | pub fn is_within_proximity(&self, other: &Self, min_proximity: u8) -> bool { 78 | self.proximity(other) >= min_proximity 79 | } 80 | } 81 | 82 | impl Debug for ChunkAddress { 83 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 84 | write!(f, "ChunkAddress({})", hex::encode(&self.0[..4])) 85 | } 86 | } 87 | 88 | impl Display for ChunkAddress { 89 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 90 | write!(f, "{}", hex::encode(&self.0[..4])) 91 | } 92 | } 93 | 94 | #[cfg(feature = "serde")] 95 | impl serde::Serialize for ChunkAddress { 96 | fn serialize(&self, serializer: S) -> core::result::Result 97 | where 98 | S: serde::Serializer, 99 | { 100 | serializer.serialize_str(&hex::encode(&self.0)) 101 | } 102 | } 103 | 104 | #[cfg(feature = "serde")] 105 | impl<'de> serde::Deserialize<'de> for ChunkAddress { 106 | fn deserialize(deserializer: D) -> core::result::Result 107 | where 108 | D: serde::Deserializer<'de>, 109 | { 110 | let s = String::deserialize(deserializer)?; 111 | let bytes = hex::decode(s).map_err(serde::de::Error::custom)?; 112 | if bytes.len() != 32 { 113 | return Err(serde::de::Error::custom( 114 | "ChunkAddress must be exactly 32 bytes", 115 | )); 116 | } 117 | let mut arr = [0u8; 32]; 118 | arr.copy_from_slice(&bytes); 119 | Ok(ChunkAddress(arr)) 120 | } 121 | } 122 | --------------------------------------------------------------------------------