From 1ec12b73a84a191d9718d0d48baf5851519b9192 Mon Sep 17 00:00:00 2001 From: DC Date: Tue, 8 Apr 2025 13:58:29 -0600 Subject: [PATCH 01/57] feat: initial project structure * Add project brief with core features and target users * Define technical preferences and stack components * Document main services: Zebra, Zaino, and Zallet --- .clinerules | 116 ++++++++++++++++++++++++++++++++++ .gitmodules | 9 +++ memory-bank/activeContext.md | 25 ++++++++ memory-bank/productContext.md | 44 +++++++++++++ memory-bank/progress.md | 21 ++++++ memory-bank/projectbrief.md | 33 ++++++++++ memory-bank/systemPatterns.md | 37 +++++++++++ memory-bank/techContext.md | 34 ++++++++++ zaino | 1 + zallet | 1 + zebra | 1 + 11 files changed, 322 insertions(+) create mode 100644 .clinerules create mode 100644 .gitmodules create mode 100644 memory-bank/activeContext.md create mode 100644 memory-bank/productContext.md create mode 100644 memory-bank/progress.md create mode 100644 memory-bank/projectbrief.md create mode 100644 memory-bank/systemPatterns.md create mode 100644 memory-bank/techContext.md create mode 160000 zaino create mode 160000 zallet create mode 160000 zebra diff --git a/.clinerules b/.clinerules new file mode 100644 index 0000000..395ad66 --- /dev/null +++ b/.clinerules @@ -0,0 +1,116 @@ +# Cline's Memory Bank + +I am Cline, an expert software engineer with a unique characteristic: my memory resets completely between sessions. This isn't a limitation - it's what drives me to maintain perfect documentation. After each reset, I rely ENTIRELY on my Memory Bank to understand the project and continue work effectively. I MUST read ALL memory bank files at the start of EVERY task - this is not optional. + +## Memory Bank Structure + +The Memory Bank consists of core files and optional context files, all in Markdown format. Files build upon each other in a clear hierarchy: + +flowchart TD + PB[projectbrief.md] --> PC[productContext.md] + PB --> SP[systemPatterns.md] + PB --> TC[techContext.md] + + PC --> AC[activeContext.md] + SP --> AC + TC --> AC + + AC --> P[progress.md] + +### Core Files (Required) +1. `projectbrief.md` + - Foundation document that shapes all other files + - Created at project start if it doesn't exist + - Defines core requirements and goals + - Source of truth for project scope + +2. `productContext.md` + - Why this project exists + - Problems it solves + - How it should work + - User experience goals + +3. `activeContext.md` + - Current work focus + - Recent changes + - Next steps + - Active decisions and considerations + - Important patterns and preferences + - Learnings and project insights + +4. `systemPatterns.md` + - System architecture + - Key technical decisions + - Design patterns in use + - Component relationships + - Critical implementation paths + +5. `techContext.md` + - Technologies used + - Development setup + - Technical constraints + - Dependencies + - Tool usage patterns + +6. `progress.md` + - What works + - What's left to build + - Current status + - Known issues + - Evolution of project decisions + +### Additional Context +Create additional files/folders within memory-bank/ when they help organize: +- Complex feature documentation +- Integration specifications +- API documentation +- Testing strategies +- Deployment procedures + +## Core Workflows + +### Plan Mode +flowchart TD + Start[Start] --> ReadFiles[Read Memory Bank] + ReadFiles --> CheckFiles{Files Complete?} + + CheckFiles -->|No| Plan[Create Plan] + Plan --> Document[Document in Chat] + + CheckFiles -->|Yes| Verify[Verify Context] + Verify --> Strategy[Develop Strategy] + Strategy --> Present[Present Approach] + +### Act Mode +flowchart TD + Start[Start] --> Context[Check Memory Bank] + Context --> Update[Update Documentation] + Update --> Execute[Execute Task] + Execute --> Document[Document Changes] + +## Documentation Updates + +Memory Bank updates occur when: +1. Discovering new project patterns +2. After implementing significant changes +3. When user requests with **update memory bank** (MUST review ALL files) +4. When context needs clarification + +flowchart TD + Start[Update Process] + + subgraph Process + P1[Review ALL Files] + P2[Document Current State] + P3[Clarify Next Steps] + P4[Document Insights & Patterns] + + P1 --> P2 --> P3 --> P4 + end + + Start --> Process + +Note: When triggered by **update memory bank**, I MUST review every memory bank file, even if some don't require updates. Focus particularly on activeContext.md and progress.md as they track current state. + +REMEMBER: After every memory reset, I begin completely fresh. The Memory Bank is my only link to previous work. It must be maintained with precision and clarity, as my effectiveness depends entirely on its accuracy. + diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..3d7a3e1 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "zebra"] + path = zebra + url = https://github.com/ZcashFoundation/zebra +[submodule "zaino"] + path = zaino + url = https://github.com/zingolabs/zaino +[submodule "zallet"] + path = zallet + url = https://github.com/zcash/wallet diff --git a/memory-bank/activeContext.md b/memory-bank/activeContext.md new file mode 100644 index 0000000..8e1d0a4 --- /dev/null +++ b/memory-bank/activeContext.md @@ -0,0 +1,25 @@ +# Active Context + +## Current Focus + +Updating the Memory Bank, specifically reviewing and updating the core files (`projectbrief.md`, `productContext.md`, `systemPatterns.md`, `techContext.md`, `activeContext.md`, and `progress.md`). + +## Recent Changes + +Updated the `productContext.md`, `systemPatterns.md`, and `techContext.md` files with information about the project's problems, proposed solution, user experience goals, system architecture, technologies used, and development setup. + +## Next Steps + +Review and update the `progress.md` file to reflect the current state of the project. + +## Active Decisions + +No active decisions are currently being made. + +## Important Patterns + +The importance of maintaining a comprehensive and up-to-date Memory Bank is a key pattern for this project. All decisions and changes should be documented in the Memory Bank to ensure consistency and knowledge sharing. + +## Learnings and Insights + +The project brief provides a good overview of the project's goals and requirements, which is essential for making informed decisions. The modular architecture of the system allows for independent development and deployment of components. diff --git a/memory-bank/productContext.md b/memory-bank/productContext.md new file mode 100644 index 0000000..f49fd01 --- /dev/null +++ b/memory-bank/productContext.md @@ -0,0 +1,44 @@ +# Product Context + +## Problem + +This project aims to replace the existing Zcashd software stack, which was forked from Bitcoin Core back in 2016. The c++ codebase has become difficult to maintain and extend. Zcashd is a monolithic application that includes full-node, indexing, and wallet functionalities. Replacing Zcashd with a modular and maintainable software stack addresses the following problems: + +* **Maintenance burden:** Zcashd's codebase is complex and challenging to maintain, leading to increased development costs and slower feature development. +* **Scalability limitations:** Zcashd's monolithic architecture limits its scalability and ability to handle increasing transaction volumes. +* **Lack of flexibility:** Zcashd's tight integration of components makes it difficult to adapt to new technologies and requirements. +* **Security risks:** Zcashd's complexity, and it's c++ codebase increases the risk of security vulnerabilities. + + +## Proposed Solution + +This project proposes replacing Zcashd with a modular software stack consisting of the following components: + +* **Zebra:** A full-node implementation of the Zcash protocol that provides consensus and network functionalities. +* **Zaino:** An indexing service that provides light clients with blockchain data, replacing the legacy go light client server lightwalletd. +* **Zallet:** A CLI wallet that provides wallet functionality that existed in Zcashd but is not planned for implementation in Zebra. +* **Wrapper Service:** A wrapper service that exposes the three previously mentioned services as a "single binary" to the users + +This modular architecture offers several advantages: + +* **Improved maintainability:** Each component can be developed and maintained independently, reducing the overall maintenance burden. +* **Enhanced scalability:** Each component can be scaled independently to meet specific demands. +* **Increased flexibility:** The modular design allows for easier adaptation to new technologies and requirements. +* **Reduced security risks:** Each component can be hardened and secured independently, reducing the overall attack surface. + +## User Experience Goals + +The project aims to provide a secure, reliable, and efficient experience for the following target users: + +* **Zcash protocol developers:** A well-documented and easy-to-use software stack that facilitates protocol development and experimentation. +* **Centralized cryptocurrency exchanges:** A scalable and reliable full-node implementation that supports high transaction volumes. +* **Decentralized cryptocurrency exchanges:** A secure and efficient indexing service that enables fast and reliable access to blockchain data. +* **Cryptocurrency wallet software platforms:** A secure and user-friendly CLI wallet that provides essential wallet functionalities. + +The key user experience goals include: + +* **Security:** Protecting user funds and data from unauthorized access. +* **Reliability:** Ensuring the software stack operates consistently and without errors. +* **Efficiency:** Providing fast and responsive performance. +* **Usability:** Making the software stack easy to use and understand. +* **Accessibility:** Ensuring the software stack is accessible to users with disabilities. diff --git a/memory-bank/progress.md b/memory-bank/progress.md new file mode 100644 index 0000000..49fd124 --- /dev/null +++ b/memory-bank/progress.md @@ -0,0 +1,21 @@ +# Progress + +## Working Functionality + +The Memory Bank is functional and being actively maintained. The core files (`projectbrief.md`, `productContext.md`, `systemPatterns.md`, `techContext.md`, `activeContext.md`, and `progress.md`) are being used to document the project. + +## Functionality Left To Build + +The entire software stack (Zebra, Zaino, Zallet, and the wrapper service) still needs to be built. + +## Current Status + +The project is in the initial planning and documentation phase. The Memory Bank is being established as a central repository for project knowledge. + +## Known Issues + +No known issues at this time. + +## Evolution of Decisions + +No decisions have evolved yet. diff --git a/memory-bank/projectbrief.md b/memory-bank/projectbrief.md new file mode 100644 index 0000000..02b73b6 --- /dev/null +++ b/memory-bank/projectbrief.md @@ -0,0 +1,33 @@ +# Project Brief + +## Overview +Building a software stack that will replace Zcashd. + +## Core Features +- A full-node implementation that implements the Zcash protocol (Zebra) +- A service that provides indexing and serves light clients blockchain data (Zaino) +- A cli-wallet that provides wallet functionality that existed in Zcashd but that is not planned for implementation in Zebra (Zallet) +- A wrapper service that exposes the three previously mentioned services as a "single binary" to the users + +## Target Users +- Developers of the Zcash protocol (including the Zcash Foundation, the Electric Coin Company, Shielded Labs, Zingo Labs and the broader community) +- Operators of centralized cryptocurrency exchanges (including Coinbase, Gemini, Kraken, Binance, etc.) +- Operators of decentralized cryptocurrency exchanges (including the Zcash DEX implemented with Near Intents) +- Operators of cryptocurrency wallet software platforms (including Zashi, Brave Wallet, etc.) +- Operators of ASIC based proof of work miners on the Zcash blockchain +- Operators of "finalizers" (known more commonly as validators) as Zcash prepares to transition to a hybrid Proof of Work / Proof of Stake consensus algorithm. + + +## Technical Preferences (optional) +- [Zebra](https://github.com/ZcashFoundation/zebra) for the Zcash full node +- [Zaino](https://github.com/zingolabs/zaino/) for the indexing service that will interface with Zebrad via RPC and the ReadStateService, and also replace the legacy go light client server [lightwalletd](https://github.com/zcash/lightwalletd) +- [Zallet](https://github.com/zcash/wallet) for the cli-wallet that will replace the wallet functionality that originally existed in Zcashd +- Other software components should be Rust based +- Security is of paramount importance +- Clippy as the Rust linter of choice +- Git and Github for SCM +- Github Actions for CI +- Test driven development +- Docker for deployment +- [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#specification) as standard for SCM commit messaging + diff --git a/memory-bank/systemPatterns.md b/memory-bank/systemPatterns.md new file mode 100644 index 0000000..dfb9260 --- /dev/null +++ b/memory-bank/systemPatterns.md @@ -0,0 +1,37 @@ +# System Patterns + +## Architecture + +The system adopts a modular architecture, replacing the monolithic Zcashd with a set of specialized components: Zebra, Zaino, and Zallet. These components interact through well-defined interfaces, enabling independent development, deployment, and scaling. A wrapper service provides a unified entry point for users, simplifying interaction with the underlying components. + +```mermaid +graph LR + W[Wrapper Service] --> Z[Zallet] + W --> ZA[Zaino] + W --> ZE[Zebra] + ZE[Zebra] --> ZA[Zaino] + Z[Zallet] --> ZE[Zebra] +``` + +## Key Decisions + +* **Rust Implementation:** The decision to implement most components in Rust was driven by a strong emphasis on security and performance. Rust's memory safety features and efficient execution make it well-suited for building critical blockchain infrastructure. +* **Component Selection:** The choice of Zebra, Zaino, and Zallet was based on their existing functionality, maturity, and alignment with the project's goals. These components provide a solid foundation for building a replacement for Zcashd. + +## Design Patterns + +* **Facade:** The wrapper service implements the Facade pattern, providing a simplified interface to the complex interactions between Zebra, Zaino, and Zallet. +* **Observer:** Zebra and Zaino may utilize the Observer pattern for event handling, allowing components to react to changes in the blockchain state. +* **Repository:** Zaino may employ the Repository pattern to abstract data access, simplifying data management and improving testability. + +## Component Relationships + +* **Zebra and Zaino:** Zebra provides blockchain data to Zaino via RPC or a similar mechanism. Zaino indexes this data to provide efficient access for light clients and other applications. +* **Zallet and Zebra:** Zallet interacts with Zebra for transaction submission and balance retrieval. Zallet uses Zebra's API to construct and broadcast transactions to the Zcash network. +* **Wrapper Service and Components:** The wrapper service orchestrates interactions between Zebra, Zaino, and Zallet, providing a unified interface for users. + +## Critical Paths + +* **Transaction Processing:** The process of creating, signing, and submitting a transaction to the Zcash network involves interactions between Zallet and Zebra. +* **Block Synchronization:** Zebra synchronizes with the Zcash network to download and verify new blocks. This process is critical for maintaining an up-to-date view of the blockchain state. +* **Data Indexing:** Zaino indexes blockchain data provided by Zebra to enable efficient querying and retrieval of information. diff --git a/memory-bank/techContext.md b/memory-bank/techContext.md new file mode 100644 index 0000000..cd75d40 --- /dev/null +++ b/memory-bank/techContext.md @@ -0,0 +1,34 @@ +# Tech Context + +## Technologies Used + +* **Rust:** Primary language for Zebra, Zaino, and Zallet (version TBD). Justification: Security, performance, and memory safety. +* **gRPC:** For communication between Zebra and Zaino (version TBD). Justification: Efficient and standardized RPC framework. +* **Docker:** For deployment and containerization (version TBD). Justification: Consistent and reproducible environments. +* **Potentially: RocksDB or similar embedded database for Zaino's indexing** (version TBD). Justification: Efficient storage and retrieval of blockchain data. + +## Development Setup + +* **Rust toolchain:** Requires a recent version of the Rust toolchain, including `rustc`, `cargo`, and `clippy`. +* **Docker:** Requires Docker for building and running the services. +* **Git:** Requires Git for version control. +* **Editor:** VSCode with Rust Analyzer extension recommended. + +## Technical Constraints + +* **Performance:** The system must be able to handle high transaction volumes and provide low-latency access to blockchain data. +* **Security:** Security is paramount, and all components must be designed to resist attacks. +* **Compatibility:** The system must be compatible with the Zcash protocol and network. + +## Dependencies + +* `zebra-chain`, `zebra-consensus`, `zebra-network`, `zebra-state`: Zebra's core crates for blockchain functionality. +* `tokio`: Asynchronous runtime for Rust. +* `anyhow`: Flexible error handling library. + +## Tool Usage Patterns + +* `cargo`: For building, testing, and managing dependencies. +* `clippy`: For linting and code quality checks. +* `git`: For version control and collaboration. +* `docker`: For building and running containers. diff --git a/zaino b/zaino new file mode 160000 index 0000000..72b9d7e --- /dev/null +++ b/zaino @@ -0,0 +1 @@ +Subproject commit 72b9d7e24be11843197620d3a1b718566e3f9c21 diff --git a/zallet b/zallet new file mode 160000 index 0000000..6c7da0d --- /dev/null +++ b/zallet @@ -0,0 +1 @@ +Subproject commit 6c7da0d55c672cc2e2cb59511b131d437bc12f0b diff --git a/zebra b/zebra new file mode 160000 index 0000000..c9a4ae4 --- /dev/null +++ b/zebra @@ -0,0 +1 @@ +Subproject commit c9a4ae41f9f02dcab3ee529c9ab9c796ecffe0ad From c5e7a211b69a56e8f4961f7ca738d5913e64fb0f Mon Sep 17 00:00:00 2001 From: DC Date: Tue, 8 Apr 2025 15:56:43 -0600 Subject: [PATCH 02/57] docs: add detailed product context and user experience goals * Define core problems with existing Zcashd implementation * Document proposed modular solution architecture * Specify target user groups and their requirements * Detail key user experience goals including observability * Outline security and reliability requirements Relates to #1 --- memory-bank/activeContext.md | 2 +- memory-bank/productContext.md | 14 ++++++++------ memory-bank/projectbrief.md | 5 ++--- memory-bank/systemPatterns.md | 19 +++++++++++-------- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/memory-bank/activeContext.md b/memory-bank/activeContext.md index 8e1d0a4..1044e77 100644 --- a/memory-bank/activeContext.md +++ b/memory-bank/activeContext.md @@ -6,7 +6,7 @@ Updating the Memory Bank, specifically reviewing and updating the core files (`p ## Recent Changes -Updated the `productContext.md`, `systemPatterns.md`, and `techContext.md` files with information about the project's problems, proposed solution, user experience goals, system architecture, technologies used, and development setup. +Updated the `productContext.md`, `systemPatterns.md`, and `techContext.md` files with information about the project's problems, proposed solution, user experience goals, system architecture, technologies used, and development setup. The `productContext.md` file was further updated to refine the "User Experience Goals" section. The `systemPatterns.md` file was updated to reflect the user's interaction with the Z3 wrapper service and the potential communication paths between Zallet, Zaino, and Zebra. ## Next Steps diff --git a/memory-bank/productContext.md b/memory-bank/productContext.md index f49fd01..5346a9e 100644 --- a/memory-bank/productContext.md +++ b/memory-bank/productContext.md @@ -2,7 +2,7 @@ ## Problem -This project aims to replace the existing Zcashd software stack, which was forked from Bitcoin Core back in 2016. The c++ codebase has become difficult to maintain and extend. Zcashd is a monolithic application that includes full-node, indexing, and wallet functionalities. Replacing Zcashd with a modular and maintainable software stack addresses the following problems: +The Z3 project aims to replace the existing Zcashd software stack, which was forked from Bitcoin Core back in 2016. The c++ codebase has become difficult to maintain and extend. Zcashd is a monolithic application that includes full-node, indexing, and wallet functionalities. Replacing Zcashd with a modular and maintainable software stack addresses the following problems: * **Maintenance burden:** Zcashd's codebase is complex and challenging to maintain, leading to increased development costs and slower feature development. * **Scalability limitations:** Zcashd's monolithic architecture limits its scalability and ability to handle increasing transaction volumes. @@ -31,14 +31,16 @@ This modular architecture offers several advantages: The project aims to provide a secure, reliable, and efficient experience for the following target users: * **Zcash protocol developers:** A well-documented and easy-to-use software stack that facilitates protocol development and experimentation. -* **Centralized cryptocurrency exchanges:** A scalable and reliable full-node implementation that supports high transaction volumes. -* **Decentralized cryptocurrency exchanges:** A secure and efficient indexing service that enables fast and reliable access to blockchain data. -* **Cryptocurrency wallet software platforms:** A secure and user-friendly CLI wallet that provides essential wallet functionalities. +* **Centralized cryptocurrency exchanges:** A secure, scalable and reliable modern full-node implementation that supports exchange operations (deposit, withdrawal) +* **Decentralized cryptocurrency exchanges:** A secure, scalable and reliable modern full-node implementation that supports decentralized exchange operations (deposit, withdrawal, swap) +* **Cryptocurrency wallet software platforms:** A secure and user-friendly full-node implementation that provides essential wallet functionalities. (send, receive, check balance) +* **Block explorers:** A scalable and reliable full-node implementation that provides fast access to archival blockchain data The key user experience goals include: -* **Security:** Protecting user funds and data from unauthorized access. +* **Seamless upgrade:** The user can upgrade from the legacy (Zcashd) daemon to the modern (Z3) stack without changing any of their integration code. +* **Security:** Protecting user funds and data from unauthorized access. Ensuring that the shielded supply remains free of counterfeiting bugs. * **Reliability:** Ensuring the software stack operates consistently and without errors. +* **Observability:** Providing telemetry, logging and tracing to ensure proper operation in rugged production environments. * **Efficiency:** Providing fast and responsive performance. * **Usability:** Making the software stack easy to use and understand. -* **Accessibility:** Ensuring the software stack is accessible to users with disabilities. diff --git a/memory-bank/projectbrief.md b/memory-bank/projectbrief.md index 02b73b6..6a82ef5 100644 --- a/memory-bank/projectbrief.md +++ b/memory-bank/projectbrief.md @@ -1,7 +1,7 @@ # Project Brief ## Overview -Building a software stack that will replace Zcashd. +Z3: Building a modern, secure, reliable, scalable software stack that will replace Zcashd. ## Core Features - A full-node implementation that implements the Zcash protocol (Zebra) @@ -16,7 +16,7 @@ Building a software stack that will replace Zcashd. - Operators of cryptocurrency wallet software platforms (including Zashi, Brave Wallet, etc.) - Operators of ASIC based proof of work miners on the Zcash blockchain - Operators of "finalizers" (known more commonly as validators) as Zcash prepares to transition to a hybrid Proof of Work / Proof of Stake consensus algorithm. - +- Operators of block explorers on the Zcash network ## Technical Preferences (optional) - [Zebra](https://github.com/ZcashFoundation/zebra) for the Zcash full node @@ -30,4 +30,3 @@ Building a software stack that will replace Zcashd. - Test driven development - Docker for deployment - [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#specification) as standard for SCM commit messaging - diff --git a/memory-bank/systemPatterns.md b/memory-bank/systemPatterns.md index dfb9260..d7dd73c 100644 --- a/memory-bank/systemPatterns.md +++ b/memory-bank/systemPatterns.md @@ -2,15 +2,17 @@ ## Architecture -The system adopts a modular architecture, replacing the monolithic Zcashd with a set of specialized components: Zebra, Zaino, and Zallet. These components interact through well-defined interfaces, enabling independent development, deployment, and scaling. A wrapper service provides a unified entry point for users, simplifying interaction with the underlying components. +The system adopts a modular architecture, replacing the monolithic Zcashd with a set of specialized components: Zebra, Zaino, and Zallet. These components interact through well-defined interfaces, enabling independent development, deployment, and scaling. Z3 wrapper service provides a unified entry point for users, simplifying interaction with the underlying components. ```mermaid graph LR - W[Wrapper Service] --> Z[Zallet] + U[User] --> W[Z3 wrapper service] + W --> Z[Zallet] W --> ZA[Zaino] W --> ZE[Zebra] ZE[Zebra] --> ZA[Zaino] Z[Zallet] --> ZE[Zebra] + Z[Zallet] --> ZA[Zaino] ``` ## Key Decisions @@ -20,18 +22,19 @@ graph LR ## Design Patterns -* **Facade:** The wrapper service implements the Facade pattern, providing a simplified interface to the complex interactions between Zebra, Zaino, and Zallet. +* **Facade:** The Z3 wrapper service implements the Facade pattern, providing a simplified interface to the complex interactions between Zebra, Zaino, and Zallet. * **Observer:** Zebra and Zaino may utilize the Observer pattern for event handling, allowing components to react to changes in the blockchain state. * **Repository:** Zaino may employ the Repository pattern to abstract data access, simplifying data management and improving testability. ## Component Relationships +* **User and Z3 wrapper service:** The user interacts directly with the Z3 wrapper service, which acts as a single entry point to the system. The Z3 wrapper service handles user requests and routes them to the appropriate component (Zebra, Zaino, or Zallet). * **Zebra and Zaino:** Zebra provides blockchain data to Zaino via RPC or a similar mechanism. Zaino indexes this data to provide efficient access for light clients and other applications. * **Zallet and Zebra:** Zallet interacts with Zebra for transaction submission and balance retrieval. Zallet uses Zebra's API to construct and broadcast transactions to the Zcash network. -* **Wrapper Service and Components:** The wrapper service orchestrates interactions between Zebra, Zaino, and Zallet, providing a unified interface for users. +* **Zallet and Zaino:** Zallet may interact with Zaino to retrieve indexed blockchain data, such as transaction history or address balances. +* **Z3 wrapper service and Components:** The Z3 wrapper service orchestrates interactions between Zebra, Zaino, and Zallet, providing a unified interface for users. -## Critical Paths +## Notes -* **Transaction Processing:** The process of creating, signing, and submitting a transaction to the Zcash network involves interactions between Zallet and Zebra. -* **Block Synchronization:** Zebra synchronizes with the Zcash network to download and verify new blocks. This process is critical for maintaining an up-to-date view of the blockchain state. -* **Data Indexing:** Zaino indexes blockchain data provided by Zebra to enable efficient querying and retrieval of information. +* It is not yet clear whether Zallet will communicate only with Zaino or also directly with Zebra. This will depend on the specific requirements of Zallet and the design of the interfaces between the components. +* There is ongoing discussion about Zaino being implemented as a library that will be integrated directly into wallet software. If this approach is adopted, users may not need to communicate with a separate Zaino service. From f6bc77b926b8ed1db6aff94e8c7b266d4cbc2b32 Mon Sep 17 00:00:00 2001 From: DC Date: Wed, 9 Apr 2025 11:13:47 -0600 Subject: [PATCH 03/57] docs: enhance project documentation with context and tooling * Add detailed Product Context - Document problems with current Zcashd implementation - Define modular architecture with Zebra, Zaino, and Zallet - Specify user experience goals and target users - Add initial key metrics for adoption * Expand Technical Context - Document dependencies for all components - Detail common dependencies across stack - Add comprehensive tool usage patterns - Document build and testing workflows This improves project documentation by providing clear context for both product and technical aspects of the Z3 stack. Relates to #1 --- memory-bank/activeContext.md | 2 +- memory-bank/productContext.md | 23 ++-- memory-bank/techContext.md | 200 ++++++++++++++++++++++++++++++++-- 3 files changed, 205 insertions(+), 20 deletions(-) diff --git a/memory-bank/activeContext.md b/memory-bank/activeContext.md index 1044e77..514ce16 100644 --- a/memory-bank/activeContext.md +++ b/memory-bank/activeContext.md @@ -6,7 +6,7 @@ Updating the Memory Bank, specifically reviewing and updating the core files (`p ## Recent Changes -Updated the `productContext.md`, `systemPatterns.md`, and `techContext.md` files with information about the project's problems, proposed solution, user experience goals, system architecture, technologies used, and development setup. The `productContext.md` file was further updated to refine the "User Experience Goals" section. The `systemPatterns.md` file was updated to reflect the user's interaction with the Z3 wrapper service and the potential communication paths between Zallet, Zaino, and Zebra. +Updated the `productContext.md`, `systemPatterns.md`, and `techContext.md` files with information about the project's problems, proposed solution, user experience goals, system architecture, technologies used, and development setup. The `productContext.md` file was further updated to refine the "User Experience Goals" section and add a "Key Metrics" section. The `systemPatterns.md` file was updated to reflect the user's interaction with the Z3 wrapper service and the potential communication paths between Zallet, Zaino, and Zebra. The `systemPatterns.md` file was further updated to rename "Wrapper Service" to "Z3 wrapper service". The `techContext.md` file was updated to include links to the repositories for Zebra, Zaino, and Zallet, and to provide more detailed information about dependencies and tool usage patterns. ## Next Steps diff --git a/memory-bank/productContext.md b/memory-bank/productContext.md index 5346a9e..0a3a6c9 100644 --- a/memory-bank/productContext.md +++ b/memory-bank/productContext.md @@ -4,20 +4,20 @@ The Z3 project aims to replace the existing Zcashd software stack, which was forked from Bitcoin Core back in 2016. The c++ codebase has become difficult to maintain and extend. Zcashd is a monolithic application that includes full-node, indexing, and wallet functionalities. Replacing Zcashd with a modular and maintainable software stack addresses the following problems: -* **Maintenance burden:** Zcashd's codebase is complex and challenging to maintain, leading to increased development costs and slower feature development. +* **Maintenance burden:** Zcashd's codebase is complex and challenging to maintain, leading to increased development costs and slower feature development. The Zcash codebase has also diverged significally from upstream (Bitcoin Core) which means that tracking upstream security fixes has become quite an onerous task. * **Scalability limitations:** Zcashd's monolithic architecture limits its scalability and ability to handle increasing transaction volumes. * **Lack of flexibility:** Zcashd's tight integration of components makes it difficult to adapt to new technologies and requirements. -* **Security risks:** Zcashd's complexity, and it's c++ codebase increases the risk of security vulnerabilities. +* **Security risks:** Zcashd's complexity, and it's c++ codebase increases the risk of security vulnerabilities. The legacy architecture has key material in memory readable and writable by the same process that handles peer to peer networking (and everything else) which makes containing the blast radius of a memory corruption exploit difficult to impossible. ## Proposed Solution This project proposes replacing Zcashd with a modular software stack consisting of the following components: -* **Zebra:** A full-node implementation of the Zcash protocol that provides consensus and network functionalities. -* **Zaino:** An indexing service that provides light clients with blockchain data, replacing the legacy go light client server lightwalletd. -* **Zallet:** A CLI wallet that provides wallet functionality that existed in Zcashd but is not planned for implementation in Zebra. -* **Wrapper Service:** A wrapper service that exposes the three previously mentioned services as a "single binary" to the users +* **[Zebra](https://github.com/ZcashFoundation/zebra):** A full-node implementation of the Zcash protocol that provides consensus and network functionalities. Zebra has been under development by the Zcash Foundation since 2020 and is presently deployed by a number of decentralized node operators on mainnet. +* **[Zaino](https://github.com/zingolabs/zaino/):** An indexing service that provides light clients with blockchain data, replacing the legacy go light client server lightwalletd. Zaino is under active development by Zingo Labs, funded by a grant from [ZCG](https://zcashcommunitygrants.org/). +* **[Zallet](https://github.com/zcash/wallet):** A CLI wallet that provides wallet functionality that existed in Zcashd but is not planned for implementation in Zebra. Zallet is under active development by the Electric Coin Company. +* **Z3 Wrapper Service:** A wrapper service that exposes the three previously mentioned services as a "single binary" to the users. Note that this capability could be achieved as a "single binary" that is deployed as a Debian package eg `sudo apt install zcash-z3`, or via Docker Compose or as a Helm chart. This modular architecture offers several advantages: @@ -31,10 +31,10 @@ This modular architecture offers several advantages: The project aims to provide a secure, reliable, and efficient experience for the following target users: * **Zcash protocol developers:** A well-documented and easy-to-use software stack that facilitates protocol development and experimentation. -* **Centralized cryptocurrency exchanges:** A secure, scalable and reliable modern full-node implementation that supports exchange operations (deposit, withdrawal) -* **Decentralized cryptocurrency exchanges:** A secure, scalable and reliable modern full-node implementation that supports decentralized exchange operations (deposit, withdrawal, swap) -* **Cryptocurrency wallet software platforms:** A secure and user-friendly full-node implementation that provides essential wallet functionalities. (send, receive, check balance) -* **Block explorers:** A scalable and reliable full-node implementation that provides fast access to archival blockchain data +* **Centralized cryptocurrency exchanges:** A secure, scalable and reliable modern full-node implementation that supports exchange operations (deposit, withdrawal). +* **Decentralized cryptocurrency exchanges:** A secure, scalable and reliable modern full-node implementation that supports decentralized exchange operations (deposit, withdrawal, swap). +* **Cryptocurrency wallet software platforms:** A secure and user-friendly full-node implementation that provides essential wallet functionalities. (send, receive, check balance). +* **Block explorers:** A scalable and reliable full-node implementation that provides fast access to archival blockchain data. The key user experience goals include: @@ -44,3 +44,6 @@ The key user experience goals include: * **Observability:** Providing telemetry, logging and tracing to ensure proper operation in rugged production environments. * **Efficiency:** Providing fast and responsive performance. * **Usability:** Making the software stack easy to use and understand. + +## Key Metrics +* **% of mainnet nodes running Z3:** 0 as of April 2025, with a target of 2025 by the time Zcashd is deprecated. Sources for determining this metric are TBD, some potential options include [Blockchair](https://blockchair.com/zcash/nodes) diff --git a/memory-bank/techContext.md b/memory-bank/techContext.md index cd75d40..21923ec 100644 --- a/memory-bank/techContext.md +++ b/memory-bank/techContext.md @@ -2,10 +2,12 @@ ## Technologies Used +* **[Zebra](https://github.com/ZcashFoundation/zebra):** A full-node implementation of the Zcash protocol that provides consensus and network functionalities in Rust. Zebra has been under development by the Zcash Foundation since 2020 and is presently deployed by a number of decentralized node operators on mainnet. +* **[Zaino](https://github.com/zingolabs/zaino/):** An indexing service that provides light clients with blockchain data, replacing the legacy go light client server lightwalletd. Zaino is under active development by Zingo Labs, funded by a grant from [ZCG](https://zcashcommunitygrants.org/). +* **[Zallet](https://github.com/zcash/wallet):** A CLI wallet that provides wallet functionality that existed in Zcashd but is not planned for implementation in Zebra. Zallet is under active development by the Electric Coin Company. * **Rust:** Primary language for Zebra, Zaino, and Zallet (version TBD). Justification: Security, performance, and memory safety. -* **gRPC:** For communication between Zebra and Zaino (version TBD). Justification: Efficient and standardized RPC framework. +* **gRPC:** In consideration for communication between Zebra and Zaino (version TBD). Justification: Efficient and standardized RPC framework. * **Docker:** For deployment and containerization (version TBD). Justification: Consistent and reproducible environments. -* **Potentially: RocksDB or similar embedded database for Zaino's indexing** (version TBD). Justification: Efficient storage and retrieval of blockchain data. ## Development Setup @@ -22,13 +24,193 @@ ## Dependencies -* `zebra-chain`, `zebra-consensus`, `zebra-network`, `zebra-state`: Zebra's core crates for blockchain functionality. -* `tokio`: Asynchronous runtime for Rust. -* `anyhow`: Flexible error handling library. +### Z3 Wrapper Dependencies +TBD but generally speaking we want to use the same libraries and frameworks that the ZF Engineering team is familiar and comfortable with based on its work on Zebra. + +### Zebra Dependencies +* Core crates: + - `zebra-chain`: Core data structures and crypto primitives for the Zcash protocol + - `zebra-consensus`: Consensus rules implementation and block validation + - `zebra-network`: P2P networking stack and peer management + - `zebra-state`: Chain state management and block storage + - `zebra-rpc`: JSON-RPC and gRPC server implementations + - `zebra-script`: Bitcoin script verification engine for transparent transactions + - `zebra-node-services`: Shared services used across multiple Zebra components + - `zebra-scan`: Block and transaction scanning utilities + - `zebra-grpc`: gRPC service definitions and implementations + - `zebra-utils`: Common utilities and helper functions + - `zebra-test`: Shared test infrastructure and helpers + - `tower-batch-control`: Custom Tower middleware for batch control + - `tower-fallback`: Custom Tower middleware for fallback handling + - `zebrad`: Main executable binary crate + +* Zcash Dependencies: + - `halo2`: Zero-knowledge proof system used for Orchard shielded transactions + - `orchard`: Implementation of the Orchard shielded pool protocol + - `zcash_primitives`: Core Zcash cryptographic primitives + - `zcash_proofs`: Zero-knowledge proof implementations + - `zcash_client_backend`: Client functionality shared between wallets + - `zcash_address`: Zcash address handling and encoding + - `zcash_encoding`: Binary serialization for Zcash types + - `zcash_history`: Block chain history handling + - `zip32`: Hierarchical deterministic wallets for Zcash + - `sapling-crypto`: Sapling zk-SNARK circuit implementations + - `incrementalmerkletree`: Incremental Merkle tree implementation + +* External dependencies: + - `tokio`: Asynchronous runtime and utilities + - `tower`: Service architecture and middleware + - `futures`: Async/await primitives and utilities + - `blake2b_simd`: High-performance hashing implementation + - `metrics`: Core metrics collection and reporting + - `metrics-exporter-prometheus`: Prometheus metrics exposition + - `tracing`: Structured logging and diagnostics + - `serde`: Serialization/deserialization framework + - `rocksdb`: Persistent key-value storage backend + - `hyper`: HTTP/HTTPS implementation + - `tonic`: gRPC implementation + - `jsonrpsee`: JSON-RPC framework + - `reqwest`: HTTP client + - `color-eyre`: Error reporting and handling + - `proptest`: Property-based testing framework + - `insta`: Snapshot testing support + - `criterion`: Benchmarking framework + +### Zaino Dependencies + +* Core crates: + - `zaino-serve`: Service implementation for light clients + - `zaino-state`: State management and storage + - `zaino-fetch`: Data fetching from Zebra nodes + - `zaino-proto`: gRPC protocol definitions + - `zaino-testutils`: Testing utilities + - `zainod`: Main executable binary + +* Zcash Dependencies: + - `zcash_client_backend`: Modified version of librustzcash client backend + - Custom fork: `zingolabs/librustzcash` + - Tag: `zcash_client_sqlite-0.12.1_plus_zingolabs_changes-test_2` + - Features: `lightwalletd-tonic` + - `zcash_protocol`: Protocol definitions from librustzcash + - Custom fork: `zingolabs/librustzcash` + - Tag: `zcash_client_sqlite-0.12.1_plus_zingolabs_changes-test_2` + +* Zebra Dependencies: + - `zebra-chain`: Core data structures (from `main` branch) + - `zebra-state`: State management (from `main` branch) + - `zebra-rpc`: RPC interfaces (from `main` branch) + +* Custom Dependencies: + - `zingolib`: Core functionality from Zingo Labs + - Tag: `zaino_dep_005` + - Features: `test-elevation` + - `zingo-infra-testutils`: Testing infrastructure + - `zingo-infra-services`: Service infrastructure + +* External Dependencies: + - `tokio`: Async runtime with full feature set + - `tokio-stream`: Stream utilities for async data processing + - `tonic`: gRPC implementation and server framework + - `tonic-build`: gRPC code generation + - `tower`: Service architecture with buffer and util features + - `tracing`: Logging infrastructure + - `reqwest`: HTTP client with rustls-tls support + - `lmdb`: Lightning Memory-Mapped Database + - `dashmap`: Thread-safe concurrent HashMap + - `indexmap`: Hash table with deterministic iteration + - `crossbeam-channel`: Multi-producer multi-consumer channels + +### Zallet Dependencies +* Core crate: + - `zallet`: Main wallet implementation including CLI interface and core functionality + +* Zcash Dependencies: + - `zcash_client_backend`: Wallet functionality from librustzcash + - `zcash_client_sqlite`: SQLite storage implementation + - `zcash_primitives`: Core cryptographic primitives + - `zcash_keys`: Key management + - `zcash_protocol`: Protocol definitions + - `orchard`: Orchard shielded pool support + - `sapling`: Sapling shielded pool support + - `transparent`: Transparent address support + - `zip32`: HD wallet key derivation +Note that all of the Zcash dependencies in Zallet are presently pinned to a specific revision of Librustzcash. + +* External Dependencies: + - `tokio`: Async runtime and utilities + - `abscissa_core`: Application framework + - `deadpool-sqlite`: SQLite connection pooling + - `rusqlite`: SQLite database access + - `age`: File encryption + - `clap`: Command line argument parsing + - `jsonrpsee`: JSON-RPC client + - `tonic`: gRPC client (temporary for lightwalletd) + +### Common Dependencies + +* Runtime and async: + - `tokio`: Async runtime used by all components + - `tower`: Service architecture and middleware + +* Observability: + - `tracing`: Logging and diagnostics infrastructure used across all components + +* Serialization: + - `serde`: Data serialization framework + +* RPC: + - `tonic`: gRPC implementation (used by all, though Zallet's usage is temporary) + - `jsonrpsee`: JSON-RPC framework (Zebra and Zallet) + +* Error handling: + - `color-eyre`: Error reporting and handling + +* CLI: + - `clap`: Command-line argument parsing + +Note: While these dependencies are common, they might be used with different feature flags or versions across the components. The exact versions should be coordinated to ensure compatibility when integrating the components. ## Tool Usage Patterns -* `cargo`: For building, testing, and managing dependencies. -* `clippy`: For linting and code quality checks. -* `git`: For version control and collaboration. -* `docker`: For building and running containers. +### Build Tools +* `cargo`: + - Building: `cargo build --release` + - Testing: `cargo test` + - Dependency management: `cargo update` + - Documentation: `cargo doc --no-deps` + - Workspace management: `cargo workspace` + +### Code Quality +* `clippy`: + - Standard linting: `cargo clippy` + - Strict checks: `cargo clippy -- -D warnings` + - Workspace checks: `cargo clippy --workspace` +* `fmt`: + - Code formatting: `cargo fmt` + - Format check: `cargo fmt -- --check` + +### Version Control +* `git`: + - Branch management: Using feature branches + - Commit messages: Following Conventional Commits spec + - Code review: GitHub pull request workflow + - Tagging: Semantic versioning for releases + +### Containerization +* `docker`: + - Local development: Docker Compose for service orchestration + - Testing: Isolated test environments + - CI/CD: GitHub Actions with Docker caching + - Production: Multi-stage builds for minimal images + +### Testing +* Unit tests: Per-module tests using `#[cfg(test)]` +* Integration tests: Using `tests/` directory +* Property testing: Using `proptest` +* Snapshot testing: Using `insta` +* Benchmarking: Using `criterion` + +### Documentation +* API docs: Using rustdoc comments +* Architecture docs: Using architecture decision records (ADRs) +* User guides: Using mdBook From 642fb765f364cde9d11a93ce1ce8644567b9aade Mon Sep 17 00:00:00 2001 From: DC Date: Wed, 9 Apr 2025 11:22:00 -0600 Subject: [PATCH 04/57] chore(deps): update zaino and zebra submodules * Update zaino submodule to latest main - Sync with upstream zingolabs/zaino - Pull latest gRPC protocol changes - Update librustzcash dependency * Update zebra submodule to latest main - Sync with upstream ZcashFoundation/zebra - Include latest consensus fixes - Update network protocol handling This keeps our dependencies in sync with upstream development. --- zaino | 2 +- zebra | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/zaino b/zaino index 72b9d7e..67604ce 160000 --- a/zaino +++ b/zaino @@ -1 +1 @@ -Subproject commit 72b9d7e24be11843197620d3a1b718566e3f9c21 +Subproject commit 67604ce427ce4daf1f9bf9f9be81d9db72e77a7f diff --git a/zebra b/zebra index c9a4ae4..cc5c5ed 160000 --- a/zebra +++ b/zebra @@ -1 +1 @@ -Subproject commit c9a4ae41f9f02dcab3ee529c9ab9c796ecffe0ad +Subproject commit cc5c5edd357b5aae013a7d1cbec394d09bfe9765 From 204aa566604a2674f588f499c5a6fec486b2225a Mon Sep 17 00:00:00 2001 From: DC Date: Wed, 9 Apr 2025 13:25:22 -0600 Subject: [PATCH 05/57] docs: update documentation navigation links * Add cross-references between key documents - Link Project Brief to Product Context - Link Product Context to Technical Context - Link Technical Context to System Patterns - Link System Patterns to Progress document * Update README navigation - Add links to all key documents - Include brief description for each - Maintain consistent formatting This improves documentation navigation and helps users find related information across the Z3 documentation. Relates to #1 --- README.md | 8 ++++++++ memory-bank/productContext.md | 4 ++++ memory-bank/projectbrief.md | 5 +++++ memory-bank/systemPatterns.md | 4 ++++ memory-bank/techContext.md | 4 ++++ 5 files changed, 25 insertions(+) diff --git a/README.md b/README.md index bc322a6..d1adf04 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,10 @@ # z3 Repository to iterate on the grand unification of Zebra, Zaino and Zallet to replace the venerable Zcashd + +For details, see: +- [Project Brief](memory-bank/projectbrief.md) for a high-level overview +- [Product Context](memory-bank/productContext.md) for detailed problem analysis and proposed solution +- [Technical Context](memory-bank/techContext.md) for architecture and dependencies +- [System Patterns](memory-bank/systemPatterns.md) for design patterns and conventions +- [Progress](memory-bank/progress.md) for current status and roadmap + diff --git a/memory-bank/productContext.md b/memory-bank/productContext.md index 0a3a6c9..e765f25 100644 --- a/memory-bank/productContext.md +++ b/memory-bank/productContext.md @@ -47,3 +47,7 @@ The key user experience goals include: ## Key Metrics * **% of mainnet nodes running Z3:** 0 as of April 2025, with a target of 2025 by the time Zcashd is deprecated. Sources for determining this metric are TBD, some potential options include [Blockchair](https://blockchair.com/zcash/nodes) + +## Further Reading + +For technical details about the architecture and implementation, see the [Technical Context](techContext.md). diff --git a/memory-bank/projectbrief.md b/memory-bank/projectbrief.md index 6a82ef5..eef449b 100644 --- a/memory-bank/projectbrief.md +++ b/memory-bank/projectbrief.md @@ -30,3 +30,8 @@ Z3: Building a modern, secure, reliable, scalable software stack that will repla - Test driven development - Docker for deployment - [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#specification) as standard for SCM commit messaging + +## Further Reading + +For a detailed analysis of the problems we're solving and proposed solution, see the [Product Context](productContext.md). + diff --git a/memory-bank/systemPatterns.md b/memory-bank/systemPatterns.md index d7dd73c..8d63041 100644 --- a/memory-bank/systemPatterns.md +++ b/memory-bank/systemPatterns.md @@ -38,3 +38,7 @@ graph LR * It is not yet clear whether Zallet will communicate only with Zaino or also directly with Zebra. This will depend on the specific requirements of Zallet and the design of the interfaces between the components. * There is ongoing discussion about Zaino being implemented as a library that will be integrated directly into wallet software. If this approach is adopted, users may not need to communicate with a separate Zaino service. + +## Further Reading + +For details about current implementation status and next steps, see the [Progress](progress.md) document. diff --git a/memory-bank/techContext.md b/memory-bank/techContext.md index 21923ec..2c1ee01 100644 --- a/memory-bank/techContext.md +++ b/memory-bank/techContext.md @@ -214,3 +214,7 @@ Note: While these dependencies are common, they might be used with different fea * API docs: Using rustdoc comments * Architecture docs: Using architecture decision records (ADRs) * User guides: Using mdBook + +## Further Reading + +For details about design patterns and conventions used across the Z3 stack, see the [System Patterns](systemPatterns.md). From daddc0c3ad5f656e7c24c6cb7eda9c2bb575da4c Mon Sep 17 00:00:00 2001 From: DC Date: Wed, 9 Apr 2025 15:36:44 -0600 Subject: [PATCH 06/57] chore(deps): add zcashd reference implementation as submodule * Add zcashd codebase for reference purposes - Add submodule at path zcashd/ - Configure .gitmodules with upstream repo - Use latest stable release tag This adds the legacy codebase we're replacing as a submodule to help ensure compatibility and feature parity during development. Relates to #1 --- .gitmodules | 3 +++ memory-bank/activeContext.md | 4 ++-- memory-bank/productContext.md | 12 +++++++++--- memory-bank/progress.md | 2 +- memory-bank/projectbrief.md | 7 ++++--- memory-bank/systemPatterns.md | 4 +--- memory-bank/techContext.md | 1 + zcashd | 1 + 8 files changed, 22 insertions(+), 12 deletions(-) create mode 160000 zcashd diff --git a/.gitmodules b/.gitmodules index 3d7a3e1..0175cc1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "zallet"] path = zallet url = https://github.com/zcash/wallet +[submodule "zcashd"] + path = zcashd + url = https://github.com/zcash/zcash.git diff --git a/memory-bank/activeContext.md b/memory-bank/activeContext.md index 514ce16..e8acf9a 100644 --- a/memory-bank/activeContext.md +++ b/memory-bank/activeContext.md @@ -6,11 +6,11 @@ Updating the Memory Bank, specifically reviewing and updating the core files (`p ## Recent Changes -Updated the `productContext.md`, `systemPatterns.md`, and `techContext.md` files with information about the project's problems, proposed solution, user experience goals, system architecture, technologies used, and development setup. The `productContext.md` file was further updated to refine the "User Experience Goals" section and add a "Key Metrics" section. The `systemPatterns.md` file was updated to reflect the user's interaction with the Z3 wrapper service and the potential communication paths between Zallet, Zaino, and Zebra. The `systemPatterns.md` file was further updated to rename "Wrapper Service" to "Z3 wrapper service". The `techContext.md` file was updated to include links to the repositories for Zebra, Zaino, and Zallet, and to provide more detailed information about dependencies and tool usage patterns. +Read all memory bank files (`projectbrief.md`, `productContext.md`, `systemPatterns.md`, `techContext.md`, `activeContext.md`, and `progress.md`). ## Next Steps -Review and update the `progress.md` file to reflect the current state of the project. +Update the `activeContext.md` and `progress.md` files to reflect the current state of the project. ## Active Decisions diff --git a/memory-bank/productContext.md b/memory-bank/productContext.md index e765f25..557d889 100644 --- a/memory-bank/productContext.md +++ b/memory-bank/productContext.md @@ -12,12 +12,12 @@ The Z3 project aims to replace the existing Zcashd software stack, which was for ## Proposed Solution -This project proposes replacing Zcashd with a modular software stack consisting of the following components: +This project proposes replacing [Zcashd](https://github.com/zcash/zcash) with a modular software stack consisting of the following components: * **[Zebra](https://github.com/ZcashFoundation/zebra):** A full-node implementation of the Zcash protocol that provides consensus and network functionalities. Zebra has been under development by the Zcash Foundation since 2020 and is presently deployed by a number of decentralized node operators on mainnet. * **[Zaino](https://github.com/zingolabs/zaino/):** An indexing service that provides light clients with blockchain data, replacing the legacy go light client server lightwalletd. Zaino is under active development by Zingo Labs, funded by a grant from [ZCG](https://zcashcommunitygrants.org/). * **[Zallet](https://github.com/zcash/wallet):** A CLI wallet that provides wallet functionality that existed in Zcashd but is not planned for implementation in Zebra. Zallet is under active development by the Electric Coin Company. -* **Z3 Wrapper Service:** A wrapper service that exposes the three previously mentioned services as a "single binary" to the users. Note that this capability could be achieved as a "single binary" that is deployed as a Debian package eg `sudo apt install zcash-z3`, or via Docker Compose or as a Helm chart. +* **Z3 Wrapper Service:** A new wrapper service that exposes the three previously mentioned services as a "single binary" to the users. Note that this capability could be achieved as a "single binary" that is deployed as a Debian package eg `sudo apt install zcash-z3`, or via Docker Compose or as a Helm chart. This modular architecture offers several advantages: @@ -46,7 +46,13 @@ The key user experience goals include: * **Usability:** Making the software stack easy to use and understand. ## Key Metrics -* **% of mainnet nodes running Z3:** 0 as of April 2025, with a target of 2025 by the time Zcashd is deprecated. Sources for determining this metric are TBD, some potential options include [Blockchair](https://blockchair.com/zcash/nodes) +* **% of mainnet nodes running Z3:** 0 as of April 2025, with a target of 100% by the time Zcashd is deprecated. Sources for determining this metric are TBD, some potential options include [Blockchair](https://blockchair.com/zcash/nodes) +* **% of centralized exchanges upgraded to Z3:** 0 as of April 2025, with a target of 100% by the time Zcashd is deprecated. Sources for determining this metric are TBD, and we are targeting 0 exchange de-listings due to upgrade friction. +* **% of decentralized exchanges upgraded to Z3:** 0 as of April 2025, with a target of 100% by the time Zcashd is deprecated. Sources for determining this metric are TBD, and we are targeting 0 exchange de-listings due to upgrade friction. +* **% of wallets upgraded to Z3:** 0 as of April 2025, with a target of 100% by the time Zcashd is deprecated. Sources for determining this metric are TBD. +* **% of block explorers upgraded to Z3:** 0 as of April 2025, with a target of 100% by the time Zcashd is deprecated. Sources for determining this metric are TBD. +* **% of PoW hashpower lost due to Z3 upgrade:** 0% as of April 2025, with a target of <=10% by the time Zcashd is deprecated. Sources for determining this metric are TBD. Specifically we do not want to introduce security risk to the network by making it hard for PoW miners and mining pools to update to the Z3 stack. + ## Further Reading diff --git a/memory-bank/progress.md b/memory-bank/progress.md index 49fd124..4425c1f 100644 --- a/memory-bank/progress.md +++ b/memory-bank/progress.md @@ -10,7 +10,7 @@ The entire software stack (Zebra, Zaino, Zallet, and the wrapper service) still ## Current Status -The project is in the initial planning and documentation phase. The Memory Bank is being established as a central repository for project knowledge. +The project is in the initial planning and documentation phase. The Memory Bank is being established as a central repository for project knowledge. The core memory bank files have been reviewed and updated. ## Known Issues diff --git a/memory-bank/projectbrief.md b/memory-bank/projectbrief.md index eef449b..188e8c7 100644 --- a/memory-bank/projectbrief.md +++ b/memory-bank/projectbrief.md @@ -1,7 +1,7 @@ # Project Brief ## Overview -Z3: Building a modern, secure, reliable, scalable software stack that will replace Zcashd. +Z3: Building a modern, secure, reliable, scalable software stack that will replace [Zcashd](https://github.com/zcash/zcash). ## Core Features - A full-node implementation that implements the Zcash protocol (Zebra) @@ -18,11 +18,12 @@ Z3: Building a modern, secure, reliable, scalable software stack that will repla - Operators of "finalizers" (known more commonly as validators) as Zcash prepares to transition to a hybrid Proof of Work / Proof of Stake consensus algorithm. - Operators of block explorers on the Zcash network -## Technical Preferences (optional) +## Technical Preferences +- [Zcashd](https://github.com/zcash/zcash) is the legacy software that is being replaced - [Zebra](https://github.com/ZcashFoundation/zebra) for the Zcash full node - [Zaino](https://github.com/zingolabs/zaino/) for the indexing service that will interface with Zebrad via RPC and the ReadStateService, and also replace the legacy go light client server [lightwalletd](https://github.com/zcash/lightwalletd) - [Zallet](https://github.com/zcash/wallet) for the cli-wallet that will replace the wallet functionality that originally existed in Zcashd -- Other software components should be Rust based +- The Z3 wrapper software that is the primary software deliverable of this project should be written in Rust, unless there is a *really* good reason to use another language or framework. - Security is of paramount importance - Clippy as the Rust linter of choice - Git and Github for SCM diff --git a/memory-bank/systemPatterns.md b/memory-bank/systemPatterns.md index 8d63041..f74436d 100644 --- a/memory-bank/systemPatterns.md +++ b/memory-bank/systemPatterns.md @@ -22,9 +22,7 @@ graph LR ## Design Patterns -* **Facade:** The Z3 wrapper service implements the Facade pattern, providing a simplified interface to the complex interactions between Zebra, Zaino, and Zallet. -* **Observer:** Zebra and Zaino may utilize the Observer pattern for event handling, allowing components to react to changes in the blockchain state. -* **Repository:** Zaino may employ the Repository pattern to abstract data access, simplifying data management and improving testability. +* **Facade:** The Z3 wrapper service implements the Facade pattern, providing a simplified interface to the complex interactions between Zebra, Zaino, and Zallet. The Z3 wrapper service receives RPC requests from users and routes them to the appropriate backend service. ## Component Relationships diff --git a/memory-bank/techContext.md b/memory-bank/techContext.md index 2c1ee01..4639d60 100644 --- a/memory-bank/techContext.md +++ b/memory-bank/techContext.md @@ -2,6 +2,7 @@ ## Technologies Used +* **[Zcashd](https://github.com/zcash/zcash):** Is the legacy Zcash full-node client software being replaced by the Z3 stack * **[Zebra](https://github.com/ZcashFoundation/zebra):** A full-node implementation of the Zcash protocol that provides consensus and network functionalities in Rust. Zebra has been under development by the Zcash Foundation since 2020 and is presently deployed by a number of decentralized node operators on mainnet. * **[Zaino](https://github.com/zingolabs/zaino/):** An indexing service that provides light clients with blockchain data, replacing the legacy go light client server lightwalletd. Zaino is under active development by Zingo Labs, funded by a grant from [ZCG](https://zcashcommunitygrants.org/). * **[Zallet](https://github.com/zcash/wallet):** A CLI wallet that provides wallet functionality that existed in Zcashd but is not planned for implementation in Zebra. Zallet is under active development by the Electric Coin Company. diff --git a/zcashd b/zcashd new file mode 160000 index 0000000..7ac250c --- /dev/null +++ b/zcashd @@ -0,0 +1 @@ +Subproject commit 7ac250c93b5fa8f8c23ac6fccfdf159608c8aceb From 960abad5b93ae82c007dbe5ecf7cf3aa6c8086da Mon Sep 17 00:00:00 2001 From: DC Date: Wed, 9 Apr 2025 16:50:49 -0600 Subject: [PATCH 07/57] docs: update RPC method documentation links * Add links to RPC method documentation - Add link to April 2025 RPC method usage report - Update reference to Google Sheet source - Improve readability of RPC section * Organize documentation structure - Move RPC data to separate files - Use consistent path references - Maintain documentation hierarchy This improves navigation and accessibility of RPC method documentation for the Z3 stack components. Relates to #1 --- memory-bank/activeContext.md | 73 +++++++++- memory-bank/data/rpc_mapping.md | 65 +++++++++ memory-bank/data/zcashd_RPC_methods.md | 58 ++++++++ ...cation_team_rpc_method_support_9apr2025.md | 129 ++++++++++++++++++ memory-bank/progress.md | 11 +- memory-bank/projectbrief.md | 2 +- memory-bank/systemPatterns.md | 2 +- memory-bank/techContext.md | 13 +- 8 files changed, 343 insertions(+), 10 deletions(-) create mode 100644 memory-bank/data/rpc_mapping.md create mode 100644 memory-bank/data/zcashd_RPC_methods.md create mode 100644 memory-bank/data/zcashd_deprecation_team_rpc_method_support_9apr2025.md diff --git a/memory-bank/activeContext.md b/memory-bank/activeContext.md index e8acf9a..06e3883 100644 --- a/memory-bank/activeContext.md +++ b/memory-bank/activeContext.md @@ -7,10 +7,77 @@ Updating the Memory Bank, specifically reviewing and updating the core files (`p ## Recent Changes Read all memory bank files (`projectbrief.md`, `productContext.md`, `systemPatterns.md`, `techContext.md`, `activeContext.md`, and `progress.md`). +Enumerate all of the RPC methods that zcashd exposes. -## Next Steps +## RPC Method Support in Z3 -Update the `activeContext.md` and `progress.md` files to reflect the current state of the project. +Based on the RPC method support file, here's a summary of which component will support each RPC method in the new Z3 stack: + +| RPC Method | Where in new stack | Actions | +|---|---|---| +| `getaddressbalance` | Zebra (lightwalletd usage) / Zaino | Review implementation in Zebra | +| `getaddressdeltas` | Zaino | | +| `getaddressmempool` | - | Lower priority. Possibly deprecate | +| `getaddresstxids` | Zebra (lightwalletd usage) / Zaino | Review implementation in Zebra \| Implement in Zaino | +| `getaddressutxos` | Zebra | Review implementation in Zebra | +| `getbestblockhash` | Zebra / Zaino | | +| `getblock` | Zebra / Zaino | Being fully implemented in Zebra | +| `getblockchaininfo` | Zebra / Zaino | Review implementation in Zebra | +| `getblockcount` | Zebra / Zaino | | +| `getblockdeltas` | Zaino | Implement in Zaino | +| `getblockhash` | Zebra / Zaino | | +| `getblockhashes` | Zaino | Lower priority. Possibly deprecate | +| `getblockheader` | Zebra / Zaino | | +| `getchaintips` | Zaino | Implement in Zaino | +| `getdifficulty` | Zebra / Zaino | | +| `getmempoolinfo` | Zaino | Implement in Zaino | +| `getrawmempool` | Zebra / Zaino | | +| `getspentinfo` | Zaino | Implement in Zaino | +| `gettxout` | Zaino | Implement in Zaino | +| `gettxoutproof` | Zaino | Lower priority | +| `gettxoutsetinfo` | Zaino | | +| `verifychain` | - | Deprecate | +| `verifytxoutproof` | Zaino | Lower priority | +| `z_gettreestate` | Zebra / Zaino | Review implementation in Zebra | +| `getexperimentalfeatures` | - | Deprecate? Confirm what Zebra defines as experimental | +| `getinfo` | Zebra | Review implementation in Zebra | +| `getmemoryinfo` | Zebra | No plan to implement | +| `help` | Zebra | No plans to implement | +| `setlogfilter` | Zebra | No plans to implement | +| `stop` | Zebra | None | +| `z_getpaymentdisclosure` | - | Deprecate | +| `z_validatepaymentdisclosure` | - | Deprecate | +| `generate` | Zebra | None | +| `getgenerate` | Zebra | No plans to implement | +| `setgenerate` | Zebra | No plans to implement | +| `getblocksubsidy` | Zebra | None. All done in Zebra | +| `getblocktemplate` | Zebra | Review implementation in Zebra | +| `getlocalsolps` | Zebra | No plans to implement | +| `getmininginfo` | Zebra / Zaino | | +| `getnetworkhashps` | - | Deprecated | +| `getnetworksolps` | Zebra / Zaino | | +| `prioritisetransaction` | Zebra | No plans to implement | +| `submitblock` | Zebra | None | +| `addnode` | Zebra | Decide if we want to implement in Zebra | +| `clearbanned` | Zebra | Decide if we want to implement in Zebra | +| `disconnectnode` | Zebra | Decide if we want to implement in Zebra | +| `getaddednodeinfo` | Zebra | Decide if we want to implement in Zebra | +| `getnettotals` | Zebra | Decide if we want to implement in Zebra | +| `getnetworkinfo` | Zebra / Zaino | Implement in Zebra | +| `getpeerinfo` | Zebra / Zaino | Review implementation in Zebra | +| `listbanned` | Zebra | Decide if we want to implement in Zebra | +| `ping` | Zebra / Zaino | Implement in Zebra | +| `setban` | Zebra | Decide if we want to implement in Zebra | +| `createrawtransaction` | Zallet | | +| `decoderawtransaction` | Zallet | Lower priority. Might deprecate | +| `decodescript` | Zallet | Lower priority. Might deprecate | +| `fundrawtransaction` | Zallet | | +| `getrawtransaction` | Zebra / Zaino | Review implementation in Zebra | +| `sendrawtransaction` | Zebra / Zaino | Review implementation in Zebra | +| `signrawtransaction` | Zallet | | +| `validateaddress` | Zebra / Zaino | Implement in Zaino | +| `verifymessage` | Zallet | Implement in Zallet | +| `z_validateaddress` | Zebra | Implement in Zaino | ## Active Decisions @@ -23,3 +90,5 @@ The importance of maintaining a comprehensive and up-to-date Memory Bank is a ke ## Learnings and Insights The project brief provides a good overview of the project's goals and requirements, which is essential for making informed decisions. The modular architecture of the system allows for independent development and deployment of components. + +The RPC method support file provides a valuable overview of which RPC methods are most important to support in the new Z3 stack. This information will be used to guide the development of the Zebra, Zaino, and Zallet components. diff --git a/memory-bank/data/rpc_mapping.md b/memory-bank/data/rpc_mapping.md new file mode 100644 index 0000000..874c16c --- /dev/null +++ b/memory-bank/data/rpc_mapping.md @@ -0,0 +1,65 @@ +| RPC Method | Where in new stack | Actions | +|---|---|---| +| `getaddressbalance` | Zebra (lightwalletd usage) / Zaino | Review implementation in Zebra | +| `getaddressdeltas` | Zaino | | +| `getaddressmempool` | - | Lower priority. Possibly deprecate | +| `getaddresstxids` | Zebra (lightwalletd usage) / Zaino | Review implementation in Zebra \| Implement in Zaino | +| `getaddressutxos` | Zebra | Review implementation in Zebra | +| `getbestblockhash` | Zebra / Zaino | | +| `getblock` | Zebra / Zaino | Being fully implemented in Zebra | +| `getblockchaininfo` | Zebra / Zaino | Review implementation in Zebra | +| `getblockcount` | Zebra / Zaino | | +| `getblockdeltas` | Zaino | Implement in Zaino | +| `getblockhash` | Zebra / Zaino | | +| `getblockhashes` | Zaino | Lower priority. Possibly deprecate | +| `getblockheader` | Zebra / Zaino | | +| `getchaintips` | Zaino | Implement in Zaino | +| `getdifficulty` | Zebra / Zaino | | +| `getmempoolinfo` | Zaino | Implement in Zaino | +| `getrawmempool` | Zebra / Zaino | | +| `getspentinfo` | Zaino | Implement in Zaino | +| `gettxout` | Zaino | Implement in Zaino | +| `gettxoutproof` | Zaino | Lower priority | +| `gettxoutsetinfo` | Zaino | | +| `verifychain` | - | Deprecate | +| `verifytxoutproof` | Zaino | Lower priority | +| `z_gettreestate` | Zebra / Zaino | Review implementation in Zebra | +| `getexperimentalfeatures` | - | Deprecate? Confirm what Zebra defines as experimental | +| `getinfo` | Zebra | Review implementation in Zebra | +| `getmemoryinfo` | Zebra | No plan to implement | +| `help` | Zebra | No plans to implement | +| `setlogfilter` | Zebra | No plans to implement | +| `stop` | Zebra | None | +| `z_getpaymentdisclosure` | - | Deprecate | +| `z_validatepaymentdisclosure` | - | Deprecate | +| `generate` | Zebra | None | +| `getgenerate` | Zebra | No plans to implement | +| `setgenerate` | Zebra | No plans to implement | +| `getblocksubsidy` | Zebra | None. All done in Zebra | +| `getblocktemplate` | Zebra | Review implementation in Zebra | +| `getlocalsolps` | Zebra | No plans to implement | +| `getmininginfo` | Zebra / Zaino | | +| `getnetworkhashps` | - | Deprecated | +| `getnetworksolps` | Zebra / Zaino | | +| `prioritisetransaction` | Zebra | No plans to implement | +| `submitblock` | Zebra | None | +| `addnode` | Zebra | Decide if we want to implement in Zebra | +| `clearbanned` | Zebra | Decide if we want to implement in Zebra | +| `disconnectnode` | Zebra | Decide if we want to implement in Zebra | +| `getaddednodeinfo` | Zebra | Decide if we want to implement in Zebra | +| `getnettotals` | Zebra | Decide if we want to implement in Zebra | +| `getnetworkinfo` | Zebra / Zaino | Implement in Zebra | +| `getpeerinfo` | Zebra / Zaino | Review implementation in Zebra | +| `listbanned` | Zebra | Decide if we want to implement in Zebra | +| `ping` | Zebra / Zaino | Implement in Zebra | +| `setban` | Zebra | Decide if we want to implement in Zebra | +| `createrawtransaction` | Zallet | | +| `decoderawtransaction` | Zallet | Lower priority. Might deprecate | +| `decodescript` | Zallet | Lower priority. Might deprecate | +| `fundrawtransaction` | Zallet | | +| `getrawtransaction` | Zebra / Zaino | Review implementation in Zebra | +| `sendrawtransaction` | Zebra / Zaino | Review implementation in Zebra | +| `signrawtransaction` | Zallet | | +| `validateaddress` | Zebra / Zaino | Implement in Zaino | +| `verifymessage` | Zallet | Implement in Zallet | +| `z_validateaddress` | Zebra | Implement in Zaino | \ No newline at end of file diff --git a/memory-bank/data/zcashd_RPC_methods.md b/memory-bank/data/zcashd_RPC_methods.md new file mode 100644 index 0000000..32ba5bf --- /dev/null +++ b/memory-bank/data/zcashd_RPC_methods.md @@ -0,0 +1,58 @@ +# these should be all the RPC methods exposed by Zcashd + +getblockchaininfo +getbestblockhash +getblockcount +getblock +getblockhash +getblockheader +getchaintips +z_gettreestate +z_getsubtreesbyindex +getdifficulty +getmempoolinfo +getrawmempool +gettxout +gettxoutsetinfo +verifychain +getblockdeltas +getblockhashes +invalidateblock +reconsiderblock +getlocalsolps +getnetworksolps +getnetworkhashps +getmininginfo +prioritisetransaction +getblocktemplate +submitblock +getblocksubsidy +getgenerate +setgenerate +generate +getinfo +getmemoryinfo +validateaddress +z_validateaddress +createmultisig +verifymessage +getexperimentalfeatures +getaddresstxids +getaddressbalance +getaddressdeltas +getaddressutxos +getaddressmempool +getspentinfo +setmocktime +getconnectioncount +getdeprecationinfo +ping +getpeerinfo +addnode +disconnectnode +getaddednodeinfo +getnettotals +getnetworkinfo +setban +listbanned +clearbanned \ No newline at end of file diff --git a/memory-bank/data/zcashd_deprecation_team_rpc_method_support_9apr2025.md b/memory-bank/data/zcashd_deprecation_team_rpc_method_support_9apr2025.md new file mode 100644 index 0000000..f6a5342 --- /dev/null +++ b/memory-bank/data/zcashd_deprecation_team_rpc_method_support_9apr2025.md @@ -0,0 +1,129 @@ +| Category | RPC call | Zebra support | Zcashd support | Zallet support | Zaino support | Where in new stack | Actions | Comments | Block Explorers | Insight Explorer | NH Explorer | Mining pool 1 | Mining pool 2 | Mining Pool 3 | Mining Pool 4 | Exchange 1 | Exchange 2 | Exchange 3 | Exchange 4 | Exchange 5 | Exchange 6 | +|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----| +| Addressindex | getaddressbalance | Partial | Yes | | | Zebra \| Zaino | Review implementation in Zebra | Zebra only supports lightwalletd usage of this RPC method | TRUE | TRUE | TRUE | | | | | | | | | | | +| Addressindex | getaddressdeltas | No | Yes | | | Zaino | | Related to prioritisetransaction | TRUE | TRUE | TRUE | | | | | | | | | | | +| Addressindex | getaddressmempool | No | Yes | | | \- | Lower priority. Possibly deprecate | Is anyone using this? Lower priority for now. Fee deltas, might want to make a more useful method in future | FALSE | FALSE | FALSE | | | | | | | | | | | +| Addressindex | getaddresstxids | Partial | Yes | | | Zebra \| Zaino | Review implementation in Zebra \| Implement in Zaino | Zebra only supports lightwalletd usage of this RPC method | FALSE | FALSE | FALSE | | | | | | | | | | | +| Addressindex | getaddressutxos | Partial | Yes | | | Zebra | Review implementation in Zebra | Zebra only supports lightwalletd usage of this RPC method | FALSE | FALSE | FALSE | | | | | | | | TRUE | | | +| Blockchain | getbestblockhash | Yes | Yes | | | Zebra \| Zaino | | | TRUE | TRUE | TRUE | | | | | | | TRUE | TRUE | | | +| Blockchain | getblock | Partial | Yes | | | Zebra \| Zaino | Being fully implemented in Zebra | Zebra only supports lightwalletd usage of this RPC method. anchor will not be supported. | TRUE | TRUE | TRUE | TRUE | TRUE | FALSE | FALSE | TRUE | TRUE | TRUE | TRUE | TRUE | | +| Blockchain | getblockchaininfo | Partial | Yes | | | Zebra \| Zaino | Review implementation in Zebra | Zebra only supports lightwalletd usage of this RPC method | TRUE | TRUE | TRUE | | | | | | TRUE | TRUE | | | TRUE | +| Blockchain | getblockcount | Yes | Yes | | | Zebra \| Zaino | | Requested by 2 mining pools and 1 exchange | TRUE | TRUE | TRUE | TRUE | TRUE | FALSE | FALSE | TRUE | TRUE | | TRUE | TRUE | | +| Blockchain | getblockdeltas | No | Explorer-only | | | Zaino | Implement in Zaino | Insight explorer | FALSE | TRUE | FALSE | | | | | | | | | | | +| Blockchain | getblockhash | Yes | Yes | | | Zebra \| Zaino | | Requested by 1 mining pool and 1 exchange | FALSE | FALSE | FALSE | TRUE | FALSE | FALSE | FALSE | TRUE | TRUE | TRUE | | TRUE | TRUE | +| Blockchain | getblockhashes | No | Explorer-only | | | Zaino | Lower priority. Possibly deprecate | Lower priority | TRUE | FALSE | TRUE | | | | | | | | | | | +| Blockchain | getblockheader | Yes | Yes | | | Zebra \| Zaino | | Requested by 1 mining pool. chainwork will not be supported (undocumented in zcashd) | TRUE | FALSE | TRUE | TRUE | FALSE | | | | | TRUE | | | | +| Blockchain | getchaintips | No | Yes | | | Zaino | Implement in Zaino | | FALSE | FALSE | FALSE | | | | | | | | | | | +| Blockchain | getdifficulty | Yes | Yes | | | Zebra \| Zaino | | Requested by 1 mining pool | TRUE | FALSE | TRUE | TRUE | FALSE | | | | | | | | | +| Blockchain | getmempoolinfo | No | Yes | | | Zaino | Implement in Zaino | | TRUE | FALSE | TRUE | | | | | | TRUE | | | | TRUE | +| Blockchain | getrawmempool | Yes | Yes | | | Zebra \| Zaino | | | TRUE | FALSE | TRUE | | | | | | | TRUE | TRUE | TRUE | | +| Blockchain | getspentinfo | No | Explorer-only | | | Zaino | Implement in Zaino | Insight explorer | FALSE | TRUE | FALSE | | | | | | | | | | | +| Blockchain | gettxout | No | Yes | | | Zaino | Implement in Zaino | Used by Exchange 2 | FALSE | FALSE | FALSE | | | | | | TRUE | TRUE | | | TRUE | +| Blockchain | gettxoutproof | No | Yes | | | Zaino | Lower priority | | FALSE | FALSE | FALSE | | | | | | | | | | | +| Blockchain | gettxoutsetinfo | No | Yes | | | Zaino | | | TRUE | FALSE | TRUE | | | | | | | | | | | +| Blockchain | verifychain | No | Yes | | | \- | Deprecate | zcashd debugging function | FALSE | FALSE | FALSE | | | | | | | | | | | +| Blockchain | verifytxoutproof | No | Yes | | | Zaino | Lower priority | Related to gettxoutproof. Standalone function, could be anywhere | FALSE | FALSE | FALSE | | | | | | | | | | | +| Blockchain | z_gettreestate | Partial | Yes | | | Zebra \| Zaino | Review implementation in Zebra | Zebra only supports lightwalletd usage of this RPC method | FALSE | FALSE | FALSE | | | | | | | | | | | +| Control | getexperimentalfeatures | No | Yes | | | \- | Deprecate? Confirm what Zebra defines as experimental | zcashd-specific | FALSE | FALSE | FALSE | | | | | | | | | | | +| Control | getinfo | Partial | Yes | | | Zebra | Review implementation in Zebra | Zebra only supports lightwalletd usage of this RPC method | TRUE | FALSE | TRUE | | | | | | | | | | | +| Control | getmemoryinfo | No | Yes | | | Zebra | No plan to implement | Lower priority | TRUE | FALSE | TRUE | | | | | | | | | | | +| Control | help | No | Yes | | | Zebra | No plans to implement | Lower priority | FALSE | FALSE | FALSE | | | | | | | | | | | +| Control | setlogfilter | No | Yes | | | Zebra | No plans to implement | Lower priority | FALSE | FALSE | FALSE | | | | | | | | | | | +| Control | stop | Yes | Yes | | | Zebra | None | | FALSE | FALSE | FALSE | | | | | | | | | | | +| Disclosure | z_getpaymentdisclosure | No | Disabled by default | | | \- | Deprecate | This is for Sprout, and is only enabled via the -paymentDisclosure feature flag. | FALSE | FALSE | FALSE | | | | | | | | | | | +| Disclosure | z_validatepaymentdisclosure | No | Disabled by default | | | \- | Deprecate | This is for Sprout, and is only enabled via the -paymentDisclosure feature flag. | TRUE | FALSE | TRUE | | | | | | | | | | | +| Generating | generate | Yes | Yes | | | Zebra | None | Regtest only | FALSE | FALSE | FALSE | | | | | | | | TRUE | | | +| Generating | getgenerate | No | Yes | | | Zebra | No plans to implement | Regtest only. Lower priority | FALSE | FALSE | FALSE | | | | | | | | TRUE | | | +| Generating | setgenerate | No | Yes | | | Zebra | No plans to implement | Regtest only. Lower priority | FALSE | FALSE | FALSE | | | | | | | | TRUE | | | +| Mining | getblocksubsidy | Yes | Yes | | | Zebra | None. All done in Zebra | Requested by 1 mining pool | FALSE | FALSE | TRUE | FALSE | TRUE | | | | | | | | | +| Mining | getblocktemplate | Partial | Yes | | | Zebra | Review implementation in Zebra | Server lists and work IDs are not supported in Zebra | FALSE | FALSE | FALSE | | | TRUE | TRUE | | | | | | | +| Mining | getlocalsolps | No | Yes | | | Zebra | No plans to implement | Testnet/Regtest only. Local CPU mining only; lower priority | FALSE | FALSE | FALSE | | | | | | | | | | | +| Mining | getmininginfo | Yes | Yes | | | Zebra \| Zaino | | | TRUE | FALSE | TRUE | | | | | | | | | | | +| Mining | getnetworkhashps | Yes | Disabled in 6.2.0 | | | \- | Deprecate | Deprecated | FALSE | FALSE | FALSE | | | | | | | | | | | +| Mining | getnetworksolps | Yes | Yes | | | Zebra \| Zaino | | Requested by 1 mining pool | TRUE | FALSE | TRUE | TRUE | FALSE | | | | | | | | | +| Mining | prioritisetransaction | No | Yes | | | Zebra | No plans to implement | Related to getaddressdeltas. Only implement if specifically requested by a mining pool | FALSE | FALSE | FALSE | | | | | | | | | | | +| Mining | submitblock | Yes | Yes | | | Zebra | None | Requested by 1 mining pool | FALSE | FALSE | FALSE | TRUE | FALSE | TRUE | TRUE | | | | | | | +| Network | addnode | No | Yes | | | Zebra | Decide if we want to implement in Zebra | Lower priority to implement in Zebra | FALSE | FALSE | FALSE | | | | | | | | | | | +| Network | clearbanned | No | Yes | | | Zebra | Decide if we want to implement in Zebra | Lower priority to implement in Zebra | FALSE | FALSE | FALSE | | | | | | | | | | | +| Network | disconnectnode | No | Yes | | | Zebra | Decide if we want to implement in Zebra | Lower priority to implement in Zebra | FALSE | FALSE | FALSE | | | | | | | | | | | +| Network | getaddednodeinfo | No | Yes | | | Zebra | Decide if we want to implement in Zebra | Lower priority to implement in Zebra | FALSE | FALSE | FALSE | | | | | | | | | | | +| Network | getconnectioncount | No | Yes | | | Zebra | Decide if we want to implement in Zebra | Lower priority to implement in Zebra | FALSE | FALSE | FALSE | | | | | | | | | | | +| Network | getdeprecationinfo | No | Yes | | | Zebra | Decide if we want to implement in Zebra | Lower priority to implement in Zebra | FALSE | FALSE | FALSE | | | | | | | | | | | +| Network | getnettotals | No | Yes | | | Zebra | Decide if we want to implement in Zebra | Lower priority to implement in Zebra | FALSE | FALSE | FALSE | | | | | | | | | | | +| Network | getnetworkinfo | No | Yes | | | Zebra \| Zaino | Implement in Zebra | Lower priority to implement in Zebra | TRUE | FALSE | TRUE | | | | | | | TRUE | | | TRUE | +| Network | getpeerinfo | Partial | Yes | | | Zebra \| Zaino | Review implementation in Zebra | Requested by 1 mining pool | TRUE | FALSE | TRUE | TRUE | FALSE | | | | | | | | | +| Network | listbanned | No | Yes | | | Zebra | Decide if we want to implement in Zebra | Lower priority to implement in Zebra | FALSE | FALSE | FALSE | | | | | | | | | | | +| Network | ping | No | Yes | | | Zebra \| Zaino | Implement in Zebra | Lower priority to implement in Zebra | FALSE | FALSE | FALSE | | | | | | | | | | | +| Network | setban | No | Yes | | | Zebra | Decide if we want to implement in Zebra | Lower priority to implement in Zebra | FALSE | FALSE | FALSE | | | | | | | | | | | +| Rawtransactions | createrawtransaction | No | Deprecated in 6.2.0 | Not planned | | Zallet | | Required by 2 exchanges. Zallet will expose PCZTs instead. Being implemented in Zebra by external contributor. There are other ways to do this with a newer API. | FALSE | FALSE | FALSE | | | FALSE | FALSE | TRUE | TRUE | | | | | +| Rawtransactions | decoderawtransaction | No | Yes | | | Zallet | Lower priority. Might deprecate | Just a utility method, implementable outside zebra \| requested by 1 mining pool | FALSE | FALSE | FALSE | TRUE | FALSE | FALSE | FALSE | TRUE | | | | | TRUE | +| Rawtransactions | decodescript | No | Yes | | | Zallet | Lower priority. Might deprecate | Just a utility method. | FALSE | FALSE | FALSE | | | | | | | | | | | +| Rawtransactions / wallet | fundrawtransaction | No | Deprecated in 6.2.0 | Not planned | | Zallet | | Zallet will expose PCZTs instead. | FALSE | FALSE | FALSE | | | | | | TRUE | | | | | +| Rawtransactions | getrawtransaction | Partial | Yes | | | Zebra \| Zaino | Review implementation in Zebra | Zebra only supports lightwalletd usage of this RPC method \| requested by 1 mining pool | TRUE | FALSE | TRUE | TRUE | FALSE | FALSE | FALSE | TRUE | | TRUE | TRUE | TRUE | TRUE | +| Rawtransactions | sendrawtransaction | Partial | Yes | | | Zebra \| Zaino | Review implementation in Zebra | Zebra only supports lightwalletd usage of this RPC method \| requested by 2 mining pools | FALSE | FALSE | FALSE | TRUE | FALSE | FALSE | FALSE | TRUE | TRUE | TRUE | TRUE | TRUE | | +| Rawtransactions / wallet | signrawtransaction | No | Deprecated in 6.2.0 | Not planned | | Zallet | | Zallet will expose PCZTs instead. | FALSE | FALSE | FALSE | | | | | | TRUE | TRUE | | | | +| Util | createmultisig | No | Yes | | | Zallet | | Blocked on P2SH support in zcash_client_sqlite | FALSE | FALSE | FALSE | | | | | | | | | | | +| Util | estimatefee | No | No (removed) | | | | Removed | Removed in https://github.com/zcash/zcash/issues/6557 | FALSE | FALSE | FALSE | | | | | | | | | | TRUE | +| Util | validateaddress | Yes | Yes | | | Zebra \| Zaino | Implement in Zaino | Requested by 1 mining pool | TRUE | FALSE | TRUE | TRUE | FALSE | FALSE | FALSE | TRUE | TRUE | | | | | +| Util | verifymessage | No | Yes | | | Zallet | Implement in Zallet | Requested by 1 mining pool | FALSE | FALSE | FALSE | TRUE | FALSE | | | | | | | | | +| Util | z_validateaddress | Yes | Yes | | | Zebra | Implement in Zaino | | TRUE | FALSE | TRUE | | | | | | | TRUE | | | | +| Wallet | addmultisigaddress | No | Yes | | | Zallet | Implement in Zallet | Blocked on P2SH support in zcash_client_sqlite: https://github.com/zcash/librustzcash/issues/1370 | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | backupwallet | No | Yes | | | Zallet | Implement in Zallet | Would be copying the SQLite database file; maybe better done as a CLI command? | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | dumpprivkey | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | TRUE | | | | +| Wallet | dumpwallet | No | No (removed) | | | | Removed | Removed in https://github.com/zcash/zcash/issues/5513 | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | encryptwallet | No | Experimental | Not planned | | Zallet | | Similar functionality would be implemented as CLI commands, not JSON-RPC methods. | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | getbalance | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | TRUE | TRUE | | | +| Wallet | getnewaddress | No | Disabled in 5.4.0 | Not planned | | Zallet | | Deprecated | FALSE | FALSE | FALSE | | | FALSE | FALSE | TRUE | TRUE | | | | | +| Wallet | getrawchangeaddress | No | Disabled in 5.4.0 | Not planned | | Zallet | | Deprecated | FALSE | FALSE | FALSE | | TRUE | FALSE | FALSE | TRUE | | | | | | +| Wallet | getreceivedbyaddress | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | gettransaction | No | Yes | | | Zallet | Implement in Zallet | Requested by 2 mining pools | FALSE | FALSE | FALSE | TRUE | TRUE | FALSE | FALSE | TRUE | TRUE | TRUE | TRUE | | | +| Wallet | getunconfirmedbalance | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | getwalletinfo | No | Yes | Stub | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | TRUE | | | TRUE | TRUE | | | | +| Wallet | importaddress | No | Yes | | | Zallet | Implement in Zallet | Less keen on supporting in JSON-RPC; if no one is using it then would leave out. | FALSE | FALSE | FALSE | | | | | | | | TRUE | | | +| Wallet | importprivkey | No | Yes | | | Zallet | Implement in Zallet | Less keen on supporting in JSON-RPC; if no one is using it then would leave out. | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | importpubkey | No | Yes | Not planned | | Zallet | | | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | importwallet | No | Yes | Not planned | | Zallet | | Would rather have specific commands for importing wallet material rather than a JSON-RPC | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | keypoolrefill | No | Deprecated in 6.2.0 | Not planned | | Zallet | | Only needed when interoperability with legacy Bitcoin infrastructure is required. | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | listaddresses | No | Yes | Yes | | Zallet | Enhance as support for other keys is added | | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | listaddressgroupings | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | listlockunspent | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | TRUE | | | | +| Wallet | listreceivedbyaddress | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | listsinceblock | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | TRUE | TRUE | | | | +| Wallet | listtransactions | No | Yes | | | Zallet | Implement in Zallet | Requested by 1 mining pool | FALSE | FALSE | FALSE | TRUE | FALSE | | | | | | | | | +| Wallet | listunspent | No | Yes | | | Zallet | Implement in Zallet | Requested by 2 mining pools | FALSE | FALSE | FALSE | TRUE | FALSE | TRUE | FALSE | TRUE | TRUE | | | | | +| Wallet | lockunspent | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | TRUE | TRUE | | | | +| Wallet | sendmany | No | Legacy | | | Zallet | Implement in Zallet | Legacy transaction creation API \| requested by 1 mining pool. May need to change semantics. | FALSE | FALSE | FALSE | FALSE | TRUE | TRUE | | | | | | | | +| Wallet | sendtoaddress | No | Legacy | | | Zallet | Implement in Zallet | Legacy transaction creation API. May need to change semantics. | FALSE | FALSE | FALSE | | | | | | TRUE | | | | | +| Wallet | settxfee | No | Deprecated in 6.2.0 | Not planned | | Zallet | Do not implement | Not ZIP 317-conformant. This is only used by legacy transaction creation APIs (sendtoaddress, sendmany, and fundrawtransaction) | FALSE | FALSE | FALSE | | | | | | TRUE | | | | | +| Wallet | signmessage | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | walletconfirmbackup | No | Internal | Not planned | | Zallet | | Internal method that is not intended to be called directly by users | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | z_exportkey | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | z_exportviewingkey | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | z_exportwallet | No | Yes | | | Zallet | Implement in Zallet | Might change output format to zeWIF, or we could add a format argument | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | z_getaddressforaccount | No | Yes | Yes | | Zallet | Implement in Zallet | Requested by 1 mining pool | FALSE | FALSE | FALSE | TRUE | FALSE | | | | | TRUE | | | | +| Wallet | z_getbalance | No | Disabled in 6.2.0 | Not planned | | Zallet | | Requested by 1 mining pool, should use z_getbalanceforaccount, z_getbalanceforviewingkey, or getbalance (for legacy transparent balance) instead. | FALSE | FALSE | FALSE | FALSE | TRUE | | | | | | | | | +| Wallet | z_getbalanceforaccount | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | TRUE | | | | +| Wallet | z_getbalanceforviewingkey | No | Yes | | | Zallet | Implement in Zallet | Zallet has UUID for all accounts including viewing keys, so not as necessary. | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | z_getmigrationstatus | No | Yes | Not planned | | Zallet | | Could implement dummy "not migrating" response | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | z_getnewaccount | No | Yes | WIP | | Zallet | Implement in Zallet | Requested by 1 mining pool | FALSE | FALSE | FALSE | TRUE | FALSE | | | | | TRUE | TRUE | | | +| Wallet | z_getnewaddress | No | Disabled in 5.4.0 | Not planned | | Zallet | | Deprecated | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | z_getnotescount | No | Yes | WIP | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | TRUE | | | | +| Wallet | z_getoperationresult | No | Yes | | | Zallet | Implement in Zallet | Requested by 2 mining pools | FALSE | FALSE | FALSE | TRUE | TRUE | | | | | TRUE | | | | +| Wallet | z_getoperationstatus | No | Yes | | | Zallet | Implement in Zallet | Requested by 2 mining pools | FALSE | FALSE | FALSE | TRUE | TRUE | | | | | | | | | +| Wallet | z_gettotalbalance | No | Deprecated in 5.0.0 | Not planned | | Zallet | | Deprecated \| requested by 1 mining pool, should use z_getbalanceforaccount or getbalance | FALSE | FALSE | FALSE | FALSE | TRUE | FALSE | FALSE | TRUE | | | | | | +| Wallet | z_importkey | No | Sprout or Sapling | | | Zallet | Implement in Zallet | Implement for Sapling. Orchard support not needed. | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | z_importviewingkey | No | Sprout or Sapling | | | Zallet | Implement in Zallet | Implement for Sapling. Unified Viewing Key support not needed. | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | z_importwallet | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | z_listaccounts | No | Yes | WIP | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | TRUE | TRUE | | | +| Wallet | z_listaddresses | No | Disabled in 5.4.0 | Not planned | | Zallet | | Deprecated, should use listaddresses | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | z_listoperationids | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | z_listreceivedbyaddress | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | z_listunifiedreceivers | Yes | Yes | Stub | | Zallet \| Zebra | Implement in Zallet | Requested by 1 mining pool | TRUE | FALSE | TRUE | TRUE | FALSE | | | | | TRUE | | | | +| Wallet | z_listunspent | No | Yes | WIP | | Zallet | | Requested by 1 mining pool | FALSE | FALSE | FALSE | TRUE | FALSE | | | | | TRUE | | | | +| Wallet | z_mergetoaddress | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | z_sendmany | No | Yes | | | Zallet | Implement in Zallet | Requested by 2 mining pools | FALSE | FALSE | FALSE | TRUE | TRUE | | | | | TRUE | TRUE | | | +| Wallet | z_setmigration | No | Yes | | | Zallet | Do not implement | zcashd only supported Sprout-to-Sapling; Zallet won't support Sprout. | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | z_shieldcoinbase | No | Yes | | | Zallet | Implement in Zallet | Requested by 2 mining pools | FALSE | FALSE | FALSE | TRUE | TRUE | | | | | | TRUE | | | +| Wallet | z_viewtransaction | No | Yes | | | Zallet | Implement in Zallet | Maybe modify semantics to show entire transaction | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | zcbenchmark | No | Yes | Not planned | | Zallet | | | FALSE | FALSE | FALSE | | | | | | | | | | | +| Wallet | zcsamplejoinsplit | No | Yes | | | Zallet | Do not implement | Sprout-only | FALSE | FALSE | FALSE | | | | | | | | | | | diff --git a/memory-bank/progress.md b/memory-bank/progress.md index 4425c1f..219e1aa 100644 --- a/memory-bank/progress.md +++ b/memory-bank/progress.md @@ -2,15 +2,15 @@ ## Working Functionality -The Memory Bank is functional and being actively maintained. The core files (`projectbrief.md`, `productContext.md`, `systemPatterns.md`, `techContext.md`, `activeContext.md`, and `progress.md`) are being used to document the project. +The Memory Bank is functional and being actively maintained. The core files (`projectbrief.md`, `productContext.md`, `systemPatterns.md`, `techContext.md`, `activeContext.md`, and `progress.md`) are being used to document the project. RPC's exposed by Zcashd have been enumerated and documented. RPC's that have been surfaced by customers have been documented, and the Z3 development teams have mostly agreed upon which service (Zebra, Zaino or Zallet) will serve which method. ## Functionality Left To Build -The entire software stack (Zebra, Zaino, Zallet, and the wrapper service) still needs to be built. +The entire software stack (Zebra, Zaino, Zallet, and the wrapper service) still needs to be built. Zebra is the closest to being "ready", but significant work remains to hit the [Zebra Ready for zcashd Deprecation milestone](https://github.com/orgs/ZcashFoundation/projects/9/views/11). Zaino is under active development, and as of the DevSummit in Sofia in March 2025 we understand that releases are cut that can serve as drop-in replacements for Lightwalletd. Zallet is also under active development. ## Current Status -The project is in the initial planning and documentation phase. The Memory Bank is being established as a central repository for project knowledge. The core memory bank files have been reviewed and updated. +The project is in the initial planning and documentation phase. The Memory Bank is being established as a central repository for project knowledge. The core memory bank files have been reviewed and updated. The next steps are to have the teams focus on the list of RPC's that need to be supported by Z3 and ensure that the routing of method to service is correct and ensure that all teams have the same understanding. ## Known Issues @@ -18,4 +18,7 @@ No known issues at this time. ## Evolution of Decisions -No decisions have evolved yet. +The most significant decisions that have been made to date are reflected in the mapping of RPC methods to backend services that was made by the Zcashd Deprecation Team in partnership with customers to figure out which methods exposed by Zcashd are in-use and which components of the new Z3 stack (Zebra, Zaino or Zallet) will service each method. Note that the RPC Method Support list seems to be imcomplete, with many methods indicating that they will be served by either Zebra or Zaino or both. We urgently need to drive these decisions to ground in order to move forward. + +We have identified the RPC methods that are most important to support in the new Z3 stack, based on the RPC method support file. We have also documented this information in `memory-bank/techContext.md`. + diff --git a/memory-bank/projectbrief.md b/memory-bank/projectbrief.md index 188e8c7..67e657f 100644 --- a/memory-bank/projectbrief.md +++ b/memory-bank/projectbrief.md @@ -8,6 +8,7 @@ Z3: Building a modern, secure, reliable, scalable software stack that will repla - A service that provides indexing and serves light clients blockchain data (Zaino) - A cli-wallet that provides wallet functionality that existed in Zcashd but that is not planned for implementation in Zebra (Zallet) - A wrapper service that exposes the three previously mentioned services as a "single binary" to the users +- Enumerate all of the RPC methods that zcashd exposes ## Target Users - Developers of the Zcash protocol (including the Zcash Foundation, the Electric Coin Company, Shielded Labs, Zingo Labs and the broader community) @@ -35,4 +36,3 @@ Z3: Building a modern, secure, reliable, scalable software stack that will repla ## Further Reading For a detailed analysis of the problems we're solving and proposed solution, see the [Product Context](productContext.md). - diff --git a/memory-bank/systemPatterns.md b/memory-bank/systemPatterns.md index f74436d..deb6f80 100644 --- a/memory-bank/systemPatterns.md +++ b/memory-bank/systemPatterns.md @@ -27,7 +27,7 @@ graph LR ## Component Relationships * **User and Z3 wrapper service:** The user interacts directly with the Z3 wrapper service, which acts as a single entry point to the system. The Z3 wrapper service handles user requests and routes them to the appropriate component (Zebra, Zaino, or Zallet). -* **Zebra and Zaino:** Zebra provides blockchain data to Zaino via RPC or a similar mechanism. Zaino indexes this data to provide efficient access for light clients and other applications. +* **Zebra and Zaino:** Zebra provides blockchain data to Zaino via RPC or the ReadStateService. Zaino indexes this data to provide efficient access for light clients and other applications. * **Zallet and Zebra:** Zallet interacts with Zebra for transaction submission and balance retrieval. Zallet uses Zebra's API to construct and broadcast transactions to the Zcash network. * **Zallet and Zaino:** Zallet may interact with Zaino to retrieve indexed blockchain data, such as transaction history or address balances. * **Z3 wrapper service and Components:** The Z3 wrapper service orchestrates interactions between Zebra, Zaino, and Zallet, providing a unified interface for users. diff --git a/memory-bank/techContext.md b/memory-bank/techContext.md index 4639d60..5cc616d 100644 --- a/memory-bank/techContext.md +++ b/memory-bank/techContext.md @@ -23,6 +23,14 @@ * **Security:** Security is paramount, and all components must be designed to resist attacks. * **Compatibility:** The system must be compatible with the Zcash protocol and network. +## RPC Methods used by Customers +The Zcashd Deprecation team engaged with customers and found that they are using the following RPC methods in production. They discussed and came to agreement on which component in the new Z3 stack should service each of these methods. The [data](./data/zcashd_deprecation_team_rpc_method_support_9apr2025.md) here is a snapshot from the Zcashd Deprecation ["RPC Method Support" Google Sheet](https://docs.google.com/spreadsheets/d/1UJxH1cowexGqadU32Uei5Qak6jGhXjb18-T_QBPmDAA) on 9 April 2025. + +It is very important that the project team focuses on the [agreed upon RPC mapping](./data/rpc_mapping.md) and figures out exactly which RPC will be served by which component, as ultimately this information will be used by Z3 to route requests to the right backend service. Note that the RPC Method Support list seems to be imcomplete, with many methods indicating that they will be served by either Zebra or Zaino or both. We urgently need to drive these decisions to ground in order to move forward. + +## RPC Methods exposed by Zcashd +The following [RPC methods](./data/zcashd_RPC_methods.md) are all of the methods historically exposed by the legacy Zcashd software. + ## Dependencies ### Z3 Wrapper Dependencies @@ -66,7 +74,7 @@ TBD but generally speaking we want to use the same libraries and frameworks that - `metrics`: Core metrics collection and reporting - `metrics-exporter-prometheus`: Prometheus metrics exposition - `tracing`: Structured logging and diagnostics - - `serde`: Serialization/deserialization framework + - `serde`: Data serialization framework - `rocksdb`: Persistent key-value storage backend - `hyper`: HTTP/HTTPS implementation - `tonic`: gRPC implementation @@ -104,7 +112,6 @@ TBD but generally speaking we want to use the same libraries and frameworks that * Custom Dependencies: - `zingolib`: Core functionality from Zingo Labs - Tag: `zaino_dep_005` - - Features: `test-elevation` - `zingo-infra-testutils`: Testing infrastructure - `zingo-infra-services`: Service infrastructure @@ -216,6 +223,8 @@ Note: While these dependencies are common, they might be used with different fea * Architecture docs: Using architecture decision records (ADRs) * User guides: Using mdBook + ## Further Reading For details about design patterns and conventions used across the Z3 stack, see the [System Patterns](systemPatterns.md). + From aafcaf4f5d72862cd148367a73c94696a28cc794 Mon Sep 17 00:00:00 2001 From: DC Date: Wed, 9 Apr 2025 16:54:02 -0600 Subject: [PATCH 08/57] chore(deps): update zebra and zaino submodules * Update zebra submodule - Bump to latest main branch - Include recent consensus fixes - Update RPC method implementations * Update zaino submodule - Bump to latest main branch - Improve indexing performance - Update lightwalletd compatibility This keeps Z3 in sync with latest upstream changes from both components. Relates to #1 --- zaino | 2 +- zebra | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/zaino b/zaino index 67604ce..3f1a507 160000 --- a/zaino +++ b/zaino @@ -1 +1 @@ -Subproject commit 67604ce427ce4daf1f9bf9f9be81d9db72e77a7f +Subproject commit 3f1a50782ba43356b877766c3e9d2a604d6f0cb1 diff --git a/zebra b/zebra index cc5c5ed..d061232 160000 --- a/zebra +++ b/zebra @@ -1 +1 @@ -Subproject commit cc5c5edd357b5aae013a7d1cbec394d09bfe9765 +Subproject commit d0612323125ec1496feac2b47d433943044508df From ddb91ad87a79e6dfae6754f933b50731478edf7d Mon Sep 17 00:00:00 2001 From: DC Date: Wed, 9 Apr 2025 17:05:03 -0600 Subject: [PATCH 09/57] docs: improve RPC method documentation formatting * Reorganize method categories - Group by functional area - Add descriptions for each method - Maintain consistent formatting - Follow markdown style guide * Add new categories - Debug and Testing - Address and Validation - Network and Node Info This improves readability and organization of the RPC method reference documentation. Relates to #1 --- memory-bank/data/zcashd_RPC_methods.md | 127 ++++++++++++++----------- memory-bank/progress.md | 2 +- memory-bank/techContext.md | 72 +++++++++++++- 3 files changed, 140 insertions(+), 61 deletions(-) diff --git a/memory-bank/data/zcashd_RPC_methods.md b/memory-bank/data/zcashd_RPC_methods.md index 32ba5bf..a3d61e4 100644 --- a/memory-bank/data/zcashd_RPC_methods.md +++ b/memory-bank/data/zcashd_RPC_methods.md @@ -1,58 +1,71 @@ -# these should be all the RPC methods exposed by Zcashd +# Zcashd RPC Methods Reference -getblockchaininfo -getbestblockhash -getblockcount -getblock -getblockhash -getblockheader -getchaintips -z_gettreestate -z_getsubtreesbyindex -getdifficulty -getmempoolinfo -getrawmempool -gettxout -gettxoutsetinfo -verifychain -getblockdeltas -getblockhashes -invalidateblock -reconsiderblock -getlocalsolps -getnetworksolps -getnetworkhashps -getmininginfo -prioritisetransaction -getblocktemplate -submitblock -getblocksubsidy -getgenerate -setgenerate -generate -getinfo -getmemoryinfo -validateaddress -z_validateaddress -createmultisig -verifymessage -getexperimentalfeatures -getaddresstxids -getaddressbalance -getaddressdeltas -getaddressutxos -getaddressmempool -getspentinfo -setmocktime -getconnectioncount -getdeprecationinfo -ping -getpeerinfo -addnode -disconnectnode -getaddednodeinfo -getnettotals -getnetworkinfo -setban -listbanned -clearbanned \ No newline at end of file +## Blockchain Information +- `getblockchaininfo` - Get current state of the blockchain +- `getbestblockhash` - Get hash of best (tip) block in the longest blockchain +- `getblockcount` - Get the current block count +- `getblock` - Get block data by hash +- `getblockhash` - Get hash of block at height +- `getblockheader` - Get block header by hash +- `getchaintips` - Get information about all known chain tips +- `z_gettreestate` - Get Sapling and Orchard tree state +- `z_getsubtreesbyindex` - Get note commitment subtrees +- `getdifficulty` - Get proof-of-work difficulty + +## Mempool Operations +- `getmempoolinfo` - Get mempool statistics +- `getrawmempool` - Get all transaction IDs in memory pool +- `gettxout` - Get details about an unspent transaction output +- `gettxoutsetinfo` - Get statistics about the unspent transaction output set + +## Chain Validation +- `verifychain` - Verify blockchain database +- `getblockdeltas` - Get block deltas by hash +- `getblockhashes` - Get block hashes in range +- `invalidateblock` - Permanently mark a block as invalid +- `reconsiderblock` - Remove invalid status from block and children + +## Mining +- `getlocalsolps` - Get local solutions per second +- `getnetworksolps` - Get network solutions per second +- `getnetworkhashps` - Get estimated network hashes per second +- `getmininginfo` - Get mining-related information +- `prioritisetransaction` - Change transaction priority +- `getblocktemplate` - Get block template for mining +- `submitblock` - Submit mined block +- `getblocksubsidy` - Get block subsidy reward +- `getgenerate` - Check if CPU mining is enabled +- `setgenerate` - Set generation on/off +- `generate` - Mine blocks immediately + +## Network and Node Info +- `getinfo` - Get general information +- `getmemoryinfo` - Get memory usage info +- `getconnectioncount` - Get connection count +- `getdeprecationinfo` - Get deprecation info +- `ping` - Ping other nodes +- `getpeerinfo` - Get peer connection info +- `addnode` - Add/remove/try a node +- `disconnectnode` - Disconnect from node +- `getaddednodeinfo` - Get info about added nodes +- `getnettotals` - Get network traffic info +- `getnetworkinfo` - Get network info +- `setban` - Ban a network address +- `listbanned` - List banned IPs/Subnets +- `clearbanned` - Clear banned addresses + +## Address and Validation +- `validateaddress` - Validate a transparent address +- `z_validateaddress` - Validate a shielded address +- `createmultisig` - Create multisig address +- `verifymessage` - Verify signed message +- `getexperimentalfeatures` - Get experimental feature state +- `getaddresstxids` - Get txids for address +- `getaddressbalance` - Get address balance +- `getaddressdeltas` - Get address deltas +- `getaddressutxos` - Get address utxos +- `getaddressmempool` - Get address mempool +- `getspentinfo` - Get spent info for output + +## Debug and Testing +- `setmocktime` - Set network time for testing \ No newline at end of file diff --git a/memory-bank/progress.md b/memory-bank/progress.md index 219e1aa..e9552a7 100644 --- a/memory-bank/progress.md +++ b/memory-bank/progress.md @@ -2,7 +2,7 @@ ## Working Functionality -The Memory Bank is functional and being actively maintained. The core files (`projectbrief.md`, `productContext.md`, `systemPatterns.md`, `techContext.md`, `activeContext.md`, and `progress.md`) are being used to document the project. RPC's exposed by Zcashd have been enumerated and documented. RPC's that have been surfaced by customers have been documented, and the Z3 development teams have mostly agreed upon which service (Zebra, Zaino or Zallet) will serve which method. +The Memory Bank is functional and being actively maintained. The core files (`projectbrief.md`, `productContext.md`, `systemPatterns.md`, `techContext.md`, `activeContext.md`, and `progress.md`) are being used to document the project. RPC's exposed by Zcashd have been enumerated and documented. RPC's that have been surfaced by customers have been documented, and the Z3 development teams have mostly agreed upon which service (Zebra, Zaino or Zallet) will serve which method. See the [RPC to service map](./data/rpc_mapping.md) for reference. ## Functionality Left To Build diff --git a/memory-bank/techContext.md b/memory-bank/techContext.md index 5cc616d..0b34185 100644 --- a/memory-bank/techContext.md +++ b/memory-bank/techContext.md @@ -24,9 +24,75 @@ * **Compatibility:** The system must be compatible with the Zcash protocol and network. ## RPC Methods used by Customers -The Zcashd Deprecation team engaged with customers and found that they are using the following RPC methods in production. They discussed and came to agreement on which component in the new Z3 stack should service each of these methods. The [data](./data/zcashd_deprecation_team_rpc_method_support_9apr2025.md) here is a snapshot from the Zcashd Deprecation ["RPC Method Support" Google Sheet](https://docs.google.com/spreadsheets/d/1UJxH1cowexGqadU32Uei5Qak6jGhXjb18-T_QBPmDAA) on 9 April 2025. - -It is very important that the project team focuses on the [agreed upon RPC mapping](./data/rpc_mapping.md) and figures out exactly which RPC will be served by which component, as ultimately this information will be used by Z3 to route requests to the right backend service. Note that the RPC Method Support list seems to be imcomplete, with many methods indicating that they will be served by either Zebra or Zaino or both. We urgently need to drive these decisions to ground in order to move forward. +The Zcashd Deprecation team engaged with customers and found that they are using the following RPC methods in production. They discussed and came to agreement on which component in the new Z3 stack should service each of these methods. + +| RPC Method | Where in new stack | Actions | +|---|---|---| +| `getaddressbalance` | Zebra (lightwalletd usage) / Zaino | Review implementation in Zebra | +| `getaddressdeltas` | Zaino | | +| `getaddressmempool` | - | Lower priority. Possibly deprecate | +| `getaddresstxids` | Zebra (lightwalletd usage) / Zaino | Review implementation in Zebra \| Implement in Zaino | +| `getaddressutxos` | Zebra | Review implementation in Zebra | +| `getbestblockhash` | Zebra / Zaino | | +| `getblock` | Zebra / Zaino | Being fully implemented in Zebra | +| `getblockchaininfo` | Zebra / Zaino | Review implementation in Zebra | +| `getblockcount` | Zebra / Zaino | | +| `getblockdeltas` | Zaino | Implement in Zaino | +| `getblockhash` | Zebra / Zaino | | +| `getblockhashes` | Zaino | Lower priority. Possibly deprecate | +| `getblockheader` | Zebra / Zaino | | +| `getchaintips` | Zaino | Implement in Zaino | +| `getdifficulty` | Zebra / Zaino | | +| `getmempoolinfo` | Zaino | Implement in Zaino | +| `getrawmempool` | Zebra / Zaino | | +| `getspentinfo` | Zaino | Implement in Zaino | +| `gettxout` | Zaino | Implement in Zaino | +| `gettxoutproof` | Zaino | Lower priority | +| `gettxoutsetinfo` | Zaino | | +| `verifychain` | - | Deprecate | +| `verifytxoutproof` | Zaino | Lower priority | +| `z_gettreestate` | Zebra / Zaino | Review implementation in Zebra | +| `getexperimentalfeatures` | - | Deprecate? Confirm what Zebra defines as experimental | +| `getinfo` | Zebra | Review implementation in Zebra | +| `getmemoryinfo` | Zebra | No plan to implement | +| `help` | Zebra | No plans to implement | +| `setlogfilter` | Zebra | No plans to implement | +| `stop` | Zebra | None | +| `z_getpaymentdisclosure` | - | Deprecate | +| `z_validatepaymentdisclosure` | - | Deprecate | +| `generate` | Zebra | None | +| `getgenerate` | Zebra | No plans to implement | +| `setgenerate` | Zebra | No plans to implement | +| `getblocksubsidy` | Zebra | None. All done in Zebra | +| `getblocktemplate` | Zebra | Review implementation in Zebra | +| `getlocalsolps` | Zebra | No plans to implement | +| `getmininginfo` | Zebra / Zaino | | +| `getnetworkhashps` | - | Deprecated | +| `getnetworksolps` | Zebra / Zaino | | +| `prioritisetransaction` | Zebra | No plans to implement | +| `submitblock` | Zebra | None | +| `addnode` | Zebra | Decide if we want to implement in Zebra | +| `clearbanned` | Zebra | Decide if we want to implement in Zebra | +| `disconnectnode` | Zebra | Decide if we want to implement in Zebra | +| `getaddednodeinfo` | Zebra | Decide if we want to implement in Zebra | +| `getnettotals` | Zebra | Decide if we want to implement in Zebra | +| `getnetworkinfo` | Zebra / Zaino | Implement in Zebra | +| `getpeerinfo` | Zebra / Zaino | Review implementation in Zebra | +| `listbanned` | Zebra | Decide if we want to implement in Zebra | +| `ping` | Zebra / Zaino | Implement in Zebra | +| `setban` | Zebra | Decide if we want to implement in Zebra | +| `createrawtransaction` | Zallet | | +| `decoderawtransaction` | Zallet | Lower priority. Might deprecate | +| `decodescript` | Zallet | Lower priority. Might deprecate | +| `fundrawtransaction` | Zallet | | +| `getrawtransaction` | Zebra / Zaino | Review implementation in Zebra | +| `sendrawtransaction` | Zebra / Zaino | Review implementation in Zebra | +| `signrawtransaction` | Zallet | | +| `validateaddress` | Zebra / Zaino | Implement in Zaino | +| `verifymessage` | Zallet | Implement in Zallet | +| `z_validateaddress` | Zebra | Implement in Zaino | + +It is very important that the project team focuses on the agreed upon RPC mapping and figures out exactly which RPC will be served by which component, as ultimately this information will be used by Z3 to route requests to the right backend service. Note that the RPC Method Support list seems to be imcomplete, with many methods indicating that they will be served by either Zebra or Zaino or both. We urgently need to drive these decisions to ground in order to move forward. ## RPC Methods exposed by Zcashd The following [RPC methods](./data/zcashd_RPC_methods.md) are all of the methods historically exposed by the legacy Zcashd software. From 63b85a9ccf6b6f6a8cd83ee1d164049816bd5b8c Mon Sep 17 00:00:00 2001 From: DC Date: Wed, 9 Apr 2025 17:09:10 -0600 Subject: [PATCH 10/57] chore(deps): update zallet submodule to latest main --- zallet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zallet b/zallet index 6c7da0d..1eabd80 160000 --- a/zallet +++ b/zallet @@ -1 +1 @@ -Subproject commit 6c7da0d55c672cc2e2cb59511b131d437bc12f0b +Subproject commit 1eabd80b7106a4918140c01587464dedba853112 From a9a069ba4b78ee0616227462496c4dd6a0818c19 Mon Sep 17 00:00:00 2001 From: DC Date: Thu, 10 Apr 2025 07:32:55 -0600 Subject: [PATCH 11/57] docs: minor updates and cleanup --- memory-bank/activeContext.md | 76 ++---------------------------------- memory-bank/projectbrief.md | 1 - 2 files changed, 4 insertions(+), 73 deletions(-) diff --git a/memory-bank/activeContext.md b/memory-bank/activeContext.md index 06e3883..67e24a1 100644 --- a/memory-bank/activeContext.md +++ b/memory-bank/activeContext.md @@ -2,82 +2,14 @@ ## Current Focus +Develop an architecture for the Z3 wrapper application that will allow it to correctly route RPC requests to Zebra, Zaino or Zallet. + Updating the Memory Bank, specifically reviewing and updating the core files (`projectbrief.md`, `productContext.md`, `systemPatterns.md`, `techContext.md`, `activeContext.md`, and `progress.md`). ## Recent Changes Read all memory bank files (`projectbrief.md`, `productContext.md`, `systemPatterns.md`, `techContext.md`, `activeContext.md`, and `progress.md`). -Enumerate all of the RPC methods that zcashd exposes. - -## RPC Method Support in Z3 - -Based on the RPC method support file, here's a summary of which component will support each RPC method in the new Z3 stack: - -| RPC Method | Where in new stack | Actions | -|---|---|---| -| `getaddressbalance` | Zebra (lightwalletd usage) / Zaino | Review implementation in Zebra | -| `getaddressdeltas` | Zaino | | -| `getaddressmempool` | - | Lower priority. Possibly deprecate | -| `getaddresstxids` | Zebra (lightwalletd usage) / Zaino | Review implementation in Zebra \| Implement in Zaino | -| `getaddressutxos` | Zebra | Review implementation in Zebra | -| `getbestblockhash` | Zebra / Zaino | | -| `getblock` | Zebra / Zaino | Being fully implemented in Zebra | -| `getblockchaininfo` | Zebra / Zaino | Review implementation in Zebra | -| `getblockcount` | Zebra / Zaino | | -| `getblockdeltas` | Zaino | Implement in Zaino | -| `getblockhash` | Zebra / Zaino | | -| `getblockhashes` | Zaino | Lower priority. Possibly deprecate | -| `getblockheader` | Zebra / Zaino | | -| `getchaintips` | Zaino | Implement in Zaino | -| `getdifficulty` | Zebra / Zaino | | -| `getmempoolinfo` | Zaino | Implement in Zaino | -| `getrawmempool` | Zebra / Zaino | | -| `getspentinfo` | Zaino | Implement in Zaino | -| `gettxout` | Zaino | Implement in Zaino | -| `gettxoutproof` | Zaino | Lower priority | -| `gettxoutsetinfo` | Zaino | | -| `verifychain` | - | Deprecate | -| `verifytxoutproof` | Zaino | Lower priority | -| `z_gettreestate` | Zebra / Zaino | Review implementation in Zebra | -| `getexperimentalfeatures` | - | Deprecate? Confirm what Zebra defines as experimental | -| `getinfo` | Zebra | Review implementation in Zebra | -| `getmemoryinfo` | Zebra | No plan to implement | -| `help` | Zebra | No plans to implement | -| `setlogfilter` | Zebra | No plans to implement | -| `stop` | Zebra | None | -| `z_getpaymentdisclosure` | - | Deprecate | -| `z_validatepaymentdisclosure` | - | Deprecate | -| `generate` | Zebra | None | -| `getgenerate` | Zebra | No plans to implement | -| `setgenerate` | Zebra | No plans to implement | -| `getblocksubsidy` | Zebra | None. All done in Zebra | -| `getblocktemplate` | Zebra | Review implementation in Zebra | -| `getlocalsolps` | Zebra | No plans to implement | -| `getmininginfo` | Zebra / Zaino | | -| `getnetworkhashps` | - | Deprecated | -| `getnetworksolps` | Zebra / Zaino | | -| `prioritisetransaction` | Zebra | No plans to implement | -| `submitblock` | Zebra | None | -| `addnode` | Zebra | Decide if we want to implement in Zebra | -| `clearbanned` | Zebra | Decide if we want to implement in Zebra | -| `disconnectnode` | Zebra | Decide if we want to implement in Zebra | -| `getaddednodeinfo` | Zebra | Decide if we want to implement in Zebra | -| `getnettotals` | Zebra | Decide if we want to implement in Zebra | -| `getnetworkinfo` | Zebra / Zaino | Implement in Zebra | -| `getpeerinfo` | Zebra / Zaino | Review implementation in Zebra | -| `listbanned` | Zebra | Decide if we want to implement in Zebra | -| `ping` | Zebra / Zaino | Implement in Zebra | -| `setban` | Zebra | Decide if we want to implement in Zebra | -| `createrawtransaction` | Zallet | | -| `decoderawtransaction` | Zallet | Lower priority. Might deprecate | -| `decodescript` | Zallet | Lower priority. Might deprecate | -| `fundrawtransaction` | Zallet | | -| `getrawtransaction` | Zebra / Zaino | Review implementation in Zebra | -| `sendrawtransaction` | Zebra / Zaino | Review implementation in Zebra | -| `signrawtransaction` | Zallet | | -| `validateaddress` | Zebra / Zaino | Implement in Zaino | -| `verifymessage` | Zallet | Implement in Zallet | -| `z_validateaddress` | Zebra | Implement in Zaino | +Enumerated all of the RPC methods that zcashd exposes. Reviewed the list of RPC methods that customers said they depend on, and the [mapping](./data/rpc_mapping.md) (proposed by the Zcashd Deprecation Team) of which RPC method should be handled by which service in the new architecture. ## Active Decisions @@ -91,4 +23,4 @@ The importance of maintaining a comprehensive and up-to-date Memory Bank is a ke The project brief provides a good overview of the project's goals and requirements, which is essential for making informed decisions. The modular architecture of the system allows for independent development and deployment of components. -The RPC method support file provides a valuable overview of which RPC methods are most important to support in the new Z3 stack. This information will be used to guide the development of the Zebra, Zaino, and Zallet components. +The RPC method support file provides a valuable overview of which RPC methods are most important to support in the new Z3 stack. This information will be used to guide the development of the Z3 wrapper around the Zebra, Zaino, and Zallet components. diff --git a/memory-bank/projectbrief.md b/memory-bank/projectbrief.md index 67e657f..37ac528 100644 --- a/memory-bank/projectbrief.md +++ b/memory-bank/projectbrief.md @@ -8,7 +8,6 @@ Z3: Building a modern, secure, reliable, scalable software stack that will repla - A service that provides indexing and serves light clients blockchain data (Zaino) - A cli-wallet that provides wallet functionality that existed in Zcashd but that is not planned for implementation in Zebra (Zallet) - A wrapper service that exposes the three previously mentioned services as a "single binary" to the users -- Enumerate all of the RPC methods that zcashd exposes ## Target Users - Developers of the Zcash protocol (including the Zcash Foundation, the Electric Coin Company, Shielded Labs, Zingo Labs and the broader community) From 0026b7d0229eef8d65401043d97b949981d4002c Mon Sep 17 00:00:00 2001 From: DC Date: Thu, 10 Apr 2025 08:01:57 -0600 Subject: [PATCH 12/57] docs: add more context re: block explorer use case --- memory-bank/projectbrief.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/memory-bank/projectbrief.md b/memory-bank/projectbrief.md index 37ac528..c522077 100644 --- a/memory-bank/projectbrief.md +++ b/memory-bank/projectbrief.md @@ -16,7 +16,7 @@ Z3: Building a modern, secure, reliable, scalable software stack that will repla - Operators of cryptocurrency wallet software platforms (including Zashi, Brave Wallet, etc.) - Operators of ASIC based proof of work miners on the Zcash blockchain - Operators of "finalizers" (known more commonly as validators) as Zcash prepares to transition to a hybrid Proof of Work / Proof of Stake consensus algorithm. -- Operators of block explorers on the Zcash network +- Operators of block explorers on the Zcash network. We can leverage the [open source Nighthawk explorer](https://github.com/nighthawk-apps/zcash-explorer) (which is unfortunately no longer maintained) as a proof of concept application consuming the Z3 stack while it's under development. There is [work already underway](https://github.com/ZcashFoundation/zebra/issues/8435) to get this explorer working with Zebra. ## Technical Preferences - [Zcashd](https://github.com/zcash/zcash) is the legacy software that is being replaced From 9d4cec31312a13b9489357ffd3b78145a9e5affd Mon Sep 17 00:00:00 2001 From: DC Date: Thu, 17 Apr 2025 10:11:46 -0600 Subject: [PATCH 13/57] docs(memory-bank): document Docker orchestration findings Add z3_docker.md summarizing Zebra and Zaino Docker configurations and unified compose plan Update activeContext.md with Docker orchestration focus, decisions, and patterns Update progress.md with detailed status of Docker integration work and next steps Clarify next steps for Zallet integration and production readiness --- .clinerules | 3 + memory-bank/activeContext.md | 78 +++++++++++++++++--- memory-bank/productContext.md | 2 +- memory-bank/progress.md | 78 ++++++++++++++++++-- memory-bank/z3_docker.md | 132 ++++++++++++++++++++++++++++++++++ 5 files changed, 278 insertions(+), 15 deletions(-) create mode 100644 memory-bank/z3_docker.md diff --git a/.clinerules b/.clinerules index 395ad66..5443202 100644 --- a/.clinerules +++ b/.clinerules @@ -59,6 +59,9 @@ flowchart TD - Known issues - Evolution of project decisions +7. `z3_docker.md` + - Documents what we know so far about how to best Dockerize the Z3 components in a single Compose file + ### Additional Context Create additional files/folders within memory-bank/ when they help organize: - Complex feature documentation diff --git a/memory-bank/activeContext.md b/memory-bank/activeContext.md index 67e24a1..ec48689 100644 --- a/memory-bank/activeContext.md +++ b/memory-bank/activeContext.md @@ -2,25 +2,87 @@ ## Current Focus -Develop an architecture for the Z3 wrapper application that will allow it to correctly route RPC requests to Zebra, Zaino or Zallet. +1. **Docker Orchestration:** + - Completed investigation of Zebra and Zaino Docker configurations. + - Documented Docker/orchestration findings in z3_docker.md. + - Next: Research Zallet's Docker requirements for full stack integration. -Updating the Memory Bank, specifically reviewing and updating the core files (`projectbrief.md`, `productContext.md`, `systemPatterns.md`, `techContext.md`, `activeContext.md`, and `progress.md`). +2. **Z3 Stack Integration:** + - Planning unified Docker Compose for all components. + - Identifying volume management and networking requirements. + - Need to verify service communication patterns. + +3. **Production Readiness:** + - Developing monitoring and observability setup. + - Planning health checks and logging configuration. + - Exploring backup and recovery procedures. + +4. **Documentation:** + - Maintaining comprehensive Memory Bank updates. + - Documenting Docker deployment patterns. + - Creating configuration and troubleshooting guides. ## Recent Changes -Read all memory bank files (`projectbrief.md`, `productContext.md`, `systemPatterns.md`, `techContext.md`, `activeContext.md`, and `progress.md`). -Enumerated all of the RPC methods that zcashd exposes. Reviewed the list of RPC methods that customers said they depend on, and the [mapping](./data/rpc_mapping.md) (proposed by the Zcashd Deprecation Team) of which RPC method should be handled by which service in the new architecture. +1. **Docker Investigation:** + - Analyzed Zebra's multi-stage Dockerfile and compose configurations. + - Examined Zaino's Docker setup and runtime requirements. + - Created z3_docker.md to document findings and orchestration plan. + - Identified key configuration patterns and integration points. + +2. **RPC Integration:** + - Mapped RPC methods to services using [RPC mapping](./data/rpc_mapping.md). + - Identified service communication paths for Docker networking. + - Examined Zebra and Zaino configuration for RPC endpoints. ## Active Decisions -No active decisions are currently being made. +1. **Docker Architecture:** + - Use multi-stage builds for all components + - Implement secure defaults (non-root users, TLS) + - Standardize volume management patterns + - Plan for production monitoring + +2. **Configuration Management:** + - Use TOML configs mounted into containers + - Implement consistent environment variable patterns + - Plan for secrets management ## Important Patterns -The importance of maintaining a comprehensive and up-to-date Memory Bank is a key pattern for this project. All decisions and changes should be documented in the Memory Bank to ensure consistency and knowledge sharing. +1. **Memory Bank Maintenance:** + - Keep documentation synchronized with implementation + - Document all configuration options and rationale + - Track deployment patterns and decisions + +2. **Docker Best Practices:** + - Multi-stage builds for efficient images + - Non-root users for security (UIDs 10001, 2003) + - Volume management for persistent data + - Service isolation and clear network boundaries + +3. **Configuration Patterns:** + - TOML files for service configuration + - Environment variables for runtime settings + - Consistent volume mount points + - Standard logging and metrics exposure ## Learnings and Insights -The project brief provides a good overview of the project's goals and requirements, which is essential for making informed decisions. The modular architecture of the system allows for independent development and deployment of components. +1. **Docker Architecture:** + - Zebra's Docker setup provides a robust template for production deployment + - Zaino's configuration allows flexible validator integration + - Volume management is critical for state persistence + - Service networking needs careful planning for security + +2. **Integration Patterns:** + - RPC routing determines network architecture + - Service discovery via Docker DNS + - Configuration must align across services + - Monitoring needs coordinated across stack -The RPC method support file provides a valuable overview of which RPC methods are most important to support in the new Z3 stack. This information will be used to guide the development of the Z3 wrapper around the Zebra, Zaino, and Zallet components. +3. **Project Architecture:** + - Modular components enable independent scaling + - Shared patterns improve maintainability + - Security considerations affect all layers + - Documentation crucial for successful deployment diff --git a/memory-bank/productContext.md b/memory-bank/productContext.md index 557d889..fcefe50 100644 --- a/memory-bank/productContext.md +++ b/memory-bank/productContext.md @@ -38,7 +38,7 @@ The project aims to provide a secure, reliable, and efficient experience for the The key user experience goals include: -* **Seamless upgrade:** The user can upgrade from the legacy (Zcashd) daemon to the modern (Z3) stack without changing any of their integration code. +* **Seamless upgrade:** The user can upgrade from the legacy (Zcashd) daemon to the modern (Z3) stack without changing any of their integration code. Note that this goal is presently disputed with engineers from the ECC team stating that it's not feasible to make Z3 a "drop in replacement" for Zcashd without adding ~6mos of work to the schedule. They are presently tracking breaking changes to the wallet RPC's in [this Github issue](https://github.com/zcash/wallet/issues/41). * **Security:** Protecting user funds and data from unauthorized access. Ensuring that the shielded supply remains free of counterfeiting bugs. * **Reliability:** Ensuring the software stack operates consistently and without errors. * **Observability:** Providing telemetry, logging and tracing to ensure proper operation in rugged production environments. diff --git a/memory-bank/progress.md b/memory-bank/progress.md index e9552a7..b3464ba 100644 --- a/memory-bank/progress.md +++ b/memory-bank/progress.md @@ -2,23 +2,89 @@ ## Working Functionality -The Memory Bank is functional and being actively maintained. The core files (`projectbrief.md`, `productContext.md`, `systemPatterns.md`, `techContext.md`, `activeContext.md`, and `progress.md`) are being used to document the project. RPC's exposed by Zcashd have been enumerated and documented. RPC's that have been surfaced by customers have been documented, and the Z3 development teams have mostly agreed upon which service (Zebra, Zaino or Zallet) will serve which method. See the [RPC to service map](./data/rpc_mapping.md) for reference. +1. **Memory Bank:** + - Core files actively maintained and updated + - New z3_docker.md documents containerization findings + - RPC mapping documented and service assignments agreed upon + +2. **Docker Configuration:** + - Zebra's Docker setup analyzed and documented + - Multi-stage Dockerfile with test/runtime stages + - Multiple compose files for different scenarios + - Production-ready security features + - Zaino's Docker configuration examined + - Multi-stage build process + - TOML-based configuration + - gRPC service exposure + +3. **Integration Planning:** + - Unified Docker Compose draft completed + - Volume management strategy defined + - Network architecture planned + - Security considerations documented ## Functionality Left To Build -The entire software stack (Zebra, Zaino, Zallet, and the wrapper service) still needs to be built. Zebra is the closest to being "ready", but significant work remains to hit the [Zebra Ready for zcashd Deprecation milestone](https://github.com/orgs/ZcashFoundation/projects/9/views/11). Zaino is under active development, and as of the DevSummit in Sofia in March 2025 we understand that releases are cut that can serve as drop-in replacements for Lightwalletd. Zallet is also under active development. +1. **Docker Integration:** + - Research and document Zallet's Docker requirements + - Create production Docker Compose with all components + - Implement monitoring and health checks + - Develop backup/restore procedures + - Create deployment documentation + +2. **Software Stack Development:** + - Complete [Zebra Ready for zcashd Deprecation milestone](https://github.com/orgs/ZcashFoundation/projects/9/views/11) + - Continue Zaino development as Lightwalletd replacement + - Progress Zallet development + - Build Z3 wrapper service for unified RPC routing + +3. **Production Readiness:** + - Implement security hardening + - Set up monitoring and alerting + - Create disaster recovery procedures + - Document operational procedures ## Current Status -The project is in the initial planning and documentation phase. The Memory Bank is being established as a central repository for project knowledge. The core memory bank files have been reviewed and updated. The next steps are to have the teams focus on the list of RPC's that need to be supported by Z3 and ensure that the routing of method to service is correct and ensure that all teams have the same understanding. +1. **Documentation Phase:** + - Memory Bank established as knowledge repository + - Docker orchestration plan documented in z3_docker.md + - RPC routing assignments in progress + +2. **Development Progress:** + - Zebra: Docker configuration mature, ready for integration + - Zaino: Docker setup analyzed, integration points identified + - Zallet: Docker requirements to be researched + - Z3 Wrapper: Architecture planning stage + +3. **Next Steps:** + - Complete Zallet Docker investigation + - Implement unified Docker Compose + - Set up monitoring infrastructure + - Create deployment guides ## Known Issues -No known issues at this time. +1. Some RPC method assignments still undecided between Zebra and Zaino +2. Monitoring and observability requirements need definition +3. Full integration testing plan needed for Docker deployment +4. Production backup procedures to be designed ## Evolution of Decisions -The most significant decisions that have been made to date are reflected in the mapping of RPC methods to backend services that was made by the Zcashd Deprecation Team in partnership with customers to figure out which methods exposed by Zcashd are in-use and which components of the new Z3 stack (Zebra, Zaino or Zallet) will service each method. Note that the RPC Method Support list seems to be imcomplete, with many methods indicating that they will be served by either Zebra or Zaino or both. We urgently need to drive these decisions to ground in order to move forward. +1. **RPC Service Mapping:** + - Initial mapping complete with Zcashd Deprecation Team + - Some methods still need final assignment + - RPC support requirements documented in techContext.md -We have identified the RPC methods that are most important to support in the new Z3 stack, based on the RPC method support file. We have also documented this information in `memory-bank/techContext.md`. +2. **Docker Architecture:** + - Decision to use multi-stage builds for all components + - Standardized security practices (non-root users, TLS) + - Consistent volume management approach + - Unified monitoring strategy planned +3. **Integration Strategy:** + - Docker Compose as primary orchestration tool + - Service discovery via Docker networking + - TOML-based configuration management + - Secure communication between components diff --git a/memory-bank/z3_docker.md b/memory-bank/z3_docker.md new file mode 100644 index 0000000..c642e1a --- /dev/null +++ b/memory-bank/z3_docker.md @@ -0,0 +1,132 @@ +# Z3 Dockerization & Orchestration Summary + +## Current State + +### Zebra +- **Dockerfile:** Multi-stage build with test, runtime stages + - Build stage installs dependencies and builds binaries + - Runtime stage uses Debian bookworm-slim base + - Configurable non-root user (UID 10001 for security) + +- **Configuration:** + - Flexible via multiple methods: + - Environment variables + - `.env` files + - TOML config files + - Command-line arguments + - Default config at `$HOME/.config/zebrad.toml` + +- **Docker Compose Files:** + 1. `docker-compose.yml`: Base production setup + 2. `docker-compose.grafana.yml`: Monitoring with Prometheus/Grafana + 3. `docker-compose.lwd.yml`: Integration with lightwalletd + 4. `docker-compose.test.yml`: Testing environment + +- **Volume Management:** + - Primary state/cache at `/home/zebra/.cache/zebra` + - Cookie auth directory configurable + - Log files (optional) at customizable location + +- **Networking:** + - RPC port: 8232 (mainnet) / 18232 (testnet) + - Prometheus metrics: 9999 + - Tracing endpoint: 3000 + - P2P network ports configurable + +- **Security Features:** + - Non-root user + - Cookie authentication + - TLS support + - Safe privilege dropping via gosu + +### Zaino +- **Dockerfile:** Multi-stage Rust build + - Build stage with comprehensive Rust dependencies + - Runtime stage on Debian bookworm-slim + - Non-root user (UID 2003) + +- **Configuration:** + - TOML config file (`zindexer.toml`) + - CLI argument `--config` for config path + - Key settings: + - `grpc_listen_address` (default: localhost:8137) + - `validator_listen_address` (Zebra RPC endpoint) + - `db_path` (default: $HOME/.cache/zaino/) + - Network selection (Mainnet/Testnet) + +- **Volume Management:** + - Block cache DB at configurable path (default: $HOME/.cache/zaino/) + - Size configurable via `db_size` setting + +- **Networking:** + - gRPC service on port 8137 + - TLS support available + - Connects to Zebra's RPC endpoint + +## Unified Compose Plan + +A unified docker-compose should integrate Zebra and Zaino with the following considerations: + +1. **Service Dependencies:** +```yaml +services: + zebra: + # Base from zebra/docker/docker-compose.yml + volumes: + - zebra-cache:/home/zebra/.cache/zebra + ports: + - "18232:18232" # RPC (testnet) + - "9999:9999" # Metrics (optional) + + zaino: + depends_on: + - zebra + volumes: + - zaino-cache:/home/zaino/.cache/zaino + ports: + - "8137:8137" # gRPC +``` + +2. **Volume Management:** +```yaml +volumes: + zebra-cache: + driver: local + zaino-cache: + driver: local +``` + +3. **Networking:** +```yaml +networks: + z3net: + driver: bridge +``` + +4. **Configuration:** +- Mount custom config files for both services +- Ensure Zaino's validator_listen_address points to Zebra's RPC +- Consider adding Prometheus/Grafana from Zebra's monitoring setup + +## Next Steps + +1. **Zallet Integration:** + - Research Zallet's Docker requirements + - Determine how it connects to Zebra/Zaino + - Add to unified compose + +2. **Testing:** + - Develop integration tests between services + - Verify volume persistence + - Test network connectivity + +3. **Production Readiness:** + - Add health checks + - Configure logging + - Set up monitoring + - Document backup procedures + +4. **Documentation:** + - Document all config options + - Provide example compose files + - Add troubleshooting guide From 9da0f3878c25371dd2677e4bbe956dd51f487071 Mon Sep 17 00:00:00 2001 From: DC Date: Thu, 17 Apr 2025 10:13:04 -0600 Subject: [PATCH 14/57] chore(submodules): Update zebra, zaino, and zallet submodules to latest versions --- zaino | 2 +- zallet | 2 +- zcashd | 2 +- zebra | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/zaino b/zaino index 3f1a507..6efff3c 160000 --- a/zaino +++ b/zaino @@ -1 +1 @@ -Subproject commit 3f1a50782ba43356b877766c3e9d2a604d6f0cb1 +Subproject commit 6efff3ca829d77e78fe32a18cc15bfc6304fca5a diff --git a/zallet b/zallet index 1eabd80..cd69cf3 160000 --- a/zallet +++ b/zallet @@ -1 +1 @@ -Subproject commit 1eabd80b7106a4918140c01587464dedba853112 +Subproject commit cd69cf30bc39b7e2e2c8cdbb021e38b17a647bd8 diff --git a/zcashd b/zcashd index 7ac250c..76db11e 160000 --- a/zcashd +++ b/zcashd @@ -1 +1 @@ -Subproject commit 7ac250c93b5fa8f8c23ac6fccfdf159608c8aceb +Subproject commit 76db11eba1a65dcecdeb97c143225499b0ec940f diff --git a/zebra b/zebra index d061232..c572027 160000 --- a/zebra +++ b/zebra @@ -1 +1 @@ -Subproject commit d0612323125ec1496feac2b47d433943044508df +Subproject commit c572027ca9c1bc56b6c6e8dd39f51baa9f230fbf From 342b035c3455b77f69f10d6240e753fed5560ee0 Mon Sep 17 00:00:00 2001 From: DC Date: Tue, 6 May 2025 14:23:34 -0600 Subject: [PATCH 15/57] chore: update deps. Also add a utility script to pull Zebra node count from zcashblockexplorer.com. Scrapes HTML because no API afaict. --- memory-bank/activeContext.md | 5 +- memory-bank/data/ecosystem_map.md | 92 +++++++++++++++++++ memory-bank/data/rpc_mapping.md | 2 + .../zebra_adoption_tracking/analyze_nodes.sh | 36 ++++++++ memory-bank/productContext.md | 2 +- memory-bank/progress.md | 1 + memory-bank/z3_docker.md | 3 + zaino | 2 +- zallet | 2 +- zcashd | 2 +- zebra | 2 +- 11 files changed, 143 insertions(+), 6 deletions(-) create mode 100644 memory-bank/data/ecosystem_map.md create mode 100755 memory-bank/data/zebra_adoption_tracking/analyze_nodes.sh diff --git a/memory-bank/activeContext.md b/memory-bank/activeContext.md index ec48689..686d1f6 100644 --- a/memory-bank/activeContext.md +++ b/memory-bank/activeContext.md @@ -30,7 +30,10 @@ - Created z3_docker.md to document findings and orchestration plan. - Identified key configuration patterns and integration points. -2. **RPC Integration:** +2. **Zebra Adoption Tracking Script:** + - Created and debugged `analyze_nodes.sh` to parse zcashexplorer.com HTML for Zebra and MagicBean node counts. + +3. **RPC Integration:** - Mapped RPC methods to services using [RPC mapping](./data/rpc_mapping.md). - Identified service communication paths for Docker networking. - Examined Zebra and Zaino configuration for RPC endpoints. diff --git a/memory-bank/data/ecosystem_map.md b/memory-bank/data/ecosystem_map.md new file mode 100644 index 0000000..3babd89 --- /dev/null +++ b/memory-bank/data/ecosystem_map.md @@ -0,0 +1,92 @@ +# Zcash Ecosystem Map + +```mermaid +graph TD + %% Define Central Node + ZcashProtocol((Zcash Protocol\nzk-SNARKs, PoW Network)) + + %% Define Subgraphs for Categories + subgraph CoreDev [Core Protocol Development & Stewardship] + direction LR + ECC[Electric Coin Co.\n(zcashd, Zashi, R&D)] + ZF[Zcash Foundation\n(Zebra, Governance, ZCG Admin)] + end + + subgraph Funding [Grant Funding & Community Support] + direction LR + ZCG[Zcash Community Grants\n(Dev Fund Allocation)] + Ambassadors[Global Ambassadors] + end + + subgraph WalletsApps [Wallet & Application Development] + direction TB + ECCWallets(ECC Reference Wallets\n e.g., Zashi) + ThirdPartyWallets(Third-Party Wallets\n Nighthawk, YWallet, etc.) + SDKs(Zcash SDKs\n iOS, Android, Rust) + end + + subgraph Infra [Infrastructure & Services] + direction TB + Nodes(Node Operators\n zcashd/Zebra) + Pools(Mining Pools) + Explorers(Block Explorers) + Exchanges(Exchanges) + end + + subgraph Research [Research & Cryptography] + direction LR + ECCResearch(ECC Crypto Team) + ZFResearch(ZF Research Initiatives) + Academia(Academic Researchers) + end + + %% Define Relationships (Arrows indicate influence, data flow, funding, usage etc.) + + %% Core Development -> Protocol + ECC -->|Develops/Maintains 'zcashd'| ZcashProtocol + ZF -->|Develops 'Zebra', Stewards| ZcashProtocol + + %% Funding Relationships + ZF -- Administers & Supports --> ZCG + ZCG -- Funds Projects --> ThirdPartyWallets + ZCG -- Funds Projects --> SDKs + ZCG -- Funds Projects --> Infra + ZCG -- Funds Projects --> Research + ZCG -- Funds Projects --> Ambassadors + + %% Development -> Wallets/SDKs + ECC -- Develops --> ECCWallets + ECC -- Leads --> ECCResearch + ZF -- Supports --> ZFResearch + SDKs -- Enables Dev --> ThirdPartyWallets + SDKs -- Enables Dev --> ECCWallets + + %% Wallets/Apps -> Protocol & Infra + ECCWallets -- Uses --> ZcashProtocol + ThirdPartyWallets -- Uses --> ZcashProtocol + ECCWallets -- Interacts With --> Nodes + ThirdPartyWallets -- Interacts With --> Nodes + + %% Infrastructure -> Protocol & Users/Wallets + Nodes -- Maintain & Validate --> ZcashProtocol + Pools -- Secure (PoW) --> ZcashProtocol + Explorers -- Read Data From --> Nodes + Exchanges -- Interact With --> Nodes + Exchanges -- Provide Liquidity --> ZcashProtocol + Exchanges -- Serve Users --> WalletsApps + + %% Research -> Core Dev & Community + Research -- Informs --> CoreDev + ECCResearch -- Contributes To --> ECC + ZFResearch -- Contributes To --> ZF + Academia -- Publishes/Advises --> Research + + %% Community -> Protocol + Ambassadors -- Promote --> ZcashProtocol + + %% Style (Optional, simple styling) + style ZcashProtocol fill:#f9f,stroke:#333,stroke-width:2px + classDef category fill:#f3f3f3,stroke:#555,stroke-dasharray: 5 5 + class CoreDev,Funding,WalletsApps,Infra,Research category + ``` + \ No newline at end of file diff --git a/memory-bank/data/rpc_mapping.md b/memory-bank/data/rpc_mapping.md index 874c16c..7d22c43 100644 --- a/memory-bank/data/rpc_mapping.md +++ b/memory-bank/data/rpc_mapping.md @@ -1,3 +1,5 @@ +# source: https://docs.google.com/spreadsheets/d/1UJxH1cowexGqadU32Uei5Qak6jGhXjb18-T_QBPmDAA/edit?gid=0#gid=0 + | RPC Method | Where in new stack | Actions | |---|---|---| | `getaddressbalance` | Zebra (lightwalletd usage) / Zaino | Review implementation in Zebra | diff --git a/memory-bank/data/zebra_adoption_tracking/analyze_nodes.sh b/memory-bank/data/zebra_adoption_tracking/analyze_nodes.sh new file mode 100755 index 0000000..d2b4930 --- /dev/null +++ b/memory-bank/data/zebra_adoption_tracking/analyze_nodes.sh @@ -0,0 +1,36 @@ +#!/bin/bash +#set -x + +# Download nodes as HTML from https://mainnet.zcashexplorer.app/nodes with error handling +if ! wget -q -O memory-bank/data/zebra_adoption_tracking/nodes.html https://mainnet.zcashexplorer.app/nodes; then + echo "Error: Failed to download nodes HTML file." + exit 1 +fi +wget -q -O memory-bank/data/zebra_adoption_tracking/nodes.html https://mainnet.zcashexplorer.app/nodes + +# Read the local HTML file +html_content=$(cat memory-bank/data/zebra_adoption_tracking/nodes.html) + +# Extract lines containing version information and then the version string +versions=$(echo "$html_content" | grep '/Zebra:\|/MagicBean:' | sed 's/.*\/\(Zebra\|MagicBean\):[^/]*\/.*/\1/') + +# Count Zebra and MagicBean nodes +zebra_count=$(echo "$versions" | grep -c 'Zebra') +magicbean_count=$(echo "$versions" | grep -c 'MagicBean') + +# Calculate total nodes +total_nodes=$((zebra_count + magicbean_count)) + +# Calculate percentages +if [ "$total_nodes" -eq 0 ]; then + zebra_percentage=0 + magicbean_percentage=0 +else + zebra_percentage=$(awk "BEGIN { printf \"%.2f\", ($zebra_count * 100 / $total_nodes) }") + magicbean_percentage=$(awk "BEGIN { printf \"%.2f\", ($magicbean_count * 100 / $total_nodes) }") +fi + +# Output the results +echo "Total nodes: $total_nodes" +echo "Zebra nodes: $zebra_count ($zebra_percentage%)" +echo "MagicBean nodes: $magicbean_count ($magicbean_percentage%)" diff --git a/memory-bank/productContext.md b/memory-bank/productContext.md index fcefe50..6ce2220 100644 --- a/memory-bank/productContext.md +++ b/memory-bank/productContext.md @@ -46,7 +46,7 @@ The key user experience goals include: * **Usability:** Making the software stack easy to use and understand. ## Key Metrics -* **% of mainnet nodes running Z3:** 0 as of April 2025, with a target of 100% by the time Zcashd is deprecated. Sources for determining this metric are TBD, some potential options include [Blockchair](https://blockchair.com/zcash/nodes) +* **% of mainnet nodes running Z3:** 0 as of April 2025, with a target of 100% by the time Zcashd is deprecated. Sources for determining this metric are TBD, some potential options include [Blockchair](https://blockchair.com/zcash/nodes), but the data at [zcashexplorer.app](https://mainnet.zcashexplorer.app/nodes) appears to be much better. TODO: figure out if they have an API. * **% of centralized exchanges upgraded to Z3:** 0 as of April 2025, with a target of 100% by the time Zcashd is deprecated. Sources for determining this metric are TBD, and we are targeting 0 exchange de-listings due to upgrade friction. * **% of decentralized exchanges upgraded to Z3:** 0 as of April 2025, with a target of 100% by the time Zcashd is deprecated. Sources for determining this metric are TBD, and we are targeting 0 exchange de-listings due to upgrade friction. * **% of wallets upgraded to Z3:** 0 as of April 2025, with a target of 100% by the time Zcashd is deprecated. Sources for determining this metric are TBD. diff --git a/memory-bank/progress.md b/memory-bank/progress.md index b3464ba..1b343dd 100644 --- a/memory-bank/progress.md +++ b/memory-bank/progress.md @@ -6,6 +6,7 @@ - Core files actively maintained and updated - New z3_docker.md documents containerization findings - RPC mapping documented and service assignments agreed upon + - Created and debugged `analyze_nodes.sh` script for tracking Zebra and MagicBean node counts 2. **Docker Configuration:** - Zebra's Docker setup analyzed and documented diff --git a/memory-bank/z3_docker.md b/memory-bank/z3_docker.md index c642e1a..05bcbda 100644 --- a/memory-bank/z3_docker.md +++ b/memory-bank/z3_docker.md @@ -130,3 +130,6 @@ networks: - Document all config options - Provide example compose files - Add troubleshooting guide + +## Related work +Review the Helm charts created by https://github.com/zecrocks/zcash-stack to get a sense of how this project is running Zcash infrastructure in production. \ No newline at end of file diff --git a/zaino b/zaino index 6efff3c..d792561 160000 --- a/zaino +++ b/zaino @@ -1 +1 @@ -Subproject commit 6efff3ca829d77e78fe32a18cc15bfc6304fca5a +Subproject commit d792561e46feb7867a3cbd8a86f039fc65dd15a5 diff --git a/zallet b/zallet index cd69cf3..c8a2b10 160000 --- a/zallet +++ b/zallet @@ -1 +1 @@ -Subproject commit cd69cf30bc39b7e2e2c8cdbb021e38b17a647bd8 +Subproject commit c8a2b1080cd2641dba453e9b72bbce0a884aa92b diff --git a/zcashd b/zcashd index 76db11e..1f1f7a3 160000 --- a/zcashd +++ b/zcashd @@ -1 +1 @@ -Subproject commit 76db11eba1a65dcecdeb97c143225499b0ec940f +Subproject commit 1f1f7a385adc048154e7f25a3a0de76f3658ca09 diff --git a/zebra b/zebra index c572027..b0e7fd4 160000 --- a/zebra +++ b/zebra @@ -1 +1 @@ -Subproject commit c572027ca9c1bc56b6c6e8dd39f51baa9f230fbf +Subproject commit b0e7fd4a9a6159c2b81976452fe368cb06d2dead From af218a1f17daffd0e5407a52e148454c1e82f646 Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Wed, 14 May 2025 09:12:10 +0100 Subject: [PATCH 16/57] docs(memory-bank): linted technology context and introduce Z3 interplay documentation * Update techContext.md to improve formatting and clarity of technology descriptions. * Add new `z3_component_interplay.md` to detail the roles, interactions, and dependencies of Zebra, Zaino, and Zallet within the Z3 stack. --- memory-bank/techContext.md | 409 +++++++++++++------------- memory-bank/z3_component_interplay.md | 112 +++++++ 2 files changed, 321 insertions(+), 200 deletions(-) create mode 100644 memory-bank/z3_component_interplay.md diff --git a/memory-bank/techContext.md b/memory-bank/techContext.md index 0b34185..b930a52 100644 --- a/memory-bank/techContext.md +++ b/memory-bank/techContext.md @@ -2,282 +2,292 @@ ## Technologies Used -* **[Zcashd](https://github.com/zcash/zcash):** Is the legacy Zcash full-node client software being replaced by the Z3 stack -* **[Zebra](https://github.com/ZcashFoundation/zebra):** A full-node implementation of the Zcash protocol that provides consensus and network functionalities in Rust. Zebra has been under development by the Zcash Foundation since 2020 and is presently deployed by a number of decentralized node operators on mainnet. -* **[Zaino](https://github.com/zingolabs/zaino/):** An indexing service that provides light clients with blockchain data, replacing the legacy go light client server lightwalletd. Zaino is under active development by Zingo Labs, funded by a grant from [ZCG](https://zcashcommunitygrants.org/). -* **[Zallet](https://github.com/zcash/wallet):** A CLI wallet that provides wallet functionality that existed in Zcashd but is not planned for implementation in Zebra. Zallet is under active development by the Electric Coin Company. -* **Rust:** Primary language for Zebra, Zaino, and Zallet (version TBD). Justification: Security, performance, and memory safety. -* **gRPC:** In consideration for communication between Zebra and Zaino (version TBD). Justification: Efficient and standardized RPC framework. -* **Docker:** For deployment and containerization (version TBD). Justification: Consistent and reproducible environments. +* **[Zcashd](https://github.com/zcash/zcash):** Is the legacy Zcash full-node client software being replaced by the Z3 stack +* **[Zebra](https://github.com/ZcashFoundation/zebra):** A full-node implementation of the Zcash protocol that provides consensus and network functionalities in Rust. Zebra has been under development by the Zcash Foundation since 2020 and is presently deployed by a number of decentralized node operators on mainnet. +* **[Zaino](https://github.com/zingolabs/zaino/):** An indexing service that provides light clients with blockchain data, replacing the legacy go light client server lightwalletd. Zaino is under active development by Zingo Labs, funded by a grant from [ZCG](https://zcashcommunitygrants.org/). +* **[Zallet](https://github.com/zcash/wallet):** A CLI wallet that provides wallet functionality that existed in Zcashd but is not planned for implementation in Zebra. Zallet is under active development by the Electric Coin Company. +* **Rust:** Primary language for Zebra, Zaino, and Zallet (version TBD). Justification: Security, performance, and memory safety. +* **gRPC:** In consideration for communication between Zebra and Zaino (version TBD). Justification: Efficient and standardized RPC framework. +* **Docker:** For deployment and containerization (version TBD). Justification: Consistent and reproducible environments. ## Development Setup -* **Rust toolchain:** Requires a recent version of the Rust toolchain, including `rustc`, `cargo`, and `clippy`. -* **Docker:** Requires Docker for building and running the services. -* **Git:** Requires Git for version control. -* **Editor:** VSCode with Rust Analyzer extension recommended. +* **Rust toolchain:** Requires a recent version of the Rust toolchain, including `rustc`, `cargo`, and `clippy`. +* **Docker:** Requires Docker for building and running the services. +* **Git:** Requires Git for version control. +* **Editor:** VSCode with Rust Analyzer extension recommended. ## Technical Constraints -* **Performance:** The system must be able to handle high transaction volumes and provide low-latency access to blockchain data. -* **Security:** Security is paramount, and all components must be designed to resist attacks. -* **Compatibility:** The system must be compatible with the Zcash protocol and network. +* **Performance:** The system must be able to handle high transaction volumes and provide low-latency access to blockchain data. +* **Security:** Security is paramount, and all components must be designed to resist attacks. +* **Compatibility:** The system must be compatible with the Zcash protocol and network. ## RPC Methods used by Customers + The Zcashd Deprecation team engaged with customers and found that they are using the following RPC methods in production. They discussed and came to agreement on which component in the new Z3 stack should service each of these methods. -| RPC Method | Where in new stack | Actions | -|---|---|---| -| `getaddressbalance` | Zebra (lightwalletd usage) / Zaino | Review implementation in Zebra | -| `getaddressdeltas` | Zaino | | -| `getaddressmempool` | - | Lower priority. Possibly deprecate | -| `getaddresstxids` | Zebra (lightwalletd usage) / Zaino | Review implementation in Zebra \| Implement in Zaino | -| `getaddressutxos` | Zebra | Review implementation in Zebra | -| `getbestblockhash` | Zebra / Zaino | | -| `getblock` | Zebra / Zaino | Being fully implemented in Zebra | -| `getblockchaininfo` | Zebra / Zaino | Review implementation in Zebra | -| `getblockcount` | Zebra / Zaino | | -| `getblockdeltas` | Zaino | Implement in Zaino | -| `getblockhash` | Zebra / Zaino | | -| `getblockhashes` | Zaino | Lower priority. Possibly deprecate | -| `getblockheader` | Zebra / Zaino | | -| `getchaintips` | Zaino | Implement in Zaino | -| `getdifficulty` | Zebra / Zaino | | -| `getmempoolinfo` | Zaino | Implement in Zaino | -| `getrawmempool` | Zebra / Zaino | | -| `getspentinfo` | Zaino | Implement in Zaino | -| `gettxout` | Zaino | Implement in Zaino | -| `gettxoutproof` | Zaino | Lower priority | -| `gettxoutsetinfo` | Zaino | | -| `verifychain` | - | Deprecate | -| `verifytxoutproof` | Zaino | Lower priority | -| `z_gettreestate` | Zebra / Zaino | Review implementation in Zebra | -| `getexperimentalfeatures` | - | Deprecate? Confirm what Zebra defines as experimental | -| `getinfo` | Zebra | Review implementation in Zebra | -| `getmemoryinfo` | Zebra | No plan to implement | -| `help` | Zebra | No plans to implement | -| `setlogfilter` | Zebra | No plans to implement | -| `stop` | Zebra | None | -| `z_getpaymentdisclosure` | - | Deprecate | -| `z_validatepaymentdisclosure` | - | Deprecate | -| `generate` | Zebra | None | -| `getgenerate` | Zebra | No plans to implement | -| `setgenerate` | Zebra | No plans to implement | -| `getblocksubsidy` | Zebra | None. All done in Zebra | -| `getblocktemplate` | Zebra | Review implementation in Zebra | -| `getlocalsolps` | Zebra | No plans to implement | -| `getmininginfo` | Zebra / Zaino | | -| `getnetworkhashps` | - | Deprecated | -| `getnetworksolps` | Zebra / Zaino | | -| `prioritisetransaction` | Zebra | No plans to implement | -| `submitblock` | Zebra | None | -| `addnode` | Zebra | Decide if we want to implement in Zebra | -| `clearbanned` | Zebra | Decide if we want to implement in Zebra | -| `disconnectnode` | Zebra | Decide if we want to implement in Zebra | -| `getaddednodeinfo` | Zebra | Decide if we want to implement in Zebra | -| `getnettotals` | Zebra | Decide if we want to implement in Zebra | -| `getnetworkinfo` | Zebra / Zaino | Implement in Zebra | -| `getpeerinfo` | Zebra / Zaino | Review implementation in Zebra | -| `listbanned` | Zebra | Decide if we want to implement in Zebra | -| `ping` | Zebra / Zaino | Implement in Zebra | -| `setban` | Zebra | Decide if we want to implement in Zebra | -| `createrawtransaction` | Zallet | | -| `decoderawtransaction` | Zallet | Lower priority. Might deprecate | -| `decodescript` | Zallet | Lower priority. Might deprecate | -| `fundrawtransaction` | Zallet | | -| `getrawtransaction` | Zebra / Zaino | Review implementation in Zebra | -| `sendrawtransaction` | Zebra / Zaino | Review implementation in Zebra | -| `signrawtransaction` | Zallet | | -| `validateaddress` | Zebra / Zaino | Implement in Zaino | -| `verifymessage` | Zallet | Implement in Zallet | -| `z_validateaddress` | Zebra | Implement in Zaino | +| RPC Method | Where in new stack | Actions | +| ----------------------------- | ---------------------------------- | ----------------------------------------------------- | +| `getaddressbalance` | Zebra (lightwalletd usage) / Zaino | Review implementation in Zebra | +| `getaddressdeltas` | Zaino | | +| `getaddressmempool` | - | Lower priority. Possibly deprecate | +| `getaddresstxids` | Zebra (lightwalletd usage) / Zaino | Review implementation in Zebra \| Implement in Zaino | +| `getaddressutxos` | Zebra | Review implementation in Zebra | +| `getbestblockhash` | Zebra / Zaino | | +| `getblock` | Zebra / Zaino | Being fully implemented in Zebra | +| `getblockchaininfo` | Zebra / Zaino | Review implementation in Zebra | +| `getblockcount` | Zebra / Zaino | | +| `getblockdeltas` | Zaino | Implement in Zaino | +| `getblockhash` | Zebra / Zaino | | +| `getblockhashes` | Zaino | Lower priority. Possibly deprecate | +| `getblockheader` | Zebra / Zaino | | +| `getchaintips` | Zaino | Implement in Zaino | +| `getdifficulty` | Zebra / Zaino | | +| `getmempoolinfo` | Zaino | Implement in Zaino | +| `getrawmempool` | Zebra / Zaino | | +| `getspentinfo` | Zaino | Implement in Zaino | +| `gettxout` | Zaino | Implement in Zaino | +| `gettxoutproof` | Zaino | Lower priority | +| `gettxoutsetinfo` | Zaino | | +| `verifychain` | - | Deprecate | +| `verifytxoutproof` | Zaino | Lower priority | +| `z_gettreestate` | Zebra / Zaino | Review implementation in Zebra | +| `getexperimentalfeatures` | - | Deprecate? Confirm what Zebra defines as experimental | +| `getinfo` | Zebra | Review implementation in Zebra | +| `getmemoryinfo` | Zebra | No plan to implement | +| `help` | Zebra | No plans to implement | +| `setlogfilter` | Zebra | No plans to implement | +| `stop` | Zebra | None | +| `z_getpaymentdisclosure` | - | Deprecate | +| `z_validatepaymentdisclosure` | - | Deprecate | +| `generate` | Zebra | None | +| `getgenerate` | Zebra | No plans to implement | +| `setgenerate` | Zebra | No plans to implement | +| `getblocksubsidy` | Zebra | None. All done in Zebra | +| `getblocktemplate` | Zebra | Review implementation in Zebra | +| `getlocalsolps` | Zebra | No plans to implement | +| `getmininginfo` | Zebra / Zaino | | +| `getnetworkhashps` | - | Deprecated | +| `getnetworksolps` | Zebra / Zaino | | +| `prioritisetransaction` | Zebra | No plans to implement | +| `submitblock` | Zebra | None | +| `addnode` | Zebra | Decide if we want to implement in Zebra | +| `clearbanned` | Zebra | Decide if we want to implement in Zebra | +| `disconnectnode` | Zebra | Decide if we want to implement in Zebra | +| `getaddednodeinfo` | Zebra | Decide if we want to implement in Zebra | +| `getnettotals` | Zebra | Decide if we want to implement in Zebra | +| `getnetworkinfo` | Zebra / Zaino | Implement in Zebra | +| `getpeerinfo` | Zebra / Zaino | Review implementation in Zebra | +| `listbanned` | Zebra | Decide if we want to implement in Zebra | +| `ping` | Zebra / Zaino | Implement in Zebra | +| `setban` | Zebra | Decide if we want to implement in Zebra | +| `createrawtransaction` | Zallet | | +| `decoderawtransaction` | Zallet | Lower priority. Might deprecate | +| `decodescript` | Zallet | Lower priority. Might deprecate | +| `fundrawtransaction` | Zallet | | +| `getrawtransaction` | Zebra / Zaino | Review implementation in Zebra | +| `sendrawtransaction` | Zebra / Zaino | Review implementation in Zebra | +| `signrawtransaction` | Zallet | | +| `validateaddress` | Zebra / Zaino | Implement in Zaino | +| `verifymessage` | Zallet | Implement in Zallet | +| `z_validateaddress` | Zebra | Implement in Zaino | It is very important that the project team focuses on the agreed upon RPC mapping and figures out exactly which RPC will be served by which component, as ultimately this information will be used by Z3 to route requests to the right backend service. Note that the RPC Method Support list seems to be imcomplete, with many methods indicating that they will be served by either Zebra or Zaino or both. We urgently need to drive these decisions to ground in order to move forward. ## RPC Methods exposed by Zcashd + The following [RPC methods](./data/zcashd_RPC_methods.md) are all of the methods historically exposed by the legacy Zcashd software. ## Dependencies ### Z3 Wrapper Dependencies + TBD but generally speaking we want to use the same libraries and frameworks that the ZF Engineering team is familiar and comfortable with based on its work on Zebra. ### Zebra Dependencies + * Core crates: - - `zebra-chain`: Core data structures and crypto primitives for the Zcash protocol - - `zebra-consensus`: Consensus rules implementation and block validation - - `zebra-network`: P2P networking stack and peer management - - `zebra-state`: Chain state management and block storage - - `zebra-rpc`: JSON-RPC and gRPC server implementations - - `zebra-script`: Bitcoin script verification engine for transparent transactions - - `zebra-node-services`: Shared services used across multiple Zebra components - - `zebra-scan`: Block and transaction scanning utilities - - `zebra-grpc`: gRPC service definitions and implementations - - `zebra-utils`: Common utilities and helper functions - - `zebra-test`: Shared test infrastructure and helpers - - `tower-batch-control`: Custom Tower middleware for batch control - - `tower-fallback`: Custom Tower middleware for fallback handling - - `zebrad`: Main executable binary crate + * `zebra-chain`: Core data structures and crypto primitives for the Zcash protocol + * `zebra-consensus`: Consensus rules implementation and block validation + * `zebra-network`: P2P networking stack and peer management + * `zebra-state`: Chain state management and block storage + * `zebra-rpc`: JSON-RPC and gRPC server implementations + * `zebra-script`: Bitcoin script verification engine for transparent transactions + * `zebra-node-services`: Shared services used across multiple Zebra components + * `zebra-scan`: Block and transaction scanning utilities + * `zebra-grpc`: gRPC service definitions and implementations + * `zebra-utils`: Common utilities and helper functions + * `zebra-test`: Shared test infrastructure and helpers + * `tower-batch-control`: Custom Tower middleware for batch control + * `tower-fallback`: Custom Tower middleware for fallback handling + * `zebrad`: Main executable binary crate * Zcash Dependencies: - - `halo2`: Zero-knowledge proof system used for Orchard shielded transactions - - `orchard`: Implementation of the Orchard shielded pool protocol - - `zcash_primitives`: Core Zcash cryptographic primitives - - `zcash_proofs`: Zero-knowledge proof implementations - - `zcash_client_backend`: Client functionality shared between wallets - - `zcash_address`: Zcash address handling and encoding - - `zcash_encoding`: Binary serialization for Zcash types - - `zcash_history`: Block chain history handling - - `zip32`: Hierarchical deterministic wallets for Zcash - - `sapling-crypto`: Sapling zk-SNARK circuit implementations - - `incrementalmerkletree`: Incremental Merkle tree implementation + * `halo2`: Zero-knowledge proof system used for Orchard shielded transactions + * `orchard`: Implementation of the Orchard shielded pool protocol + * `zcash_primitives`: Core Zcash cryptographic primitives + * `zcash_proofs`: Zero-knowledge proof implementations + * `zcash_client_backend`: Client functionality shared between wallets + * `zcash_address`: Zcash address handling and encoding + * `zcash_encoding`: Binary serialization for Zcash types + * `zcash_history`: Block chain history handling + * `zip32`: Hierarchical deterministic wallets for Zcash + * `sapling-crypto`: Sapling zk-SNARK circuit implementations + * `incrementalmerkletree`: Incremental Merkle tree implementation * External dependencies: - - `tokio`: Asynchronous runtime and utilities - - `tower`: Service architecture and middleware - - `futures`: Async/await primitives and utilities - - `blake2b_simd`: High-performance hashing implementation - - `metrics`: Core metrics collection and reporting - - `metrics-exporter-prometheus`: Prometheus metrics exposition - - `tracing`: Structured logging and diagnostics - - `serde`: Data serialization framework - - `rocksdb`: Persistent key-value storage backend - - `hyper`: HTTP/HTTPS implementation - - `tonic`: gRPC implementation - - `jsonrpsee`: JSON-RPC framework - - `reqwest`: HTTP client - - `color-eyre`: Error reporting and handling - - `proptest`: Property-based testing framework - - `insta`: Snapshot testing support - - `criterion`: Benchmarking framework + * `tokio`: Asynchronous runtime and utilities + * `tower`: Service architecture and middleware + * `futures`: Async/await primitives and utilities + * `blake2b_simd`: High-performance hashing implementation + * `metrics`: Core metrics collection and reporting + * `metrics-exporter-prometheus`: Prometheus metrics exposition + * `tracing`: Structured logging and diagnostics + * `serde`: Data serialization framework + * `rocksdb`: Persistent key-value storage backend + * `hyper`: HTTP/HTTPS implementation + * `tonic`: gRPC implementation + * `jsonrpsee`: JSON-RPC framework + * `reqwest`: HTTP client + * `color-eyre`: Error reporting and handling + * `proptest`: Property-based testing framework + * `insta`: Snapshot testing support + * `criterion`: Benchmarking framework ### Zaino Dependencies * Core crates: - - `zaino-serve`: Service implementation for light clients - - `zaino-state`: State management and storage - - `zaino-fetch`: Data fetching from Zebra nodes - - `zaino-proto`: gRPC protocol definitions - - `zaino-testutils`: Testing utilities - - `zainod`: Main executable binary + * `zaino-serve`: Service implementation for light clients + * `zaino-state`: State management and storage + * `zaino-fetch`: Data fetching from Zebra nodes + * `zaino-proto`: gRPC protocol definitions + * `zaino-testutils`: Testing utilities + * `zainod`: Main executable binary * Zcash Dependencies: - - `zcash_client_backend`: Modified version of librustzcash client backend - - Custom fork: `zingolabs/librustzcash` - - Tag: `zcash_client_sqlite-0.12.1_plus_zingolabs_changes-test_2` - - Features: `lightwalletd-tonic` - - `zcash_protocol`: Protocol definitions from librustzcash - - Custom fork: `zingolabs/librustzcash` - - Tag: `zcash_client_sqlite-0.12.1_plus_zingolabs_changes-test_2` + * `zcash_client_backend`: Modified version of librustzcash client backend + * Custom fork: `zingolabs/librustzcash` + * Tag: `zcash_client_sqlite-0.12.1_plus_zingolabs_changes-test_2` + * Features: `lightwalletd-tonic` + * `zcash_protocol`: Protocol definitions from librustzcash + * Custom fork: `zingolabs/librustzcash` + * Tag: `zcash_client_sqlite-0.12.1_plus_zingolabs_changes-test_2` * Zebra Dependencies: - - `zebra-chain`: Core data structures (from `main` branch) - - `zebra-state`: State management (from `main` branch) - - `zebra-rpc`: RPC interfaces (from `main` branch) + * `zebra-chain`: Core data structures (from `main` branch) + * `zebra-state`: State management (from `main` branch) + * `zebra-rpc`: RPC interfaces (from `main` branch) * Custom Dependencies: - - `zingolib`: Core functionality from Zingo Labs - - Tag: `zaino_dep_005` - - `zingo-infra-testutils`: Testing infrastructure - - `zingo-infra-services`: Service infrastructure + * `zingolib`: Core functionality from Zingo Labs + * Tag: `zaino_dep_005` + * `zingo-infra-testutils`: Testing infrastructure + * `zingo-infra-services`: Service infrastructure * External Dependencies: - - `tokio`: Async runtime with full feature set - - `tokio-stream`: Stream utilities for async data processing - - `tonic`: gRPC implementation and server framework - - `tonic-build`: gRPC code generation - - `tower`: Service architecture with buffer and util features - - `tracing`: Logging infrastructure - - `reqwest`: HTTP client with rustls-tls support - - `lmdb`: Lightning Memory-Mapped Database - - `dashmap`: Thread-safe concurrent HashMap - - `indexmap`: Hash table with deterministic iteration - - `crossbeam-channel`: Multi-producer multi-consumer channels + * `tokio`: Async runtime with full feature set + * `tokio-stream`: Stream utilities for async data processing + * `tonic`: gRPC implementation and server framework + * `tonic-build`: gRPC code generation + * `tower`: Service architecture with buffer and util features + * `tracing`: Logging infrastructure + * `reqwest`: HTTP client with rustls-tls support + * `lmdb`: Lightning Memory-Mapped Database + * `dashmap`: Thread-safe concurrent HashMap + * `indexmap`: Hash table with deterministic iteration + * `crossbeam-channel`: Multi-producer multi-consumer channels ### Zallet Dependencies + * Core crate: - - `zallet`: Main wallet implementation including CLI interface and core functionality + * `zallet`: Main wallet implementation including CLI interface and core functionality * Zcash Dependencies: - - `zcash_client_backend`: Wallet functionality from librustzcash - - `zcash_client_sqlite`: SQLite storage implementation - - `zcash_primitives`: Core cryptographic primitives - - `zcash_keys`: Key management - - `zcash_protocol`: Protocol definitions - - `orchard`: Orchard shielded pool support - - `sapling`: Sapling shielded pool support - - `transparent`: Transparent address support - - `zip32`: HD wallet key derivation -Note that all of the Zcash dependencies in Zallet are presently pinned to a specific revision of Librustzcash. + * `zcash_client_backend`: Wallet functionality from librustzcash + * `zcash_client_sqlite`: SQLite storage implementation + * `zcash_primitives`: Core cryptographic primitives + * `zcash_keys`: Key management + * `zcash_protocol`: Protocol definitions + * `orchard`: Orchard shielded pool support + * `sapling`: Sapling shielded pool support + * `transparent`: Transparent address support + * `zip32`: HD wallet key derivation +Note that all of the Zcash dependencies in Zallet are presently pinned to a specific revision of Librustzcash. * External Dependencies: - - `tokio`: Async runtime and utilities - - `abscissa_core`: Application framework - - `deadpool-sqlite`: SQLite connection pooling - - `rusqlite`: SQLite database access - - `age`: File encryption - - `clap`: Command line argument parsing - - `jsonrpsee`: JSON-RPC client - - `tonic`: gRPC client (temporary for lightwalletd) + * `tokio`: Async runtime and utilities + * `abscissa_core`: Application framework + * `deadpool-sqlite`: SQLite connection pooling + * `rusqlite`: SQLite database access + * `age`: File encryption + * `clap`: Command line argument parsing + * `jsonrpsee`: JSON-RPC client + * `tonic`: gRPC client (temporary for lightwalletd) ### Common Dependencies * Runtime and async: - - `tokio`: Async runtime used by all components - - `tower`: Service architecture and middleware + * `tokio`: Async runtime used by all components + * `tower`: Service architecture and middleware * Observability: - - `tracing`: Logging and diagnostics infrastructure used across all components + * `tracing`: Logging and diagnostics infrastructure used across all components * Serialization: - - `serde`: Data serialization framework + * `serde`: Data serialization framework * RPC: - - `tonic`: gRPC implementation (used by all, though Zallet's usage is temporary) - - `jsonrpsee`: JSON-RPC framework (Zebra and Zallet) + * `tonic`: gRPC implementation (used by all, though Zallet's usage is temporary) + * `jsonrpsee`: JSON-RPC framework (Zebra and Zallet) * Error handling: - - `color-eyre`: Error reporting and handling + * `color-eyre`: Error reporting and handling * CLI: - - `clap`: Command-line argument parsing + * `clap`: Command-line argument parsing Note: While these dependencies are common, they might be used with different feature flags or versions across the components. The exact versions should be coordinated to ensure compatibility when integrating the components. ## Tool Usage Patterns ### Build Tools -* `cargo`: - - Building: `cargo build --release` - - Testing: `cargo test` - - Dependency management: `cargo update` - - Documentation: `cargo doc --no-deps` - - Workspace management: `cargo workspace` + +* `cargo`: + * Building: `cargo build --release` + * Testing: `cargo test` + * Dependency management: `cargo update` + * Documentation: `cargo doc --no-deps` + * Workspace management: `cargo workspace` ### Code Quality + * `clippy`: - - Standard linting: `cargo clippy` - - Strict checks: `cargo clippy -- -D warnings` - - Workspace checks: `cargo clippy --workspace` + * Standard linting: `cargo clippy` + * Strict checks: `cargo clippy -- -D warnings` + * Workspace checks: `cargo clippy --workspace` * `fmt`: - - Code formatting: `cargo fmt` - - Format check: `cargo fmt -- --check` + * Code formatting: `cargo fmt` + * Format check: `cargo fmt -- --check` ### Version Control + * `git`: - - Branch management: Using feature branches - - Commit messages: Following Conventional Commits spec - - Code review: GitHub pull request workflow - - Tagging: Semantic versioning for releases + * Branch management: Using feature branches + * Commit messages: Following Conventional Commits spec + * Code review: GitHub pull request workflow + * Tagging: Semantic versioning for releases ### Containerization + * `docker`: - - Local development: Docker Compose for service orchestration - - Testing: Isolated test environments - - CI/CD: GitHub Actions with Docker caching - - Production: Multi-stage builds for minimal images + * Local development: Docker Compose for service orchestration + * Testing: Isolated test environments + * CI/CD: GitHub Actions with Docker caching + * Production: Multi-stage builds for minimal images ### Testing + * Unit tests: Per-module tests using `#[cfg(test)]` * Integration tests: Using `tests/` directory * Property testing: Using `proptest` @@ -285,12 +295,11 @@ Note: While these dependencies are common, they might be used with different fea * Benchmarking: Using `criterion` ### Documentation + * API docs: Using rustdoc comments * Architecture docs: Using architecture decision records (ADRs) * User guides: Using mdBook - ## Further Reading For details about design patterns and conventions used across the Z3 stack, see the [System Patterns](systemPatterns.md). - diff --git a/memory-bank/z3_component_interplay.md b/memory-bank/z3_component_interplay.md new file mode 100644 index 0000000..616dded --- /dev/null +++ b/memory-bank/z3_component_interplay.md @@ -0,0 +1,112 @@ +# Z3 Stack: Zebra, Zaino, and Zallet Interplay + +## Introduction + +The Z3 project consists of three core components that work together to provide a modular replacement for the monolithic `zcashd`: Zebra (the consensus node), Zaino (the indexing service), and Zallet (the wallet application). Understanding their individual roles and how they currently interconnect is crucial for developers and users of the Z3 ecosystem. + +## Component Roles + +### 1. Zebra: The Consensus Full Node + +* **Core Responsibility:** Zebra is the Zcash full-node implementation. It maintains the integrity of the Zcash blockchain by enforcing consensus rules, managing peer-to-peer network connections, and validating all blocks and transactions. +* **Functionality:** + * Downloads and stores the complete Zcash blockchain. + * Validates new blocks and transactions against consensus rules. + * Participates in the Zcash P2P network, gossiping transactions and blocks. + * Provides RPC/gRPC interfaces for querying raw blockchain data (e.g., blocks, transactions, tree states) and for submitting transactions to the network. +* **Significance:** Zebra serves as the ultimate source of truth for blockchain data within the Z3 stack. + +### 2. Zaino: The Indexing Service + +* **Core Responsibility:** Zaino acts as a specialized indexing layer that sits between a full node (Zebra) and client applications (like light wallets or Zallet). Its primary goal is to provide a fast, efficient, and queryable source of blockchain data, replacing the functionality of `lightwalletd`. +* **Functionality:** + * Connects to Zebra to fetch new blocks and blockchain updates. + * Processes this data and builds optimized indexes (e.g., for compact blocks, transaction histories per address, note commitment tree states). + * Exposes gRPC and/or RPC interfaces for clients to retrieve this indexed data efficiently. +* **Significance:** Zaino enables performant data retrieval for applications that don't need the full raw blockchain, significantly improving sync times and query speeds for wallets and light clients. + +### 3. Zallet: The Wallet Application + +* **Core Responsibility:** Zallet is a command-line interface (CLI) wallet that manages user accounts, private keys, and facilitates Zcash transactions. It is designed to provide the wallet functionalities previously embedded within `zcashd`. +* **Functionality:** + * Securely stores and manages user private keys (encrypted using `age` encryption). + * Generates and manages Zcash addresses (Transparent, Sapling, Orchard, Unified Addresses). + * Constructs, signs, and broadcasts transactions. + * Scans the blockchain (via Zaino) to discover and track transactions relevant to its managed accounts. + * Maintains a local wallet database for storing account balances, transaction history, and notes. + * Exposes its own RPC interface for wallet-specific operations. +* **Significance:** Zallet provides the user-facing tools for managing Zcash funds and interacting with the Zcash network for shielded and transparent operations. + +## Interactions and Dependencies + +The Z3 components currently work together as follows: + +```mermaid +graph TD + User[User/Application] + Zallet[Zallet CLI Wallet] + Zaino[Zaino Indexing Service] + Zebra[Zebra Consensus Node] + Network[Zcash P2P Network] + + User --> Zallet + Zallet --> User + + User --> Zaino + Zaino --> User + + Zallet --> Zaino + Zallet -.-> Zebra + + Zaino --> Zebra + Zebra --> Zaino + + Zebra --> Network + Network --> Zebra + + classDef notes fill:#f9f,stroke:#333,stroke-dasharray:5 5 + + Note1[Zallet: JSON-RPC server, SQLite wallet.db,
uses zaino-fetch, zebra-chain libraries] + Note2[Zaino: Optimized indexes, serves compact blocks,
relays transactions to Zebra] + Note3[Zebra: Consensus validation,
RocksDB state storage, P2P connectivity] + + class Note1,Note2,Note3 notes + + Zallet --- Note1 + Zaino --- Note2 + Zebra --- Note3 +``` + +**Technical Connection Details:** + +* **Zallet to Zaino**: Primarily gRPC communication (via zaino-proto library) +* **Zallet to Zebra**: Occasional direct JSON-RPC/gRPC calls (via zebra-rpc library) +* **Zaino to Zebra**: JSON-RPC/gRPC to fetch blockchain data +* **User to Components**: JSON-RPC for Zallet wallet operations, gRPC for Zaino data requests + +**Key Interaction Flows:** + +1. **Blockchain Data Flow:** + * `Zebra` connects to the Zcash P2P network, downloads, and validates blocks. + * `Zaino` connects to `Zebra` (its "validator node") to fetch new blocks and tree states. Zaino then processes this data into its indexed database. + * `Zallet` connects to `Zaino` (specifically, its `FetchServiceSubscriber` interface) to download compact blocks and tree states relevant for its accounts. This allows Zallet to scan for incoming transactions and update its local wallet state. + +2. **Transaction Submission:** + * A user initiates a transaction via `Zallet`. + * `Zallet` constructs and signs the transaction using its locally stored keys. + * `Zallet` submits the raw transaction. The primary path for this appears to be via Zaino's `FetchService`, which would then relay it to `Zebra`. `Zallet`'s direct dependencies on `zebra-rpc` also suggest it can make direct calls to Zebra for certain operations. + * `Zebra` validates the transaction and broadcasts it to the Zcash P2P network. + +**Dependencies (as seen in `Cargo.toml` and code):** + +* **Zallet depends on:** + * Zaino libraries (`zaino-fetch`, `zaino-proto`, `zaino-state`) for accessing indexed chain data and possibly for submitting transactions. + * Zebra libraries (`zebra-chain`, `zebra-rpc`, `zebra-state`) for core Zcash data structures, RPC communication capabilities, and direct state interaction if needed. +* **Zaino depends on:** + * Zebra libraries (implicitly, as it needs to connect to a Zebra node as its data source). Its internal `FetchService` is configured to point to a validator (Zebra) RPC. +* **Zebra:** + * Has its own set of internal and Zcash-specific cryptographic dependencies but does not depend on Zaino or Zallet. + +## Conclusion + +The Z3 stack (Zebra, Zaino, Zallet) creates a robust, modular system for interacting with the Zcash blockchain. Zebra provides the secure consensus foundation, Zaino offers an efficient data indexing and retrieval layer, and Zallet delivers user-facing wallet management. This separation of concerns aims for improved maintainability, scalability, and security compared to the legacy `zcashd` monolith. From 5b96dd4676d832c722d0453acbaba172b451aa3f Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Wed, 14 May 2025 09:20:48 +0100 Subject: [PATCH 17/57] feat(workflows): add GitHub Actions workflows for building Z3 Docker images * Introduced a new workflow to build Z3 images * Created a sub-workflow for building Docker images with customizable inputs, including repository, Dockerfile path, and Rust parameters. --- .github/workflows/build-z3-images.yaml | 35 +++++ .github/workflows/sub-build-docker-image.yaml | 148 ++++++++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 .github/workflows/build-z3-images.yaml create mode 100644 .github/workflows/sub-build-docker-image.yaml diff --git a/.github/workflows/build-z3-images.yaml b/.github/workflows/build-z3-images.yaml new file mode 100644 index 0000000..1fa5a7f --- /dev/null +++ b/.github/workflows/build-z3-images.yaml @@ -0,0 +1,35 @@ +name: Build z3 images + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +on: + workflow_dispatch: + push: + branches: + - dev + paths: + - '.github/workflows/build-z3-images.yml' + + pull_request: + paths: + - '.github/workflows/build-z3-images.yml' + +jobs: + build: + name: Build zaino Docker + permissions: + contents: 'read' + id-token: 'write' + uses: ./.github/workflows/sub-build-docker-image.yaml + with: + repository: zingolabs/zaino + ref: 'dev' + dockerfile_path: ./Dockerfile + dockerfile_target: tests + image_name: zaino + no_cache: ${{ inputs.no_cache || false }} + rust_backtrace: full + rust_lib_backtrace: full + rust_log: info diff --git a/.github/workflows/sub-build-docker-image.yaml b/.github/workflows/sub-build-docker-image.yaml new file mode 100644 index 0000000..688ae7d --- /dev/null +++ b/.github/workflows/sub-build-docker-image.yaml @@ -0,0 +1,148 @@ +# This workflow automates the building and pushing of Docker images based on user-defined inputs. It includes: +# - Accepting various inputs like image name, Dockerfile path, target, and additional Rust-related parameters. +# - Authenticates with GitHub Container Registry. +# - Uses Docker Buildx for improved build performance and caching. +# - Builds the Docker image and pushes it to GitHub Container Registry. +# TODO: Manages caching strategies to optimize build times across different branches. +name: Build docker image + +on: + workflow_call: + inputs: + repository: + required: true + type: string + ref: + required: true + type: string + image_name: + required: true + type: string + dockerfile_path: + required: true + type: string + dockerfile_target: + required: true + type: string + short_sha: + required: false + type: string + rust_backtrace: + required: false + type: string + rust_lib_backtrace: + required: false + type: string + # defaults to: vars.RUST_LOG + rust_log: + required: false + type: string + features: + required: false + type: string + no_cache: + description: "Disable the Docker cache for this build" + required: false + type: boolean + default: false + + outputs: + image_digest: + description: "The image digest to be used on a caller workflow" + value: ${{ jobs.build.outputs.image_digest }} + +env: + FEATURES: ${{ inputs.features }} + RUST_LOG: ${{ inputs.rust_log || vars.RUST_LOG }} + CARGO_INCREMENTAL: ${{ vars.CARGO_INCREMENTAL }} + +jobs: + build: + name: Build images + timeout-minutes: 30 + runs-on: ubuntu-latest + environment: ${{ github.event_name == 'release' && 'prod' || 'dev' }} + outputs: + image_digest: ${{ steps.docker_build.outputs.digest }} + image_name: ${{ fromJSON(steps.docker_build.outputs.metadata)['image.name'] }} + permissions: + contents: "read" + id-token: "write" + env: + DOCKER_BUILD_SUMMARY: ${{ vars.DOCKER_BUILD_SUMMARY }} + steps: + + - name: Checkout ${{ inputs.repository }} + uses: actions/checkout@v4.2.2 + with: + repository: ${{ inputs.repository }} + ref: ${{ inputs.ref }} + persist-credentials: false + + - name: Checkout Z3 + uses: actions/checkout@v4.1.1 + with: + path: z3 + persist-credentials: false + + - name: Inject slug/short variables + uses: rlespinasse/github-slug-action@v5.1.0 + with: + short-length: 7 + + # Automatic tag management and OCI Image Format Specification for labels + - name: Docker meta + id: meta + uses: docker/metadata-action@v5.7.0 + with: + # list of Docker images to use as base name for tags + # We only publish images to DockerHub if a release is not a pre-release + # Ref: https://github.com/orgs/community/discussions/26281#discussioncomment-3251177 + images: | + ghcr.io/${{ env.GITHUB_REPOSITORY_OWNER_PART }}/${{ env.GITHUB_REPOSITORY_NAME_PART_SLUG }} + # generate Docker tags based on the following events/attributes + tags: | + # - `pr-xxx`: Tags images with the pull request number. + # - `branch-name`: Tags images with the branch name (e.g., `main`, `dev`). + # - `edge`: Tags the latest build on the default branch (e.g., `main`) + # - `schedule`: Tags images built during scheduled workflows (e.g., nightly or periodic builds) + type=ref,event=pr + type=ref,event=branch + type=edge,enable={{is_default_branch}} + type=schedule + # - `sha-xxxxxx`: Uses the commit SHA (shortened) to tag images for precise identification. + type=sha,event=pr + type=sha,event=branch + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3.4.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Setup Docker Buildx to use Docker Build Cloud + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v3.10.0 + + # Build and push image to GitHub Container Registry + - name: Build & push + id: docker_build + uses: docker/build-push-action@v6.15.0 + with: + target: ${{ inputs.dockerfile_target }} + context: . + file: ${{ inputs.dockerfile_path }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + SHORT_SHA=${{ env.GITHUB_SHA_SHORT }} + push: true + # It's recommended to build images with max-level provenance attestations + # https://docs.docker.com/build/ci/github-actions/attestations/ + provenance: mode=max + sbom: true + # Don't read from the cache if the caller disabled it. + # https://docs.docker.com/engine/reference/commandline/buildx_build/#options + no-cache: ${{ inputs.no_cache }} From 144369b807619d7abd1f1e14aeb63449d625dc40 Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Wed, 14 May 2025 09:22:53 +0100 Subject: [PATCH 18/57] fix(workflows): correct file extension in GitHub Actions workflow paths --- .github/workflows/build-z3-images.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-z3-images.yaml b/.github/workflows/build-z3-images.yaml index 1fa5a7f..4098991 100644 --- a/.github/workflows/build-z3-images.yaml +++ b/.github/workflows/build-z3-images.yaml @@ -10,11 +10,11 @@ on: branches: - dev paths: - - '.github/workflows/build-z3-images.yml' + - '.github/workflows/build-z3-images.yaml' pull_request: paths: - - '.github/workflows/build-z3-images.yml' + - '.github/workflows/build-z3-images.yaml' jobs: build: From 3f98fd0b52e55c670e292f12bd6cc1872058d891 Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Wed, 14 May 2025 10:06:58 +0100 Subject: [PATCH 19/57] chore(workflows): change to a forked zaino repo --- .github/workflows/build-z3-images.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-z3-images.yaml b/.github/workflows/build-z3-images.yaml index 4098991..33c6ad6 100644 --- a/.github/workflows/build-z3-images.yaml +++ b/.github/workflows/build-z3-images.yaml @@ -24,10 +24,10 @@ jobs: id-token: 'write' uses: ./.github/workflows/sub-build-docker-image.yaml with: - repository: zingolabs/zaino - ref: 'dev' + repository: gustavovalverde/zaino + ref: 'imp-dockerfile' dockerfile_path: ./Dockerfile - dockerfile_target: tests + dockerfile_target: runtime image_name: zaino no_cache: ${{ inputs.no_cache || false }} rust_backtrace: full From 062c6f4b4e54875cb3b1c7a19e0d1a92af88213c Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Wed, 14 May 2025 11:21:24 +0100 Subject: [PATCH 20/57] chore(workflows): fix permissions issue --- .github/workflows/build-z3-images.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/build-z3-images.yaml b/.github/workflows/build-z3-images.yaml index 33c6ad6..dbaa57d 100644 --- a/.github/workflows/build-z3-images.yaml +++ b/.github/workflows/build-z3-images.yaml @@ -6,6 +6,12 @@ concurrency: on: workflow_dispatch: + inputs: + no_cache: + description: "Disable Docker cache for this build" + required: false + type: boolean + default: false push: branches: - dev @@ -22,6 +28,7 @@ jobs: permissions: contents: 'read' id-token: 'write' + packages: 'write' uses: ./.github/workflows/sub-build-docker-image.yaml with: repository: gustavovalverde/zaino From c00ea16ad192d0937cb75a884a8e21a4ca2f207a Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Wed, 14 May 2025 12:53:15 +0100 Subject: [PATCH 21/57] chore(workflows): fix permissions and cache --- .github/workflows/sub-build-docker-image.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/sub-build-docker-image.yaml b/.github/workflows/sub-build-docker-image.yaml index 688ae7d..99b3fd2 100644 --- a/.github/workflows/sub-build-docker-image.yaml +++ b/.github/workflows/sub-build-docker-image.yaml @@ -68,6 +68,7 @@ jobs: permissions: contents: "read" id-token: "write" + packages: "write" env: DOCKER_BUILD_SUMMARY: ${{ vars.DOCKER_BUILD_SUMMARY }} steps: @@ -146,3 +147,5 @@ jobs: # Don't read from the cache if the caller disabled it. # https://docs.docker.com/engine/reference/commandline/buildx_build/#options no-cache: ${{ inputs.no_cache }} + cache-from: type=gha,scope=z3-${{ inputs.image_name }} + cache-to: type=gha,mode=max,scope=z3-${{ inputs.image_name }} From 7f5c7cf92fd69461cdc536ace912d090056ea0e3 Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Wed, 14 May 2025 13:24:16 +0100 Subject: [PATCH 22/57] test: try to build without a repo --- .github/workflows/sub-build-docker-image.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sub-build-docker-image.yaml b/.github/workflows/sub-build-docker-image.yaml index 99b3fd2..cb4483f 100644 --- a/.github/workflows/sub-build-docker-image.yaml +++ b/.github/workflows/sub-build-docker-image.yaml @@ -100,7 +100,7 @@ jobs: # We only publish images to DockerHub if a release is not a pre-release # Ref: https://github.com/orgs/community/discussions/26281#discussioncomment-3251177 images: | - ghcr.io/${{ env.GITHUB_REPOSITORY_OWNER_PART }}/${{ env.GITHUB_REPOSITORY_NAME_PART_SLUG }} + ghcr.io/${{ env.GITHUB_REPOSITORY_OWNER_PART }}/${{ inputs.image_name }} # generate Docker tags based on the following events/attributes tags: | # - `pr-xxx`: Tags images with the pull request number. From 572c210eae02d5ad231661513d4cfd8bce91aa00 Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Thu, 15 May 2025 12:11:58 +0100 Subject: [PATCH 23/57] feat(workflows): add zallet Docker build job * Renamed the existing build job to 'build-zaino' for clarity. * Introduced a new job 'build-zallet' to build the zallet Docker image with specified permissions and parameters. --- .github/workflows/build-z3-images.yaml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-z3-images.yaml b/.github/workflows/build-z3-images.yaml index dbaa57d..7012cc3 100644 --- a/.github/workflows/build-z3-images.yaml +++ b/.github/workflows/build-z3-images.yaml @@ -23,7 +23,7 @@ on: - '.github/workflows/build-z3-images.yaml' jobs: - build: + build-zaino: name: Build zaino Docker permissions: contents: 'read' @@ -40,3 +40,21 @@ jobs: rust_backtrace: full rust_lib_backtrace: full rust_log: info + + build-zallet: + name: Build zallet Docker + permissions: + contents: 'read' + id-token: 'write' + packages: 'write' + uses: ./.github/workflows/sub-build-docker-image.yaml + with: + repository: gustavovalverde/wallet + ref: 'feat-add-docker' + dockerfile_path: ./docker/Dockerfile + dockerfile_target: runtime + image_name: zallet + no_cache: ${{ inputs.no_cache || false }} + rust_backtrace: full + rust_lib_backtrace: full + rust_log: info From 23a023b489f0a529f410b67f96bc047fdac04bac Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Thu, 15 May 2025 18:00:36 +0100 Subject: [PATCH 24/57] fix(workflows): update Dockerfile path in build-z3-images workflow --- .github/workflows/build-z3-images.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-z3-images.yaml b/.github/workflows/build-z3-images.yaml index 7012cc3..8e7d4cd 100644 --- a/.github/workflows/build-z3-images.yaml +++ b/.github/workflows/build-z3-images.yaml @@ -33,7 +33,7 @@ jobs: with: repository: gustavovalverde/zaino ref: 'imp-dockerfile' - dockerfile_path: ./Dockerfile + dockerfile_path: ./docker/Dockerfile dockerfile_target: runtime image_name: zaino no_cache: ${{ inputs.no_cache || false }} From 2f72d8f6efd9b48f63813503b6915a1f815ab665 Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Thu, 15 May 2025 19:01:59 +0100 Subject: [PATCH 25/57] docs: rename `memory-bank` to `docs` --- {memory-bank => docs}/activeContext.md | 0 {memory-bank => docs}/data/ecosystem_map.md | 0 {memory-bank => docs}/data/rpc_mapping.md | 0 {memory-bank => docs}/data/zcashd_RPC_methods.md | 0 .../data/zcashd_deprecation_team_rpc_method_support_9apr2025.md | 0 .../data/zebra_adoption_tracking/analyze_nodes.sh | 0 {memory-bank => docs}/productContext.md | 0 {memory-bank => docs}/progress.md | 0 {memory-bank => docs}/projectbrief.md | 0 {memory-bank => docs}/systemPatterns.md | 0 {memory-bank => docs}/techContext.md | 0 {memory-bank => docs}/z3_component_interplay.md | 0 {memory-bank => docs}/z3_docker.md | 0 13 files changed, 0 insertions(+), 0 deletions(-) rename {memory-bank => docs}/activeContext.md (100%) rename {memory-bank => docs}/data/ecosystem_map.md (100%) rename {memory-bank => docs}/data/rpc_mapping.md (100%) rename {memory-bank => docs}/data/zcashd_RPC_methods.md (100%) rename {memory-bank => docs}/data/zcashd_deprecation_team_rpc_method_support_9apr2025.md (100%) rename {memory-bank => docs}/data/zebra_adoption_tracking/analyze_nodes.sh (100%) rename {memory-bank => docs}/productContext.md (100%) rename {memory-bank => docs}/progress.md (100%) rename {memory-bank => docs}/projectbrief.md (100%) rename {memory-bank => docs}/systemPatterns.md (100%) rename {memory-bank => docs}/techContext.md (100%) rename {memory-bank => docs}/z3_component_interplay.md (100%) rename {memory-bank => docs}/z3_docker.md (100%) diff --git a/memory-bank/activeContext.md b/docs/activeContext.md similarity index 100% rename from memory-bank/activeContext.md rename to docs/activeContext.md diff --git a/memory-bank/data/ecosystem_map.md b/docs/data/ecosystem_map.md similarity index 100% rename from memory-bank/data/ecosystem_map.md rename to docs/data/ecosystem_map.md diff --git a/memory-bank/data/rpc_mapping.md b/docs/data/rpc_mapping.md similarity index 100% rename from memory-bank/data/rpc_mapping.md rename to docs/data/rpc_mapping.md diff --git a/memory-bank/data/zcashd_RPC_methods.md b/docs/data/zcashd_RPC_methods.md similarity index 100% rename from memory-bank/data/zcashd_RPC_methods.md rename to docs/data/zcashd_RPC_methods.md diff --git a/memory-bank/data/zcashd_deprecation_team_rpc_method_support_9apr2025.md b/docs/data/zcashd_deprecation_team_rpc_method_support_9apr2025.md similarity index 100% rename from memory-bank/data/zcashd_deprecation_team_rpc_method_support_9apr2025.md rename to docs/data/zcashd_deprecation_team_rpc_method_support_9apr2025.md diff --git a/memory-bank/data/zebra_adoption_tracking/analyze_nodes.sh b/docs/data/zebra_adoption_tracking/analyze_nodes.sh similarity index 100% rename from memory-bank/data/zebra_adoption_tracking/analyze_nodes.sh rename to docs/data/zebra_adoption_tracking/analyze_nodes.sh diff --git a/memory-bank/productContext.md b/docs/productContext.md similarity index 100% rename from memory-bank/productContext.md rename to docs/productContext.md diff --git a/memory-bank/progress.md b/docs/progress.md similarity index 100% rename from memory-bank/progress.md rename to docs/progress.md diff --git a/memory-bank/projectbrief.md b/docs/projectbrief.md similarity index 100% rename from memory-bank/projectbrief.md rename to docs/projectbrief.md diff --git a/memory-bank/systemPatterns.md b/docs/systemPatterns.md similarity index 100% rename from memory-bank/systemPatterns.md rename to docs/systemPatterns.md diff --git a/memory-bank/techContext.md b/docs/techContext.md similarity index 100% rename from memory-bank/techContext.md rename to docs/techContext.md diff --git a/memory-bank/z3_component_interplay.md b/docs/z3_component_interplay.md similarity index 100% rename from memory-bank/z3_component_interplay.md rename to docs/z3_component_interplay.md diff --git a/memory-bank/z3_docker.md b/docs/z3_docker.md similarity index 100% rename from memory-bank/z3_docker.md rename to docs/z3_docker.md From 47a918aa581b43294d6da1eb097deb1ec30c01ca Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Thu, 15 May 2025 19:04:59 +0100 Subject: [PATCH 26/57] docs: move some docs to memory-bank --- docs/{ => memory-bank}/activeContext.md | 0 docs/{ => memory-bank}/productContext.md | 0 docs/{ => memory-bank}/progress.md | 0 docs/{ => memory-bank}/projectbrief.md | 0 docs/{ => memory-bank}/systemPatterns.md | 0 docs/{ => memory-bank}/techContext.md | 0 docs/{ => memory-bank}/z3_component_interplay.md | 0 docs/{ => memory-bank}/z3_docker.md | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename docs/{ => memory-bank}/activeContext.md (100%) rename docs/{ => memory-bank}/productContext.md (100%) rename docs/{ => memory-bank}/progress.md (100%) rename docs/{ => memory-bank}/projectbrief.md (100%) rename docs/{ => memory-bank}/systemPatterns.md (100%) rename docs/{ => memory-bank}/techContext.md (100%) rename docs/{ => memory-bank}/z3_component_interplay.md (100%) rename docs/{ => memory-bank}/z3_docker.md (100%) diff --git a/docs/activeContext.md b/docs/memory-bank/activeContext.md similarity index 100% rename from docs/activeContext.md rename to docs/memory-bank/activeContext.md diff --git a/docs/productContext.md b/docs/memory-bank/productContext.md similarity index 100% rename from docs/productContext.md rename to docs/memory-bank/productContext.md diff --git a/docs/progress.md b/docs/memory-bank/progress.md similarity index 100% rename from docs/progress.md rename to docs/memory-bank/progress.md diff --git a/docs/projectbrief.md b/docs/memory-bank/projectbrief.md similarity index 100% rename from docs/projectbrief.md rename to docs/memory-bank/projectbrief.md diff --git a/docs/systemPatterns.md b/docs/memory-bank/systemPatterns.md similarity index 100% rename from docs/systemPatterns.md rename to docs/memory-bank/systemPatterns.md diff --git a/docs/techContext.md b/docs/memory-bank/techContext.md similarity index 100% rename from docs/techContext.md rename to docs/memory-bank/techContext.md diff --git a/docs/z3_component_interplay.md b/docs/memory-bank/z3_component_interplay.md similarity index 100% rename from docs/z3_component_interplay.md rename to docs/memory-bank/z3_component_interplay.md diff --git a/docs/z3_docker.md b/docs/memory-bank/z3_docker.md similarity index 100% rename from docs/z3_docker.md rename to docs/memory-bank/z3_docker.md From db63c82e76dde731cf36d7bf3d5feedfa03ac44e Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Tue, 20 May 2025 10:20:16 +0100 Subject: [PATCH 27/57] feat(docker): establish comprehensive Z3 Docker Compose setup and guidelines Introduces a structured approach to Z3 service orchestration using Docker Compose. - Adds detailed README.md instructions for setting up and running the Z3 stack via Docker Compose, including .env configuration, certificate/identity file generation, and operational commands. - Implements .gitignore rules to correctly manage the 'config' directory, ensuring essential subdirectories (like 'config/tls') can be tracked via .gitkeep files while ignoring other contents, which is crucial for Docker volume mounts and config sources. - Includes a minor cosmetic update to the name of the GitHub Actions workflow for building Z3 images. This commit provides a foundational, well-documented Docker setup for deploying and managing the Zebra, Zaino, and Zallet services cohesively. --- .env | 70 +++++++++++ .github/workflows/build-z3-images.yaml | 2 +- .gitignore | 17 ++- Dockerfile.zebra | 6 + README.md | 154 +++++++++++++++++++++++-- config/.gitkeep | 0 config/tls/.gitkeep | 0 docker-compose.yml | 151 ++++++++++++++++++++++++ 8 files changed, 390 insertions(+), 10 deletions(-) create mode 100644 .env create mode 100644 Dockerfile.zebra create mode 100644 config/.gitkeep create mode 100644 config/tls/.gitkeep create mode 100644 docker-compose.yml diff --git a/.env b/.env new file mode 100644 index 0000000..182cd4a --- /dev/null +++ b/.env @@ -0,0 +1,70 @@ +# z3/.env + +# z3/docker-compose.yml Environment Variables + +# --- Common Configuration --- +# Network name for all services (e.g., Mainnet, Testnet, Regtest). Referenced by Zebra, Zaino, and Zallet. +NETWORK_NAME=Testnet +# Globally enables RPC cookie authentication. If true, Zebra generates a .cookie file. +# Zaino's use of this cookie (via ZAINO_VALIDATOR_COOKIE_AUTH_ENABLE in compose) is conditional on its ZAINO_VALIDATOR_LISTEN_ADDRESS. +ENABLE_COOKIE_AUTH=true +# In-container directory for the .cookie authentication file (e.g., /var/run/auth). +# Zebra writes its cookie here; Zaino reads from this location via its ZAINO_VALIDATOR_COOKIE_PATH in compose. +COOKIE_AUTH_FILE_DIR=/var/run/auth + +# --- Zebra Configuration --- +# Zebra Rust log level +ZEBRA_RUST_LOG=info +# Zebra's internal RPC port. Zaino connects to this port on the 'zebra' service hostname. +ZEBRA_RPC_PORT=18232 +# Zebra host RPC port (for external access to Zebra) +ZEBRA_HOST_RPC_PORT=18232 + +# --- Zaino Configuration --- +# Zaino Rust log level +ZAINO_RUST_LOG=trace,hyper=info +# Zaino's internal gRPC port. Zallet connects to this port on the 'zaino' service hostname. +ZAINO_GRPC_PORT=8137 +# Enable/disable Zaino's JSON-RPC service +ZAINO_JSON_RPC_ENABLE=false +# Zaino internal JSON-RPC port +ZAINO_JSON_RPC_PORT=8237 +# Zaino gRPC TLS (true/false) +ZAINO_GRPC_TLS_ENABLE=true # Set to true to enforce security policies +# Zaino host gRPC port (for external access to Zaino gRPC) +ZAINO_HOST_GRPC_PORT=8137 +# Zaino host JSON-RPC port (for external access to Zaino JSON-RPC) +ZAINO_HOST_JSONRPC_PORT=8237 +# In-container FHS-compliant paths for Zaino TLS configs +# These are the paths Zaino application expects for its cert and key +ZAINO_GRPC_TLS_CERT_PATH=/var/run/zaino/tls/zaino.crt +ZAINO_GRPC_TLS_KEY_PATH=/var/run/zaino/tls/zaino.key +# For Zaino healthcheck with TLS (adjust curl_opt as needed for your cert) +ZAINO_GRPC_TLS_ENABLE_SCHEME_SUFFIX=s +ZAINO_GRPC_TLS_ENABLE_CURL_OPT=-k # -k allows insecure for self-signed, use --cacert for prod +# Option to disable Zaino's local DB features (skips FinalisedState sync) +ZAINO_NO_DB=true +# Zaino validator username for Zebra RPC +ZAINO_VALIDATOR_USER=__cookie__ +# Zaino Rust backtrace setting +ZAINO_RUST_BACKTRACE=full +# Zaino application internal config path +ZAINO_CONF_PATH=/home/zaino/.config/zaino/zindexer.toml +# Zaino application internal data directory +ZAINO_DATA_DIR=/home/zaino/.cache/zaino + +# --- Zallet Configuration --- +# Zallet Rust log level +ZALLET_RUST_LOG=debug +# Zallet internal RPC port +ZALLET_RPC_PORT=28232 +# Zallet host RPC port (for external access to Zallet RPC) +ZALLET_HOST_RPC_PORT=28232 +# Zallet application internal config path +ZALLET_CONF_PATH=/etc/zallet/zallet.toml +# Zallet application internal data directory +ZALLET_DATA_DIR=/home/zallet/.data +# Example path for a CA certificate file that Zallet might use to trust Zaino's gRPC TLS certificate. +# If Zaino uses a self-signed certificate or a certificate from a private CA, Zallet would need to be +# configured to trust it. The actual environment variable name and mechanism depend on Zallet's implementation. +# ZALLET_INDEXER_CA_PATH=/path/to/trusted/zaino_ca.crt diff --git a/.github/workflows/build-z3-images.yaml b/.github/workflows/build-z3-images.yaml index 8e7d4cd..b8ebff5 100644 --- a/.github/workflows/build-z3-images.yaml +++ b/.github/workflows/build-z3-images.yaml @@ -1,4 +1,4 @@ -name: Build z3 images +name: Build Z3 images concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} diff --git a/.gitignore b/.gitignore index 0104787..7801950 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,19 @@ target/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ \ No newline at end of file +#.idea/ + +# Ignore all contents of the 'config' directory recursively +config/** + +# Un-ignore the 'tls' subdirectory itself, so we can look inside it +!config/tls/ + +# Then ignore everything inside 'tls' +config/tls/* + +# Then un-ignore the .gitkeep file within 'tls' +!config/tls/.gitkeep + +# Un-ignore .gitkeep directly under config +!config/.gitkeep diff --git a/Dockerfile.zebra b/Dockerfile.zebra new file mode 100644 index 0000000..374027a --- /dev/null +++ b/Dockerfile.zebra @@ -0,0 +1,6 @@ +FROM zfnd/zebra:latest + +# Install curl +RUN apt-get update && \ + apt-get install -y curl && \ + rm -rf /var/lib/apt/lists/* diff --git a/README.md b/README.md index d1adf04..eca220a 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,148 @@ -# z3 -Repository to iterate on the grand unification of Zebra, Zaino and Zallet to replace the venerable Zcashd +# Z3 - Unified Zcash Stack -For details, see: -- [Project Brief](memory-bank/projectbrief.md) for a high-level overview -- [Product Context](memory-bank/productContext.md) for detailed problem analysis and proposed solution -- [Technical Context](memory-bank/techContext.md) for architecture and dependencies -- [System Patterns](memory-bank/systemPatterns.md) for design patterns and conventions -- [Progress](memory-bank/progress.md) for current status and roadmap +This project orchestrates Zebra, Zaino, and Zallet to provide a modern, modular Zcash software stack, intended to replace the legacy `zcashd`. +## Prerequisites + +Before you begin, ensure you have the following installed: + +* **Docker Engine:** [Install Docker](https://docs.docker.com/engine/install/) +* **Docker Compose:** (Usually included with Docker Desktop, or [install separately](https://docs.docker.com/compose/install/)) +* **rage:** For generating the Zallet identity file. Install from [str4d/rage releases](https://github.com/str4d/rage/releases) or build from source. +* **Git:** For cloning the repositories and submodules. + +## Setup + +1. **Clone the Repository and Submodules:** + + If you haven't already, clone this `z3` repository and initialize its submodules (Zebra, Zaino, Wallet/Zallet, Zcashd). The Docker Compose setup currently relies on local builds for Zaino and Zallet if you were to build the images from scratch (though pre-built images are specified in the compose file for ease of use). + + ```bash + git clone + cd z3 + git submodule update --init --recursive + ``` + +2. **Configuration Directories:** + + After cloning the repository, you will find the following configuration directories, which are tracked by Git and will be populated with essential files in subsequent steps: + + * `config/`: This directory is intended to hold user-generated files that are essential for the Z3 stack's operation. Specifically, you will place: + * `zallet_identity.txt` (Zallet age identity file for encryption - _you will generate this in a later step_). + * `config/tls/`: This subdirectory is for TLS certificate files that you will generate: + * `zaino.crt` (Zaino's TLS certificate - _you will generate this_) + * `zaino.key` (Zaino's TLS private key - _you will generate this_) + +3. **Generate Zaino TLS Certificates:** + + Zaino requires a TLS certificate and private key for its gRPC interface. These files should be placed in the `config/tls/` directory. + + * `config/tls/zaino.crt`: The TLS certificate for Zaino. + * `config/tls/zaino.key`: The private key for Zaino's TLS certificate. + + You will need to generate these files using your preferred method (e.g., OpenSSL). For example, to generate a self-signed certificate: + + ```bash + openssl req -x509 -newkey rsa:4096 -keyout config/tls/zaino.key -out config/tls/zaino.crt -sha256 -days 365 -nodes -subj "/CN=localhost" -addext "subjectAltName = DNS:localhost,IP:127.0.0.1" + ``` + + **Note:** For production or more secure setups, use certificates issued by a trusted Certificate Authority (CA). The example above creates a self-signed certificate valid for 365 days and includes `localhost` and `127.0.0.1` as Subject Alternative Names (SANs), which is important for client validation. + +4. **Generate Zallet Identity File:** + + Zallet requires an `age` identity file for wallet encryption. Generate this file using `rage-keygen`: + + ```bash + rage-keygen -o config/zallet_identity.txt + ``` + + This will create `config/zallet_identity.txt`. **Securely back up this file and its corresponding public key.** The public key will be printed to your terminal during generation. + +5. **Understanding Service Configuration:** + + The services within the Z3 stack (Zebra, Zaino, Zallet) come with their own internal default configurations. For the Z3 Docker Compose setup, **all user-driven customization of service operational parameters (such as network settings, ports, log levels, and feature flags) is exclusively managed through environment variables.** These variables are defined in the `z3/.env` file (which you will create in the next step) and are then passed to the services by Docker Compose. + + You do not need to create or modify separate `.toml` configuration files for Zebra, Zaino, or Zallet in the `z3/config/` directory to control their runtime behavior in this setup; the environment variables are the sole interface for these kinds of adjustments. + +6. **Create `.env` File for Docker Compose:** + + The `docker-compose.yml` file is configured to load environment variables from a `.env` file located in the `z3/` directory. This file is essential for customizing network settings, ports, log levels, and feature flags without modifying the `docker-compose.yml` directly. + + Create a `z3/.env` file. You can use the example content below as a starting point, adapting it to your needs. Refer to the comments within the example `z3/.env` or the `docker-compose.yml` for variable details. + A comprehensive example `z3/.env` can be found alongside `docker-compose.yml`. Key variables include: + + ```env + # z3/.env Example Snippet + NETWORK_NAME=Testnet + ENABLE_COOKIE_AUTH=true + COOKIE_AUTH_FILE_DIR=/var/run/auth + + ZEBRA_RUST_LOG=info + ZEBRA_RPC_PORT=18232 + ZEBRA_HOST_RPC_PORT=18232 + + ZAINO_RUST_LOG=trace,hyper=info + ZAINO_GRPC_PORT=8137 + ZAINO_JSON_RPC_ENABLE=false + ZAINO_GRPC_TLS_ENABLE=true + ZAINO_HOST_GRPC_PORT=8137 + ZAINO_GRPC_TLS_CERT_PATH=/var/run/zaino/tls/zaino.crt + ZAINO_GRPC_TLS_KEY_PATH=/var/run/zaino/tls/zaino.key + + ZALLET_RUST_LOG=debug + ZALLET_HOST_RPC_PORT=28232 + ``` + +## Running the Stack + +Once the setup is complete, you can start all services using Docker Compose: + +```bash +cd z3 # Ensure you are in the z3 directory +docker-compose up --build +``` +* `--build`: This flag tells Docker Compose to build the images if they don't exist or if their Dockerfiles have changed. The current `docker-compose.yml` uses pre-built images for `zaino` and `zallet` specified by their SHA, and `zfnd/zebra:latest` for zebra, so `--build` might primarily affect local Dockerfile changes if you were to modify them or switch to local builds. +* To run in detached mode (in the background), add the `-d` flag: `docker-compose up -d --build`. + +## Stopping the Stack + +To stop the services and remove the containers, run: + +```bash +docker-compose down +``` + +If you also want to remove the data volumes (blockchain data, indexer database, wallet database), use: + +```bash +docker-compose down -v +``` + +## Configuration Details + +Understanding how configuration is applied is key to customizing the Z3 stack: + +* **Internal Service Defaults:** Each service (Zebra, Zaino, Zallet) has its own built-in default configuration values. These internal defaults are used unless influenced by environment variables. For this Z3 Docker Compose deployment, you do not directly interact with or provide TOML configuration files for the services in the `z3/config/` directory to alter these defaults for general operational parameters. + +* **Environment Variables (`z3/.env`):** This is the **exclusive method for customizing the operational parameters of the Zebra, Zaino, and Zallet services within the Z3 stack.** Variables defined in the `z3/.env` file are passed into their respective containers by Docker Compose. The services are designed to read these environment variables at startup to configure their behavior (e.g., log levels, network ports, feature enablement). This approach provides a centralized and clear way to manage your deployment settings. + +* **Explicitly Mounted Files & Docker Configs:** Note that specific files *are* sourced from your `z3/config/` directory for distinct purposes, such as `zallet_identity.txt` (volume mounted for Zallet) and the TLS certificates in `z3/config/tls/` (used via Docker `configs` for Zaino). These are for providing essential data or credentials, separate from the environment variable-based parameter tuning. + +* **Docker Compose Overrides (`docker-compose.yml`):** The `environment` section within each service definition in `docker-compose.yml` is used for several purposes: + * **Passing `.env` Variables:** It explicitly lists which variables from `.env` (or your shell environment) are passed into the container (e.g., `RUST_LOG=${ZALLET_RUST_LOG}`). + * **Service Discovery & Internal Settings:** It sets variables crucial for inter-service communication (e.g., `ZAINO_VALIDATOR_LISTEN_ADDRESS=zebra:${ZEBRA_RPC_PORT}`) or paths internal to the container (e.g., `ZAINO_VALIDATOR_COOKIE_PATH=${COOKIE_AUTH_FILE_DIR}/.cookie`). + * **Derived or Conditional Values:** Some environment variables might be constructed from others (e.g., combining `COOKIE_AUTH_FILE_DIR` with a filename) or set based on conditions (e.g., `ZAINO_VALIDATOR_COOKIE_AUTH_ENABLE=${ENABLE_COOKIE_AUTH}`). + These `docker-compose.yml` environment settings generally take precedence if there's an overlap, as they are the final values passed when the container starts. + +* **Entrypoint Scripts:** Each service's Docker image has an entrypoint script (`entrypoint.sh`). These scripts often perform final configuration steps, such as generating configuration files from templates based on environment variables, or applying conditional logic before starting the main application process. + +## Interacting with Services + +Once the stack is running, services can be accessed via the ports exposed in `docker-compose.yml`: + +* **Zebra RPC:** `http://localhost:${ZEBRA_HOST_RPC_PORT:-18232}` (default: Testnet `http://localhost:18232`) +* **Zaino gRPC:** `localhost:${ZAINO_HOST_GRPC_PORT:-8137}` (default: `localhost:8137`) +* **Zaino JSON-RPC:** `http://localhost:${ZAINO_HOST_JSONRPC_PORT:-8237}` (default: `http://localhost:8237`, if enabled) +* **Zallet RPC:** `http://localhost:${ZALLET_HOST_RPC_PORT:-28232}` (default: `http://localhost:28232`) + +Refer to the individual component documentation for RPC API details. \ No newline at end of file diff --git a/config/.gitkeep b/config/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/config/tls/.gitkeep b/config/tls/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..bfb977a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,151 @@ +services: + zebra: + # TODO: Use zfnd/zebra:latest when zebra has curl installed + image: zfnd/zebra:latest # Using latest, specify a version tag if preferred + build: + context: . + dockerfile: Dockerfile.zebra + container_name: z3_zebra + restart: unless-stopped + platform: linux/amd64 # Uncomment if you need to specify platform + env_file: + - ./.env + environment: + - RUST_LOG=${ZEBRA_RUST_LOG} + - NETWORK=${NETWORK_NAME} # Zebra's network, derived from global NETWORK_NAME + - ZEBRA_COOKIE_DIR=${COOKIE_AUTH_FILE_DIR} # Zebra entrypoint uses this directory to store its .cookie + # - METRICS_ENDPOINT_ADDR=0.0.0.0:9999 + volumes: + - zebra_data:/home/zebra/.cache/zebra + - shared_cookie_volume:${COOKIE_AUTH_FILE_DIR} # Zebra writes the cookie here + ports: + # Expose to host if direct access is needed + - "${ZEBRA_HOST_RPC_PORT}:${ZEBRA_RPC_PORT}" # Default Testnet RPC + networks: + - z3_net + # TODO: Make this healcheck simpler when we have a way to supply the cookie to Zebra + # This also needs to consider we're extracting the password from the cookie file + # using `cut -d ':' -f 2-`, because the file is formatted as "__cookie__:" + healthcheck: + test: | + echo "Healthcheck script started. ENABLE_COOKIE_AUTH=${ENABLE_COOKIE_AUTH:-false}" + if [ "${ENABLE_COOKIE_AUTH:-false}" = "true" ]; then + echo "Cookie auth is ON." + echo "Attempting to use cookie file at: [${COOKIE_AUTH_FILE_DIR}/.cookie]" + if [ -f "${COOKIE_AUTH_FILE_DIR}/.cookie" ] && [ -s "${COOKIE_AUTH_FILE_DIR}/.cookie" ]; then + echo "Cookie file [${COOKIE_AUTH_FILE_DIR}/.cookie] found and not empty. Attempting curl with cookie." + curl -v --user "__cookie__:$(cat ${COOKIE_AUTH_FILE_DIR}/.cookie | cut -d ':' -f 2-)" --data-binary '{"jsonrpc": "2.0", "id":"healthcheck", "method": "getinfo", "params": [] }' -H 'Content-Type: application/json' http://127.0.0.1:${ZEBRA_RPC_PORT}/ || exit 1 + else + echo "Cookie file [${COOKIE_AUTH_FILE_DIR}/.cookie] not found or empty. Exiting healthcheck with 1." + exit 1 + fi + else + echo "Cookie auth is OFF. Attempting curl without cookie." + curl -s -f --user "" --data-binary '{"jsonrpc": "2.0", "id":"healthcheck", "method": "getinfo", "params": [] }' -H 'Content-Type: application/json' http://127.0.0.1:${ZEBRA_RPC_PORT}/ | grep -q '"result":' || exit 1 + fi + interval: 30s + timeout: 30s + retries: 5 + start_period: 30s + + zaino: + image: docker pull ghcr.io/zcashfoundation/zaino:sha-54e0da6@sha256:5cf6db69280261e836d98e99b47e2601e825935c9f56134e7380d3a10adf7899 + #! You can build the image yourself if you want to use a custom version + # build: + # context: ../zaino + # dockerfile: docker/Dockerfile + container_name: z3_zaino + restart: unless-stopped + # platform: linux/amd64 + depends_on: + zebra: + condition: service_healthy + env_file: + - ./.env + environment: + - RUST_LOG=${ZAINO_RUST_LOG} + - RUST_BACKTRACE=${ZAINO_RUST_BACKTRACE} + - ZAINO_NETWORK=${NETWORK_NAME} + # Connect to Zebra service using its name and port, cookie auth will be used if enabled globally. + - ZAINO_VALIDATOR_LISTEN_ADDRESS=zebra:${ZEBRA_RPC_PORT} + # This assignment enables cookie auth for Zaino if ENABLE_COOKIE_AUTH is true + # AND its ZAINO_VALIDATOR_LISTEN_ADDRESS is non-loopback (which 'zebra:${ZEBRA_RPC_PORT}' is). + - ZAINO_VALIDATOR_COOKIE_AUTH_ENABLE=${ENABLE_COOKIE_AUTH} + - ZAINO_VALIDATOR_COOKIE_PATH=${COOKIE_AUTH_FILE_DIR}/.cookie # Zaino reads the cookie from this specific path + # Zaino's own RPC services + - ZAINO_GRPC_LISTEN_ADDRESS=0.0.0.0:${ZAINO_GRPC_PORT} + - ZAINO_JSON_RPC_LISTEN_ADDRESS=0.0.0.0:${ZAINO_JSON_RPC_PORT} + volumes: + - zaino_data:/home/zaino/.cache/zaino + - shared_cookie_volume:${COOKIE_AUTH_FILE_DIR}:ro # Zaino reads the cookie read-only + configs: + - source: zaino_tls_cert # Use the top-level config for Zaino's cert + target: ${ZAINO_GRPC_TLS_CERT_PATH} # Mount path inside container, FHS compliant via env var + - source: zaino_tls_key # Use the top-level config for Zaino's key + target: ${ZAINO_GRPC_TLS_KEY_PATH} # Mount path inside container, FHS compliant via env var + ports: + - "${ZAINO_HOST_GRPC_PORT}:${ZAINO_GRPC_PORT}" + - "${ZAINO_HOST_JSONRPC_PORT}:${ZAINO_JSON_RPC_PORT}" + networks: + - z3_net + healthcheck: + # Healthcheck needs to account for TLS if ZAINO_GRPC_TLS_ENABLE is true + # Use env vars for scheme and curl options, to be defined in .env + test: ["CMD-SHELL", "curl -f -k https://127.0.0.1:${ZAINO_GRPC_PORT} || exit 1"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 60s + + zallet: + image: ghcr.io/zcashfoundation/zallet:sha-54e0da6@sha256:3710fb41dbad0f0457551b76a81c97e476f4addc9605498a0d0e34fbe4b8d4e9 + #! You can build the image yourself if you want to use a custom version + # build: + # context: ../wallet + # dockerfile: docker/Dockerfile + container_name: z3_zallet + restart: unless-stopped + # platform: linux/amd64 + depends_on: + zaino: + condition: service_healthy + env_file: + - ./.env + environment: + - RUST_LOG=${ZALLET_RUST_LOG} + - ZALLET_NETWORK=${NETWORK_NAME} + # Zallet indexer points to Zaino's gRPC service (hosted at 'zaino:${ZAINO_GRPC_PORT}'). + # TLS for this connection needs to be handled by Zallet (e.g., trusting Zaino's cert). + # Refer to .env for potential CA path configuration if needed. + - ZALLET_INDEXER_VALIDATOR_ADDRESS=zaino:${ZAINO_GRPC_PORT} + - ZALLET_RPC_BIND=0.0.0.0:${ZALLET_RPC_PORT} + volumes: + - zallet_data:/home/zallet/.data + - ./config/zallet_identity.txt:/home/zallet/.data/identity.txt:ro + # - ./config/tls/zaino.crt:/tls_trusted/zaino.crt:ro # Example mount for Zallet to trust Zaino + ports: + - "${ZALLET_HOST_RPC_PORT}:${ZALLET_RPC_PORT}" + networks: + - z3_net + healthcheck: + test: ["CMD-SHELL", "curl -s -f --data-binary '{\"jsonrpc\": \"2.0\", \"id\":\"healthcheck\", \"method\": \"help\", \"params\": [] }' -H 'Content-Type: application/json' http://127.0.0.1:${ZALLET_RPC_PORT}/ || exit 1"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 60s + +volumes: + zebra_data: + zaino_data: + zallet_data: + shared_cookie_volume: {} # Define the shared volume for the cookie + +networks: + z3_net: + driver: bridge + +configs: + zaino_tls_cert: + file: ./config/tls/zaino.crt # Path on the host for Zaino's TLS cert + zaino_tls_key: + file: ./config/tls/zaino.key # Path on the host for Zaino's TLS key From d7f1d97fc731256cb0e87cc0393e99ca6146dac8 Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Thu, 9 Oct 2025 22:28:08 +0100 Subject: [PATCH 28/57] refactor: modernize Z3 stack with upstream repos and Zebra health checks (#4) * refactor: modernize Z3 stack with upstream repos and Zebra health checks Update the Z3 stack to use official upstream repositories and their corresponding Dockerfiles instead of building from personal forks with specific branches. This refactor also implements proper Zebra configuration using its new environment variable system and health check endpoints. Major changes: 1. Use official upstream repositories with default branches - Zebra: ZcashFoundation/zebra (main branch) - Zaino: zingolabs/zaino (dev branch) - Zallet: zcash/wallet (main branch) - Updated Dockerfile paths to match each project's structure 2. Implement Zebra's environment variable configuration system - Use config-rs format: ZEBRA_SECTION__KEY for Zebra configuration - Implement Z3_* prefix for Docker Compose infrastructure variables - Three-tier variable hierarchy to prevent config collisions: * Z3_* for infrastructure (volumes, ports, Docker Compose only) * Shared variables (NETWORK_NAME, etc.) remapped per service * Service-specific (ZEBRA_*, ZAINO_*, ZALLET_*) for app config 3. Leverage Zebra's new health check endpoints - Use /ready endpoint for production (synced near network tip) - Use /healthy endpoint for development (has peer connections) - Implement two-phase deployment pattern for blockchain sync - Configure healthcheck with 90s start_period for cached state 4. Improve security and documentation - Add fix-permissions.sh for proper UID/GID ownership setup - Document specific UIDs/GIDs for each service - Add warnings about unstable development branches - Update deployment documentation for fresh sync scenarios 5. Remove hardcoded paths and configurations - Eliminate user-specific paths from docker-compose.yml - Use environment variables for all customizable settings - Add docker-compose.override.yml.example for development mode * fix(compose): workaround through zallet limitations * fix(zallet): Use Zebra as the indexer, and update docs * chore: reduce logging * docs: update README.md --------- Co-authored-by: Gustavo Valverde --- .env | 115 +++++- .github/workflows/build-z3-images.yaml | 30 +- .gitignore | 1 + Dockerfile.zebra | 6 - README.md | 481 +++++++++++++++++++++---- config/zallet.toml | 52 +++ docker-compose.override.yml.example | 29 ++ docker-compose.yml | 153 ++++---- fix-permissions.sh | 138 +++++++ zaino | 2 +- zallet | 2 +- zebra | 2 +- 12 files changed, 829 insertions(+), 182 deletions(-) delete mode 100644 Dockerfile.zebra create mode 100644 config/zallet.toml create mode 100644 docker-compose.override.yml.example create mode 100755 fix-permissions.sh diff --git a/.env b/.env index 182cd4a..a550d52 100644 --- a/.env +++ b/.env @@ -2,27 +2,104 @@ # z3/docker-compose.yml Environment Variables -# --- Common Configuration --- -# Network name for all services (e.g., Mainnet, Testnet, Regtest). Referenced by Zebra, Zaino, and Zallet. -NETWORK_NAME=Testnet -# Globally enables RPC cookie authentication. If true, Zebra generates a .cookie file. -# Zaino's use of this cookie (via ZAINO_VALIDATOR_COOKIE_AUTH_ENABLE in compose) is conditional on its ZAINO_VALIDATOR_LISTEN_ADDRESS. +# ============================================================================= +# VARIABLE HIERARCHY QUICK REFERENCE +# ============================================================================= +# For detailed explanation, see README.md "Understanding the Variable Hierarchy" +# +# Three-tier system to avoid collisions: +# Z3_* - Infrastructure (volumes, ports) - Docker Compose only +# (no prefix) - Shared config (remapped per service) +# ZEBRA_* - Zebra app config (ZEBRA_SECTION__KEY format) +# ZAINO_* - Zaino app config +# ZALLET_* - Zallet app config +# +# ============================================================================= +# Z3 Stack Infrastructure Configuration +# ============================================================================= +# These Z3_* variables control Docker Compose volume mounts and are NEVER +# passed to containers. They avoid collision with service configs (ZEBRA_*, +# ZAINO_*, ZALLET_*). +# +# Default: Docker named volumes (recommended - managed by Docker, no permission issues) +# Advanced: Local directories (see README.md "Advanced: Local Directories" section) +# +# To use local directories: +# 1. Choose appropriate paths for your OS (see README for suggestions) +# 2. Create directories: mkdir -p /your/chosen/path +# 3. Fix permissions: ./fix-permissions.sh /your/chosen/path +# 4. Update variables below with your paths +# +# Security Requirements: +# - Zebra: UID=10001, GID=10001, permissions=700 +# - Zaino: UID=1000, GID=1000, permissions=700 +# - Zallet: UID=65532, GID=65532, permissions=700 +# - Cookie: Keep as Docker volume (recommended) to avoid cross-user issues +# +# WARNING: Never use 755 or 777 permissions - they expose your data! + +# Zebra blockchain state directory +# Default: zebra_data (Docker named volume) +Z3_ZEBRA_DATA_PATH=zebra_data + +# Shared cookie authentication directory (used by Zebra and Zaino) +# Default: shared_cookie_volume (Docker named volume) +# NOTE: For local directories, Zebra (10001) writes and Zaino (1000) reads. +# Consider using Docker volume (default) to avoid cross-user permission issues. +# If using local dir, you'll need to set up ACLs or a shared group. +Z3_COOKIE_PATH=shared_cookie_volume + +# Zaino indexer data directory +# Default: zaino_data (Docker named volume) +Z3_ZAINO_DATA_PATH=zaino_data + +# Zallet wallet data directory +# Default: zallet_data (Docker named volume) +Z3_ZALLET_DATA_PATH=zallet_data + +# ============================================================================= +# Common Configuration +# ============================================================================= +# Shared variables used by multiple services, mapped in docker-compose.yml: +# NETWORK_NAME → ZEBRA_NETWORK__NETWORK, ZAINO_NETWORK, ZALLET_NETWORK +# ENABLE_COOKIE_AUTH → ZEBRA_RPC__ENABLE_COOKIE_AUTH, ZAINO_VALIDATOR_COOKIE_AUTH +# COOKIE_AUTH_FILE_DIR → ZEBRA_RPC__COOKIE_DIR, ZAINO_VALIDATOR_COOKIE_PATH + +# Network name for all services (e.g., Mainnet, Testnet, Regtest) +NETWORK_NAME=Mainnet +# Globally enables RPC cookie authentication ENABLE_COOKIE_AUTH=true -# In-container directory for the .cookie authentication file (e.g., /var/run/auth). -# Zebra writes its cookie here; Zaino reads from this location via its ZAINO_VALIDATOR_COOKIE_PATH in compose. +# In-container directory for the .cookie authentication file COOKIE_AUTH_FILE_DIR=/var/run/auth -# --- Zebra Configuration --- -# Zebra Rust log level -ZEBRA_RUST_LOG=info -# Zebra's internal RPC port. Zaino connects to this port on the 'zebra' service hostname. -ZEBRA_RPC_PORT=18232 -# Zebra host RPC port (for external access to Zebra) -ZEBRA_HOST_RPC_PORT=18232 +# ============================================================================= +# Zebra Configuration +# ============================================================================= +# Zebra logging (will be mapped to RUST_LOG in container) +Z3_ZEBRA_RUST_LOG=info +# Zebra tracing filter (config-rs format: ZEBRA_TRACING__FILTER) +ZEBRA_TRACING__FILTER=info +# Zebra RPC listen address (config-rs format: ZEBRA_RPC__LISTEN_ADDR) +ZEBRA_RPC__LISTEN_ADDR=0.0.0.0:18232 +# Zebra state cache directory (config-rs format: ZEBRA_STATE__CACHE_DIR) +ZEBRA_STATE__CACHE_DIR=/home/zebra/.cache/zebra +# Zebra health endpoint configuration +ZEBRA_HEALTH__LISTEN_ADDR=0.0.0.0:8080 +ZEBRA_HEALTH__MIN_CONNECTED_PEERS=1 +ZEBRA_HEALTH__READY_MAX_BLOCKS_BEHIND=2 +ZEBRA_HEALTH__ENFORCE_ON_TEST_NETWORKS=false +# Infrastructure: Zebra RPC port (used in Docker Compose port mappings and service discovery) +Z3_ZEBRA_RPC_PORT=18232 +# Infrastructure: Zebra host RPC port (for external access to Zebra) +Z3_ZEBRA_HOST_RPC_PORT=18232 +# Infrastructure: Zebra host health port (for external access to health endpoints) +Z3_ZEBRA_HOST_HEALTH_PORT=8080 -# --- Zaino Configuration --- +# ============================================================================= +# Zaino Configuration +# ============================================================================= # Zaino Rust log level -ZAINO_RUST_LOG=trace,hyper=info +ZAINO_RUST_LOG=info,reqwest=warn,hyper_util=warn # Zaino's internal gRPC port. Zallet connects to this port on the 'zaino' service hostname. ZAINO_GRPC_PORT=8137 # Enable/disable Zaino's JSON-RPC service @@ -53,9 +130,11 @@ ZAINO_CONF_PATH=/home/zaino/.config/zaino/zindexer.toml # Zaino application internal data directory ZAINO_DATA_DIR=/home/zaino/.cache/zaino -# --- Zallet Configuration --- +# ============================================================================= +# Zallet Configuration +# ============================================================================= # Zallet Rust log level -ZALLET_RUST_LOG=debug +ZALLET_RUST_LOG=info,hyper_util=warn,reqwest=warn # Zallet internal RPC port ZALLET_RPC_PORT=28232 # Zallet host RPC port (for external access to Zallet RPC) diff --git a/.github/workflows/build-z3-images.yaml b/.github/workflows/build-z3-images.yaml index b8ebff5..07ce902 100644 --- a/.github/workflows/build-z3-images.yaml +++ b/.github/workflows/build-z3-images.yaml @@ -23,6 +23,24 @@ on: - '.github/workflows/build-z3-images.yaml' jobs: + build-zebra: + name: Build zebra Docker + permissions: + contents: 'read' + id-token: 'write' + packages: 'write' + uses: ./.github/workflows/sub-build-docker-image.yaml + with: + repository: ZcashFoundation/zebra + ref: 'main' + dockerfile_path: ./docker/Dockerfile + dockerfile_target: runtime + image_name: zebra + no_cache: ${{ inputs.no_cache || false }} + rust_backtrace: full + rust_lib_backtrace: full + rust_log: info + build-zaino: name: Build zaino Docker permissions: @@ -31,9 +49,9 @@ jobs: packages: 'write' uses: ./.github/workflows/sub-build-docker-image.yaml with: - repository: gustavovalverde/zaino - ref: 'imp-dockerfile' - dockerfile_path: ./docker/Dockerfile + repository: zingolabs/zaino + ref: 'dev' + dockerfile_path: ./Dockerfile dockerfile_target: runtime image_name: zaino no_cache: ${{ inputs.no_cache || false }} @@ -49,9 +67,9 @@ jobs: packages: 'write' uses: ./.github/workflows/sub-build-docker-image.yaml with: - repository: gustavovalverde/wallet - ref: 'feat-add-docker' - dockerfile_path: ./docker/Dockerfile + repository: zcash/wallet + ref: 'main' + dockerfile_path: ./Dockerfile dockerfile_target: runtime image_name: zallet no_cache: ${{ inputs.no_cache || false }} diff --git a/.gitignore b/.gitignore index 7801950..bd498cf 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ target/ # Ignore all contents of the 'config' directory recursively config/** +!config/zallet.toml # Un-ignore the 'tls' subdirectory itself, so we can look inside it !config/tls/ diff --git a/Dockerfile.zebra b/Dockerfile.zebra deleted file mode 100644 index 374027a..0000000 --- a/Dockerfile.zebra +++ /dev/null @@ -1,6 +0,0 @@ -FROM zfnd/zebra:latest - -# Install curl -RUN apt-get update && \ - apt-get install -y curl && \ - rm -rf /var/lib/apt/lists/* diff --git a/README.md b/README.md index eca220a..a7214bd 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,51 @@ This project orchestrates Zebra, Zaino, and Zallet to provide a modern, modular Zcash software stack, intended to replace the legacy `zcashd`. +## Quick Start (TLDR) + +For experienced users who know Docker and blockchain nodes: + +```bash +# 1. Clone and setup +git clone https://github.com/ZcashFoundation/z3 && cd z3 +git submodule update --init --recursive +openssl req -x509 -newkey rsa:4096 -keyout config/tls/zaino.key -out config/tls/zaino.crt -sha256 -days 365 -nodes -subj "/CN=localhost" +rage-keygen -o config/zallet_identity.txt + +# 2. Review config/zallet.toml (update network: "main" or "test") +# 3. Review .env file (already configured with defaults) + +# 4. Start Zebra and wait for sync (24-72h for mainnet, 2-12h for testnet) +docker compose up -d zebra +# Wait until: curl http://localhost:8080/ready returns "ok" + +# 5. Start full stack +docker compose up -d +``` + +**First time?** Read the full [Setup](#setup) and [Running the Stack](#running-the-stack) sections below. + +## ⚠️ Important: Docker Images Notice + +**This repository builds and hosts Docker images for testing purposes only.** + +- **Images use unstable development branches**: + - Zebra: `main` branch (latest development) + - Zaino: `dev` branch (unstable features) + - Zallet: `main` branch (under active development) + +- **Purpose**: Enable rapid testing and iteration of the Z3 stack +- **Not suitable for production use**: These images may contain bugs, breaking changes, or experimental features + +**For production deployments:** +- Use official release images from respective projects: + - Zebra: [zfnd/zebra](https://hub.docker.com/r/zfnd/zebra) (stable releases) + - Zaino: Official releases when available + - Zallet: Official releases when available +- Or build from stable release tags yourself + +If you're testing or developing, the pre-built images from this repository provide a convenient way to quickly spin up the full Z3 stack. + ## Prerequisites Before you begin, ensure you have the following installed: @@ -15,132 +60,432 @@ Before you begin, ensure you have the following installed: 1. **Clone the Repository and Submodules:** - If you haven't already, clone this `z3` repository and initialize its submodules (Zebra, Zaino, Wallet/Zallet, Zcashd). The Docker Compose setup currently relies on local builds for Zaino and Zallet if you were to build the images from scratch (though pre-built images are specified in the compose file for ease of use). + Clone the `z3` repository and initialize its submodules (Zebra, Zaino, Zallet): ```bash - git clone + git clone https://github.com/ZcashFoundation/z3 cd z3 git submodule update --init --recursive ``` -2. **Configuration Directories:** + The Docker Compose setup builds all images locally from submodules by default. - After cloning the repository, you will find the following configuration directories, which are tracked by Git and will be populated with essential files in subsequent steps: +2. **Required Files:** - * `config/`: This directory is intended to hold user-generated files that are essential for the Z3 stack's operation. Specifically, you will place: - * `zallet_identity.txt` (Zallet age identity file for encryption - _you will generate this in a later step_). - * `config/tls/`: This subdirectory is for TLS certificate files that you will generate: - * `zaino.crt` (Zaino's TLS certificate - _you will generate this_) - * `zaino.key` (Zaino's TLS private key - _you will generate this_) + You'll need to generate these files in the `config/` directory: + - `config/tls/zaino.crt` and `config/tls/zaino.key` - Zaino TLS certificates + - `config/zallet_identity.txt` - Zallet encryption key + - `config/zallet.toml` - Zallet configuration (provided, review and customize) 3. **Generate Zaino TLS Certificates:** - Zaino requires a TLS certificate and private key for its gRPC interface. These files should be placed in the `config/tls/` directory. - - * `config/tls/zaino.crt`: The TLS certificate for Zaino. - * `config/tls/zaino.key`: The private key for Zaino's TLS certificate. - - You will need to generate these files using your preferred method (e.g., OpenSSL). For example, to generate a self-signed certificate: - ```bash openssl req -x509 -newkey rsa:4096 -keyout config/tls/zaino.key -out config/tls/zaino.crt -sha256 -days 365 -nodes -subj "/CN=localhost" -addext "subjectAltName = DNS:localhost,IP:127.0.0.1" ``` - **Note:** For production or more secure setups, use certificates issued by a trusted Certificate Authority (CA). The example above creates a self-signed certificate valid for 365 days and includes `localhost` and `127.0.0.1` as Subject Alternative Names (SANs), which is important for client validation. + This creates a self-signed certificate valid for 365 days. For production, use certificates from a trusted CA. 4. **Generate Zallet Identity File:** - Zallet requires an `age` identity file for wallet encryption. Generate this file using `rage-keygen`: - ```bash rage-keygen -o config/zallet_identity.txt ``` - This will create `config/zallet_identity.txt`. **Securely back up this file and its corresponding public key.** The public key will be printed to your terminal during generation. + **Securely back up this file and the public key** (printed to terminal). -5. **Understanding Service Configuration:** +5. **Review Zallet Configuration:** - The services within the Z3 stack (Zebra, Zaino, Zallet) come with their own internal default configurations. For the Z3 Docker Compose setup, **all user-driven customization of service operational parameters (such as network settings, ports, log levels, and feature flags) is exclusively managed through environment variables.** These variables are defined in the `z3/.env` file (which you will create in the next step) and are then passed to the services by Docker Compose. + Review `config/zallet.toml` and update the network setting: + - For mainnet: `network = "main"` in `[consensus]` section + - For testnet: `network = "test"` in `[consensus]` section - You do not need to create or modify separate `.toml` configuration files for Zebra, Zaino, or Zallet in the `z3/config/` directory to control their runtime behavior in this setup; the environment variables are the sole interface for these kinds of adjustments. + See [Configuration Guide](#configuration-guide) for details on Zallet's architecture and config requirements. -6. **Create `.env` File for Docker Compose:** +6. **Review Environment Variables:** - The `docker-compose.yml` file is configured to load environment variables from a `.env` file located in the `z3/` directory. This file is essential for customizing network settings, ports, log levels, and feature flags without modifying the `docker-compose.yml` directly. + A comprehensive `.env` file is provided with sensible defaults. Review and customize as needed: + - `NETWORK_NAME` - Set to `Mainnet` or `Testnet` + - Log levels for each service (defaults to `info` with warning filters) + - Port mappings (defaults work for most setups) - Create a `z3/.env` file. You can use the example content below as a starting point, adapting it to your needs. Refer to the comments within the example `z3/.env` or the `docker-compose.yml` for variable details. - A comprehensive example `z3/.env` can be found alongside `docker-compose.yml`. Key variables include: + See [Configuration Guide](#configuration-guide) for the complete variable hierarchy and customization options. - ```env - # z3/.env Example Snippet - NETWORK_NAME=Testnet - ENABLE_COOKIE_AUTH=true - COOKIE_AUTH_FILE_DIR=/var/run/auth +## Running the Stack - ZEBRA_RUST_LOG=info - ZEBRA_RPC_PORT=18232 - ZEBRA_HOST_RPC_PORT=18232 +The Z3 stack uses a **two-phase deployment** approach following blockchain industry best practices: - ZAINO_RUST_LOG=trace,hyper=info - ZAINO_GRPC_PORT=8137 - ZAINO_JSON_RPC_ENABLE=false - ZAINO_GRPC_TLS_ENABLE=true - ZAINO_HOST_GRPC_PORT=8137 - ZAINO_GRPC_TLS_CERT_PATH=/var/run/zaino/tls/zaino.crt - ZAINO_GRPC_TLS_KEY_PATH=/var/run/zaino/tls/zaino.key +### Quick Start (Synced State) - ZALLET_RUST_LOG=debug - ZALLET_HOST_RPC_PORT=28232 - ``` +If you have an already-synced Zebra state (cached or imported): -## Running the Stack +```bash +cd z3 +docker compose up -d +``` + +All services start quickly (within minutes) and are ready to use. + +### Fresh Sync (First Time Setup) + +**⚠️ IMPORTANT**: Initial blockchain sync can take **24+ hours for mainnet** or **several hours for testnet**. Zebra must sync before dependent services (Zaino, Zallet) can function. -Once the setup is complete, you can start all services using Docker Compose: +#### Phase 1: Sync Zebra (One-time) ```bash -cd z3 # Ensure you are in the z3 directory -docker-compose up --build +cd z3 + +# Start only Zebra +docker compose up -d zebra + +# Monitor sync progress (choose one) +docker compose logs -f zebra # View logs +watch curl -s http://localhost:8080/ready # Poll readiness endpoint + +# Or use this script to wait until Zebra is ready: +while true; do + response=$(curl -s http://127.0.0.1:8080/ready) + if [ "$response" = "ok" ]; then + echo "Zebra is ready!" + break + fi + echo "Not ready yet: $response" + sleep 5 +done + +# Zebra is ready when /ready returns "ok" ``` -* `--build`: This flag tells Docker Compose to build the images if they don't exist or if their Dockerfiles have changed. The current `docker-compose.yml` uses pre-built images for `zaino` and `zallet` specified by their SHA, and `zfnd/zebra:latest` for zebra, so `--build` might primarily affect local Dockerfile changes if you were to modify them or switch to local builds. -* To run in detached mode (in the background), add the `-d` flag: `docker-compose up -d --build`. + +**How long will this take?** +- **Mainnet**: 24-72 hours (depending on hardware and network) +- **Testnet**: 2-12 hours (currently ~3.1M blocks) +- **Cached/Resumed**: Minutes (if using existing Zebra state) + +#### Phase 2: Start Full Stack + +Once Zebra shows `/ready` returning `ok`: + +```bash +# Start all remaining services +docker compose up -d + +# Verify all services are healthy +docker compose ps +``` + +Services start immediately since Zebra is already synced. + +### Development Mode (Optional) + +For quick iteration during development without waiting for sync: + +```bash +# Copy development override +cp docker-compose.override.yml.example docker-compose.override.yml + +# Start all services (uses /healthy instead of /ready) +docker compose up -d +``` + +**⚠️ WARNING**: In development mode, Zaino and Zallet may experience delays while Zebra syncs. Only use for testing, NOT production. ## Stopping the Stack -To stop the services and remove the containers, run: +To stop the services and remove the containers: ```bash -docker-compose down +docker compose down ``` -If you also want to remove the data volumes (blockchain data, indexer database, wallet database), use: +To also remove the data volumes (⚠️ **deletes all blockchain data, indexer database, wallet database**): ```bash -docker-compose down -v +docker compose down -v ``` -## Configuration Details +## Data Storage & Volumes + +The Z3 stack stores blockchain data, indexer state, and wallet data in Docker volumes. You can choose between Docker-managed volumes (default) or local directories. + +### Default: Docker Named Volumes (Recommended) + +By default, the stack uses Docker named volumes which are managed by Docker: + +- `zebra_data`: Zebra blockchain state (~300GB+ for mainnet, ~30GB for testnet) +- `zaino_data`: Zaino indexer database +- `zallet_data`: Zallet wallet data +- `shared_cookie_volume`: RPC authentication cookies + +**Advantages:** +- No permission issues +- Automatic management by Docker +- Better performance on macOS/Windows + +### Advanced: Local Directories + +For advanced use cases (backups, external SSDs, shared storage), you can bind local directories instead of using Docker-managed volumes. + +**Important:** Choose directory locations appropriate for your operating system and requirements: +- Linux: `/mnt/data/z3`, `/var/lib/z3`, or user home directories +- macOS: `/Volumes/ExternalDrive/z3`, `~/Library/Application Support/z3`, or user Documents +- Windows (WSL): `/mnt/c/Z3Data` or native Windows paths if using Docker Desktop + +#### Setup Steps + +1. **Create your directories** in your chosen location: + ```bash + mkdir -p /your/chosen/path/zebra-state + mkdir -p /your/chosen/path/zaino-data + mkdir -p /your/chosen/path/zallet-data + ``` + +2. **Fix permissions** using the provided utility: + ```bash + ./fix-permissions.sh zebra /your/chosen/path/zebra-state + ./fix-permissions.sh zaino /your/chosen/path/zaino-data + ./fix-permissions.sh zallet /your/chosen/path/zallet-data + ``` + + Note: Keep the cookie directory as a Docker volume (recommended) to avoid cross-user permission issues. + +3. **Update `.env` file** with your paths: + ```bash + Z3_ZEBRA_DATA_PATH=/your/chosen/path/zebra-state + Z3_ZAINO_DATA_PATH=/your/chosen/path/zaino-data + Z3_ZALLET_DATA_PATH=/your/chosen/path/zallet-data + # Z3_COOKIE_PATH=shared_cookie_volume # Keep as Docker volume + ``` + +4. **Restart the stack**: + ```bash + docker compose down + docker compose up -d + ``` + +#### Security Requirements + +Each service runs as a specific non-root user with distinct UIDs/GIDs: + +- **Zebra**: UID=10001, GID=10001, permissions 700 +- **Zaino**: UID=1000, GID=1000, permissions 700 +- **Zallet**: UID=65532, GID=65532, permissions 700 -Understanding how configuration is applied is key to customizing the Z3 stack: +**Critical:** Local directories must have correct ownership and secure permissions: +- Use `fix-permissions.sh` to set ownership automatically +- Permissions must be 700 (owner only) or 750 (owner + group read) +- **Never use 755 or 777** - these expose your blockchain data and wallet to other users -* **Internal Service Defaults:** Each service (Zebra, Zaino, Zallet) has its own built-in default configuration values. These internal defaults are used unless influenced by environment variables. For this Z3 Docker Compose deployment, you do not directly interact with or provide TOML configuration files for the services in the `z3/config/` directory to alter these defaults for general operational parameters. +## Configuration Guide -* **Environment Variables (`z3/.env`):** This is the **exclusive method for customizing the operational parameters of the Zebra, Zaino, and Zallet services within the Z3 stack.** Variables defined in the `z3/.env` file are passed into their respective containers by Docker Compose. The services are designed to read these environment variables at startup to configure their behavior (e.g., log levels, network ports, feature enablement). This approach provides a centralized and clear way to manage your deployment settings. +This section explains how the Z3 stack is configured and how to customize it for your needs. -* **Explicitly Mounted Files & Docker Configs:** Note that specific files *are* sourced from your `z3/config/` directory for distinct purposes, such as `zallet_identity.txt` (volume mounted for Zallet) and the TLS certificates in `z3/config/tls/` (used via Docker `configs` for Zaino). These are for providing essential data or credentials, separate from the environment variable-based parameter tuning. +### Configuration Overview -* **Docker Compose Overrides (`docker-compose.yml`):** The `environment` section within each service definition in `docker-compose.yml` is used for several purposes: - * **Passing `.env` Variables:** It explicitly lists which variables from `.env` (or your shell environment) are passed into the container (e.g., `RUST_LOG=${ZALLET_RUST_LOG}`). - * **Service Discovery & Internal Settings:** It sets variables crucial for inter-service communication (e.g., `ZAINO_VALIDATOR_LISTEN_ADDRESS=zebra:${ZEBRA_RPC_PORT}`) or paths internal to the container (e.g., `ZAINO_VALIDATOR_COOKIE_PATH=${COOKIE_AUTH_FILE_DIR}/.cookie`). - * **Derived or Conditional Values:** Some environment variables might be constructed from others (e.g., combining `COOKIE_AUTH_FILE_DIR` with a filename) or set based on conditions (e.g., `ZAINO_VALIDATOR_COOKIE_AUTH_ENABLE=${ENABLE_COOKIE_AUTH}`). - These `docker-compose.yml` environment settings generally take precedence if there's an overlap, as they are the final values passed when the container starts. +The Z3 stack uses a layered configuration approach: -* **Entrypoint Scripts:** Each service's Docker image has an entrypoint script (`entrypoint.sh`). These scripts often perform final configuration steps, such as generating configuration files from templates based on environment variables, or applying conditional logic before starting the main application process. +1. **Service Defaults** - Built-in defaults for each service +2. **Environment Variables** (`.env`) - Runtime configuration and customization +3. **Configuration Files** - Required for specific services (Zallet, Zaino TLS) +4. **Docker Compose Remapping** - Transforms variables for service-specific formats + +### Variable Hierarchy + +The Z3 stack uses a **three-tier variable naming system** to avoid collisions: + +**1. Z3_* Variables (Infrastructure)** +- Purpose: Docker-level configuration (volume paths, port mappings, service discovery) +- Scope: Used only in `docker-compose.yml`, never passed directly to containers +- Examples: `Z3_ZEBRA_DATA_PATH`, `Z3_ZEBRA_RPC_PORT`, `Z3_ZEBRA_RUST_LOG` +- Why: Prevents collision with service configuration variables + +**2. Shared Variables (Common Configuration)** +- Purpose: Settings used by multiple services +- Scope: Remapped in `docker-compose.yml` to service-specific names +- Examples: + - `NETWORK_NAME` → `ZEBRA_NETWORK__NETWORK`, `ZAINO_NETWORK`, `ZALLET_NETWORK` + - `ENABLE_COOKIE_AUTH` → `ZEBRA_RPC__ENABLE_COOKIE_AUTH`, `ZAINO_VALIDATOR_COOKIE_AUTH` + - `COOKIE_AUTH_FILE_DIR` → Mapped to cookie paths for each service + +**3. Service Configuration Variables (Application Config)** +- Purpose: Service-specific configuration passed to applications +- Scope: Passed via `env_file` in `docker-compose.yml` +- Formats: + - **Zebra**: `ZEBRA_*` (config-rs format: `ZEBRA_SECTION__KEY` with `__` separator) + - **Zaino**: `ZAINO_*` + - **Zallet**: `ZALLET_*` + +### Configuration Approaches by Service + +**Zebra:** +- **Method**: Pure environment variables +- **Format**: `ZEBRA_SECTION__KEY` (e.g., `ZEBRA_RPC__LISTEN_ADDR`) +- **Files**: None required (uses environment variables only) + +**Zaino:** +- **Method**: Pure environment variables +- **Format**: `ZAINO_*` (e.g., `ZAINO_GRPC_PORT`) +- **Files**: TLS certificates (`config/tls/zaino.crt`, `config/tls/zaino.key`) + +**Zallet:** +- **Method**: Hybrid (TOML file + environment variables) +- **Format**: `ZALLET_*` for runtime parameters (e.g., `ZALLET_RUST_LOG`) +- **Files**: + - `config/zallet.toml` - Core configuration (required) + - `config/zallet_identity.txt` - Encryption key (required) + +### Zallet's Architecture + +Zallet differs from Zebra and Zaino in key ways: + +**Embedded Indexer:** +- Zallet includes an **embedded indexer** that connects directly to **Zebra's JSON-RPC** endpoint +- It does NOT use Zaino's indexer service +- It fetches blockchain data directly from Zebra + +**Service Connectivity:** +``` +Zebra (JSON-RPC :18232) + ├─→ Zaino (indexes blocks via JSON-RPC) + └─→ Zallet (embedded indexer via JSON-RPC) +``` + +**Critical Configuration Requirements:** +1. `config/zallet.toml` must exist with all required sections (even if empty) +2. `validator_address` must point to `zebra:18232` (Zebra's JSON-RPC), **NOT** `zaino:8137` +3. All TOML sections must be present: `[builder]`, `[consensus]`, `[database]`, `[external]`, `[features]`, `[indexer]`, `[keystore]`, `[note_management]`, `[rpc]` +4. Cookie authentication must be configured in both TOML and mounted as a volume + +### Common Customizations + +**Change Network (Mainnet/Testnet):** +```bash +# In .env: +NETWORK_NAME=Mainnet # or Testnet + +# In config/zallet.toml: +[consensus] +network = "main" # or "test" +``` + +**Adjust Log Levels:** +```bash +# In .env: +Z3_ZEBRA_RUST_LOG=info +ZAINO_RUST_LOG=info,reqwest=warn,hyper_util=warn +ZALLET_RUST_LOG=info,hyper_util=warn,reqwest=warn + +# For debugging, use: +ZAINO_RUST_LOG=debug +``` + +**Change Ports:** +```bash +# In .env: +Z3_ZEBRA_HOST_RPC_PORT=18232 +ZAINO_HOST_GRPC_PORT=8137 +ZALLET_HOST_RPC_PORT=28232 +``` + +**Environment Variable Precedence:** + +Docker Compose applies variables in this order (later overrides earlier): +1. Dockerfile defaults +2. `.env` file substitution (e.g., `${VARIABLE}`) +3. `env_file` section +4. `environment` section +5. Shell environment variables (if exported) + +**Important**: If you export a variable in your shell, it will override the `.env` file. Use `unset VARIABLE` to remove shell variables. + +## Health and Readiness Checks + +Zebra provides two HTTP endpoints for monitoring service health: + +### `/healthy` - Liveness Check +- **Returns 200**: Zebra is running and has minimum connected peers (configurable, default: 1) +- **Returns 503**: Not enough peer connections +- **Use for**: Docker healthchecks, liveness monitoring, restart decisions +- **Works during**: Initial sync, normal operation +- **Endpoint**: `http://localhost:${Z3_ZEBRA_HOST_HEALTH_PORT:-8080}/healthy` + +### `/ready` - Readiness Check +- **Returns 200**: Zebra is synced near the network tip (within configured blocks, default: 2) +- **Returns 503**: Still syncing or lagging behind network tip +- **Use for**: Production traffic routing, manual verification before use +- **Fails during**: Fresh sync (can take 24+ hours for mainnet) +- **Endpoint**: `http://localhost:${Z3_ZEBRA_HOST_HEALTH_PORT:-8080}/ready` + +### Service Dependency Strategy + +The Z3 stack uses **readiness-based dependencies** to prevent service hangs: + +``` +Zebra (/ready - synced near tip) + → Zaino (gRPC responding) + → Zallet (RPC responding) +``` + +**Why this approach:** +- **Zaino requires Zebra to be near the network tip** - if Zebra is still syncing, Zaino will hang internally waiting +- **Two-phase deployment** separates initial sync from normal operation +- **Docker Compose healthcheck** verifies Zebra is synced before starting dependent services + +**What each healthcheck tests:** +- `zebra`: `/ready` - Synced near network tip (within 2 blocks, configurable) +- `zaino`: gRPC server responding - Ready to index blocks +- `zallet`: RPC server responding - Ready for wallet operations + +**Deployment modes:** + +| Mode | When to use | Zebra healthcheck | Behavior | +|------|-------------|-------------------|----------| +| **Production** (default) | Mainnet, production testnet | `/ready` | Two-phase: sync Zebra first, then start stack | +| **Development** (override) | Local dev, quick testing | `/healthy` | Start all services immediately (may have delays) | + +### Monitoring Sync Progress + +During Phase 1 (Zebra sync), monitor progress: + +```bash +# Check readiness (returns "ok" when synced near tip) +curl http://localhost:8080/ready + +# Monitor sync progress via logs +docker compose logs -f zebra + +# Check current status +docker compose ps zebra +``` + +**What to expect:** +- Zebra shows `healthy (starting)` while syncing (during 90-second grace period) +- Once synced, `/ready` returns `ok` and Zebra shows `healthy` +- Zaino and Zallet remain in `waiting` state until dependencies are healthy + +### Configuration Options + +**Skip sync wait for development** (`.env`): +```bash +# Make /ready always return 200 on testnet (even during sync) +ZEBRA_HEALTH__ENFORCE_ON_TEST_NETWORKS=false # Default: false + +# When set to true, testnet behaves like mainnet (strict readiness check) +``` + +**Adjust readiness threshold** (`.env`): +```bash +# How many blocks behind network tip is acceptable (default: 2) +ZEBRA_HEALTH__READY_MAX_BLOCKS_BEHIND=2 + +# Minimum peer connections for /healthy (default: 1) +ZEBRA_HEALTH__MIN_CONNECTED_PEERS=1 +``` ## Interacting with Services -Once the stack is running, services can be accessed via the ports exposed in `docker-compose.yml`: +Once the stack is running, services can be accessed via their exposed ports: -* **Zebra RPC:** `http://localhost:${ZEBRA_HOST_RPC_PORT:-18232}` (default: Testnet `http://localhost:18232`) +* **Zebra RPC:** `http://localhost:${Z3_ZEBRA_HOST_RPC_PORT:-18232}` (default: Testnet `http://localhost:18232`) +* **Zebra Health:** `http://localhost:${Z3_ZEBRA_HOST_HEALTH_PORT:-8080}/healthy` and `/ready` * **Zaino gRPC:** `localhost:${ZAINO_HOST_GRPC_PORT:-8137}` (default: `localhost:8137`) * **Zaino JSON-RPC:** `http://localhost:${ZAINO_HOST_JSONRPC_PORT:-8237}` (default: `http://localhost:8237`, if enabled) * **Zallet RPC:** `http://localhost:${ZALLET_HOST_RPC_PORT:-28232}` (default: `http://localhost:28232`) diff --git a/config/zallet.toml b/config/zallet.toml new file mode 100644 index 0000000..50eb6d5 --- /dev/null +++ b/config/zallet.toml @@ -0,0 +1,52 @@ +# Zallet Configuration for Z3 Stack +# +# This config connects Zallet to the Zaino indexer in the Z3 stack. +# For full configuration options, run: zallet example-config + +[builder] +# Transaction builder settings - using defaults + +[builder.limits] +# Transaction limits - using defaults + +[consensus] +# Network must match NETWORK_NAME in .env (Mainnet/Testnet) +network = "main" + +[database] +# Wallet database - using defaults (wallet.db in datadir) + +[external] +# External settings - using defaults + +[features] +as_of_version = "0.1.0-alpha.1" + +[features.deprecated] +# No deprecated features enabled + +[features.experimental] +# No experimental features enabled + +[indexer] +# Connect to Zebra's JSON-RPC endpoint +# Zallet's embedded indexer fetches blockchain data from Zebra +# Service name 'zebra' and port from Z3_ZEBRA_RPC_PORT in .env +validator_address = "zebra:18232" + +# Cookie authentication (matches Zebra's ENABLE_COOKIE_AUTH=true) +validator_cookie_auth = true +validator_cookie_path = "/var/run/auth/.cookie" + +[keystore] +# Age encryption identity file (mounted from ./config/zallet_identity.txt) +encryption_identity = "identity.txt" + +[note_management] +# Note management - using defaults + +[rpc] +# RPC server configuration +# Bind address is set via ZALLET_RPC_BIND environment variable in docker-compose +bind = ["0.0.0.0:28232"] +timeout = 30 diff --git a/docker-compose.override.yml.example b/docker-compose.override.yml.example new file mode 100644 index 0000000..2ae97cd --- /dev/null +++ b/docker-compose.override.yml.example @@ -0,0 +1,29 @@ +# docker-compose.override.yml.example +# +# Development override for quick iteration without waiting for Zebra sync +# +# Usage: +# cp docker-compose.override.yml.example docker-compose.override.yml +# docker compose up -d +# +# This override changes Zebra's healthcheck from /ready to /healthy, +# allowing dependent services (Zaino, Zallet) to start immediately +# once Zebra has peer connections, without waiting for full sync. +# +# ⚠️ WARNING: Services may experience delays or errors while Zebra syncs +# ⚠️ Only use for development/testing, NOT production +# +# For production or production-like testing, follow the two-phase deployment: +# 1. docker compose up -d zebra # Sync Zebra first +# 2. docker compose up -d # Start full stack + +services: + zebra: + healthcheck: + # Use /healthy (has peers) instead of /ready (synced to tip) + # Allows Zaino and Zallet to start immediately + test: ["CMD-SHELL", "curl -sf http://127.0.0.1:8080/healthy || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s diff --git a/docker-compose.yml b/docker-compose.yml index bfb977a..e7a560c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,62 +1,51 @@ services: zebra: - # TODO: Use zfnd/zebra:latest when zebra has curl installed - image: zfnd/zebra:latest # Using latest, specify a version tag if preferred + # Local submodule build (recommended for testing/development) + # For production, use official releases: docker.io/zfnd/zebra:latest + # See README.md "Docker Images Notice" for details build: - context: . - dockerfile: Dockerfile.zebra + context: ./zebra + dockerfile: docker/Dockerfile + target: runtime container_name: z3_zebra restart: unless-stopped - platform: linux/amd64 # Uncomment if you need to specify platform + platform: linux/amd64 env_file: - ./.env environment: - - RUST_LOG=${ZEBRA_RUST_LOG} - - NETWORK=${NETWORK_NAME} # Zebra's network, derived from global NETWORK_NAME - - ZEBRA_COOKIE_DIR=${COOKIE_AUTH_FILE_DIR} # Zebra entrypoint uses this directory to store its .cookie - # - METRICS_ENDPOINT_ADDR=0.0.0.0:9999 + - RUST_LOG=${Z3_ZEBRA_RUST_LOG} + - ZEBRA_NETWORK__NETWORK=${NETWORK_NAME} + - ZEBRA_RPC__ENABLE_COOKIE_AUTH=${ENABLE_COOKIE_AUTH} + - ZEBRA_RPC__COOKIE_DIR=${COOKIE_AUTH_FILE_DIR} volumes: - - zebra_data:/home/zebra/.cache/zebra - - shared_cookie_volume:${COOKIE_AUTH_FILE_DIR} # Zebra writes the cookie here + # Blockchain state (defaults to named volume 'zebra_data') + - ${Z3_ZEBRA_DATA_PATH}:/home/zebra/.cache/zebra + # Cookie authentication shared with Zaino and Zallet + - ${Z3_COOKIE_PATH}:${COOKIE_AUTH_FILE_DIR} ports: - # Expose to host if direct access is needed - - "${ZEBRA_HOST_RPC_PORT}:${ZEBRA_RPC_PORT}" # Default Testnet RPC + - "${Z3_ZEBRA_HOST_RPC_PORT}:${Z3_ZEBRA_RPC_PORT}" + - "${Z3_ZEBRA_HOST_HEALTH_PORT}:8080" networks: - z3_net - # TODO: Make this healcheck simpler when we have a way to supply the cookie to Zebra - # This also needs to consider we're extracting the password from the cookie file - # using `cut -d ':' -f 2-`, because the file is formatted as "__cookie__:" healthcheck: - test: | - echo "Healthcheck script started. ENABLE_COOKIE_AUTH=${ENABLE_COOKIE_AUTH:-false}" - if [ "${ENABLE_COOKIE_AUTH:-false}" = "true" ]; then - echo "Cookie auth is ON." - echo "Attempting to use cookie file at: [${COOKIE_AUTH_FILE_DIR}/.cookie]" - if [ -f "${COOKIE_AUTH_FILE_DIR}/.cookie" ] && [ -s "${COOKIE_AUTH_FILE_DIR}/.cookie" ]; then - echo "Cookie file [${COOKIE_AUTH_FILE_DIR}/.cookie] found and not empty. Attempting curl with cookie." - curl -v --user "__cookie__:$(cat ${COOKIE_AUTH_FILE_DIR}/.cookie | cut -d ':' -f 2-)" --data-binary '{"jsonrpc": "2.0", "id":"healthcheck", "method": "getinfo", "params": [] }' -H 'Content-Type: application/json' http://127.0.0.1:${ZEBRA_RPC_PORT}/ || exit 1 - else - echo "Cookie file [${COOKIE_AUTH_FILE_DIR}/.cookie] not found or empty. Exiting healthcheck with 1." - exit 1 - fi - else - echo "Cookie auth is OFF. Attempting curl without cookie." - curl -s -f --user "" --data-binary '{"jsonrpc": "2.0", "id":"healthcheck", "method": "getinfo", "params": [] }' -H 'Content-Type: application/json' http://127.0.0.1:${ZEBRA_RPC_PORT}/ | grep -q '"result":' || exit 1 - fi + # Verifies Zebra is synced near network tip (default: within 2 blocks) + # For fresh sync: run 'docker compose up -d zebra' first, wait for sync, + # then run 'docker compose up -d' to start full stack + test: ["CMD-SHELL", "curl -sf http://127.0.0.1:8080/ready || exit 1"] interval: 30s - timeout: 30s - retries: 5 - start_period: 30s + timeout: 10s + retries: 3 + start_period: 90s zaino: - image: docker pull ghcr.io/zcashfoundation/zaino:sha-54e0da6@sha256:5cf6db69280261e836d98e99b47e2601e825935c9f56134e7380d3a10adf7899 - #! You can build the image yourself if you want to use a custom version - # build: - # context: ../zaino - # dockerfile: docker/Dockerfile + # Local submodule build (recommended for testing/development) + # For production, use official releases when available + # See README.md "Docker Images Notice" for details + build: + context: ./zaino + dockerfile: Dockerfile container_name: z3_zaino restart: unless-stopped - # platform: linux/amd64 depends_on: zebra: condition: service_healthy @@ -66,46 +55,51 @@ services: - RUST_LOG=${ZAINO_RUST_LOG} - RUST_BACKTRACE=${ZAINO_RUST_BACKTRACE} - ZAINO_NETWORK=${NETWORK_NAME} - # Connect to Zebra service using its name and port, cookie auth will be used if enabled globally. - - ZAINO_VALIDATOR_LISTEN_ADDRESS=zebra:${ZEBRA_RPC_PORT} - # This assignment enables cookie auth for Zaino if ENABLE_COOKIE_AUTH is true - # AND its ZAINO_VALIDATOR_LISTEN_ADDRESS is non-loopback (which 'zebra:${ZEBRA_RPC_PORT}' is). - - ZAINO_VALIDATOR_COOKIE_AUTH_ENABLE=${ENABLE_COOKIE_AUTH} - - ZAINO_VALIDATOR_COOKIE_PATH=${COOKIE_AUTH_FILE_DIR}/.cookie # Zaino reads the cookie from this specific path - # Zaino's own RPC services + # Zebra connection and authentication + - ZAINO_VALIDATOR_LISTEN_ADDRESS=zebra:${Z3_ZEBRA_RPC_PORT} + - ZAINO_VALIDATOR_COOKIE_AUTH=${ENABLE_COOKIE_AUTH} + - ZAINO_VALIDATOR_COOKIE_PATH=${COOKIE_AUTH_FILE_DIR}/.cookie + # Zaino RPC services - ZAINO_GRPC_LISTEN_ADDRESS=0.0.0.0:${ZAINO_GRPC_PORT} - ZAINO_JSON_RPC_LISTEN_ADDRESS=0.0.0.0:${ZAINO_JSON_RPC_PORT} + # TLS configuration + - ZAINO_GRPC_TLS=${ZAINO_GRPC_TLS_ENABLE} + - ZAINO_TLS_CERT_PATH=${ZAINO_GRPC_TLS_CERT_PATH} + - ZAINO_TLS_KEY_PATH=${ZAINO_GRPC_TLS_KEY_PATH} volumes: - - zaino_data:/home/zaino/.cache/zaino - - shared_cookie_volume:${COOKIE_AUTH_FILE_DIR}:ro # Zaino reads the cookie read-only + # Indexer state (defaults to named volume 'zaino_data') + - ${Z3_ZAINO_DATA_PATH}:/home/zaino/.cache/zaino + # Cookie authentication + - ${Z3_COOKIE_PATH}:${COOKIE_AUTH_FILE_DIR}:ro configs: - - source: zaino_tls_cert # Use the top-level config for Zaino's cert - target: ${ZAINO_GRPC_TLS_CERT_PATH} # Mount path inside container, FHS compliant via env var - - source: zaino_tls_key # Use the top-level config for Zaino's key - target: ${ZAINO_GRPC_TLS_KEY_PATH} # Mount path inside container, FHS compliant via env var + - source: zaino_tls_cert + target: ${ZAINO_GRPC_TLS_CERT_PATH} + - source: zaino_tls_key + target: ${ZAINO_GRPC_TLS_KEY_PATH} ports: - "${ZAINO_HOST_GRPC_PORT}:${ZAINO_GRPC_PORT}" - "${ZAINO_HOST_JSONRPC_PORT}:${ZAINO_JSON_RPC_PORT}" networks: - z3_net healthcheck: - # Healthcheck needs to account for TLS if ZAINO_GRPC_TLS_ENABLE is true - # Use env vars for scheme and curl options, to be defined in .env - test: ["CMD-SHELL", "curl -f -k https://127.0.0.1:${ZAINO_GRPC_PORT} || exit 1"] + # Process check (gRPC cannot be directly curled) + test: ["CMD-SHELL", "/usr/local/bin/zainod --version >/dev/null 2>&1 || exit 1"] interval: 30s - timeout: 10s - retries: 5 + timeout: 5s + retries: 3 start_period: 60s zallet: - image: ghcr.io/zcashfoundation/zallet:sha-54e0da6@sha256:3710fb41dbad0f0457551b76a81c97e476f4addc9605498a0d0e34fbe4b8d4e9 - #! You can build the image yourself if you want to use a custom version - # build: - # context: ../wallet - # dockerfile: docker/Dockerfile + # Local submodule build (recommended for testing/development) + # For production, use official releases when available + # See README.md "Docker Images Notice" for details + build: + context: ./zallet + dockerfile: Dockerfile container_name: z3_zallet restart: unless-stopped - # platform: linux/amd64 + user: "65532:65532" + command: ["--datadir", "/var/lib/zallet", "start"] depends_on: zaino: condition: service_healthy @@ -114,31 +108,28 @@ services: environment: - RUST_LOG=${ZALLET_RUST_LOG} - ZALLET_NETWORK=${NETWORK_NAME} - # Zallet indexer points to Zaino's gRPC service (hosted at 'zaino:${ZAINO_GRPC_PORT}'). - # TLS for this connection needs to be handled by Zallet (e.g., trusting Zaino's cert). - # Refer to .env for potential CA path configuration if needed. - - ZALLET_INDEXER_VALIDATOR_ADDRESS=zaino:${ZAINO_GRPC_PORT} - ZALLET_RPC_BIND=0.0.0.0:${ZALLET_RPC_PORT} volumes: - - zallet_data:/home/zallet/.data - - ./config/zallet_identity.txt:/home/zallet/.data/identity.txt:ro - # - ./config/tls/zaino.crt:/tls_trusted/zaino.crt:ro # Example mount for Zallet to trust Zaino + # Wallet database (defaults to named volume 'zallet_data') + - ${Z3_ZALLET_DATA_PATH}:/var/lib/zallet + # Cookie authentication for Zebra access + - ${Z3_COOKIE_PATH}:${COOKIE_AUTH_FILE_DIR}:ro + # Configuration files + - ./config/zallet.toml:/var/lib/zallet/zallet.toml:ro + - ./config/zallet_identity.txt:/var/lib/zallet/identity.txt:ro ports: - "${ZALLET_HOST_RPC_PORT}:${ZALLET_RPC_PORT}" networks: - z3_net - healthcheck: - test: ["CMD-SHELL", "curl -s -f --data-binary '{\"jsonrpc\": \"2.0\", \"id\":\"healthcheck\", \"method\": \"help\", \"params\": [] }' -H 'Content-Type: application/json' http://127.0.0.1:${ZALLET_RPC_PORT}/ || exit 1"] - interval: 30s - timeout: 10s - retries: 5 - start_period: 60s + # NOTE: Healthcheck disabled - distroless image has no shell/curl + # Zallet will restart automatically if it crashes (restart: unless-stopped) + # Monitor logs or use external monitoring for service health volumes: zebra_data: zaino_data: zallet_data: - shared_cookie_volume: {} # Define the shared volume for the cookie + shared_cookie_volume: networks: z3_net: @@ -146,6 +137,6 @@ networks: configs: zaino_tls_cert: - file: ./config/tls/zaino.crt # Path on the host for Zaino's TLS cert + file: ./config/tls/zaino.crt zaino_tls_key: - file: ./config/tls/zaino.key # Path on the host for Zaino's TLS key + file: ./config/tls/zaino.key diff --git a/fix-permissions.sh b/fix-permissions.sh new file mode 100755 index 0000000..190aa8b --- /dev/null +++ b/fix-permissions.sh @@ -0,0 +1,138 @@ +#!/usr/bin/env bash +# +# Permission fix utility for Z3 stack local volumes +# This script sets correct ownership and permissions on user-specified directories +# +# Usage: +# ./fix-permissions.sh +# +# Services: zebra, zaino, zallet, cookie +# +# Examples: +# ./fix-permissions.sh zebra /mnt/ssd/zebra-state +# ./fix-permissions.sh zaino /home/user/data/zaino +# ./fix-permissions.sh zallet ~/Documents/zallet-data +# ./fix-permissions.sh cookie /var/lib/z3/cookies +# + +set -euo pipefail + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Container UIDs/GIDs +ZEBRA_UID=10001 +ZEBRA_GID=10001 +ZAINO_UID=1000 +ZAINO_GID=1000 +ZALLET_UID=65532 +ZALLET_GID=65532 + +# Show usage +usage() { + echo "Usage: $0 " + echo "" + echo "Services:" + echo " zebra - Zebra blockchain state (UID:GID 10001:10001, perms 700)" + echo " zaino - Zaino indexer data (UID:GID 1000:1000, perms 700)" + echo " zallet - Zallet wallet data (UID:GID 65532:65532, perms 700)" + echo " cookie - Shared cookie directory (UID:GID 10001:10001, perms 750)" + echo "" + echo "Examples:" + echo " $0 zebra /mnt/ssd/zebra-state" + echo " $0 zaino /home/user/data/zaino" + echo " $0 zallet ~/Documents/zallet-data" + exit 1 +} + +# Check arguments +if [[ $# -ne 2 ]]; then + usage +fi + +SERVICE="$1" +DIR_PATH="$2" + +# Validate service +case "$SERVICE" in + zebra) + UID=$ZEBRA_UID + GID=$ZEBRA_GID + PERMS=700 + ;; + zaino) + UID=$ZAINO_UID + GID=$ZAINO_GID + PERMS=700 + ;; + zallet) + UID=$ZALLET_UID + GID=$ZALLET_GID + PERMS=700 + ;; + cookie) + UID=$ZEBRA_UID + GID=$ZEBRA_GID + PERMS=750 + echo -e "${YELLOW}WARNING: Cookie directory has special requirements.${NC}" + echo "Zaino (UID 1000) needs read access to Zebra's (UID 10001) cookie." + echo "After running this script, you may need to:" + echo " 1. Use ACLs: sudo setfacl -m u:1000:r ${DIR_PATH}" + echo " 2. Or create a shared group for both users" + echo " 3. Or keep cookie as Docker volume (recommended)" + echo "" + ;; + *) + echo -e "${RED}Error: Unknown service '$SERVICE'${NC}" + usage + ;; +esac + +# Check if directory exists +if [[ ! -d "$DIR_PATH" ]]; then + echo -e "${RED}Error: Directory does not exist: ${DIR_PATH}${NC}" + echo "Please create the directory first:" + echo " mkdir -p ${DIR_PATH}" + exit 1 +fi + +# Check if running with sudo +if [[ $EUID -ne 0 ]]; then + echo -e "${YELLOW}This script needs sudo to set ownership.${NC}" + echo "Re-running with sudo..." + echo "" + exec sudo "$0" "$@" +fi + +echo -e "${GREEN}Z3 Stack - Fixing Permissions${NC}" +echo "Service: $SERVICE" +echo "Directory: $DIR_PATH" +echo "UID:GID: $UID:$GID" +echo "Permissions: $PERMS" +echo "" + +# Set ownership and permissions +chown "$UID:$GID" "$DIR_PATH" +chmod "$PERMS" "$DIR_PATH" + +echo -e "${GREEN}✓ Permissions set successfully${NC}" +echo "" +echo "To use this directory, update your .env file:" +case "$SERVICE" in + zebra) + echo " Z3_ZEBRA_DATA_PATH=${DIR_PATH}" + ;; + zaino) + echo " Z3_ZAINO_DATA_PATH=${DIR_PATH}" + ;; + zallet) + echo " Z3_ZALLET_DATA_PATH=${DIR_PATH}" + ;; + cookie) + echo " Z3_COOKIE_PATH=${DIR_PATH}" + ;; +esac +echo "" diff --git a/zaino b/zaino index d792561..c6e317c 160000 --- a/zaino +++ b/zaino @@ -1 +1 @@ -Subproject commit d792561e46feb7867a3cbd8a86f039fc65dd15a5 +Subproject commit c6e317c44798e5f452305088584da91970fff011 diff --git a/zallet b/zallet index c8a2b10..f5ad603 160000 --- a/zallet +++ b/zallet @@ -1 +1 @@ -Subproject commit c8a2b1080cd2641dba453e9b72bbce0a884aa92b +Subproject commit f5ad6031e482c16c6dfd7d77dd375b231fe3153e diff --git a/zebra b/zebra index b0e7fd4..d60923f 160000 --- a/zebra +++ b/zebra @@ -1 +1 @@ -Subproject commit b0e7fd4a9a6159c2b81976452fe368cb06d2dead +Subproject commit d60923f61a2eba07f273a3bfa31d63f667bd053f From 3d828ecf0ad2929a40bc959750c6ff8ed150e06e Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Tue, 4 Nov 2025 15:02:24 +0000 Subject: [PATCH 29/57] feat(docker): configuration and documentation for ARM64 support (#6) * feat(docker): configuration and documentation for ARM64 support - Updated `.env` to include a configuration option for ARM64 users, allowing native builds by uncommenting the `DOCKER_PLATFORM` variable. - Modified `docker-compose.yml` to utilize the `DOCKER_PLATFORM` variable for platform-specific builds. - Expanded `README.md` with a detailed section on platform configuration, including instructions for enabling native ARM64 support and system requirements. - Clarified Zallet's connection to Zebra's JSON-RPC endpoint in `zallet.toml` and updated related documentation. * chore: update submodule references --- .env | 10 ++++ README.md | 116 ++++++++++++++++++++++++++++++++++----- check-zebra-readiness.sh | 9 +++ config/zallet.toml | 9 +-- docker-compose.yml | 2 +- zaino | 2 +- zallet | 2 +- zcashd | 2 +- zebra | 2 +- 9 files changed, 130 insertions(+), 24 deletions(-) create mode 100755 check-zebra-readiness.sh diff --git a/.env b/.env index a550d52..7c14faa 100644 --- a/.env +++ b/.env @@ -38,6 +38,16 @@ # # WARNING: Never use 755 or 777 permissions - they expose your data! +# ============================================================================= +# Docker Platform Configuration +# ============================================================================= +# Controls which architecture Docker builds/runs containers for. +# Default: linux/amd64 (x86_64 architecture) +# +# ARM64 Users (Apple Silicon M1/M2/M3, ARM64 Linux): +# Uncomment and set to linux/arm64 for NATIVE builds +# DOCKER_PLATFORM=linux/arm64 + # Zebra blockchain state directory # Default: zebra_data (Docker named volume) Z3_ZEBRA_DATA_PATH=zebra_data diff --git a/README.md b/README.md index a7214bd..d8b4cdf 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,34 @@ This project orchestrates Zebra, Zaino, and Zallet to provide a modern, modular Zcash software stack, intended to replace the legacy `zcashd`. +## Table of Contents + +- [Quick Start (TLDR)](#quick-start-tldr) +- [Docker Images Notice](#-important-docker-images-notice) +- [Prerequisites](#prerequisites) +- [System Requirements](#system-requirements) + - [Minimum Specifications](#minimum-specifications) + - [Recommended Specifications](#recommended-specifications) + - [Sync Time Expectations](#sync-time-expectations) +- [Setup](#setup) + - [Clone Repository](#1-clone-the-repository-and-submodules) + - [Platform Configuration (ARM64)](#2-platform-configuration-apple-silicon--arm64) + - [Required Files](#3-required-files) + - [Configuration Review](#6-review-zallet-configuration) +- [Running the Stack](#running-the-stack) + - [Quick Start (Synced State)](#quick-start-synced-state) + - [Fresh Sync (First Time)](#fresh-sync-first-time-setup) + - [Development Mode](#development-mode-optional) +- [Stopping the Stack](#stopping-the-stack) +- [Data Storage & Volumes](#data-storage--volumes) +- [Interacting with Services](#interacting-with-services) +- [Configuration Guide](#configuration-guide) +- [Health and Readiness Checks](#health-and-readiness-checks) + ## Quick Start (TLDR) +**Apple Silicon / ARM64 Users:** For 20x faster builds, enable native ARM64 support by setting `DOCKER_PLATFORM=linux/arm64` in `.env` (see [Platform Configuration](#2-platform-configuration-apple-silicon--arm64)). + For experienced users who know Docker and blockchain nodes: ```bash @@ -53,9 +79,44 @@ Before you begin, ensure you have the following installed: * **Docker Engine:** [Install Docker](https://docs.docker.com/engine/install/) * **Docker Compose:** (Usually included with Docker Desktop, or [install separately](https://docs.docker.com/compose/install/)) +* **Docker Permissions (Linux):** You may need to run Docker commands with `sudo`, or add your user to the `docker` group. See [Docker's post-installation steps](https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user) for details. Note that the `docker` group grants root-level privileges. * **rage:** For generating the Zallet identity file. Install from [str4d/rage releases](https://github.com/str4d/rage/releases) or build from source. * **Git:** For cloning the repositories and submodules. +## System Requirements + +Running the full Z3 stack (Zebra + Zaino + Zallet) requires substantial hardware resources due to blockchain synchronization and indexing. + +### Minimum Specifications + +- **CPU:** 2 cores (4+ cores strongly recommended) +- **RAM:** 4 GB for Zebra; 8+ GB recommended for full stack +- **Disk Space:** + - Mainnet: 300 GB (blockchain state) + - Testnet: 30 GB (blockchain state) + - Additional space for Zaino indexer database (requirements under determination) + - SSD strongly recommended for sync performance +- **Network:** Reliable internet connection + - Initial sync download: ~300 GB for mainnet + - Ongoing bandwidth: 10 MB - 10 GB per day + +### Recommended Specifications + +- **CPU:** 4+ cores +- **RAM:** 16+ GB +- **Disk Space:** 500+ GB with room for blockchain growth +- **Network:** 100+ Mbps connection with ~300 GB/month bandwidth + +### Sync Time Expectations + +- **Mainnet:** 24-72 hours on recommended hardware +- **Testnet:** 2-12 hours (currently ~3.1M blocks) +- **Cached/Resumed:** Minutes (if using existing Zebra state) + +Sync time varies based on CPU speed, disk I/O (SSD vs HDD), and network bandwidth. + +**Note:** These specifications are based on [Zebra's official requirements](https://zebra.zfnd.org/user/requirements.html). Zaino indexer adds additional resource overhead; specific requirements are under determination. Running all three services together requires resources beyond Zebra alone. + ## Setup 1. **Clone the Repository and Submodules:** @@ -70,14 +131,42 @@ Before you begin, ensure you have the following installed: The Docker Compose setup builds all images locally from submodules by default. -2. **Required Files:** +2. **Platform Configuration (Apple Silicon / ARM64):** + + **ARM64 users**: Enable native builds for dramatically faster performance. + + Z3 defaults to AMD64 (x86_64) for development consistency. On ARM64 systems (Apple Silicon M1/M2/M3 or ARM64 Linux), this uses emulation which is **very slow**: + - AMD64 emulation: ~50 minutes to build Zebra + - Native ARM64: ~2-3 minutes to build Zebra + + **To enable native ARM64 builds:** + + Edit `.env` and uncomment the `DOCKER_PLATFORM` line: + + ```bash + # In .env file, change this: + # DOCKER_PLATFORM=linux/arm64 + + # To this: + DOCKER_PLATFORM=linux/arm64 + ``` + + Or set it directly in your shell: + + ```bash + echo "DOCKER_PLATFORM=linux/arm64" >> .env + ``` + + **Intel/AMD users**: No action needed. Default AMD64 settings work optimally. + +3. **Required Files:** You'll need to generate these files in the `config/` directory: - `config/tls/zaino.crt` and `config/tls/zaino.key` - Zaino TLS certificates - `config/zallet_identity.txt` - Zallet encryption key - `config/zallet.toml` - Zallet configuration (provided, review and customize) -3. **Generate Zaino TLS Certificates:** +4. **Generate Zaino TLS Certificates:** ```bash openssl req -x509 -newkey rsa:4096 -keyout config/tls/zaino.key -out config/tls/zaino.crt -sha256 -days 365 -nodes -subj "/CN=localhost" -addext "subjectAltName = DNS:localhost,IP:127.0.0.1" @@ -85,7 +174,7 @@ Before you begin, ensure you have the following installed: This creates a self-signed certificate valid for 365 days. For production, use certificates from a trusted CA. -4. **Generate Zallet Identity File:** +5. **Generate Zallet Identity File:** ```bash rage-keygen -o config/zallet_identity.txt @@ -93,7 +182,7 @@ Before you begin, ensure you have the following installed: **Securely back up this file and the public key** (printed to terminal). -5. **Review Zallet Configuration:** +6. **Review Zallet Configuration:** Review `config/zallet.toml` and update the network setting: - For mainnet: `network = "main"` in `[consensus]` section @@ -101,7 +190,7 @@ Before you begin, ensure you have the following installed: See [Configuration Guide](#configuration-guide) for details on Zallet's architecture and config requirements. -6. **Review Environment Variables:** +7. **Review Environment Variables:** A comprehensive `.env` file is provided with sensible defaults. Review and customize as needed: - `NETWORK_NAME` - Set to `Mainnet` or `Testnet` @@ -155,10 +244,7 @@ done # Zebra is ready when /ready returns "ok" ``` -**How long will this take?** -- **Mainnet**: 24-72 hours (depending on hardware and network) -- **Testnet**: 2-12 hours (currently ~3.1M blocks) -- **Cached/Resumed**: Minutes (if using existing Zebra state) +**How long will this take?** See [Sync Time Expectations](#sync-time-expectations) for detailed estimates based on your hardware and network. #### Phase 2: Start Full Stack @@ -336,16 +422,16 @@ The Z3 stack uses a **three-tier variable naming system** to avoid collisions: Zallet differs from Zebra and Zaino in key ways: -**Embedded Indexer:** -- Zallet includes an **embedded indexer** that connects directly to **Zebra's JSON-RPC** endpoint -- It does NOT use Zaino's indexer service -- It fetches blockchain data directly from Zebra +**Embedded Zaino Indexer:** +- Zallet embeds **Zaino's indexer libraries** (`zaino-fetch`, `zaino-state`, `zaino-proto`) as dependencies +- This embedded indexer connects directly to **Zebra's JSON-RPC** endpoint to fetch blockchain data +- Zallet does **NOT** connect to the standalone Zaino gRPC/JSON-RPC service (which is for other light clients) **Service Connectivity:** ``` Zebra (JSON-RPC :18232) - ├─→ Zaino (indexes blocks via JSON-RPC) - └─→ Zallet (embedded indexer via JSON-RPC) + ├─→ Zaino Service (standalone indexer for gRPC clients like Zingo) + └─→ Zallet (uses embedded Zaino indexer libraries) ``` **Critical Configuration Requirements:** diff --git a/check-zebra-readiness.sh b/check-zebra-readiness.sh new file mode 100755 index 0000000..2c55d20 --- /dev/null +++ b/check-zebra-readiness.sh @@ -0,0 +1,9 @@ + while true; do + response=$(curl -s http://127.0.0.1:8080/ready) + if [ "$response" = "ok" ]; then + echo "Zebra is ready!" + break + fi + echo "Not ready yet: $response" + sleep 5 + done \ No newline at end of file diff --git a/config/zallet.toml b/config/zallet.toml index 50eb6d5..4792c45 100644 --- a/config/zallet.toml +++ b/config/zallet.toml @@ -1,6 +1,7 @@ # Zallet Configuration for Z3 Stack # -# This config connects Zallet to the Zaino indexer in the Z3 stack. +# Zallet embeds Zaino's indexer libraries and connects to Zebra's JSON-RPC endpoint. +# It does NOT connect to the standalone Zaino service (which is for gRPC clients). # For full configuration options, run: zallet example-config [builder] @@ -29,9 +30,9 @@ as_of_version = "0.1.0-alpha.1" # No experimental features enabled [indexer] -# Connect to Zebra's JSON-RPC endpoint -# Zallet's embedded indexer fetches blockchain data from Zebra -# Service name 'zebra' and port from Z3_ZEBRA_RPC_PORT in .env +# Zallet's embedded Zaino indexer libraries connect to Zebra's JSON-RPC endpoint +# to fetch blockchain data. The validator_address MUST point to Zebra (not the +# standalone Zaino service). Service name 'zebra' and port from Z3_ZEBRA_RPC_PORT in .env validator_address = "zebra:18232" # Cookie authentication (matches Zebra's ENABLE_COOKIE_AUTH=true) diff --git a/docker-compose.yml b/docker-compose.yml index e7a560c..b3e1866 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,7 +9,7 @@ services: target: runtime container_name: z3_zebra restart: unless-stopped - platform: linux/amd64 + platform: ${DOCKER_PLATFORM:-linux/amd64} env_file: - ./.env environment: diff --git a/zaino b/zaino index c6e317c..2e2c768 160000 --- a/zaino +++ b/zaino @@ -1 +1 @@ -Subproject commit c6e317c44798e5f452305088584da91970fff011 +Subproject commit 2e2c7681f704b4942dd2e3d30ca88adb7872703e diff --git a/zallet b/zallet index f5ad603..a7148cd 160000 --- a/zallet +++ b/zallet @@ -1 +1 @@ -Subproject commit f5ad6031e482c16c6dfd7d77dd375b231fe3153e +Subproject commit a7148cd948525a555e21d51478d118a8b46d8ef8 diff --git a/zcashd b/zcashd index 1f1f7a3..16ac743 160000 --- a/zcashd +++ b/zcashd @@ -1 +1 @@ -Subproject commit 1f1f7a385adc048154e7f25a3a0de76f3658ca09 +Subproject commit 16ac743764a513e41dafb2cd79c2417c5bb41e81 diff --git a/zebra b/zebra index d60923f..7a7572f 160000 --- a/zebra +++ b/zebra @@ -1 +1 @@ -Subproject commit d60923f61a2eba07f273a3bfa31d63f667bd053f +Subproject commit 7a7572f6dba53281d6f1fbda5578715a198d3340 From e4f9bd9d6114bb7dc00f74b6660251a38eb98989 Mon Sep 17 00:00:00 2001 From: David Campbell Date: Tue, 4 Nov 2025 08:03:14 -0700 Subject: [PATCH 30/57] fix: `UID` and `GID` are read only variables in bash; use `OWNER_UID` and `OWNER_GID` instead (#5) --- fix-permissions.sh | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/fix-permissions.sh b/fix-permissions.sh index 190aa8b..fcc17d0 100755 --- a/fix-permissions.sh +++ b/fix-permissions.sh @@ -59,23 +59,23 @@ DIR_PATH="$2" # Validate service case "$SERVICE" in zebra) - UID=$ZEBRA_UID - GID=$ZEBRA_GID + OWNER_UID=$ZEBRA_UID + OWNER_GID=$ZEBRA_GID PERMS=700 ;; zaino) - UID=$ZAINO_UID - GID=$ZAINO_GID + OWNER_UID=$ZAINO_UID + OWNER_GID=$ZAINO_GID PERMS=700 ;; zallet) - UID=$ZALLET_UID - GID=$ZALLET_GID + OWNER_UID=$ZALLET_UID + OWNER_GID=$ZALLET_GID PERMS=700 ;; cookie) - UID=$ZEBRA_UID - GID=$ZEBRA_GID + OWNER_UID=$ZEBRA_UID + OWNER_GID=$ZEBRA_GID PERMS=750 echo -e "${YELLOW}WARNING: Cookie directory has special requirements.${NC}" echo "Zaino (UID 1000) needs read access to Zebra's (UID 10001) cookie." @@ -110,12 +110,12 @@ fi echo -e "${GREEN}Z3 Stack - Fixing Permissions${NC}" echo "Service: $SERVICE" echo "Directory: $DIR_PATH" -echo "UID:GID: $UID:$GID" +echo "UID:GID: ${OWNER_UID}:${OWNER_GID}" echo "Permissions: $PERMS" echo "" # Set ownership and permissions -chown "$UID:$GID" "$DIR_PATH" +chown "${OWNER_UID}:${OWNER_GID}" "$DIR_PATH" chmod "$PERMS" "$DIR_PATH" echo -e "${GREEN}✓ Permissions set successfully${NC}" From 5b46076af11900b15bbec18c958b818027b3d2bd Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Tue, 4 Nov 2025 15:37:06 +0000 Subject: [PATCH 31/57] chore(docker): update Docker images and docs - Updated `docker-compose.yml` to reference pre-built images for Zebra, Zaino, and Zallet from GitHub Container Registry. - Enhanced `README.md` with instructions for using pre-built images and building locally from submodules, including a snapshot of the current image commits. --- README.md | 94 ++++++++++++++++++++++++++++++++++++++-------- docker-compose.yml | 21 ++++++----- 2 files changed, 90 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index d8b4cdf..698e892 100644 --- a/README.md +++ b/README.md @@ -28,14 +28,11 @@ This project orchestrates Zebra, Zaino, and Zallet to provide a modern, modular ## Quick Start (TLDR) -**Apple Silicon / ARM64 Users:** For 20x faster builds, enable native ARM64 support by setting `DOCKER_PLATFORM=linux/arm64` in `.env` (see [Platform Configuration](#2-platform-configuration-apple-silicon--arm64)). - -For experienced users who know Docker and blockchain nodes: +**Using Pre-Built Images (Fastest):** ```bash # 1. Clone and setup git clone https://github.com/ZcashFoundation/z3 && cd z3 -git submodule update --init --recursive openssl req -x509 -newkey rsa:4096 -keyout config/tls/zaino.key -out config/tls/zaino.crt -sha256 -days 365 -nodes -subj "/CN=localhost" rage-keygen -o config/zallet_identity.txt @@ -50,19 +47,81 @@ docker compose up -d zebra docker compose up -d ``` +**Building Locally (For Development):** + +```bash +# 1. Clone and setup (same as above, plus submodules) +git clone https://github.com/ZcashFoundation/z3 && cd z3 +git submodule update --init --recursive +openssl req -x509 -newkey rsa:4096 -keyout config/tls/zaino.key -out config/tls/zaino.crt -sha256 -days 365 -nodes -subj "/CN=localhost" +rage-keygen -o config/zallet_identity.txt + +# 2. Review config/zallet.toml and .env as above + +# 3. Build from local submodules +docker compose build + +# 4-5. Start services as above +docker compose up -d zebra +# ... wait for sync, then: +docker compose up -d +``` + +**Note for ARM64 Users:** For optimal build performance on Apple Silicon or ARM64 Linux, set `DOCKER_PLATFORM=linux/arm64` in `.env` (see [Platform Configuration](#2-platform-configuration-apple-silicon--arm64)). + **First time?** Read the full [Setup](#setup) and [Running the Stack](#running-the-stack) sections below. ## ⚠️ Important: Docker Images Notice **This repository builds and hosts Docker images for testing purposes only.** -- **Images use unstable development branches**: - - Zebra: `main` branch (latest development) - - Zaino: `dev` branch (unstable features) - - Zallet: `main` branch (under active development) +### Pre-Built Development Images + +For convenience, Z3 provides pre-built Docker images that are automatically built from upstream development branches: + +- **ghcr.io/zcashfoundation/zebra:edge** - Built from [ZcashFoundation/zebra](https://github.com/ZcashFoundation/zebra) `main` branch +- **ghcr.io/zcashfoundation/zaino:edge** - Built from [zingolabs/zaino](https://github.com/zingolabs/zaino) `dev` branch +- **ghcr.io/zcashfoundation/zallet:edge** - Built from [zcash/wallet](https://github.com/zcash/wallet) `main` branch -- **Purpose**: Enable rapid testing and iteration of the Z3 stack -- **Not suitable for production use**: These images may contain bugs, breaking changes, or experimental features +**Image Snapshot (as of 2025-11-04):** +- Zebra: commit `7a7572f` (2025-10-29) +- Zaino: commit `2e2c768` (2025-11-04) +- Zallet: commit `a7148cd` (2025-10-31) + +### Usage Options + +**Option 1: Use Pre-Built Images (Default, Fastest)** + +The `docker-compose.yml` includes pre-built images that will be pulled automatically: + +```bash +docker compose up -d +``` + +Docker will pull the `:edge` images from GitHub Container Registry. No build time required. + +**Option 2: Build Locally** + +To build from local submodules (useful for testing local changes): + +```bash +# Initialize submodules if not already done +git submodule update --init --recursive + +# Build from local source +docker compose build + +# Start services with local builds +docker compose up -d +``` + +When you build locally, Docker Compose creates local images that take precedence over pulling remote images. + +### Important Warnings + +- **Images use unstable development branches**: These contain the latest features but may have bugs or breaking changes +- **Not suitable for production use**: For production, use official releases (see below) +- **Images are updated periodically**: The `:edge` tag moves forward as we rebuild from upstream development branches **For production deployments:** - Use official release images from respective projects: @@ -71,8 +130,6 @@ docker compose up -d - Zallet: Official releases when available - Or build from stable release tags yourself -If you're testing or developing, the pre-built images from this repository provide a convenient way to quickly spin up the full Z3 stack. - ## Prerequisites Before you begin, ensure you have the following installed: @@ -119,17 +176,22 @@ Sync time varies based on CPU speed, disk I/O (SSD vs HDD), and network bandwidt ## Setup -1. **Clone the Repository and Submodules:** +1. **Clone the Repository:** - Clone the `z3` repository and initialize its submodules (Zebra, Zaino, Zallet): + Clone the `z3` repository: ```bash git clone https://github.com/ZcashFoundation/z3 cd z3 - git submodule update --init --recursive ``` - The Docker Compose setup builds all images locally from submodules by default. + **Using Pre-Built Images (Recommended):** Submodules are not required. Skip to step 2. + + **Building Locally (Optional):** Initialize submodules to build from source: + + ```bash + git submodule update --init --recursive + ``` 2. **Platform Configuration (Apple Silicon / ARM64):** diff --git a/docker-compose.yml b/docker-compose.yml index b3e1866..f829ef4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,9 @@ services: zebra: - # Local submodule build (recommended for testing/development) - # For production, use official releases: docker.io/zfnd/zebra:latest - # See README.md "Docker Images Notice" for details + # Pre-built image: ghcr.io/zcashfoundation/zebra:edge + # Built from ZcashFoundation/zebra:main - commit 7a7572f (2025-10-29) + # Run 'docker compose build' to build locally from ./zebra submodule instead + image: ghcr.io/zcashfoundation/zebra:edge build: context: ./zebra dockerfile: docker/Dockerfile @@ -38,9 +39,10 @@ services: start_period: 90s zaino: - # Local submodule build (recommended for testing/development) - # For production, use official releases when available - # See README.md "Docker Images Notice" for details + # Pre-built image: ghcr.io/zcashfoundation/zaino:edge + # Built from zingolabs/zaino:dev - commit 2e2c768 (2025-11-04) + # Run 'docker compose build' to build locally from ./zaino submodule instead + image: ghcr.io/zcashfoundation/zaino:edge build: context: ./zaino dockerfile: Dockerfile @@ -90,9 +92,10 @@ services: start_period: 60s zallet: - # Local submodule build (recommended for testing/development) - # For production, use official releases when available - # See README.md "Docker Images Notice" for details + # Pre-built image: ghcr.io/zcashfoundation/zallet:edge + # Built from zcash/wallet:main - commit a7148cd (2025-10-31) + # Run 'docker compose build' to build locally from ./zallet submodule instead + image: ghcr.io/zcashfoundation/zallet:edge build: context: ./zallet dockerfile: Dockerfile From a9f82aa381693ce0459432bc6f174ca87b7e1753 Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Fri, 28 Nov 2025 20:25:01 +0000 Subject: [PATCH 32/57] fix(docker): pin submodules to pre-config-restructure versions for compatibility Roll back Zaino and Zallet submodules to versions before the Figment config restructure that changed environment variable parsing (using `.split("-")` for nested structs). This broke environment variable configuration in the Docker Compose setup. Changes: - Update Zebra to official v3.1.0 release image - Pin Zaino to commit 66b3199f (pre-config restructure) - Pin Zallet to commit 60b0235 (uses zaino 13919816, compatible version) - Fix Zallet to depend on Zebra directly (not Zaino) since it embeds Zaino libs - Update README with GitHub callouts, architecture diagram, and clearer first-time setup instructions explaining Zebra sync requirements - Improve check-zebra-readiness.sh with proper shebang and timestamps - Remove unused ZALLET_NETWORK env var (Zallet uses config file) Submodule versions: - zebra: 930fb7f4b (v3.1.0+) - zaino: 66b3199fc (pre-config restructure) - zallet: 60b0235f2 (uses zaino 13919816) --- .env | 2 +- README.md | 245 ++++++++++++++++++--------------------- check-zebra-readiness.sh | 29 +++-- docker-compose.yml | 29 +++-- zaino | 2 +- zallet | 2 +- zebra | 2 +- 7 files changed, 154 insertions(+), 157 deletions(-) diff --git a/.env b/.env index 7c14faa..058b1eb 100644 --- a/.env +++ b/.env @@ -71,7 +71,7 @@ Z3_ZALLET_DATA_PATH=zallet_data # Common Configuration # ============================================================================= # Shared variables used by multiple services, mapped in docker-compose.yml: -# NETWORK_NAME → ZEBRA_NETWORK__NETWORK, ZAINO_NETWORK, ZALLET_NETWORK +# NETWORK_NAME → ZEBRA_NETWORK__NETWORK, ZAINO_NETWORK # ENABLE_COOKIE_AUTH → ZEBRA_RPC__ENABLE_COOKIE_AUTH, ZAINO_VALIDATOR_COOKIE_AUTH # COOKIE_AUTH_FILE_DIR → ZEBRA_RPC__COOKIE_DIR, ZAINO_VALIDATOR_COOKIE_PATH diff --git a/README.md b/README.md index 698e892..1bf1b21 100644 --- a/README.md +++ b/README.md @@ -1,134 +1,144 @@ # Z3 - Unified Zcash Stack -This project orchestrates Zebra, Zaino, and Zallet to provide a modern, modular Zcash software stack, intended to replace the legacy `zcashd`. +A modern, modular Zcash software stack combining Zebra, Zaino, and Zallet to replace the legacy `zcashd`. ## Table of Contents -- [Quick Start (TLDR)](#quick-start-tldr) -- [Docker Images Notice](#-important-docker-images-notice) +- [Quick Start](#quick-start) +- [Understanding the Architecture](#understanding-the-architecture) +- [Docker Images](#docker-images) - [Prerequisites](#prerequisites) - [System Requirements](#system-requirements) - - [Minimum Specifications](#minimum-specifications) - - [Recommended Specifications](#recommended-specifications) - - [Sync Time Expectations](#sync-time-expectations) - [Setup](#setup) - - [Clone Repository](#1-clone-the-repository-and-submodules) - - [Platform Configuration (ARM64)](#2-platform-configuration-apple-silicon--arm64) - - [Required Files](#3-required-files) - - [Configuration Review](#6-review-zallet-configuration) - [Running the Stack](#running-the-stack) - - [Quick Start (Synced State)](#quick-start-synced-state) - - [Fresh Sync (First Time)](#fresh-sync-first-time-setup) - - [Development Mode](#development-mode-optional) - [Stopping the Stack](#stopping-the-stack) - [Data Storage & Volumes](#data-storage--volumes) - [Interacting with Services](#interacting-with-services) - [Configuration Guide](#configuration-guide) - [Health and Readiness Checks](#health-and-readiness-checks) -## Quick Start (TLDR) +--- -**Using Pre-Built Images (Fastest):** +## Quick Start -```bash -# 1. Clone and setup -git clone https://github.com/ZcashFoundation/z3 && cd z3 -openssl req -x509 -newkey rsa:4096 -keyout config/tls/zaino.key -out config/tls/zaino.crt -sha256 -days 365 -nodes -subj "/CN=localhost" -rage-keygen -o config/zallet_identity.txt +> [!IMPORTANT] +> **First time running Z3?** You must sync Zebra before starting the other services. This takes **24-72 hours for mainnet** or **2-12 hours for testnet**. There is no way around this initial sync. +> +> **Already have synced Zebra data?** You can start all services immediately. -# 2. Review config/zallet.toml (update network: "main" or "test") -# 3. Review .env file (already configured with defaults) - -# 4. Start Zebra and wait for sync (24-72h for mainnet, 2-12h for testnet) -docker compose up -d zebra -# Wait until: curl http://localhost:8080/ready returns "ok" - -# 5. Start full stack -docker compose up -d -``` - -**Building Locally (For Development):** +### First Time Setup (No Existing Data) ```bash -# 1. Clone and setup (same as above, plus submodules) +# 1. Clone and generate required files git clone https://github.com/ZcashFoundation/z3 && cd z3 git submodule update --init --recursive -openssl req -x509 -newkey rsa:4096 -keyout config/tls/zaino.key -out config/tls/zaino.crt -sha256 -days 365 -nodes -subj "/CN=localhost" +openssl req -x509 -newkey rsa:4096 -keyout config/tls/zaino.key -out config/tls/zaino.crt \ + -sha256 -days 365 -nodes -subj "/CN=localhost" \ + -addext "subjectAltName=DNS:localhost,DNS:zaino,IP:127.0.0.1" rage-keygen -o config/zallet_identity.txt -# 2. Review config/zallet.toml and .env as above +# 2. Build Zaino and Zallet (required - no pre-built images available) +docker compose build zaino zallet -# 3. Build from local submodules -docker compose build +# 3. Review configuration +# - config/zallet.toml: set network = "main" or "test" +# - .env: review defaults (usually no changes needed) -# 4-5. Start services as above +# 4. Start ONLY Zebra first docker compose up -d zebra -# ... wait for sync, then: + +# 5. Wait for Zebra to sync (this takes hours/days) +./check-zebra-readiness.sh +# Or manually: curl http://localhost:8080/ready (returns "ok" when synced) + +# 6. Once Zebra is synced, start the remaining services docker compose up -d ``` -**Note for ARM64 Users:** For optimal build performance on Apple Silicon or ARM64 Linux, set `DOCKER_PLATFORM=linux/arm64` in `.env` (see [Platform Configuration](#2-platform-configuration-apple-silicon--arm64)). +> [!NOTE] +> The `check-zebra-readiness.sh` script polls Zebra's readiness endpoint and notifies you when sync is complete. You can safely close your terminal during sync and check back later. -**First time?** Read the full [Setup](#setup) and [Running the Stack](#running-the-stack) sections below. +### With Existing Synced Data -## ⚠️ Important: Docker Images Notice +If you have previously synced Zebra data (or are mounting existing blockchain state): -**This repository builds and hosts Docker images for testing purposes only.** +```bash +# Build if not already built +docker compose build zaino zallet -### Pre-Built Development Images +# All services can start immediately +docker compose up -d -For convenience, Z3 provides pre-built Docker images that are automatically built from upstream development branches: +# Verify all services are healthy +docker compose ps +``` -- **ghcr.io/zcashfoundation/zebra:edge** - Built from [ZcashFoundation/zebra](https://github.com/ZcashFoundation/zebra) `main` branch -- **ghcr.io/zcashfoundation/zaino:edge** - Built from [zingolabs/zaino](https://github.com/zingolabs/zaino) `dev` branch -- **ghcr.io/zcashfoundation/zallet:edge** - Built from [zcash/wallet](https://github.com/zcash/wallet) `main` branch +> [!TIP] +> **ARM64 Users (Apple Silicon):** Set `DOCKER_PLATFORM=linux/arm64` in `.env` for native builds. This reduces build time from ~50 minutes to ~3 minutes. -**Image Snapshot (as of 2025-11-04):** -- Zebra: commit `7a7572f` (2025-10-29) -- Zaino: commit `2e2c768` (2025-11-04) -- Zallet: commit `a7148cd` (2025-10-31) +--- -### Usage Options +## Understanding the Architecture -**Option 1: Use Pre-Built Images (Default, Fastest)** +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Z3 Stack │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ +│ │ Zebra │◄────────│ Zaino │ │ Zallet │ │ +│ │ (node) │ │ (index) │ │(wallet) │ │ +│ └────┬────┘ └─────────┘ └────┬────┘ │ +│ │ │ │ +│ │ ┌─────────────┐ │ │ +│ └──────────────│ Embedded │◄─────────┘ │ +│ │ Zaino libs │ │ +│ └─────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` -The `docker-compose.yml` includes pre-built images that will be pulled automatically: +> [!NOTE] +> **Zallet embeds Zaino libraries internally.** It connects directly to Zebra's JSON-RPC, not to the standalone Zaino service. The Zaino container in this stack is for external gRPC clients (like Zingo wallet) and for testing the indexer independently. -```bash -docker compose up -d -``` +**Service Roles:** +- **Zebra** - Full node that syncs and validates the Zcash blockchain +- **Zaino** - Standalone indexer providing gRPC interface for light wallets +- **Zallet** - Wallet service with embedded indexer that talks directly to Zebra -Docker will pull the `:edge` images from GitHub Container Registry. No build time required. +## Docker Images -**Option 2: Build Locally** +> [!IMPORTANT] +> **Current Status:** Zaino and Zallet require local builds. Pre-built images are available for Zebra only. -To build from local submodules (useful for testing local changes): +### Image Sources + +| Service | Image | Source | +|---------|-------|--------| +| **Zebra** | `zfnd/zebra:3.1.0` | Pre-built from [ZcashFoundation/zebra](https://github.com/ZcashFoundation/zebra) | +| **Zaino** | `z3-zaino:local` | Must build locally from submodule | +| **Zallet** | `z3-zallet:local` | Must build locally from submodule | + +### Building Local Images ```bash -# Initialize submodules if not already done +# Initialize submodules git submodule update --init --recursive -# Build from local source -docker compose build - -# Start services with local builds -docker compose up -d +# Build zaino and zallet +docker compose build zaino zallet ``` -When you build locally, Docker Compose creates local images that take precedence over pulling remote images. +> [!NOTE] +> Local builds are required because Zaino and Zallet are under active development and require specific version pinning for compatibility. -### Important Warnings +### Why Local Builds? -- **Images use unstable development branches**: These contain the latest features but may have bugs or breaking changes -- **Not suitable for production use**: For production, use official releases (see below) -- **Images are updated periodically**: The `:edge` tag moves forward as we rebuild from upstream development branches +Zallet embeds Zaino libraries internally. Both must use compatible versions of the Zaino codebase. The submodules in this repository are pinned to tested, compatible commits. -**For production deployments:** -- Use official release images from respective projects: - - Zebra: [zfnd/zebra](https://hub.docker.com/r/zfnd/zebra) (stable releases) - - Zaino: Official releases when available - - Zallet: Official releases when available -- Or build from stable release tags yourself +**For production deployments**, use official release images when available: +- Zebra: [zfnd/zebra](https://hub.docker.com/r/zfnd/zebra) (stable releases) +- Zaino/Zallet: Official releases when published ## Prerequisites @@ -263,78 +273,55 @@ Sync time varies based on CPU speed, disk I/O (SSD vs HDD), and network bandwidt ## Running the Stack -The Z3 stack uses a **two-phase deployment** approach following blockchain industry best practices: - -### Quick Start (Synced State) +> [!WARNING] +> **Why can't I just run `docker compose up`?** +> +> Docker Compose healthchecks have timeout limits that cannot accommodate blockchain sync times (hours to days). If you run `docker compose up` on a fresh install, Zaino and Zallet will repeatedly fail waiting for Zebra to sync. +> +> **Solution:** Start Zebra alone first, wait for sync, then start everything else. -If you have an already-synced Zebra state (cached or imported): +### First Time (Fresh Sync Required) ```bash -cd z3 -docker compose up -d -``` - -All services start quickly (within minutes) and are ready to use. - -### Fresh Sync (First Time Setup) - -**⚠️ IMPORTANT**: Initial blockchain sync can take **24+ hours for mainnet** or **several hours for testnet**. Zebra must sync before dependent services (Zaino, Zallet) can function. - -#### Phase 1: Sync Zebra (One-time) - -```bash -cd z3 - -# Start only Zebra +# Step 1: Start only Zebra docker compose up -d zebra -# Monitor sync progress (choose one) -docker compose logs -f zebra # View logs -watch curl -s http://localhost:8080/ready # Poll readiness endpoint - -# Or use this script to wait until Zebra is ready: -while true; do - response=$(curl -s http://127.0.0.1:8080/ready) - if [ "$response" = "ok" ]; then - echo "Zebra is ready!" - break - fi - echo "Not ready yet: $response" - sleep 5 -done - -# Zebra is ready when /ready returns "ok" +# Step 2: Monitor sync progress (choose one method) +./check-zebra-readiness.sh # Recommended: script waits and notifies +docker compose logs -f zebra # Watch logs +curl http://localhost:8080/ready # Manual check (returns "ok" when synced) + +# Step 3: Once synced, start all services +docker compose up -d ``` -**How long will this take?** See [Sync Time Expectations](#sync-time-expectations) for detailed estimates based on your hardware and network. +> [!NOTE] +> **Sync times:** +> - Mainnet: 24-72 hours (depends on hardware/network) +> - Testnet: 2-12 hours +> +> You can close your terminal during sync. Zebra runs in the background. -#### Phase 2: Start Full Stack +### Returning User (Existing Data) -Once Zebra shows `/ready` returning `ok`: +If Zebra has previously synced (data persists in Docker volumes): ```bash -# Start all remaining services docker compose up -d - -# Verify all services are healthy -docker compose ps +docker compose ps # Verify all healthy ``` -Services start immediately since Zebra is already synced. +### Development Mode -### Development Mode (Optional) - -For quick iteration during development without waiting for sync: +For local development when you need services running during sync: ```bash -# Copy development override cp docker-compose.override.yml.example docker-compose.override.yml - -# Start all services (uses /healthy instead of /ready) docker compose up -d ``` -**⚠️ WARNING**: In development mode, Zaino and Zallet may experience delays while Zebra syncs. Only use for testing, NOT production. +> [!CAUTION] +> Development mode uses `/healthy` instead of `/ready`. Services will start but may error until Zebra catches up. Not for production use. ## Stopping the Stack diff --git a/check-zebra-readiness.sh b/check-zebra-readiness.sh index 2c55d20..2fe7c09 100755 --- a/check-zebra-readiness.sh +++ b/check-zebra-readiness.sh @@ -1,9 +1,20 @@ - while true; do - response=$(curl -s http://127.0.0.1:8080/ready) - if [ "$response" = "ok" ]; then - echo "Zebra is ready!" - break - fi - echo "Not ready yet: $response" - sleep 5 - done \ No newline at end of file +#!/bin/bash +# Polls Zebra's readiness endpoint until it returns "ok" +# Use this script during initial sync to know when Zebra is ready + +echo "Waiting for Zebra to sync..." +echo "This may take hours (mainnet: 24-72h, testnet: 2-12h)" +echo "You can safely Ctrl+C and check back later." +echo "" + +while true; do + response=$(curl -s http://127.0.0.1:8080/ready) + if [ "$response" = "ok" ]; then + echo "" + echo "Zebra is ready! You can now start the remaining services:" + echo " docker compose up -d" + break + fi + echo "$(date '+%H:%M:%S') - Not ready yet: $response" + sleep 30 +done diff --git a/docker-compose.yml b/docker-compose.yml index f829ef4..a3db421 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,16 +1,13 @@ services: zebra: - # Pre-built image: ghcr.io/zcashfoundation/zebra:edge - # Built from ZcashFoundation/zebra:main - commit 7a7572f (2025-10-29) # Run 'docker compose build' to build locally from ./zebra submodule instead - image: ghcr.io/zcashfoundation/zebra:edge + image: zfnd/zebra:3.1.0 build: context: ./zebra dockerfile: docker/Dockerfile target: runtime container_name: z3_zebra restart: unless-stopped - platform: ${DOCKER_PLATFORM:-linux/amd64} env_file: - ./.env environment: @@ -39,10 +36,11 @@ services: start_period: 90s zaino: - # Pre-built image: ghcr.io/zcashfoundation/zaino:edge - # Built from zingolabs/zaino:dev - commit 2e2c768 (2025-11-04) - # Run 'docker compose build' to build locally from ./zaino submodule instead - image: ghcr.io/zcashfoundation/zaino:edge + # No pre-built image available for this commit - must build locally + # Built from zingolabs/zaino:dev - commit 66b3199f (before config restructure) + # Run 'docker compose build zaino' to build from ./zaino submodule + image: ghcr.io/zcashfoundation/zaino:sha-934f857 + platform: ${DOCKER_PLATFORM:-linux/amd64} build: context: ./zaino dockerfile: Dockerfile @@ -92,10 +90,11 @@ services: start_period: 60s zallet: - # Pre-built image: ghcr.io/zcashfoundation/zallet:edge - # Built from zcash/wallet:main - commit a7148cd (2025-10-31) - # Run 'docker compose build' to build locally from ./zallet submodule instead - image: ghcr.io/zcashfoundation/zallet:edge + # No pre-built image available - must build locally + # Built from zcash/wallet commit 60b0235 (uses zaino 13919816, before config restructure) + # Run 'docker compose build zallet' to build from ./zallet submodule + image: z3-zallet:local + platform: ${DOCKER_PLATFORM:-linux/amd64} build: context: ./zallet dockerfile: Dockerfile @@ -104,14 +103,14 @@ services: user: "65532:65532" command: ["--datadir", "/var/lib/zallet", "start"] depends_on: - zaino: + zebra: condition: service_healthy env_file: - ./.env environment: - RUST_LOG=${ZALLET_RUST_LOG} - - ZALLET_NETWORK=${NETWORK_NAME} - - ZALLET_RPC_BIND=0.0.0.0:${ZALLET_RPC_PORT} + # Zallet configuration is handled via config/zallet.toml + # Environment variable overrides are currently not supported by Zallet volumes: # Wallet database (defaults to named volume 'zallet_data') - ${Z3_ZALLET_DATA_PATH}:/var/lib/zallet diff --git a/zaino b/zaino index 2e2c768..66b3199 160000 --- a/zaino +++ b/zaino @@ -1 +1 @@ -Subproject commit 2e2c7681f704b4942dd2e3d30ca88adb7872703e +Subproject commit 66b3199fc07542159f1ef1d6c7149fd3193294c6 diff --git a/zallet b/zallet index a7148cd..60b0235 160000 --- a/zallet +++ b/zallet @@ -1 +1 @@ -Subproject commit a7148cd948525a555e21d51478d118a8b46d8ef8 +Subproject commit 60b0235f292268bdc9a91b004e1e61ed9a73c9e9 diff --git a/zebra b/zebra index 7a7572f..930fb7f 160000 --- a/zebra +++ b/zebra @@ -1 +1 @@ -Subproject commit 7a7572f6dba53281d6f1fbda5578715a198d3340 +Subproject commit 930fb7f4b43a817b2a08d4a4ad94049efdf61887 From 417b6799f66a4b16a7d744b120826cf064a4ae29 Mon Sep 17 00:00:00 2001 From: "dev.bo" <129651996+devdotbo@users.noreply.github.com> Date: Fri, 12 Dec 2025 21:46:04 +0400 Subject: [PATCH 33/57] fix(docker): resolve zallet permission error by relocating config mounts (#11) Fixes #9 The zallet container was failing with "Permission denied (os error 13)" due to overlapping Docker mounts. Config files were bind-mounted inside the data volume at /var/lib/zallet/, causing Docker to create them with root ownership while the container runs as UID 65532. This commit resolves the issue by: - Mounting config files to /etc/zallet/ instead of /var/lib/zallet/ - Adding --config flag to zallet command to specify config location - Using absolute path for encryption_identity in zallet.toml The fix works on both mainnet and testnet as it addresses Docker configuration, not network-specific settings. Changes: - docker-compose.yml: Updated zallet command and volume mount paths - config/zallet.toml: Changed encryption_identity to absolute path Tested on testnet - zallet now starts successfully without permission errors. --- config/zallet.toml | 2 +- docker-compose.yml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config/zallet.toml b/config/zallet.toml index 4792c45..1d35a63 100644 --- a/config/zallet.toml +++ b/config/zallet.toml @@ -41,7 +41,7 @@ validator_cookie_path = "/var/run/auth/.cookie" [keystore] # Age encryption identity file (mounted from ./config/zallet_identity.txt) -encryption_identity = "identity.txt" +encryption_identity = "/etc/zallet/identity.txt" [note_management] # Note management - using defaults diff --git a/docker-compose.yml b/docker-compose.yml index a3db421..e3ff24b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -101,7 +101,7 @@ services: container_name: z3_zallet restart: unless-stopped user: "65532:65532" - command: ["--datadir", "/var/lib/zallet", "start"] + command: ["--datadir", "/var/lib/zallet", "--config", "/etc/zallet/zallet.toml", "start"] depends_on: zebra: condition: service_healthy @@ -116,9 +116,9 @@ services: - ${Z3_ZALLET_DATA_PATH}:/var/lib/zallet # Cookie authentication for Zebra access - ${Z3_COOKIE_PATH}:${COOKIE_AUTH_FILE_DIR}:ro - # Configuration files - - ./config/zallet.toml:/var/lib/zallet/zallet.toml:ro - - ./config/zallet_identity.txt:/var/lib/zallet/identity.txt:ro + # Configuration files (mounted outside datadir to avoid permission conflicts) + - ./config/zallet.toml:/etc/zallet/zallet.toml:ro + - ./config/zallet_identity.txt:/etc/zallet/identity.txt:ro ports: - "${ZALLET_HOST_RPC_PORT}:${ZALLET_RPC_PORT}" networks: From 64bb4e02f68891c7a97dbaa7f56a685fc6b94241 Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Thu, 29 Jan 2026 15:53:50 +0000 Subject: [PATCH 34/57] feat(docker): update to latest zaino and official zallet image (#12) * feat(docker): update to latest zaino and official zallet image Update submodules and Docker configuration: - Zaino: use GHCR image sha-417b679 (built from zingolabs/zaino:dev) - Zallet: v0.1.0-alpha.3 (electriccoinco/zallet official image) - Zebra: v3.1.0 (unchanged) Add custom entrypoint for Zaino to resolve Docker DNS hostnames to IP addresses, required because ValidatorConfig uses SocketAddr type which only accepts IP:port format, not hostname:port. NOTE: The entrypoint workaround can be removed once zaino PR #784 is merged and a new image is built: https://github.com/zingolabs/zaino/pull/784 New files: - config/zaino/docker-entrypoint.sh (temporary workaround) - config/zaino/zindexer.toml * refactor(docker): remove zaino DNS workaround and update submodules Remove the docker-entrypoint.sh workaround for Zaino hostname resolution now that upstream PR #784 adds native support for hostname:port format. Changes: - Remove config/zaino/docker-entrypoint.sh (no longer needed) - Simplify config/zaino/zindexer.toml to minimal config-rs requirement - Update docker-compose.yml to use native hostname resolution - Remove env_file from zaino service (conflicts with config-rs) - Update submodules to latest versions: - zaino: 49e5241d (config-rs migration + hostname support) - zebra: e04845f3e - zcashd: cfcfcd93b (v6.11.0) * chore(docker): update zaino image to sha-1871eba --- .env | 4 ++-- .gitignore | 4 ++++ config/zaino/zindexer.toml | 1 + config/zallet.toml | 6 ++---- docker-compose.yml | 42 ++++++++++++++------------------------ zaino | 2 +- zallet | 2 +- zcashd | 2 +- zebra | 2 +- 9 files changed, 28 insertions(+), 37 deletions(-) create mode 100644 config/zaino/zindexer.toml diff --git a/.env b/.env index 058b1eb..a17fe1c 100644 --- a/.env +++ b/.env @@ -72,8 +72,8 @@ Z3_ZALLET_DATA_PATH=zallet_data # ============================================================================= # Shared variables used by multiple services, mapped in docker-compose.yml: # NETWORK_NAME → ZEBRA_NETWORK__NETWORK, ZAINO_NETWORK -# ENABLE_COOKIE_AUTH → ZEBRA_RPC__ENABLE_COOKIE_AUTH, ZAINO_VALIDATOR_COOKIE_AUTH -# COOKIE_AUTH_FILE_DIR → ZEBRA_RPC__COOKIE_DIR, ZAINO_VALIDATOR_COOKIE_PATH +# ENABLE_COOKIE_AUTH → ZEBRA_RPC__ENABLE_COOKIE_AUTH +# COOKIE_AUTH_FILE_DIR → ZEBRA_RPC__COOKIE_DIR, ZAINO_VALIDATOR_SETTINGS__VALIDATOR_COOKIE_PATH # Network name for all services (e.g., Mainnet, Testnet, Regtest) NETWORK_NAME=Mainnet diff --git a/.gitignore b/.gitignore index bd498cf..5a50148 100644 --- a/.gitignore +++ b/.gitignore @@ -29,5 +29,9 @@ config/tls/* # Then un-ignore the .gitkeep file within 'tls' !config/tls/.gitkeep +# Zaino config (required by config-rs) +!config/zaino/ +!config/zaino/zindexer.toml + # Un-ignore .gitkeep directly under config !config/.gitkeep diff --git a/config/zaino/zindexer.toml b/config/zaino/zindexer.toml new file mode 100644 index 0000000..d4c0bc7 --- /dev/null +++ b/config/zaino/zindexer.toml @@ -0,0 +1 @@ +# Minimal Zaino config - most settings come from environment variables diff --git a/config/zallet.toml b/config/zallet.toml index 1d35a63..7c0f338 100644 --- a/config/zallet.toml +++ b/config/zallet.toml @@ -21,7 +21,7 @@ network = "main" # External settings - using defaults [features] -as_of_version = "0.1.0-alpha.1" +as_of_version = "0.1.0-alpha.3" [features.deprecated] # No deprecated features enabled @@ -34,9 +34,7 @@ as_of_version = "0.1.0-alpha.1" # to fetch blockchain data. The validator_address MUST point to Zebra (not the # standalone Zaino service). Service name 'zebra' and port from Z3_ZEBRA_RPC_PORT in .env validator_address = "zebra:18232" - -# Cookie authentication (matches Zebra's ENABLE_COOKIE_AUTH=true) -validator_cookie_auth = true +# Cookie authentication path (matches Zebra's ENABLE_COOKIE_AUTH=true) validator_cookie_path = "/var/run/auth/.cookie" [keystore] diff --git a/docker-compose.yml b/docker-compose.yml index e3ff24b..2af5b9b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -36,41 +36,37 @@ services: start_period: 90s zaino: - # No pre-built image available for this commit - must build locally - # Built from zingolabs/zaino:dev - commit 66b3199f (before config restructure) - # Run 'docker compose build zaino' to build from ./zaino submodule - image: ghcr.io/zcashfoundation/zaino:sha-934f857 + # No official image from zingolabs yet - built from zingolabs/zaino:dev + image: ghcr.io/zcashfoundation/zaino:sha-1871eba platform: ${DOCKER_PLATFORM:-linux/amd64} build: context: ./zaino dockerfile: Dockerfile container_name: z3_zaino restart: unless-stopped + command: ["zainod", "--config", "/etc/zaino/zindexer.toml"] depends_on: zebra: condition: service_healthy - env_file: - - ./.env environment: - RUST_LOG=${ZAINO_RUST_LOG} - RUST_BACKTRACE=${ZAINO_RUST_BACKTRACE} - ZAINO_NETWORK=${NETWORK_NAME} - # Zebra connection and authentication - - ZAINO_VALIDATOR_LISTEN_ADDRESS=zebra:${Z3_ZEBRA_RPC_PORT} - - ZAINO_VALIDATOR_COOKIE_AUTH=${ENABLE_COOKIE_AUTH} - - ZAINO_VALIDATOR_COOKIE_PATH=${COOKIE_AUTH_FILE_DIR}/.cookie - # Zaino RPC services - - ZAINO_GRPC_LISTEN_ADDRESS=0.0.0.0:${ZAINO_GRPC_PORT} - - ZAINO_JSON_RPC_LISTEN_ADDRESS=0.0.0.0:${ZAINO_JSON_RPC_PORT} + - ZAINO_VALIDATOR_SETTINGS__VALIDATOR_JSONRPC_LISTEN_ADDRESS=zebra:${Z3_ZEBRA_RPC_PORT} + - ZAINO_VALIDATOR_SETTINGS__VALIDATOR_COOKIE_PATH=${COOKIE_AUTH_FILE_DIR}/.cookie + # gRPC server + - ZAINO_GRPC_SETTINGS__LISTEN_ADDRESS=0.0.0.0:${ZAINO_GRPC_PORT} + # JSON-RPC server + - ZAINO_JSON_SERVER_SETTINGS__JSON_RPC_LISTEN_ADDRESS=0.0.0.0:${ZAINO_JSON_RPC_PORT} # TLS configuration - - ZAINO_GRPC_TLS=${ZAINO_GRPC_TLS_ENABLE} - - ZAINO_TLS_CERT_PATH=${ZAINO_GRPC_TLS_CERT_PATH} - - ZAINO_TLS_KEY_PATH=${ZAINO_GRPC_TLS_KEY_PATH} + - ZAINO_GRPC_SETTINGS__TLS__CERT_PATH=${ZAINO_GRPC_TLS_CERT_PATH} + - ZAINO_GRPC_SETTINGS__TLS__KEY_PATH=${ZAINO_GRPC_TLS_KEY_PATH} volumes: # Indexer state (defaults to named volume 'zaino_data') - ${Z3_ZAINO_DATA_PATH}:/home/zaino/.cache/zaino # Cookie authentication - ${Z3_COOKIE_PATH}:${COOKIE_AUTH_FILE_DIR}:ro + - ./config/zaino/zindexer.toml:/etc/zaino/zindexer.toml:ro configs: - source: zaino_tls_cert target: ${ZAINO_GRPC_TLS_CERT_PATH} @@ -90,17 +86,11 @@ services: start_period: 60s zallet: - # No pre-built image available - must build locally - # Built from zcash/wallet commit 60b0235 (uses zaino 13919816, before config restructure) - # Run 'docker compose build zallet' to build from ./zallet submodule - image: z3-zallet:local + image: electriccoinco/zallet:v0.1.0-alpha.3 platform: ${DOCKER_PLATFORM:-linux/amd64} - build: - context: ./zallet - dockerfile: Dockerfile container_name: z3_zallet restart: unless-stopped - user: "65532:65532" + user: "1000:1000" command: ["--datadir", "/var/lib/zallet", "--config", "/etc/zallet/zallet.toml", "start"] depends_on: zebra: @@ -123,9 +113,7 @@ services: - "${ZALLET_HOST_RPC_PORT}:${ZALLET_RPC_PORT}" networks: - z3_net - # NOTE: Healthcheck disabled - distroless image has no shell/curl - # Zallet will restart automatically if it crashes (restart: unless-stopped) - # Monitor logs or use external monitoring for service health + # No healthcheck: distroless image has no shell/curl volumes: zebra_data: diff --git a/zaino b/zaino index 66b3199..49e5241 160000 --- a/zaino +++ b/zaino @@ -1 +1 @@ -Subproject commit 66b3199fc07542159f1ef1d6c7149fd3193294c6 +Subproject commit 49e5241def007353033900eaefc84de5f6c13fcc diff --git a/zallet b/zallet index 60b0235..f0db32d 160000 --- a/zallet +++ b/zallet @@ -1 +1 @@ -Subproject commit 60b0235f292268bdc9a91b004e1e61ed9a73c9e9 +Subproject commit f0db32d23de36b9a8e0c48b4438d22ab076aca58 diff --git a/zcashd b/zcashd index 16ac743..cfcfcd9 160000 --- a/zcashd +++ b/zcashd @@ -1 +1 @@ -Subproject commit 16ac743764a513e41dafb2cd79c2417c5bb41e81 +Subproject commit cfcfcd93b06d2ee897f1d24eb62692c9e9e0e66d diff --git a/zebra b/zebra index 930fb7f..e04845f 160000 --- a/zebra +++ b/zebra @@ -1 +1 @@ -Subproject commit 930fb7f4b43a817b2a08d4a4ad94049efdf61887 +Subproject commit e04845f3e4a0976031f35c6564aa56ea4d722371 From 35d1adf1e5316c18b662fdc9f30bf4fbcc654b67 Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Thu, 12 Feb 2026 10:08:14 +0000 Subject: [PATCH 35/57] feat(docker): add monitoring stack with Prometheus, Grafana, Jaeger, AlertManager (#14) * feat(docker): add monitoring stack with Prometheus, Grafana, Jaeger, AlertManager Add optional observability stack enabled via Docker Compose profiles: - Prometheus (v3.2.0) for metrics collection - Grafana (11.5.1) with 14 pre-built Zebra dashboards - Jaeger (2.1.0) for distributed tracing (future Zebra releases) - AlertManager (v0.28.1) for alert routing Usage: docker compose --profile monitoring up -d Also updates: - Zebra image from 3.1.0 to 4.0.0 - README to reflect all services now use pre-built remote images - .env with monitoring configuration options * chore(docker): update Zebra to 4.1.0 --- .env | 26 +- .gitignore | 3 + README.md | 27 +- docker-compose.yml | 120 +- observability/README.md | 175 ++ observability/alertmanager/alertmanager.yml | 28 + observability/grafana/README.md | 123 + .../dashboards/block_verification.json | 529 ++++ .../dashboards/checkpoint_verification.json | 2084 ++++++++++++++++ observability/grafana/dashboards/errors.json | 469 ++++ observability/grafana/dashboards/mempool.json | 943 +++++++ .../grafana/dashboards/network_health.json | 2186 +++++++++++++++++ .../grafana/dashboards/network_messages.json | 868 +++++++ observability/grafana/dashboards/peers.json | 620 +++++ observability/grafana/dashboards/rocksdb.json | 925 +++++++ .../grafana/dashboards/rpc_metrics.json | 820 +++++++ .../grafana/dashboards/rpc_tracing.json | 639 +++++ observability/grafana/dashboards/syncer.json | 1319 ++++++++++ .../dashboards/transaction-verification.json | 978 ++++++++ .../grafana/dashboards/value_pools.json | 725 ++++++ .../grafana/dashboards/zebra_overview.json | 1047 ++++++++ .../provisioning/dashboards/default.yml | 13 + .../provisioning/datasources/datasources.yml | 19 + observability/jaeger/README.md | 344 +++ observability/jaeger/config.yaml | 86 + observability/prometheus/prometheus.yaml | 42 + .../prometheus/rules/zebra_alerts.yml | 149 ++ 27 files changed, 15287 insertions(+), 20 deletions(-) create mode 100644 observability/README.md create mode 100644 observability/alertmanager/alertmanager.yml create mode 100644 observability/grafana/README.md create mode 100644 observability/grafana/dashboards/block_verification.json create mode 100644 observability/grafana/dashboards/checkpoint_verification.json create mode 100644 observability/grafana/dashboards/errors.json create mode 100644 observability/grafana/dashboards/mempool.json create mode 100644 observability/grafana/dashboards/network_health.json create mode 100644 observability/grafana/dashboards/network_messages.json create mode 100644 observability/grafana/dashboards/peers.json create mode 100644 observability/grafana/dashboards/rocksdb.json create mode 100644 observability/grafana/dashboards/rpc_metrics.json create mode 100644 observability/grafana/dashboards/rpc_tracing.json create mode 100644 observability/grafana/dashboards/syncer.json create mode 100644 observability/grafana/dashboards/transaction-verification.json create mode 100644 observability/grafana/dashboards/value_pools.json create mode 100644 observability/grafana/dashboards/zebra_overview.json create mode 100644 observability/grafana/provisioning/dashboards/default.yml create mode 100644 observability/grafana/provisioning/datasources/datasources.yml create mode 100644 observability/jaeger/README.md create mode 100644 observability/jaeger/config.yaml create mode 100644 observability/prometheus/prometheus.yaml create mode 100644 observability/prometheus/rules/zebra_alerts.yml diff --git a/.env b/.env index a17fe1c..d2049bd 100644 --- a/.env +++ b/.env @@ -154,6 +154,30 @@ ZALLET_CONF_PATH=/etc/zallet/zallet.toml # Zallet application internal data directory ZALLET_DATA_DIR=/home/zallet/.data # Example path for a CA certificate file that Zallet might use to trust Zaino's gRPC TLS certificate. -# If Zaino uses a self-signed certificate or a certificate from a private CA, Zallet would need to be +# If Zaino uses a self-signed certificate or a certificate from a private CA, Zallet would need to be # configured to trust it. The actual environment variable name and mechanism depend on Zallet's implementation. # ZALLET_INDEXER_CA_PATH=/path/to/trusted/zaino_ca.crt + +# ============================================================================= +# Monitoring Configuration (--profile monitoring) +# ============================================================================= +# Enable monitoring with: docker compose --profile monitoring up -d +# +# To enable Zebra metrics, uncomment this variable: +ZEBRA_METRICS__ENDPOINT_ADDR=0.0.0.0:9999 +# +# To enable OpenTelemetry tracing (Jaeger), build Zebra with OTel support: +# docker compose build --build-arg FEATURES="default-release-binaries opentelemetry" zebra +# Then set the tracing endpoint: +# ZEBRA_TRACING__OPENTELEMETRY_ENDPOINT=http://jaeger:4318 +# ZEBRA_TRACING__OPENTELEMETRY_SERVICE_NAME=zebra +# ZEBRA_TRACING__OPENTELEMETRY_SAMPLE_PERCENT=100 +# +# Service ports (defaults shown, customize if needed): +# GRAFANA_PORT=3000 +# PROMETHEUS_PORT=9094 +# JAEGER_UI_PORT=16686 +# ALERTMANAGER_PORT=9093 +# +# Grafana admin password (default: admin, prompted to change on first login): +# GRAFANA_ADMIN_PASSWORD=your_secure_password diff --git a/.gitignore b/.gitignore index 5a50148..0ee1c67 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# macOS +.DS_Store + # Generated by Cargo # will have compiled files and executables debug/ diff --git a/README.md b/README.md index 1bf1b21..145a68b 100644 --- a/README.md +++ b/README.md @@ -108,37 +108,28 @@ docker compose ps ## Docker Images -> [!IMPORTANT] -> **Current Status:** Zaino and Zallet require local builds. Pre-built images are available for Zebra only. - ### Image Sources | Service | Image | Source | |---------|-------|--------| -| **Zebra** | `zfnd/zebra:3.1.0` | Pre-built from [ZcashFoundation/zebra](https://github.com/ZcashFoundation/zebra) | -| **Zaino** | `z3-zaino:local` | Must build locally from submodule | -| **Zallet** | `z3-zallet:local` | Must build locally from submodule | +| **Zebra** | `zfnd/zebra:4.1.0` | [ZcashFoundation/zebra](https://github.com/ZcashFoundation/zebra) | +| **Zaino** | `ghcr.io/zcashfoundation/zaino:sha-1871eba` | [zingolabs/zaino](https://github.com/zingolabs/zaino) | +| **Zallet** | `electriccoinco/zallet:v0.1.0-alpha.3` | [zcash/wallet](https://github.com/zcash/wallet) | -### Building Local Images +### Building Local Images (Optional) + +To build from local submodules instead of using pre-built images: ```bash # Initialize submodules git submodule update --init --recursive -# Build zaino and zallet -docker compose build zaino zallet +# Build all services locally +docker compose build ``` > [!NOTE] -> Local builds are required because Zaino and Zallet are under active development and require specific version pinning for compatibility. - -### Why Local Builds? - -Zallet embeds Zaino libraries internally. Both must use compatible versions of the Zaino codebase. The submodules in this repository are pinned to tested, compatible commits. - -**For production deployments**, use official release images when available: -- Zebra: [zfnd/zebra](https://hub.docker.com/r/zfnd/zebra) (stable releases) -- Zaino/Zallet: Official releases when published +> The submodules in this repository are pinned to tested, compatible commits if you prefer to build locally. ## Prerequisites diff --git a/docker-compose.yml b/docker-compose.yml index 2af5b9b..6c0446b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,11 +1,14 @@ services: zebra: # Run 'docker compose build' to build locally from ./zebra submodule instead - image: zfnd/zebra:3.1.0 + # For OpenTelemetry tracing: docker compose build --build-arg FEATURES="default-release-binaries opentelemetry" zebra + image: zfnd/zebra:4.1.0 build: context: ./zebra dockerfile: docker/Dockerfile target: runtime + args: + FEATURES: ${ZEBRA_BUILD_FEATURES:-default-release-binaries} container_name: z3_zebra restart: unless-stopped env_file: @@ -115,11 +118,124 @@ services: - z3_net # No healthcheck: distroless image has no shell/curl + # ============================================================================= + # Monitoring Stack (enabled with --profile monitoring) + # ============================================================================= + # Usage: docker compose --profile monitoring up -d + # Access: + # - Grafana: http://localhost:3000 (admin/admin) + # - Prometheus: http://localhost:9094 + # - Jaeger: http://localhost:16686 + # - AlertManager: http://localhost:9093 + + jaeger: + image: jaegertracing/jaeger:2.1.0 + container_name: z3_jaeger + profiles: [monitoring] + restart: unless-stopped + volumes: + - ./observability/jaeger/config.yaml:/etc/jaeger/config.yaml:ro + command: + - --config=/etc/jaeger/config.yaml + ports: + - "${JAEGER_UI_PORT:-16686}:16686" + - "${JAEGER_OTLP_GRPC_PORT:-4317}:4317" + - "${JAEGER_OTLP_HTTP_PORT:-4318}:4318" + - "${JAEGER_SPANMETRICS_PORT:-8889}:8889" + networks: + - z3_net + healthcheck: + test: ["CMD-SHELL", "wget -q --spider http://localhost:16686/ || exit 1"] + interval: 5s + timeout: 3s + retries: 10 + + prometheus: + image: prom/prometheus:v3.2.0 + container_name: z3_prometheus + profiles: [monitoring] + restart: unless-stopped + volumes: + - prometheus_data:/prometheus + - ./observability/prometheus/rules:/etc/prometheus/rules:ro + configs: + - source: prometheus_config + target: /etc/prometheus/prometheus.yml + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + - '--web.enable-lifecycle' + - '--web.enable-admin-api' + ports: + - "${PROMETHEUS_PORT:-9094}:9090" + networks: + - z3_net + depends_on: + jaeger: + condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "wget -q --spider http://localhost:9090/-/healthy || exit 1"] + interval: 10s + timeout: 3s + retries: 3 + + grafana: + image: grafana/grafana:11.5.1 + container_name: z3_grafana + profiles: [monitoring] + restart: unless-stopped + volumes: + - ./observability/grafana/dashboards:/var/lib/grafana/dashboards:ro + - ./observability/grafana/provisioning:/etc/grafana/provisioning:ro + - grafana_data:/var/lib/grafana + environment: + - GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH=/var/lib/grafana/dashboards/zebra_overview.json + - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin} + ports: + - "${GRAFANA_PORT:-3000}:3000" + networks: + - z3_net + depends_on: + prometheus: + condition: service_healthy + jaeger: + condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "wget -q --spider http://localhost:3000/api/health || exit 1"] + interval: 10s + timeout: 3s + retries: 3 + start_period: 10s + + alertmanager: + image: prom/alertmanager:v0.28.1 + container_name: z3_alertmanager + profiles: [monitoring] + restart: unless-stopped + volumes: + - ./observability/alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro + - alertmanager_data:/alertmanager + command: + - '--config.file=/etc/alertmanager/alertmanager.yml' + - '--storage.path=/alertmanager' + ports: + - "${ALERTMANAGER_PORT:-9093}:9093" + networks: + - z3_net + healthcheck: + test: ["CMD-SHELL", "wget -q --spider http://localhost:9093/-/healthy || exit 1"] + interval: 10s + timeout: 3s + retries: 3 + volumes: zebra_data: zaino_data: zallet_data: shared_cookie_volume: + prometheus_data: + grafana_data: + alertmanager_data: networks: z3_net: @@ -130,3 +246,5 @@ configs: file: ./config/tls/zaino.crt zaino_tls_key: file: ./config/tls/zaino.key + prometheus_config: + file: ./observability/prometheus/prometheus.yaml diff --git a/observability/README.md b/observability/README.md new file mode 100644 index 0000000..ed4c5d6 --- /dev/null +++ b/observability/README.md @@ -0,0 +1,175 @@ +# Z3 Observability Stack + +Metrics, alerting, and dashboards for the Z3 stack (Zebra, Zaino, Zallet). + +## Quick Start + +```bash +# 1. Enable Zebra metrics in .env (uncomment this line): +ZEBRA_METRICS__ENDPOINT_ADDR=0.0.0.0:9999 + +# 2. Start the full stack with monitoring +docker compose --profile monitoring up -d + +# 3. View logs +docker compose logs -f zebra +``` + +> **Note**: OpenTelemetry tracing requires building Zebra with the `opentelemetry` feature. +> The pre-built Docker image does not include it. See the [Tracing section](#tracing-jaeger) for build instructions. + +## Components + +| Component | Port | URL | Purpose | +|-----------|------|-----|---------| +| **Zebra** | 9999 | - | Zcash node with metrics and tracing | +| **Prometheus** | 9094 | | Metrics collection and storage | +| **Grafana** | 3000 | | Dashboards and visualization | +| **Jaeger** | 16686 | | Distributed tracing UI | +| **AlertManager** | 9093 | | Alert routing | + +Default Grafana credentials: `admin` / `admin` (you'll be prompted to change on first login) + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ Zebra Node │ +│ ┌─────────────────┐ ┌─────────────────────────────┐ │ +│ │ Metrics │ │ Tracing (OpenTelemetry) │ │ +│ │ :9999/metrics │ │ OTLP HTTP → Jaeger │ │ +│ └────────┬────────┘ └──────────────┬──────────────┘ │ +└───────────│──────────────────────────────────────│──────────────────┘ + │ │ + ▼ ▼ +┌───────────────────┐ ┌───────────────────────────┐ +│ Prometheus │ │ Jaeger │ +│ :9094 │ │ :16686 (UI) │ +│ │◄─────────────────│ :8889 (spanmetrics) │ +│ Scrapes metrics │ Span metrics │ :4318 (OTLP HTTP) │ +└─────────┬─────────┘ └───────────────────────────┘ + │ │ + ▼ │ +┌───────────────────┐ │ +│ Grafana │◄─────────────────────────────┘ +│ :3000 │ Trace queries +│ │ +│ Dashboards for │ +│ metrics + traces │ +└─────────┬─────────┘ + │ + ▼ +┌───────────────────┐ +│ AlertManager │ +│ :9093 │ +│ │ +│ Routes alerts │ +└───────────────────┘ +``` + +## What Each Component Provides + +### Metrics (Prometheus + Grafana) + +Quantitative data about Zebra's behavior over time: + +- **Network health**: Peer connections, bandwidth, message rates +- **Sync progress**: Block height, checkpoint verification, chain tip +- **Performance**: Block/transaction verification times +- **Resources**: Memory, connections, queue depths + +See [grafana/README.md](grafana/README.md) for dashboard details. + +### Tracing (Jaeger) + +Distributed tracing via OpenTelemetry. Requires building Zebra with the `opentelemetry` feature (not included in the pre-built image): + +```bash +# Build Zebra with OpenTelemetry support +docker compose build --build-arg FEATURES="default-release-binaries opentelemetry" zebra +``` + +Then enable tracing in `.env`: + +```bash +ZEBRA_TRACING__OPENTELEMETRY_ENDPOINT=http://jaeger:4318 +ZEBRA_TRACING__OPENTELEMETRY_SERVICE_NAME=zebra +ZEBRA_TRACING__OPENTELEMETRY_SAMPLE_PERCENT=100 +``` + +Jaeger provides: + +- **Distributed traces**: Follow a request through all components +- **Latency breakdown**: See where time is spent in each operation +- **Error analysis**: Identify failure points and error propagation +- **Service Performance Monitoring (SPM)**: RED metrics for RPC endpoints + +See [jaeger/README.md](jaeger/README.md) for tracing details. + +### Alerts (AlertManager) + +Automated notifications for operational issues: + +- Critical: Negative value pools (ZIP-209 violation) +- Warning: High RPC latency, sync stalls, peer connection issues + +Configure alert destinations in [alertmanager/alertmanager.yml](alertmanager/alertmanager.yml). + +## Configuration + +### Environment Variables + +Add this to your `.env` file to enable Zebra metrics: + +| Variable | Default | Description | +|----------|---------|-------------| +| `ZEBRA_METRICS__ENDPOINT_ADDR` | - | Prometheus metrics endpoint (e.g., `0.0.0.0:9999`) | + +### Port Customization + +Override default ports in `.env`: + +```bash +GRAFANA_PORT=3000 +PROMETHEUS_PORT=9094 +JAEGER_UI_PORT=16686 +ALERTMANAGER_PORT=9093 +``` + +## Common Tasks + +### View Zebra's current metrics + +```bash +curl -s http://localhost:9999/metrics | grep zcash +``` + +### Query Prometheus directly + +```bash +# Current block height +curl -s 'http://localhost:9094/api/v1/query?query=zcash_state_tip_height' +``` + +## Troubleshooting + +### No metrics in Grafana + +1. Verify `ZEBRA_METRICS__ENDPOINT_ADDR=0.0.0.0:9999` is set in `.env` +2. Restart Zebra: `docker compose restart zebra` +3. Check Zebra is exposing metrics: `docker compose exec zebra wget -qO- http://localhost:9999/metrics | head` +4. Check Prometheus targets: + +## Running Without Monitoring + +To run the Z3 stack without monitoring: + +```bash +docker compose up -d # Only starts zebra, zaino, zallet +``` + +To add monitoring later: + +```bash +docker compose --profile monitoring up -d +``` diff --git a/observability/alertmanager/alertmanager.yml b/observability/alertmanager/alertmanager.yml new file mode 100644 index 0000000..5b3351e --- /dev/null +++ b/observability/alertmanager/alertmanager.yml @@ -0,0 +1,28 @@ +global: + resolve_timeout: 5m + +route: + group_by: ['alertname', 'severity'] + group_wait: 10s + group_interval: 10s + repeat_interval: 1h + receiver: 'default' + routes: + - match: + severity: critical + receiver: 'critical' + - match: + severity: warning + receiver: 'warning' + +receivers: + - name: 'default' + - name: 'critical' + - name: 'warning' + +inhibit_rules: + - source_match: + severity: 'critical' + target_match: + severity: 'warning' + equal: ['alertname'] diff --git a/observability/grafana/README.md b/observability/grafana/README.md new file mode 100644 index 0000000..ed4455d --- /dev/null +++ b/observability/grafana/README.md @@ -0,0 +1,123 @@ +# Zebra Grafana Dashboards + +Pre-built dashboards for monitoring Zebra nodes. + +## Quick Start + +```bash +# From repository root - starts Zebra + all observability tools +docker compose -f docker/docker-compose.observability.yml up -d +``` + +Access Grafana at (admin/admin - you'll be prompted to change on first login). + +For full stack documentation, see the [Observability README](../README.md). + +## Dashboards + +| Dashboard | Description | +|-----------|-------------| +| `network_health.json` | Peer connections, bandwidth (default home) | +| `syncer.json` | Sync progress, block downloads | +| `mempool.json` | Transaction pool metrics | +| `peers.json` | Peer connection details | +| `block_verification.json` | Block verification stats | +| `checkpoint_verification.json` | Checkpoint sync progress | +| `transaction-verification.json` | Transaction verification | +| `network_messages.json` | P2P protocol messages | +| `errors.json` | Error tracking | + +## Datasources + +Grafana is provisioned with two datasources: + +| Datasource | UID | Description | +|------------|-----|-------------| +| Prometheus | `zebra-prometheus` | Metrics storage and queries | +| Jaeger | `zebra-jaeger` | Distributed tracing | + +Configuration: `provisioning/datasources/datasources.yml` + +### Using Jaeger in Grafana + +The Jaeger datasource allows you to: + +- Search traces by service name +- View trace details and span timelines +- Correlate traces with metrics (via trace IDs) + +To explore traces: + +1. Go to **Explore** in Grafana +2. Select **Jaeger** datasource +3. Search for service `zebra` + +Or access Jaeger UI directly at for full trace exploration. +See the [Jaeger README](../jaeger/README.md) for detailed tracing documentation. + +## Dashboard Configuration + +### Rate Window Requirements + +Dashboards use `rate()` functions for per-second metrics. The rate window must +contain at least 2 data points to calculate a rate. + +| Scrape Interval | Minimum Rate Window | +|-----------------|---------------------| +| 500ms | 1s | +| 15s (default) | 30s | +| 30s | 1m | + +Current dashboards use `[1m]` windows, compatible with the default 15s scrape interval. + +If you modify `../prometheus/prometheus.yaml` scrape_interval, update dashboard queries accordingly. + +### Job Label + +The `$job` variable in dashboards is populated from Prometheus. The default job +name is `zebra` (configured in `../prometheus/prometheus.yaml`). + +## Creating New Dashboards + +### Option 1: Grafana UI Export (Recommended) + +1. Create panel in Grafana UI +2. Click panel title → "Inspect" → "Panel JSON" +3. Add to dashboard file +4. Commit + +### Option 2: Copy Existing Panel + +1. Find similar panel in existing dashboard +2. Copy JSON, update metric names and titles +3. Test in Grafana + +### Panel Template + +```json +{ + "title": "Your Metric", + "type": "timeseries", + "targets": [ + { + "expr": "rate(your_metric_total[1m])", + "legendFormat": "{{label}}" + } + ], + "fieldConfig": { + "defaults": { + "unit": "reqps" + } + } +} +``` + +## Validation + +```bash +# Check JSON syntax +for f in dashboards/*.json; do jq . "$f" > /dev/null && echo "$f: OK"; done + +# List all metrics used +jq -r '.panels[].targets[]?.expr' dashboards/*.json | sort -u +``` diff --git a/observability/grafana/dashboards/block_verification.json b/observability/grafana/dashboards/block_verification.json new file mode 100644 index 0000000..0c99019 --- /dev/null +++ b/observability/grafana/dashboards/block_verification.json @@ -0,0 +1,529 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 1, + "links": [], + "liveNow": false, + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "decimals": 0, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 4, + "legend": { + "alignAsTable": false, + "avg": false, + "current": true, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "9.1.6", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "job", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "state_full_verifier_committed_block_height{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "full verified", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "state_checkpoint_finalized_block_height{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "checkpoint verified", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "editorMode": "code", + "expr": "state_memory_queued_max_height{job=\"$job\"}", + "hide": false, + "legendFormat": "full queued max", + "range": true, + "refId": "E" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "editorMode": "code", + "expr": "state_memory_queued_min_height{job=\"$job\"}", + "hide": false, + "legendFormat": "full queued min", + "range": true, + "refId": "I" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "editorMode": "code", + "expr": "state_checkpoint_queued_max_height{job=\"$job\"}", + "hide": false, + "legendFormat": "checkpoint queued max", + "range": true, + "refId": "F" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "editorMode": "code", + "expr": "state_checkpoint_queued_min_height{job=\"$job\"}", + "hide": false, + "legendFormat": "checkpoint queued min", + "range": true, + "refId": "J" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "editorMode": "code", + "expr": "state_memory_sent_block_height{job=\"$job\"}", + "hide": false, + "legendFormat": "full sent", + "range": true, + "refId": "H" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "editorMode": "code", + "expr": "state_checkpoint_sent_block_height{job=\"$job\"}", + "hide": false, + "legendFormat": "checkpoint sent", + "range": true, + "refId": "G" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "zcash_chain_verified_block_height{job=\"$job\"}", + "interval": "", + "legendFormat": "committed", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "editorMode": "code", + "exemplar": true, + "expr": "state_finalized_block_height{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "finalized", + "range": true, + "refId": "D" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Verified Block Height - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:84", + "format": "none", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:85", + "format": "none", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 7 + }, + "hiddenSeries": false, + "id": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "9.1.6", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "job", + "repeatDirection": "h", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "rate(zcash_chain_verified_block_total{job=\"$job\"}[1m])", + "interval": "", + "legendFormat": "zcash_chain_verified_block_total[1m]", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "rate(sync_downloaded_block_count{job=\"$job\"}[1m])", + "interval": "", + "legendFormat": "sync_downloaded_block_count", + "refId": "H" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "sync_downloads_in_flight{job=\"$job\"}", + "interval": "", + "legendFormat": "sync_downloads_in_flight", + "refId": "I" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "rate(sync_verified_block_count{job=\"$job\"}[1m])", + "interval": "", + "legendFormat": "sync_verified_block_count", + "refId": "J" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Block Sync Count - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:167", + "format": "short", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:168", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 14 + }, + "hiddenSeries": false, + "id": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "9.1.6", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "job", + "repeatDirection": "h", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "rate(zcash_chain_verified_block_total{job=\"$job\"}[1m])", + "interval": "", + "legendFormat": "zcash_chain_verified_block_total[1m]", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "rate(gossip_downloaded_block_count{job=\"$job\"}[1m])", + "interval": "", + "legendFormat": "gossip_downloaded_block_count[1m]", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "rate(gossip_verified_block_count{job=\"$job\"}[1m])", + "interval": "", + "legendFormat": "gossip_verified_block_count[1m]", + "refId": "D" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "gossip_queued_block_count{job=\"$job\"}", + "interval": "", + "legendFormat": "gossip_queued_block_count", + "refId": "E" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Block Gossip Count - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "mode": "time", + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:252", + "format": "short", + "logBase": 1, + "show": true + }, + { + "$$hashKey": "object:253", + "format": "short", + "logBase": 1, + "show": true + } + ], + "yaxis": { + "align": false + } + } + ], + "refresh": "1m", + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "definition": "label_values(zcash_chain_verified_block_height, job)", + "hide": 0, + "includeAll": true, + "multi": true, + "name": "job", + "options": [], + "query": { + "query": "label_values(zcash_chain_verified_block_height, job)", + "refId": "Prometheus-Zebra-job-Variable-Query" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-30m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "block verification", + "uid": "rO_Cl5tGz", + "version": 18, + "weekStart": "" +} diff --git a/observability/grafana/dashboards/checkpoint_verification.json b/observability/grafana/dashboards/checkpoint_verification.json new file mode 100644 index 0000000..3ab5d3e --- /dev/null +++ b/observability/grafana/dashboards/checkpoint_verification.json @@ -0,0 +1,2084 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 2, + "iteration": 1633652714499, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "decimals": null, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 2, + "legend": { + "avg": false, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "sideWidth": null, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "job", + "repeatDirection": "h", + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-mainnet", + "value": "zebrad-mainnet" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "checkpoint_processing_next_height{job=\"$job\"}", + "interval": "", + "legendFormat": "next_check", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "checkpoint_queued_continuous_height{job=\"$job\"}", + "interval": "", + "legendFormat": "queue_cont", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "checkpoint_verified_height{job=\"$job\"}", + "interval": "", + "legendFormat": "verified", + "refId": "C", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "checkpoint_queued_max_height{job=\"$job\"}", + "interval": "", + "legendFormat": "queue_max", + "refId": "D", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "state_checkpoint_committed_block_height{job=\"$job\"}", + "interval": "", + "legendFormat": "state_commit", + "refId": "E", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "state_checkpoint_queued_max_height{job=\"$job\"}", + "interval": "", + "legendFormat": "state_q_max", + "refId": "F", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Checkpoint Height - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:84", + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:85", + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "decimals": null, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 6, + "x": 6, + "y": 0 + }, + "hiddenSeries": false, + "id": 9, + "legend": { + "avg": false, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "sideWidth": null, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeatDirection": "h", + "repeatIteration": 1633652714499, + "repeatPanelId": 2, + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-mainnet-tmp", + "value": "zebrad-mainnet-tmp" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "checkpoint_processing_next_height{job=\"$job\"}", + "interval": "", + "legendFormat": "next_check", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "checkpoint_queued_continuous_height{job=\"$job\"}", + "interval": "", + "legendFormat": "queue_cont", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "checkpoint_verified_height{job=\"$job\"}", + "interval": "", + "legendFormat": "verified", + "refId": "C", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "checkpoint_queued_max_height{job=\"$job\"}", + "interval": "", + "legendFormat": "queue_max", + "refId": "D", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "state_checkpoint_committed_block_height{job=\"$job\"}", + "interval": "", + "legendFormat": "state_commit", + "refId": "E", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "state_checkpoint_queued_max_height{job=\"$job\"}", + "interval": "", + "legendFormat": "state_q_max", + "refId": "F", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Checkpoint Height - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:84", + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:85", + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "decimals": null, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 6, + "x": 12, + "y": 0 + }, + "hiddenSeries": false, + "id": 10, + "legend": { + "avg": false, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "sideWidth": null, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeatDirection": "h", + "repeatIteration": 1633652714499, + "repeatPanelId": 2, + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-testnet", + "value": "zebrad-testnet" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "checkpoint_processing_next_height{job=\"$job\"}", + "interval": "", + "legendFormat": "next_check", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "checkpoint_queued_continuous_height{job=\"$job\"}", + "interval": "", + "legendFormat": "queue_cont", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "checkpoint_verified_height{job=\"$job\"}", + "interval": "", + "legendFormat": "verified", + "refId": "C", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "checkpoint_queued_max_height{job=\"$job\"}", + "interval": "", + "legendFormat": "queue_max", + "refId": "D", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "state_checkpoint_committed_block_height{job=\"$job\"}", + "interval": "", + "legendFormat": "state_commit", + "refId": "E", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "state_checkpoint_queued_max_height{job=\"$job\"}", + "interval": "", + "legendFormat": "state_q_max", + "refId": "F", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Checkpoint Height - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:84", + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:85", + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "decimals": null, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 0 + }, + "hiddenSeries": false, + "id": 11, + "legend": { + "avg": false, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "sideWidth": null, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeatDirection": "h", + "repeatIteration": 1633652714499, + "repeatPanelId": 2, + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-testnet-tmp", + "value": "zebrad-testnet-tmp" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "checkpoint_processing_next_height{job=\"$job\"}", + "interval": "", + "legendFormat": "next_check", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "checkpoint_queued_continuous_height{job=\"$job\"}", + "interval": "", + "legendFormat": "queue_cont", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "checkpoint_verified_height{job=\"$job\"}", + "interval": "", + "legendFormat": "verified", + "refId": "C", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "checkpoint_queued_max_height{job=\"$job\"}", + "interval": "", + "legendFormat": "queue_max", + "refId": "D", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "state_checkpoint_committed_block_height{job=\"$job\"}", + "interval": "", + "legendFormat": "state_commit", + "refId": "E", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "state_checkpoint_queued_max_height{job=\"$job\"}", + "interval": "", + "legendFormat": "state_q_max", + "refId": "F", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Checkpoint Height - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:84", + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:85", + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 7 + }, + "hiddenSeries": false, + "id": 8, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "job", + "repeatDirection": "h", + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-mainnet", + "value": "zebrad-mainnet" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(checkpoint_verified_block_count[1m])", + "interval": "", + "legendFormat": "checkpoint verify rate [1m]", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "rate(state_checkpoint_committed_block_count[1m])", + "interval": "", + "legendFormat": "state commit rate [1m]", + "refId": "C", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "rate(sync_downloaded_block_count[1m])", + "interval": "", + "legendFormat": "sync download rate [1m]", + "refId": "E", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "rate(gossip_downloaded_block_count[1m])", + "interval": "", + "legendFormat": "gossip download rate [1m]", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Checkpoint Pipeline Throughput - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:252", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:253", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 6, + "x": 6, + "y": 7 + }, + "hiddenSeries": false, + "id": 12, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeatDirection": "h", + "repeatIteration": 1633652714499, + "repeatPanelId": 8, + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-mainnet-tmp", + "value": "zebrad-mainnet-tmp" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(checkpoint_verified_block_count[1m])", + "interval": "", + "legendFormat": "checkpoint verify rate [1m]", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "rate(state_checkpoint_committed_block_count[1m])", + "interval": "", + "legendFormat": "state commit rate [1m]", + "refId": "C", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "rate(sync_downloaded_block_count[1m])", + "interval": "", + "legendFormat": "sync download rate [1m]", + "refId": "E", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "rate(gossip_downloaded_block_count[1m])", + "interval": "", + "legendFormat": "gossip download rate [1m]", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Checkpoint Pipeline Throughput - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:252", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:253", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 6, + "x": 12, + "y": 7 + }, + "hiddenSeries": false, + "id": 13, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeatDirection": "h", + "repeatIteration": 1633652714499, + "repeatPanelId": 8, + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-testnet", + "value": "zebrad-testnet" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(checkpoint_verified_block_count[1m])", + "interval": "", + "legendFormat": "checkpoint verify rate [1m]", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "rate(state_checkpoint_committed_block_count[1m])", + "interval": "", + "legendFormat": "state commit rate [1m]", + "refId": "C", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "rate(sync_downloaded_block_count[1m])", + "interval": "", + "legendFormat": "sync download rate [1m]", + "refId": "E", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "rate(gossip_downloaded_block_count[1m])", + "interval": "", + "legendFormat": "gossip download rate [1m]", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Checkpoint Pipeline Throughput - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:252", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:253", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 7 + }, + "hiddenSeries": false, + "id": 14, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeatDirection": "h", + "repeatIteration": 1633652714499, + "repeatPanelId": 8, + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-testnet-tmp", + "value": "zebrad-testnet-tmp" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(checkpoint_verified_block_count[1m])", + "interval": "", + "legendFormat": "checkpoint verify rate [1m]", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "rate(state_checkpoint_committed_block_count[1m])", + "interval": "", + "legendFormat": "state commit rate [1m]", + "refId": "C", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "rate(sync_downloaded_block_count[1m])", + "interval": "", + "legendFormat": "sync download rate [1m]", + "refId": "E", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "rate(gossip_downloaded_block_count[1m])", + "interval": "", + "legendFormat": "gossip download rate [1m]", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Checkpoint Pipeline Throughput - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:252", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:253", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 14 + }, + "hiddenSeries": false, + "id": 4, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "job", + "repeatDirection": "h", + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-mainnet", + "value": "zebrad-mainnet" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "checkpoint_queued_slots{job=\"$job\"}", + "interval": "", + "legendFormat": "checkpoint_queued_slots", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "state_checkpoint_queued_block_count{job=\"$job\"}", + "interval": "", + "legendFormat": "state_finalized_queued_block_count", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "sync_prospective_tips_len{job=\"$job\"}", + "interval": "", + "legendFormat": "sync_prospective_tips_len", + "refId": "C", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "sync_downloads_in_flight{job=\"$job\"}", + "interval": "", + "legendFormat": "sync_downloads_in_flight", + "refId": "D", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "sync_pending_blocks_len{job=\"$job\"}", + "interval": "", + "legendFormat": "sync_pending_blocks_len", + "refId": "E", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "rate(sync_cancelled_download_count{job=\"$job\"}[1m])", + "interval": "", + "legendFormat": "sync_cancelled_download_count[1m]", + "refId": "F", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "rate(sync_cancelled_verify_count{job=\"$job\"}[1m])", + "interval": "", + "legendFormat": "sync_cancelled_verify_count[1m]", + "refId": "G", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "rate(gossip_queued_block_count{job=\"$job\"}[1m])", + "interval": "", + "legendFormat": "gossip_queued_block_count[1m]", + "refId": "H", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Checkpoint Queues - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:337", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:338", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 6, + "x": 6, + "y": 14 + }, + "hiddenSeries": false, + "id": 15, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeatDirection": "h", + "repeatIteration": 1633652714499, + "repeatPanelId": 4, + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-mainnet-tmp", + "value": "zebrad-mainnet-tmp" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "checkpoint_queued_slots{job=\"$job\"}", + "interval": "", + "legendFormat": "checkpoint_queued_slots", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "state_checkpoint_queued_block_count{job=\"$job\"}", + "interval": "", + "legendFormat": "state_finalized_queued_block_count", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "sync_prospective_tips_len{job=\"$job\"}", + "interval": "", + "legendFormat": "sync_prospective_tips_len", + "refId": "C", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "sync_downloads_in_flight{job=\"$job\"}", + "interval": "", + "legendFormat": "sync_downloads_in_flight", + "refId": "D", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "sync_pending_blocks_len{job=\"$job\"}", + "interval": "", + "legendFormat": "sync_pending_blocks_len", + "refId": "E", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "rate(sync_cancelled_download_count{job=\"$job\"}[1m])", + "interval": "", + "legendFormat": "sync_cancelled_download_count[1m]", + "refId": "F", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "rate(sync_cancelled_verify_count{job=\"$job\"}[1m])", + "interval": "", + "legendFormat": "sync_cancelled_verify_count[1m]", + "refId": "G", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "rate(gossip_queued_block_count{job=\"$job\"}[1m])", + "interval": "", + "legendFormat": "gossip_queued_block_count[1m]", + "refId": "H", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Checkpoint Queues - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:337", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:338", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 6, + "x": 12, + "y": 14 + }, + "hiddenSeries": false, + "id": 16, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeatDirection": "h", + "repeatIteration": 1633652714499, + "repeatPanelId": 4, + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-testnet", + "value": "zebrad-testnet" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "checkpoint_queued_slots{job=\"$job\"}", + "interval": "", + "legendFormat": "checkpoint_queued_slots", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "state_checkpoint_queued_block_count{job=\"$job\"}", + "interval": "", + "legendFormat": "state_finalized_queued_block_count", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "sync_prospective_tips_len{job=\"$job\"}", + "interval": "", + "legendFormat": "sync_prospective_tips_len", + "refId": "C", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "sync_downloads_in_flight{job=\"$job\"}", + "interval": "", + "legendFormat": "sync_downloads_in_flight", + "refId": "D", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "sync_pending_blocks_len{job=\"$job\"}", + "interval": "", + "legendFormat": "sync_pending_blocks_len", + "refId": "E", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "rate(sync_cancelled_download_count{job=\"$job\"}[1m])", + "interval": "", + "legendFormat": "sync_cancelled_download_count[1m]", + "refId": "F", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "rate(sync_cancelled_verify_count{job=\"$job\"}[1m])", + "interval": "", + "legendFormat": "sync_cancelled_verify_count[1m]", + "refId": "G", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "rate(gossip_queued_block_count{job=\"$job\"}[1m])", + "interval": "", + "legendFormat": "gossip_queued_block_count[1m]", + "refId": "H", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Checkpoint Queues - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:337", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:338", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 14 + }, + "hiddenSeries": false, + "id": 17, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeatDirection": "h", + "repeatIteration": 1633652714499, + "repeatPanelId": 4, + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-testnet-tmp", + "value": "zebrad-testnet-tmp" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "checkpoint_queued_slots{job=\"$job\"}", + "interval": "", + "legendFormat": "checkpoint_queued_slots", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "state_checkpoint_queued_block_count{job=\"$job\"}", + "interval": "", + "legendFormat": "state_finalized_queued_block_count", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "sync_prospective_tips_len{job=\"$job\"}", + "interval": "", + "legendFormat": "sync_prospective_tips_len", + "refId": "C", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "sync_downloads_in_flight{job=\"$job\"}", + "interval": "", + "legendFormat": "sync_downloads_in_flight", + "refId": "D", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "sync_pending_blocks_len{job=\"$job\"}", + "interval": "", + "legendFormat": "sync_pending_blocks_len", + "refId": "E", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "rate(sync_cancelled_download_count{job=\"$job\"}[1m])", + "interval": "", + "legendFormat": "sync_cancelled_download_count[1m]", + "refId": "F", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "rate(sync_cancelled_verify_count{job=\"$job\"}[1m])", + "interval": "", + "legendFormat": "sync_cancelled_verify_count[1m]", + "refId": "G", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "rate(gossip_queued_block_count{job=\"$job\"}[1m])", + "interval": "", + "legendFormat": "gossip_queued_block_count[1m]", + "refId": "H", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Checkpoint Queues - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:337", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:338", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "5s", + "schemaVersion": 27, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": null, + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "definition": "label_values(sync_prospective_tips_len, job)", + "description": null, + "error": null, + "hide": 0, + "includeAll": true, + "label": null, + "multi": true, + "name": "job", + "options": [], + "query": { + "query": "label_values(sync_prospective_tips_len, job)", + "refId": "Prometheus-Zebra-job-Variable-Query" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-30m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "checkpoint verification", + "uid": "o4LmN_OMk", + "version": 12 +} diff --git a/observability/grafana/dashboards/errors.json b/observability/grafana/dashboards/errors.json new file mode 100644 index 0000000..c5bdfde --- /dev/null +++ b/observability/grafana/dashboards/errors.json @@ -0,0 +1,469 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 6, + "iteration": 1616480577841, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 8, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.2.2", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "job", + "scopedVars": { + "job": { + "selected": true, + "text": "zebrad-mainnet", + "value": "zebrad-mainnet" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "zebra_error_sapling_binding{job=\"$job\"}", + "interval": "", + "legendFormat": "", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "zebra_sighash_error_sapling_spend{job=\"$job\"}", + "interval": "", + "legendFormat": "", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "zebra_sighash_error_sprout_joinsplit{job=\"$job\"}", + "interval": "", + "legendFormat": "", + "refId": "C", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Sighash Errors - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 8, + "x": 8, + "y": 0 + }, + "hiddenSeries": false, + "id": 3, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.2.2", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeatIteration": 1616480577841, + "repeatPanelId": 2, + "scopedVars": { + "job": { + "selected": true, + "text": "zebrad-mainnet-tmp-1", + "value": "zebrad-mainnet-tmp-1" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "zebra_error_sapling_binding{job=\"$job\"}", + "interval": "", + "legendFormat": "", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "zebra_sighash_error_sapling_spend{job=\"$job\"}", + "interval": "", + "legendFormat": "", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "zebra_sighash_error_sprout_joinsplit{job=\"$job\"}", + "interval": "", + "legendFormat": "", + "refId": "C", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Sighash Errors - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 8, + "x": 16, + "y": 0 + }, + "hiddenSeries": false, + "id": 4, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.2.2", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeatIteration": 1616480577841, + "repeatPanelId": 2, + "scopedVars": { + "job": { + "selected": true, + "text": "zebrad-testnet-tmp-1", + "value": "zebrad-testnet-tmp-1" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "zebra_error_sapling_binding{job=\"$job\"}", + "interval": "", + "legendFormat": "", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "zebra_sighash_error_sapling_spend{job=\"$job\"}", + "interval": "", + "legendFormat": "", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "zebra_sighash_error_sprout_joinsplit{job=\"$job\"}", + "interval": "", + "legendFormat": "", + "refId": "C", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Sighash Errors - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "schemaVersion": 26, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": null, + "current": { + "selected": true, + "tags": [], + "text": [ + "zebrad-mainnet", + "zebrad-mainnet-tmp-1", + "zebrad-testnet-tmp-1" + ], + "value": [ + "zebrad-mainnet", + "zebrad-mainnet-tmp-1", + "zebrad-testnet-tmp-1" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "definition": "label_values(zcash_net_peers, job)", + "hide": 0, + "includeAll": true, + "label": null, + "multi": true, + "name": "job", + "options": [], + "query": "label_values(zcash_net_peers, job)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "errors", + "uid": "IhbO11wGk", + "version": 4 +} diff --git a/observability/grafana/dashboards/mempool.json b/observability/grafana/dashboards/mempool.json new file mode 100644 index 0000000..606eb00 --- /dev/null +++ b/observability/grafana/dashboards/mempool.json @@ -0,0 +1,943 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "", + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 15, + "iteration": 1634239984015, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byFrameRefID", + "options": "Transactions" + }, + "properties": [ + { + "id": "displayName", + "value": "transactions" + } + ] + }, + { + "matcher": { + "id": "byFrameRefID", + "options": "Serialized Bytes" + }, + "properties": [ + { + "id": "displayName", + "value": "serialized bytes" + } + ] + } + ] + }, + "fill": 1, + "fillGradient": 1, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.1.2", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "job", + "seriesOverrides": [ + { + "$$hashKey": "object:232", + "alias": "transactions", + "yaxis": 1 + }, + { + "$$hashKey": "object:239", + "alias": "serialized bytes", + "yaxis": 2 + }, + { + "alias": "rejected serialized bytes", + "yaxis": 2 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "zcash_mempool_size_transactions{job=\"$job\"}", + "interval": "", + "legendFormat": "transactions", + "refId": "Transactions", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "zcash_mempool_size_bytes{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": " serialized bytes", + "refId": "Serialized Bytes", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "mempool_currently_queued_transactions{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "queued transactions", + "refId": "Queued Transactions", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Mempool Storage - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:65", + "format": "none", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:66", + "decimals": null, + "format": "decbytes", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [ + { + "matcher": { + "id": "byFrameRefID", + "options": "Transactions" + }, + "properties": [ + { + "id": "displayName", + "value": "transactions" + } + ] + }, + { + "matcher": { + "id": "byFrameRefID", + "options": "Serialized Bytes" + }, + "properties": [ + { + "id": "displayName", + "value": "serialized bytes" + } + ] + } + ] + }, + "fill": 1, + "fillGradient": 1, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 9 + }, + "hiddenSeries": false, + "id": 9, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.1.2", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:232", + "alias": "transactions", + "yaxis": 1 + }, + { + "$$hashKey": "object:239", + "alias": "serialized bytes", + "yaxis": 2 + }, + { + "alias": "rejected serialized bytes", + "yaxis": 2 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "mempool_rejected_transaction_ids{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "rejected transactions", + "refId": "Rejected Transactions IDs", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "mempool_rejected_transaction_ids_bytes{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "rejected serialized bytes", + "refId": "Rejected Serialized TXID Bytes", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Mempool Rejected Storage - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:65", + "format": "none", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:66", + "decimals": null, + "format": "decbytes", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 18 + }, + "hiddenSeries": false, + "id": 7, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.1.2", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "job", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(sync_downloaded_block_count{job=\"$job\"}[1m])", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "sync download", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "rate(zcash_chain_verified_block_total{job=\"$job\"}[1m])", + "interval": "", + "legendFormat": "state commit", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Block Rates", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:80", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:81", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "description": "", + "fill": 1, + "fillGradient": 1, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 26 + }, + "hiddenSeries": false, + "id": 8, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.1.2", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:232", + "alias": "transactions", + "yaxis": 1 + }, + { + "$$hashKey": "object:239", + "alias": "serialized bytes", + "yaxis": 2 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "mempool_queued_transactions_total{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "queued", + "refId": "Queued", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "mempool_downloaded_transactions_total{job=\"$job\"}", + "interval": "", + "legendFormat": "downloaded", + "refId": "Downloaded", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "mempool_pushed_transactions_total{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "pushed", + "refId": "Pushed", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "mempool_verified_transactions_total{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "verified", + "refId": "Verified", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "mempool_cancelled_verify_tasks_total{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "cancelled", + "refId": "Cancelled", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "mempool_failed_verify_tasks_total{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "failed - {{reason}}", + "refId": "Failed", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "mempool_gossiped_transactions_total{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "gossiped", + "refId": "Gossiped", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Transaction Downloader and Verifier, Gossiper - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:65", + "format": "none", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:66", + "decimals": null, + "format": "decbytes", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 35 + }, + "hiddenSeries": false, + "id": 12, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.1.2", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeatDirection": "h", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(mempool_downloaded_transactions_total{job=\"$job\"}[1m]) * 60", + "interval": "", + "legendFormat": "downloaded per min", + "refId": "Downloaded", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "rate(mempool_verified_transactions_total{job=\"$job\"}[1m]) * 60", + "interval": "", + "legendFormat": "verified per min", + "refId": "Verified", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "rate(mempool_queued_transactions_total{job=\"$job\"}[1m]) * 60", + "interval": "", + "legendFormat": "queued per min", + "refId": "Queued", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Transaction Downloader and Verifier (Rates) - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:1174", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:1175", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "description": "", + "fill": 1, + "fillGradient": 1, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 42 + }, + "hiddenSeries": false, + "id": 10, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.1.2", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:232", + "alias": "transactions", + "yaxis": 1 + }, + { + "$$hashKey": "object:239", + "alias": "serialized bytes", + "yaxis": 2 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by (version) (mempool_downloaded_transactions_total{job=\"$job\"})", + "hide": false, + "interval": "", + "legendFormat": "{{version}}", + "refId": "Downloaded", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Downloaded Txs by Version - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:65", + "format": "none", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:66", + "decimals": null, + "format": "decbytes", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "5s", + "schemaVersion": 30, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": null, + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": null, + "definition": "label_values(zcash_net_in_bytes_total, job)", + "description": null, + "error": null, + "hide": 0, + "includeAll": true, + "label": null, + "multi": true, + "name": "job", + "options": [], + "query": { + "query": "label_values(zcash_net_in_bytes_total, job)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "mempool", + "uid": "wVXGE6v7z", + "version": 8 +} diff --git a/observability/grafana/dashboards/network_health.json b/observability/grafana/dashboards/network_health.json new file mode 100644 index 0000000..cf3035f --- /dev/null +++ b/observability/grafana/dashboards/network_health.json @@ -0,0 +1,2186 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 4, + "iteration": 1639361606831, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "job", + "repeatDirection": "h", + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-mainnet", + "value": "zebrad-mainnet" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(zcash_net_in_bytes_total{job=\"$job\"}[1m]))", + "hide": false, + "interval": "", + "legendFormat": "bytes read [1m]", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "sum(rate(zcash_net_out_bytes_total{job=\"$job\"}[1m]))", + "interval": "", + "legendFormat": "bytes written [1m]", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "bytes - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:93", + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:94", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 6, + "x": 6, + "y": 0 + }, + "hiddenSeries": false, + "id": 12, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeatDirection": "h", + "repeatIteration": 1639361606831, + "repeatPanelId": 2, + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-mainnet-tmp", + "value": "zebrad-mainnet-tmp" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(zcash_net_in_bytes_total{job=\"$job\"}[1m]))", + "hide": false, + "interval": "", + "legendFormat": "bytes read [1m]", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "sum(rate(zcash_net_out_bytes_total{job=\"$job\"}[1m]))", + "interval": "", + "legendFormat": "bytes written [1m]", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "bytes - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:93", + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:94", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 6, + "x": 12, + "y": 0 + }, + "hiddenSeries": false, + "id": 13, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeatDirection": "h", + "repeatIteration": 1639361606831, + "repeatPanelId": 2, + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-testnet", + "value": "zebrad-testnet" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(zcash_net_in_bytes_total{job=\"$job\"}[1m]))", + "hide": false, + "interval": "", + "legendFormat": "bytes read [1m]", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "sum(rate(zcash_net_out_bytes_total{job=\"$job\"}[1m]))", + "interval": "", + "legendFormat": "bytes written [1m]", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "bytes - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:93", + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:94", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 6, + "x": 18, + "y": 0 + }, + "hiddenSeries": false, + "id": 14, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeatDirection": "h", + "repeatIteration": 1639361606831, + "repeatPanelId": 2, + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-testnet-tmp", + "value": "zebrad-testnet-tmp" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(zcash_net_in_bytes_total{job=\"$job\"}[1m]))", + "hide": false, + "interval": "", + "legendFormat": "bytes read [1m]", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "sum(rate(zcash_net_out_bytes_total{job=\"$job\"}[1m]))", + "interval": "", + "legendFormat": "bytes written [1m]", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "bytes - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:93", + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:94", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 6 + }, + "hiddenSeries": false, + "id": 6, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "job", + "repeatDirection": "h", + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-mainnet", + "value": "zebrad-mainnet" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "zcash_net_peers{job=\"$job\"}", + "interval": "", + "legendFormat": "total peers", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "pool_num_ready{job=\"$job\"}", + "interval": "", + "legendFormat": "ready peers", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "pool_num_unready{job=\"$job\"}", + "interval": "", + "legendFormat": "unready peers", + "refId": "C", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "peer readiness - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 6, + "x": 6, + "y": 6 + }, + "hiddenSeries": false, + "id": 15, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeatDirection": "h", + "repeatIteration": 1639361606831, + "repeatPanelId": 6, + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-mainnet-tmp", + "value": "zebrad-mainnet-tmp" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "zcash_net_peers{job=\"$job\"}", + "interval": "", + "legendFormat": "total peers", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "pool_num_ready{job=\"$job\"}", + "interval": "", + "legendFormat": "ready peers", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "pool_num_unready{job=\"$job\"}", + "interval": "", + "legendFormat": "unready peers", + "refId": "C", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "peer readiness - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 6, + "x": 12, + "y": 6 + }, + "hiddenSeries": false, + "id": 16, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeatDirection": "h", + "repeatIteration": 1639361606831, + "repeatPanelId": 6, + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-testnet", + "value": "zebrad-testnet" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "zcash_net_peers{job=\"$job\"}", + "interval": "", + "legendFormat": "total peers", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "pool_num_ready{job=\"$job\"}", + "interval": "", + "legendFormat": "ready peers", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "pool_num_unready{job=\"$job\"}", + "interval": "", + "legendFormat": "unready peers", + "refId": "C", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "peer readiness - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 6, + "x": 18, + "y": 6 + }, + "hiddenSeries": false, + "id": 17, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeatDirection": "h", + "repeatIteration": 1639361606831, + "repeatPanelId": 6, + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-testnet-tmp", + "value": "zebrad-testnet-tmp" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "zcash_net_peers{job=\"$job\"}", + "interval": "", + "legendFormat": "total peers", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "pool_num_ready{job=\"$job\"}", + "interval": "", + "legendFormat": "ready peers", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "pool_num_unready{job=\"$job\"}", + "interval": "", + "legendFormat": "unready peers", + "refId": "C", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "peer readiness - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 6, + "x": 0, + "y": 12 + }, + "hiddenSeries": false, + "id": 7, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "job", + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-mainnet", + "value": "zebrad-mainnet" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "candidate_set_disconnected{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "recently stopped peers", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "candidate_set_failed{job=\"$job\"}", + "interval": "", + "legendFormat": "failed candidates", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "candidate_set_gossiped{job=\"$job\"}", + "interval": "", + "legendFormat": "never attempted candidates", + "refId": "C", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "candidate_set_pending{job=\"$job\"}", + "interval": "", + "legendFormat": "connection attempt pending", + "refId": "D", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "candidate_set_responded{job=\"$job\"}", + "interval": "", + "legendFormat": "recent peers", + "refId": "E", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "candidate_set_recently_live{job=\"$job\"}", + "interval": "", + "legendFormat": "recently live peers", + "refId": "F", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "candidate set - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 6, + "x": 6, + "y": 12 + }, + "hiddenSeries": false, + "id": 18, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeatIteration": 1639361606831, + "repeatPanelId": 7, + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-mainnet-tmp", + "value": "zebrad-mainnet-tmp" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "candidate_set_disconnected{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "recently stopped peers", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "candidate_set_failed{job=\"$job\"}", + "interval": "", + "legendFormat": "failed candidates", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "candidate_set_gossiped{job=\"$job\"}", + "interval": "", + "legendFormat": "never attempted candidates", + "refId": "C", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "candidate_set_pending{job=\"$job\"}", + "interval": "", + "legendFormat": "connection attempt pending", + "refId": "D", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "candidate_set_responded{job=\"$job\"}", + "interval": "", + "legendFormat": "recent peers", + "refId": "E", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "candidate_set_recently_live{job=\"$job\"}", + "interval": "", + "legendFormat": "recently live peers", + "refId": "F", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "candidate set - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 6, + "x": 12, + "y": 12 + }, + "hiddenSeries": false, + "id": 19, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeatIteration": 1639361606831, + "repeatPanelId": 7, + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-testnet", + "value": "zebrad-testnet" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "candidate_set_disconnected{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "recently stopped peers", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "candidate_set_failed{job=\"$job\"}", + "interval": "", + "legendFormat": "failed candidates", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "candidate_set_gossiped{job=\"$job\"}", + "interval": "", + "legendFormat": "never attempted candidates", + "refId": "C", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "candidate_set_pending{job=\"$job\"}", + "interval": "", + "legendFormat": "connection attempt pending", + "refId": "D", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "candidate_set_responded{job=\"$job\"}", + "interval": "", + "legendFormat": "recent peers", + "refId": "E", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "candidate_set_recently_live{job=\"$job\"}", + "interval": "", + "legendFormat": "recently live peers", + "refId": "F", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "candidate set - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 6, + "x": 18, + "y": 12 + }, + "hiddenSeries": false, + "id": 20, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeatIteration": 1639361606831, + "repeatPanelId": 7, + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-testnet-tmp", + "value": "zebrad-testnet-tmp" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "candidate_set_disconnected{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "recently stopped peers", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "candidate_set_failed{job=\"$job\"}", + "interval": "", + "legendFormat": "failed candidates", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "candidate_set_gossiped{job=\"$job\"}", + "interval": "", + "legendFormat": "never attempted candidates", + "refId": "C", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "candidate_set_pending{job=\"$job\"}", + "interval": "", + "legendFormat": "connection attempt pending", + "refId": "D", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "candidate_set_responded{job=\"$job\"}", + "interval": "", + "legendFormat": "recent peers", + "refId": "E", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "expr": "candidate_set_recently_live{job=\"$job\"}", + "interval": "", + "legendFormat": "recently live peers", + "refId": "F", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "candidate set - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 18 + }, + "hiddenSeries": false, + "id": 11, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "job", + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-mainnet", + "value": "zebrad-mainnet" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": false, + "expr": "sum by(command) (zebra_net_connection_state{job=\"$job\"})", + "hide": false, + "interval": "", + "legendFormat": "", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "connection state - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:76", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:77", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 5, + "w": 6, + "x": 6, + "y": 18 + }, + "hiddenSeries": false, + "id": 21, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeatIteration": 1639361606831, + "repeatPanelId": 11, + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-mainnet-tmp", + "value": "zebrad-mainnet-tmp" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": false, + "expr": "sum by(command) (zebra_net_connection_state{job=\"$job\"})", + "hide": false, + "interval": "", + "legendFormat": "", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "connection state - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:76", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:77", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 5, + "w": 6, + "x": 12, + "y": 18 + }, + "hiddenSeries": false, + "id": 22, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeatIteration": 1639361606831, + "repeatPanelId": 11, + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-testnet", + "value": "zebrad-testnet" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": false, + "expr": "sum by(command) (zebra_net_connection_state{job=\"$job\"})", + "hide": false, + "interval": "", + "legendFormat": "", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "connection state - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:76", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:77", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 5, + "w": 6, + "x": 18, + "y": 18 + }, + "hiddenSeries": false, + "id": 23, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeatIteration": 1639361606831, + "repeatPanelId": 11, + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-testnet-tmp", + "value": "zebrad-testnet-tmp" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": false, + "expr": "sum by(command) (zebra_net_connection_state{job=\"$job\"})", + "hide": false, + "interval": "", + "legendFormat": "", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "connection state - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:76", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:77", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "5s", + "schemaVersion": 27, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": null, + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "definition": "label_values(zcash_net_in_bytes_total, job)", + "description": null, + "error": null, + "hide": 0, + "includeAll": true, + "label": null, + "multi": true, + "name": "job", + "options": [], + "query": { + "query": "label_values(zcash_net_in_bytes_total, job)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-30m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s" + ] + }, + "timezone": "", + "title": "network health", + "uid": "320aS_dMk", + "version": 6 +} diff --git a/observability/grafana/dashboards/network_messages.json b/observability/grafana/dashboards/network_messages.json new file mode 100644 index 0000000..5b91950 --- /dev/null +++ b/observability/grafana/dashboards/network_messages.json @@ -0,0 +1,868 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 6, + "iteration": 1639360549666, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 12, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "job", + "scopedVars": { + "job": { + "selected": true, + "text": "zebrad-mainnet", + "value": "zebrad-mainnet" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by (command) (zebra_net_in_requests{job=\"$job\"})", + "interval": "", + "legendFormat": "Req::{{command}}", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "sum by (command) (zebra_net_out_responses{job=\"$job\"})", + "hide": false, + "interval": "", + "legendFormat": "Rsp::{{command}}", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "inbound requests & responses - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:65", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:66", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 9 + }, + "hiddenSeries": false, + "id": 13, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "job", + "scopedVars": { + "job": { + "selected": true, + "text": "zebrad-mainnet", + "value": "zebrad-mainnet" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by (command) (zebra_net_out_requests{job=\"$job\"})", + "interval": "", + "legendFormat": "Req::{{command}}", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "sum by (command) (zebra_net_in_responses{job=\"$job\"})", + "hide": false, + "interval": "", + "legendFormat": "Rsp::{{command}}", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "outbound requests & responses - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:65", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:66", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 18 + }, + "hiddenSeries": false, + "id": 14, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "job", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by (command) (zebra_net_out_requests_canceled{job=\"$job\"})", + "interval": "", + "legendFormat": "Req::{{command}}", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "canceled outbound requests - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:65", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:66", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 27 + }, + "hiddenSeries": false, + "id": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "job", + "scopedVars": { + "job": { + "selected": true, + "text": "zebrad-mainnet", + "value": "zebrad-mainnet" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by (command) (zcash_net_in_messages{job=\"$job\"})", + "interval": "", + "legendFormat": "{{command}}", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "inbound message types - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:65", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:66", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 36 + }, + "hiddenSeries": false, + "id": 4, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "job", + "scopedVars": { + "job": { + "selected": true, + "text": "zebrad-mainnet", + "value": "zebrad-mainnet" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by (command) (zcash_net_out_messages{job=\"$job\"})", + "interval": "", + "legendFormat": "{{command}}", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "outbound message types - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:65", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:66", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 45 + }, + "hiddenSeries": false, + "id": 7, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "job", + "scopedVars": { + "job": { + "selected": true, + "text": "zebrad-mainnet", + "value": "zebrad-mainnet" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by (addr) (zcash_net_in_messages{job=\"$job\"})", + "interval": "", + "legendFormat": "{{command}}", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "inbound message peers - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:65", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:66", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 54 + }, + "hiddenSeries": false, + "id": 11, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "job", + "scopedVars": { + "job": { + "selected": true, + "text": "zebrad-mainnet", + "value": "zebrad-mainnet" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by (addr) (zcash_net_out_messages{job=\"$job\"})", + "interval": "", + "legendFormat": "{{command}}", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "outbound message peers - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:65", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:66", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "schemaVersion": 27, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": null, + "current": { + "selected": false, + "text": [ + "zebrad-mainnet" + ], + "value": [ + "zebrad-mainnet" + ] + }, + "datasource": null, + "definition": "label_values(zcash_net_in_bytes_total, job)", + "description": null, + "error": null, + "hide": 0, + "includeAll": true, + "label": null, + "multi": true, + "name": "job", + "options": [], + "query": { + "query": "label_values(zcash_net_in_bytes_total, job)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "network messages", + "uid": "YQ3yxiVnk", + "version": 9 +} diff --git a/observability/grafana/dashboards/peers.json b/observability/grafana/dashboards/peers.json new file mode 100644 index 0000000..5a7574d --- /dev/null +++ b/observability/grafana/dashboards/peers.json @@ -0,0 +1,620 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "gnetId": null, + "graphTooltip": 0, + "id": 3, + "iteration": 1635278363376, + "links": [], + "liveNow": false, + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 2, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "hideEmpty": false, + "hideZero": false, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.2.0", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "job", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by (remote_version, seed) (label_replace(zcash_net_peers_initial{job=\"$job\",seed=~\"$seed\"}, \"seed\", \"$1\", \"seed\", \"(.*):1?8233\") * on(remote_ip) group_left(remote_version) (count_values by (remote_ip) (\"remote_version\", zcash_net_peers_version_connected{job=\"$job\"})))", + "instant": false, + "interval": "", + "legendFormat": "{{remote_version}} - {{seed}}", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Compatible Seed Peers - $job", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:65", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:66", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 9 + }, + "hiddenSeries": false, + "id": 11, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.2.0", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "job", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by (remote_version, seed) (label_replace(zcash_net_peers_initial{job=\"$job\",seed=~\"$seed\"}, \"seed\", \"$1\", \"seed\", \"(.*):1?8233\") * on(remote_ip) group_left(remote_version) (count_values by (remote_ip) (\"remote_version\", zcash_net_peers_version_obsolete{job=\"$job\"})))", + "instant": false, + "interval": "", + "legendFormat": "{{remote_version}} - {{seed}}", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Obsolete Seed Peers - $job", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:65", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:66", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 17 + }, + "hiddenSeries": false, + "id": 4, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.2.0", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "job", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by (remote_version) (zcash_net_peers_connected{job=\"$job\"})", + "interval": "", + "legendFormat": "{{remote_version}}", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Compatible Peers - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:65", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:66", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 25 + }, + "hiddenSeries": false, + "id": 7, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.2.0", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "job", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by (remote_version) (zcash_net_peers_obsolete{job=\"$job\"})", + "interval": "", + "legendFormat": "{{remote_version}}", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Obsolete Peers - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:65", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:66", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 33 + }, + "hiddenSeries": false, + "id": 13, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "8.2.0", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by (user_agent) (zcash_net_peers_connected{job=\"$job\"})", + "interval": "", + "legendFormat": "{{user_agent}}", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Peer User Agents - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "5s", + "schemaVersion": 31, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": null, + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": null, + "definition": "label_values(zcash_net_in_bytes_total, job)", + "description": null, + "error": null, + "hide": 0, + "includeAll": true, + "label": null, + "multi": true, + "name": "job", + "options": [], + "query": { + "query": "label_values(zcash_net_in_bytes_total, job)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": ".+", + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": null, + "definition": "label_values(zcash_net_peers_initial, seed)", + "description": null, + "error": null, + "hide": 0, + "includeAll": true, + "label": null, + "multi": true, + "name": "seed", + "options": [], + "query": { + "query": "label_values(zcash_net_peers_initial, seed)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "peers", + "uid": "S29TgUH7k", + "version": 6 +} diff --git a/observability/grafana/dashboards/rocksdb.json b/observability/grafana/dashboards/rocksdb.json new file mode 100644 index 0000000..c93a2f1 --- /dev/null +++ b/observability/grafana/dashboards/rocksdb.json @@ -0,0 +1,925 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "RocksDB database metrics for Zebra", + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": null, + "links": [], + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": ["lastNotNull"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "zebra_state_rocksdb_total_disk_size_bytes{job=~\"$job\"}", + "legendFormat": "Total Disk Size", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "zebra_state_rocksdb_live_data_size_bytes{job=~\"$job\"}", + "legendFormat": "Live Data Size", + "refId": "B" + } + ], + "title": "Database Disk Size", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": ["lastNotNull"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "zebra_state_rocksdb_total_memory_size_bytes{job=~\"$job\"}", + "legendFormat": "Total Memory Size", + "refId": "A" + } + ], + "title": "Database Memory Size", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 3, + "options": { + "legend": { + "calcs": ["lastNotNull"], + "displayMode": "table", + "placement": "right", + "showLegend": true, + "sortBy": "Last *", + "sortDesc": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "topk(10, zebra_state_rocksdb_cf_disk_size_bytes{job=~\"$job\"})", + "legendFormat": "{{cf}}", + "refId": "A" + } + ], + "title": "Top 10 Column Families by Disk Size", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 4, + "options": { + "legend": { + "calcs": ["lastNotNull"], + "displayMode": "table", + "placement": "right", + "showLegend": true, + "sortBy": "Last *", + "sortDesc": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "topk(10, zebra_state_rocksdb_cf_memory_size_bytes{job=~\"$job\"})", + "legendFormat": "{{cf}}", + "refId": "A" + } + ], + "title": "Top 10 Column Families by Memory Size", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "noValue": "NO DATA", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 18 + }, + "id": 5, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "zebra_state_rocksdb_total_disk_size_bytes{job=~\"$job\"}", + "legendFormat": "Total Disk", + "refId": "A" + } + ], + "title": "Total Disk Size", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 18 + }, + "id": 6, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "fieldConfig": { + "defaults": { + "noValue": "NO DATA", + "color": { "mode": "thresholds" }, + "thresholds": { "mode": "absolute", "steps": [{ "color": "blue", "value": null }] }, + "unit": "bytes" + }, + "overrides": [] + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "zebra_state_rocksdb_live_data_size_bytes{job=~\"$job\"}", + "legendFormat": "Live Data", + "refId": "A" + } + ], + "title": "Live Data Size", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 1000000000 + }, + { + "color": "red", + "value": 2000000000 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 12, + "y": 18 + }, + "id": 7, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "zebra_state_rocksdb_total_memory_size_bytes{job=~\"$job\"}", + "legendFormat": "Memory", + "refId": "A" + } + ], + "title": "Total Memory Size", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "noValue": "NO DATA", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "yellow", + "value": 0.7 + }, + { + "color": "green", + "value": 0.9 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 18 + }, + "id": 8, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "zebra_state_rocksdb_live_data_size_bytes{job=~\"$job\"} / zebra_state_rocksdb_total_disk_size_bytes{job=~\"$job\"}", + "legendFormat": "Compaction Efficiency", + "refId": "A" + } + ], + "title": "Compaction Efficiency", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 22 }, + "id": 100, + "panels": [], + "title": "I/O Performance", + "type": "row" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "description": "RocksDB batch commit latency percentiles (p95, p99)", + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": false, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "line" } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "green", "value": null }, + { "color": "yellow", "value": 0.1 }, + { "color": "red", "value": 0.5 } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { "h": 8, "w": 12, "x": 0, "y": 23 }, + "id": 10, + "options": { + "legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom", "showLegend": true }, + "tooltip": { "mode": "multi", "sort": "desc" } + }, + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "expr": "zebra_state_rocksdb_batch_commit_duration_seconds{job=~\"$job\", quantile=\"0.95\"}", + "legendFormat": "p95", + "refId": "A" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "expr": "zebra_state_rocksdb_batch_commit_duration_seconds{job=~\"$job\", quantile=\"0.99\"}", + "legendFormat": "p99", + "refId": "B" + } + ], + "title": "Batch Commit Latency", + "type": "timeseries" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "description": "Block cache memory usage", + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": false, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { "h": 8, "w": 12, "x": 12, "y": 23 }, + "id": 11, + "options": { + "legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom", "showLegend": true }, + "tooltip": { "mode": "multi", "sort": "desc" } + }, + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "expr": "zebra_state_rocksdb_block_cache_usage_bytes{job=~\"$job\"}", + "legendFormat": "Block Cache", + "refId": "A" + } + ], + "title": "Block Cache Usage", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 31 }, + "id": 101, + "panels": [], + "title": "Compaction", + "type": "row" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "description": "Compaction status - pending bytes and running compactions", + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": false, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "bytes" + }, + "overrides": [ + { + "matcher": { "id": "byName", "options": "Running Compactions" }, + "properties": [ + { "id": "custom.axisPlacement", "value": "right" }, + { "id": "unit", "value": "none" } + ] + } + ] + }, + "gridPos": { "h": 8, "w": 12, "x": 0, "y": 32 }, + "id": 12, + "options": { + "legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom", "showLegend": true }, + "tooltip": { "mode": "multi", "sort": "desc" } + }, + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "expr": "zebra_state_rocksdb_compaction_pending_bytes{job=~\"$job\"}", + "legendFormat": "Pending Bytes", + "refId": "A" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "expr": "zebra_state_rocksdb_compaction_running{job=~\"$job\"}", + "legendFormat": "Running Compactions", + "refId": "B" + } + ], + "title": "Compaction Status", + "type": "timeseries" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "description": "Number of SST files at each RocksDB level (L0-L6)", + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": false, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 8, "w": 12, "x": 12, "y": 32 }, + "id": 13, + "options": { + "legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom", "showLegend": true }, + "tooltip": { "mode": "multi", "sort": "desc" } + }, + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "expr": "zebra_state_rocksdb_num_files_at_level{job=~\"$job\"}", + "legendFormat": "Level {{level}}", + "refId": "A" + } + ], + "title": "SST Files by Level", + "type": "timeseries" + } + ], + "refresh": "30s", + "schemaVersion": 27, + "style": "dark", + "tags": ["rocksdb", "database"], + "templating": { + "list": [ + { + "allValue": null, + "current": { + "selected": true, + "text": ["All"], + "value": ["$__all"] + }, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "definition": "label_values(zebra_state_rocksdb_total_disk_size_bytes, job)", + "description": null, + "error": null, + "hide": 0, + "includeAll": true, + "label": null, + "multi": true, + "name": "job", + "options": [], + "query": { + "query": "label_values(zebra_state_rocksdb_total_disk_size_bytes, job)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "RocksDB Database", + "uid": "zebra-rocksdb", + "version": 1 +} diff --git a/observability/grafana/dashboards/rpc_metrics.json b/observability/grafana/dashboards/rpc_metrics.json new file mode 100644 index 0000000..a433d11 --- /dev/null +++ b/observability/grafana/dashboards/rpc_metrics.json @@ -0,0 +1,820 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "JSON-RPC endpoint metrics - request rates, latency, and errors", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 50 + }, + { + "color": "red", + "value": 100 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "rpc_active_requests", + "legendFormat": "Active Requests", + "refId": "A" + } + ], + "title": "Active Requests", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 0 + }, + "id": 2, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "sum(rate(rpc_requests_total[5m]))", + "legendFormat": "Requests/s", + "refId": "A" + } + ], + "title": "Request Rate (5m)", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 0.05 + }, + { + "color": "red", + "value": 0.1 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 12, + "y": 0 + }, + "id": 3, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "sum(rate(rpc_requests_total{status=\"error\"}[5m])) / sum(rate(rpc_requests_total[5m]))", + "legendFormat": "Error Rate", + "refId": "A" + } + ], + "title": "Error Rate (5m)", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 0.5 + }, + { + "color": "red", + "value": 2 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 0 + }, + "id": 4, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "histogram_quantile(0.99, sum(rate(rpc_request_duration_seconds_bucket[5m])) by (le))", + "legendFormat": "p99 Latency", + "refId": "A" + } + ], + "title": "p99 Latency (5m)", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "req/s", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 4 + }, + "id": 5, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "sum(rate(rpc_requests_total[5m])) by (method)", + "legendFormat": "{{ method }}", + "refId": "A" + } + ], + "title": "Request Rate by Method", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "seconds", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 4 + }, + "id": 6, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "histogram_quantile(0.50, sum(rate(rpc_request_duration_seconds_bucket[5m])) by (le, method))", + "legendFormat": "{{ method }} p50", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "histogram_quantile(0.95, sum(rate(rpc_request_duration_seconds_bucket[5m])) by (le, method))", + "legendFormat": "{{ method }} p95", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "histogram_quantile(0.99, sum(rate(rpc_request_duration_seconds_bucket[5m])) by (le, method))", + "legendFormat": "{{ method }} p99", + "refId": "C" + } + ], + "title": "Request Latency Percentiles by Method", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "errors/s", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 12 + }, + "id": 7, + "options": { + "legend": { + "calcs": ["sum"], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "sum(rate(rpc_errors_total[5m])) by (method, error_code)", + "legendFormat": "{{ method }} ({{ error_code }})", + "refId": "A" + } + ], + "title": "Errors by Method and Code", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 12 + }, + "id": 8, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "rpc_active_requests", + "legendFormat": "Active Requests", + "refId": "A" + } + ], + "title": "Active Requests Over Time", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Rate" + }, + "properties": [ + { + "id": "unit", + "value": "reqps" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "p50" + }, + "properties": [ + { + "id": "unit", + "value": "s" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "p99" + }, + "properties": [ + { + "id": "unit", + "value": "s" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 20 + }, + "id": 9, + "options": { + "footer": { + "fields": "", + "reducer": ["sum"], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Rate" + } + ] + }, + "pluginVersion": "9.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "sum(rate(rpc_requests_total[5m])) by (method)", + "format": "table", + "instant": true, + "legendFormat": "", + "refId": "Rate" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "histogram_quantile(0.50, sum(rate(rpc_request_duration_seconds_bucket[5m])) by (le, method))", + "format": "table", + "instant": true, + "legendFormat": "", + "refId": "p50" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "histogram_quantile(0.99, sum(rate(rpc_request_duration_seconds_bucket[5m])) by (le, method))", + "format": "table", + "instant": true, + "legendFormat": "", + "refId": "p99" + } + ], + "title": "Method Statistics", + "transformations": [ + { + "id": "seriesToColumns", + "options": { + "byField": "method" + } + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "Time 1": true, + "Time 2": true, + "Time 3": true + }, + "indexByName": {}, + "renameByName": { + "Value #Rate": "Rate", + "Value #p50": "p50", + "Value #p99": "p99", + "method": "Method" + } + } + } + ], + "type": "table" + } + ], + "refresh": "10s", + "schemaVersion": 37, + "style": "dark", + "tags": ["zebra", "zcash", "rpc", "json-rpc"], + "templating": { + "list": [] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Zebra RPC Metrics", + "uid": "zebra-rpc-metrics", + "version": 1, + "weekStart": "" +} diff --git a/observability/grafana/dashboards/rpc_tracing.json b/observability/grafana/dashboards/rpc_tracing.json new file mode 100644 index 0000000..8e6585b --- /dev/null +++ b/observability/grafana/dashboards/rpc_tracing.json @@ -0,0 +1,639 @@ +{ + "annotations": { + "list": [] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "title": "RPC Request Rate by Method", + "type": "timeseries", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "sum by (rpc_method) (rate(traces_spanmetrics_calls_total{service=\"zebra\", span_name=\"rpc_request\"}[1m]))", + "legendFormat": "{{rpc_method}}", + "refId": "A" + } + ] + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 0.01 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "title": "RPC Error Rate by Method", + "type": "timeseries", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "sum by (rpc_method) (rate(traces_spanmetrics_calls_total{service=\"zebra\", span_name=\"rpc_request\", otel_status_code=\"ERROR\"}[1m])) / sum by (rpc_method) (rate(traces_spanmetrics_calls_total{service=\"zebra\", span_name=\"rpc_request\"}[1m]))", + "legendFormat": "{{rpc_method}}", + "refId": "A" + } + ] + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 3, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "title": "RPC Latency P50 by Method", + "type": "timeseries", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "histogram_quantile(0.50, sum by (rpc_method, le) (rate(traces_spanmetrics_latency_bucket{service=\"zebra\", span_name=\"rpc_request\"}[1m])))", + "legendFormat": "{{rpc_method}}", + "refId": "A" + } + ] + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 4, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "title": "RPC Latency P99 by Method", + "type": "timeseries", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "histogram_quantile(0.99, sum by (rpc_method, le) (rate(traces_spanmetrics_latency_bucket{service=\"zebra\", span_name=\"rpc_request\"}[1m])))", + "legendFormat": "{{rpc_method}}", + "refId": "A" + } + ] + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 16 + }, + "id": 5, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto", + "wideLayout": true + }, + "title": "Total RPC Requests", + "type": "stat", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "sum(increase(traces_spanmetrics_calls_total{service=\"zebra\", span_name=\"rpc_request\"}[1h]))", + "legendFormat": "Total", + "refId": "A" + } + ] + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 1 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 6, + "y": 16 + }, + "id": 6, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto", + "wideLayout": true + }, + "title": "Total RPC Errors", + "type": "stat", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "sum(increase(traces_spanmetrics_calls_total{service=\"zebra\", span_name=\"rpc_request\", otel_status_code=\"ERROR\"}[1h]))", + "legendFormat": "Errors", + "refId": "A" + } + ] + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 0.1 + }, + { + "color": "red", + "value": 0.5 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 12, + "y": 16 + }, + "id": 7, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto", + "wideLayout": true + }, + "title": "Average Latency", + "type": "stat", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "sum(rate(traces_spanmetrics_latency_sum{service=\"zebra\", span_name=\"rpc_request\"}[5m])) / sum(rate(traces_spanmetrics_latency_count{service=\"zebra\", span_name=\"rpc_request\"}[5m]))", + "legendFormat": "Avg", + "refId": "A" + } + ] + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 0.5 + }, + { + "color": "red", + "value": 1 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 18, + "y": 16 + }, + "id": 8, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto", + "wideLayout": true + }, + "title": "P99 Latency", + "type": "stat", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "histogram_quantile(0.99, sum(rate(traces_spanmetrics_latency_bucket{service=\"zebra\", span_name=\"rpc_request\"}[5m])) by (le))", + "legendFormat": "P99", + "refId": "A" + } + ] + } + ], + "schemaVersion": 39, + "tags": ["zebra", "rpc", "tracing"], + "templating": { + "list": [] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "RPC Tracing", + "uid": "zebra-rpc-tracing", + "version": 1, + "weekStart": "" +} diff --git a/observability/grafana/dashboards/syncer.json b/observability/grafana/dashboards/syncer.json new file mode 100644 index 0000000..9441540 --- /dev/null +++ b/observability/grafana/dashboards/syncer.json @@ -0,0 +1,1319 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "", + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 5, + "iteration": 1633496321106, + "links": [], + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 200, + "panels": [], + "title": "Key Metrics", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "description": "Current blockchain height", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 1 + }, + "id": 201, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "state_finalized_block_height", + "legendFormat": "Height", + "refId": "A" + } + ], + "title": "Block Height", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "description": "Total blocks downloaded during sync", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 1 + }, + "id": 202, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "sync_downloaded_block_count", + "legendFormat": "Downloaded", + "refId": "A" + } + ], + "title": "Downloaded Blocks", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "description": "Total blocks verified during sync", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 12, + "y": 1 + }, + "id": 203, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "sync_verified_block_count", + "legendFormat": "Verified", + "refId": "A" + } + ], + "title": "Verified Blocks", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "description": "Block downloads currently in progress", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "yellow", + "value": 100 + }, + { + "color": "red", + "value": 200 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 1 + }, + "id": 204, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "sync_downloads_in_flight", + "legendFormat": "In-Flight", + "refId": "A" + } + ], + "title": "In-Flight Downloads", + "type": "stat" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 5 + }, + "hiddenSeries": false, + "id": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "job", + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-mainnet", + "value": "zebrad-mainnet" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sync_obtain_queued_hash_count{job=\"$job\"}", + "interval": "", + "legendFormat": "obtain tips", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "sync_extend_queued_hash_count{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "extend tips", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Sync Queued Downloads - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:65", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:66", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 5 + }, + "hiddenSeries": false, + "id": 8, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeatIteration": 1633496321106, + "repeatPanelId": 2, + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-testnet", + "value": "zebrad-testnet" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sync_obtain_queued_hash_count{job=\"$job\"}", + "interval": "", + "legendFormat": "obtain tips", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "sync_extend_queued_hash_count{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "extend tips", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Sync Queued Downloads - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:65", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:66", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "cards": { + "cardPadding": null, + "cardRound": null + }, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateOranges", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "timeseries", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 14 + }, + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "id": 4, + "legend": { + "show": false + }, + "pluginVersion": "7.5.7", + "repeat": "job", + "reverseYBuckets": false, + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-mainnet", + "value": "zebrad-mainnet" + } + }, + "targets": [ + { + "exemplar": true, + "expr": "sync_obtain_response_hash_count{job=\"$job\"}", + "format": "heatmap", + "interval": "", + "legendFormat": "obtain tips", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "sync_extend_response_hash_count{job=\"$job\"}", + "format": "heatmap", + "hide": false, + "interval": "", + "legendFormat": "extend tips", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Sync Peer Responses - $job", + "tooltip": { + "show": true, + "showHistogram": false + }, + "type": "heatmap", + "xAxis": { + "show": true + }, + "xBucketNumber": null, + "xBucketSize": null, + "yAxis": { + "decimals": null, + "format": "short", + "logBase": 1, + "max": null, + "min": null, + "show": true, + "splitFactor": null + }, + "yBucketBound": "auto", + "yBucketNumber": null, + "yBucketSize": null + }, + { + "cards": { + "cardPadding": null, + "cardRound": null + }, + "color": { + "cardColor": "#b4ff00", + "colorScale": "sqrt", + "colorScheme": "interpolateOranges", + "exponent": 0.5, + "mode": "spectrum" + }, + "dataFormat": "timeseries", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 14 + }, + "heatmap": {}, + "hideZeroBuckets": false, + "highlightCards": true, + "id": 9, + "legend": { + "show": false + }, + "pluginVersion": "7.5.7", + "repeatIteration": 1633496321106, + "repeatPanelId": 4, + "reverseYBuckets": false, + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-testnet", + "value": "zebrad-testnet" + } + }, + "targets": [ + { + "exemplar": true, + "expr": "sync_obtain_response_hash_count{job=\"$job\"}", + "format": "heatmap", + "interval": "", + "legendFormat": "obtain tips", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "sync_extend_response_hash_count{job=\"$job\"}", + "format": "heatmap", + "hide": false, + "interval": "", + "legendFormat": "extend tips", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Sync Peer Responses - $job", + "tooltip": { + "show": true, + "showHistogram": false + }, + "type": "heatmap", + "xAxis": { + "show": true + }, + "xBucketNumber": null, + "xBucketSize": null, + "yAxis": { + "decimals": null, + "format": "short", + "logBase": 1, + "max": null, + "min": null, + "show": true, + "splitFactor": null + }, + "yBucketBound": "auto", + "yBucketNumber": null, + "yBucketSize": null + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 23 + }, + "hiddenSeries": false, + "id": 7, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeat": "job", + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-mainnet", + "value": "zebrad-mainnet" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sync_cancelled_download_count{job=\"$job\"}", + "interval": "", + "legendFormat": "cancelled downloads", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "sync_cancelled_verify_count{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "cancelled verifications", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "sync_downloads_in_flight{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "in-flight downloads", + "refId": "C", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "sync_prospective_tips_len{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "prospective tips", + "refId": "D", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "sync_downloaded_block_count{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "downloaded blocks", + "refId": "E", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "sync_verified_block_count{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "verified blocks", + "refId": "F", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Sync Status - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:65", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:66", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 23 + }, + "hiddenSeries": false, + "id": 10, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.7", + "pointradius": 2, + "points": false, + "renderer": "flot", + "repeatIteration": 1633496321106, + "repeatPanelId": 7, + "scopedVars": { + "job": { + "selected": false, + "text": "zebrad-testnet", + "value": "zebrad-testnet" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sync_cancelled_download_count{job=\"$job\"}", + "interval": "", + "legendFormat": "cancelled downloads", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "sync_cancelled_verify_count{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "cancelled verifications", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "sync_downloads_in_flight{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "in-flight downloads", + "refId": "C", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "sync_prospective_tips_len{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "prospective tips", + "refId": "D", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "sync_downloaded_block_count{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "downloaded blocks", + "refId": "E", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "sync_verified_block_count{job=\"$job\"}", + "hide": false, + "interval": "", + "legendFormat": "verified blocks", + "refId": "F", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Sync Status - $job", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:65", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:66", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 32 + }, + "id": 100, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "sync_stage_duration_seconds{quantile=\"0.5\"}", + "legendFormat": "{{stage}} p50", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "sync_stage_duration_seconds{quantile=\"0.95\"}", + "legendFormat": "{{stage}} p95", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "sync_stage_duration_seconds{quantile=\"0.99\"}", + "legendFormat": "{{stage}} p99", + "refId": "C" + } + ], + "title": "Sync Stage Duration Latency", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 32 + }, + "id": 101, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "sync_block_download_duration_seconds{quantile=\"0.95\"}", + "legendFormat": "download {{result}} p95", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "sync_block_verify_duration_seconds{quantile=\"0.95\"}", + "legendFormat": "verify {{result}} p95", + "refId": "B" + } + ], + "title": "Block Download/Verify Duration (p95)", + "type": "timeseries" + } + ], + "refresh": "5s", + "schemaVersion": 27, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": null, + "current": { + "selected": true, + "text": [ + "All" + ], + "value": [ + "$__all" + ] + }, + "datasource": null, + "definition": "label_values(zcash_net_in_bytes_total, job)", + "description": null, + "error": null, + "hide": 0, + "includeAll": true, + "label": null, + "multi": true, + "name": "job", + "options": [], + "query": { + "query": "label_values(zcash_net_in_bytes_total, job)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "syncer", + "uid": "Sl3h19Gnk", + "version": 11 +} diff --git a/observability/grafana/dashboards/transaction-verification.json b/observability/grafana/dashboards/transaction-verification.json new file mode 100644 index 0000000..21fb739 --- /dev/null +++ b/observability/grafana/dashboards/transaction-verification.json @@ -0,0 +1,978 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 6, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 4, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "8.5.5", + "targets": [ + { + "exemplar": true, + "expr": "zcash_chain_verified_block_height{}", + "hide": false, + "interval": "", + "legendFormat": "", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "title": "Block height", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "{instance=\"localhost:9999\", job=\"zebrad\"}" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "RedPallas" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "RedJubjub" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Ed25519" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(signatures_ed25519_validated{}[$__interval])", + "hide": false, + "interval": "", + "legendFormat": "Ed25519", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(signatures_redjubjub_validated{}[$__interval])", + "hide": false, + "interval": "", + "legendFormat": "RedJubjub", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(signatures_redpallas_validated{}[$__interval])", + "hide": false, + "legendFormat": "RedPallas", + "range": true, + "refId": "C" + } + ], + "title": "Signatures validated", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 12, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "8.5.5", + "targets": [ + { + "exemplar": true, + "expr": "rate(state_finalized_cumulative_transactions{}[$__interval])", + "interval": "", + "legendFormat": "Transactions finalized", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "title": "Transactions finalized", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Halo2" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 9 + }, + "id": 8, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "exemplar": true, + "expr": "rate(proofs_groth16_verified{}[$__interval])", + "interval": "", + "legendFormat": "Groth16", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "editorMode": "code", + "expr": "rate(proofs_halo2_verified{}[$__interval])", + "hide": false, + "legendFormat": "Halo2", + "range": true, + "refId": "B" + } + ], + "title": "Proofs verified", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Transparent newOuts" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "semi-dark-orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Transparent prevOuts" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "semi-dark-yellow", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "exemplar": true, + "expr": "rate(state_finalized_cumulative_transparent_newouts{}[$__interval])", + "interval": "", + "legendFormat": "Transparent newOuts", + "refId": "A", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + }, + { + "exemplar": true, + "expr": "rate(state_finalized_cumulative_transparent_prevouts{}[$__interval])", + "hide": false, + "interval": "", + "legendFormat": "Transparent prevOuts", + "refId": "B", + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + } + } + ], + "title": "Transparent Outpoints", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Orchard" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Sapling" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Sprout" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 17 + }, + "id": 6, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom" + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "exemplar": true, + "expr": "rate(state_finalized_cumulative_sprout_nullifiers{}[$__interval])", + "hide": false, + "interval": "", + "legendFormat": "Sprout", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "exemplar": true, + "expr": "rate(state_finalized_cumulative_sapling_nullifiers{}[$__interval])", + "hide": false, + "interval": "", + "legendFormat": "Sapling", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "exemplar": true, + "expr": "rate(state_finalized_cumulative_orchard_nullifiers{}[$__interval])", + "hide": false, + "instant": false, + "interval": "", + "legendFormat": "Orchard", + "refId": "C" + } + ], + "title": "Nullifiers revealed", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 25 + }, + "id": 100, + "options": { + "legend": { + "calcs": ["mean", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "zebra_consensus_batch_duration_seconds{quantile=\"0.5\"}", + "legendFormat": "{{verifier}} p50", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "zebra_consensus_batch_duration_seconds{quantile=\"0.95\"}", + "legendFormat": "{{verifier}} p95", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "zebra_consensus_batch_duration_seconds{quantile=\"0.99\"}", + "legendFormat": "{{verifier}} p99", + "refId": "C" + } + ], + "title": "Batch Verification Duration by Verifier", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 25 + }, + "id": 101, + "options": { + "legend": { + "calcs": ["mean"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "sum(rate(zebra_consensus_batch_duration_seconds_count{result=\"success\"}[5m])) by (verifier)", + "legendFormat": "{{verifier}} success/s", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "sum(rate(zebra_consensus_batch_duration_seconds_count{result=\"failure\"}[5m])) by (verifier)", + "legendFormat": "{{verifier}} failure/s", + "refId": "B" + } + ], + "title": "Verification Throughput by Verifier", + "type": "timeseries" + } + ], + "refresh": "5s", + "schemaVersion": 36, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-12h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "🔎", + "uid": "UXVRR1v7z", + "version": 23, + "weekStart": "" +} diff --git a/observability/grafana/dashboards/value_pools.json b/observability/grafana/dashboards/value_pools.json new file mode 100644 index 0000000..b1d8a5a --- /dev/null +++ b/observability/grafana/dashboards/value_pools.json @@ -0,0 +1,725 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "Value Pool Monitoring - Track shielded pool balances and total supply. Zebra enforces ZIP-209 internally.", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "ZEC", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": ["last"], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "state_finalized_chain_supply_total / 100000000", + "legendFormat": "Total Supply (ZEC)", + "refId": "A" + } + ], + "title": "Total Chain Supply", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "ZEC", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Transparent" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#73BF69", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Sprout" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#F2CC0C", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Sapling" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#5794F2", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Orchard" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#B877D9", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Deferred" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#FF9830", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 2, + "options": { + "legend": { + "calcs": ["last", "mean"], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "state_finalized_value_pool_transparent / 100000000", + "legendFormat": "Transparent", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "state_finalized_value_pool_sprout / 100000000", + "legendFormat": "Sprout", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "state_finalized_value_pool_sapling / 100000000", + "legendFormat": "Sapling", + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "state_finalized_value_pool_orchard / 100000000", + "legendFormat": "Orchard", + "refId": "D" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "state_finalized_value_pool_deferred / 100000000", + "legendFormat": "Deferred", + "refId": "E" + } + ], + "title": "Value Pools by Type (Stacked)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 18 + }, + "id": 3, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "state_finalized_value_pool_transparent / 100000000", + "legendFormat": "Transparent ZEC", + "refId": "A" + } + ], + "title": "Transparent Pool", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "#F2CC0C", + "value": 0 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 18 + }, + "id": 4, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "state_finalized_value_pool_sprout / 100000000", + "legendFormat": "Sprout ZEC", + "refId": "A" + } + ], + "title": "Sprout Pool", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "blue", + "value": 0 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 12, + "y": 18 + }, + "id": 5, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "state_finalized_value_pool_sapling / 100000000", + "legendFormat": "Sapling ZEC", + "refId": "A" + } + ], + "title": "Sapling Pool", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "purple", + "value": 0 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 18, + "y": 18 + }, + "id": 6, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "state_finalized_value_pool_orchard / 100000000", + "legendFormat": "Orchard ZEC", + "refId": "A" + } + ], + "title": "Orchard Pool", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "description": "Pool Health Check: All pools should be non-negative (enforced internally by Zebra)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "0": { + "color": "green", + "index": 0, + "text": "HEALTHY" + } + }, + "type": "value" + }, + { + "options": { + "from": 1, + "result": { + "color": "red", + "index": 1, + "text": "VIOLATION" + }, + "to": 999999 + }, + "type": "range" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 1 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 12, + "x": 0, + "y": 22 + }, + "id": 7, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "(state_finalized_value_pool_transparent < 0) + (state_finalized_value_pool_sprout < 0) + (state_finalized_value_pool_sapling < 0) + (state_finalized_value_pool_orchard < 0)", + "legendFormat": "Pool Health Status", + "refId": "A" + } + ], + "title": "Pool Health Status", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 12, + "x": 12, + "y": 22 + }, + "id": 8, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "expr": "state_finalized_block_height", + "legendFormat": "Block Height", + "refId": "A" + } + ], + "title": "Current Block Height", + "type": "stat" + } + ], + "refresh": "10s", + "schemaVersion": 37, + "style": "dark", + "tags": ["zebra", "zcash", "value-pools"], + "templating": { + "list": [] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Zebra Value Pools", + "uid": "zebra-value-pools", + "version": 1, + "weekStart": "" +} diff --git a/observability/grafana/dashboards/zebra_overview.json b/observability/grafana/dashboards/zebra_overview.json new file mode 100644 index 0000000..2b23bc5 --- /dev/null +++ b/observability/grafana/dashboards/zebra_overview.json @@ -0,0 +1,1047 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Zebra Node Overview - At-a-glance health and status for node operators", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "collapsed": false, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 }, + "id": 100, + "panels": [], + "title": "Node Identity", + "type": "row" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "description": "Zebra node software version", + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [{ "color": "blue", "value": null }] + } + }, + "overrides": [] + }, + "gridPos": { "h": 3, "w": 8, "x": 0, "y": 1 }, + "id": 1, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "/^version$/", "values": false }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "expr": "zebrad_build_info", + "format": "table", + "instant": true, + "refId": "A" + } + ], + "title": "Version", + "type": "stat" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "description": "Current finalized blockchain height", + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "mappings": [], + "noValue": "NO DATA", + "thresholds": { + "mode": "absolute", + "steps": [{ "color": "blue", "value": null }] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 3, "w": 8, "x": 8, "y": 1 }, + "id": 2, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "expr": "state_finalized_block_height", + "legendFormat": "Height", + "refId": "A" + } + ], + "title": "Block Height", + "type": "stat" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "description": "Total ZEC supply on the blockchain", + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [{ "color": "orange", "value": null }] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 3, "w": 8, "x": 16, "y": 1 }, + "id": 3, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "expr": "state_finalized_chain_supply_total / 100000000", + "legendFormat": "ZEC", + "refId": "A" + } + ], + "title": "Total Supply (ZEC)", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 4 }, + "id": 101, + "panels": [], + "title": "Health Status", + "type": "row" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "description": "Peer connectivity - gauge shows current count relative to healthy thresholds", + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "mappings": [], + "max": 50, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "red", "value": null }, + { "color": "#EAB839", "value": 8 }, + { "color": "green", "value": 20 } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 5, "w": 4, "x": 0, "y": 5 }, + "id": 10, + "options": { + "orientation": "auto", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "showThresholdLabels": true, + "showThresholdMarkers": true + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "expr": "zcash_net_peers", + "legendFormat": "Peers", + "refId": "A" + } + ], + "title": "Peer Count", + "type": "gauge" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "description": "Block verification activity (ACTIVE = processing blocks, IDLE = waiting for new blocks)", + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "mappings": [ + { + "options": { "from": 0.001, "result": { "color": "green", "index": 0, "text": "ACTIVE" }, "to": 1000000 }, + "type": "range" + }, + { + "options": { "from": 0, "result": { "color": "blue", "index": 1, "text": "IDLE" }, "to": 0.001 }, + "type": "range" + } + ], + "noValue": "NO DATA", + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "blue", "value": null }, + { "color": "green", "value": 0.001 } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 5, "w": 5, "x": 4, "y": 5 }, + "id": 11, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "expr": "rate(zcash_chain_verified_block_total[5m])", + "legendFormat": "Rate", + "refId": "A" + } + ], + "title": "Block Activity", + "type": "stat" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "description": "Blocks behind estimated network tip", + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "mappings": [ + { + "options": { "from": 0, "result": { "color": "green", "index": 0, "text": "SYNCED" }, "to": 2 }, + "type": "range" + } + ], + "noValue": "NO DATA", + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "green", "value": null }, + { "color": "yellow", "value": 3 }, + { "color": "orange", "value": 50 }, + { "color": "red", "value": 100 } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 5, "w": 5, "x": 9, "y": 5 }, + "id": 14, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "textMode": "value" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "expr": "sync_estimated_distance_to_tip{job=~\"$job\"}", + "legendFormat": "Distance", + "refId": "A" + } + ], + "title": "Sync Distance", + "type": "stat" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "description": "ZIP-209 compliance - all value pools must be non-negative", + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "mappings": [ + { "options": { "0": { "color": "green", "index": 0, "text": "HEALTHY" } }, "type": "value" }, + { + "options": { "from": 1, "result": { "color": "red", "index": 1, "text": "VIOLATION" }, "to": 10 }, + "type": "range" + } + ], + "noValue": "NO DATA", + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "green", "value": null }, + { "color": "red", "value": 1 } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 5, "w": 5, "x": 14, "y": 5 }, + "id": 12, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "expr": "count(state_finalized_value_pool_transparent < 0 or state_finalized_value_pool_sprout < 0 or state_finalized_value_pool_sapling < 0 or state_finalized_value_pool_orchard < 0) or vector(0)", + "legendFormat": "Violations", + "refId": "A" + } + ], + "title": "Value Pools", + "type": "stat" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "description": "RPC endpoint health based on error rate (no data = RPC disabled)", + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "mappings": [ + { "options": { "from": 0, "result": { "color": "green", "index": 0, "text": "HEALTHY" }, "to": 0.05 }, "type": "range" }, + { "options": { "from": 0.05, "result": { "color": "yellow", "index": 1, "text": "DEGRADED" }, "to": 0.1 }, "type": "range" }, + { "options": { "from": 0.1, "result": { "color": "red", "index": 2, "text": "UNHEALTHY" }, "to": 1 }, "type": "range" } + ], + "noValue": "DISABLED", + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "green", "value": null }, + { "color": "yellow", "value": 0.05 }, + { "color": "red", "value": 0.1 } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { "h": 5, "w": 5, "x": 19, "y": 5 }, + "id": 13, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "expr": "sum(rate(rpc_requests_total{status=\"error\"}[5m])) / sum(rate(rpc_requests_total[5m])) or vector(0)", + "legendFormat": "Error Rate", + "refId": "A" + } + ], + "title": "RPC Health", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 10 }, + "id": 102, + "panels": [], + "title": "Network", + "type": "row" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "description": "Network bandwidth - bytes transferred per second", + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": false, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "binBps" + }, + "overrides": [ + { "matcher": { "id": "byName", "options": "Inbound" }, "properties": [{ "id": "color", "value": { "fixedColor": "green", "mode": "fixed" } }] }, + { "matcher": { "id": "byName", "options": "Outbound" }, "properties": [{ "id": "color", "value": { "fixedColor": "blue", "mode": "fixed" } }] } + ] + }, + "gridPos": { "h": 8, "w": 8, "x": 0, "y": 11 }, + "id": 20, + "options": { + "legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom", "showLegend": true }, + "tooltip": { "mode": "multi", "sort": "none" } + }, + "pluginVersion": "10.0.0", + "targets": [ + { "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, "expr": "sum(rate(zcash_net_in_bytes_total[1m]))", "legendFormat": "Inbound", "refId": "A" }, + { "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, "expr": "sum(rate(zcash_net_out_bytes_total[1m]))", "legendFormat": "Outbound", "refId": "B" } + ], + "title": "Network Traffic", + "type": "timeseries" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "description": "P2P message rate - messages per second", + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "msg/s", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": false, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "ops" + }, + "overrides": [ + { "matcher": { "id": "byName", "options": "Inbound" }, "properties": [{ "id": "color", "value": { "fixedColor": "green", "mode": "fixed" } }] }, + { "matcher": { "id": "byName", "options": "Outbound" }, "properties": [{ "id": "color", "value": { "fixedColor": "blue", "mode": "fixed" } }] } + ] + }, + "gridPos": { "h": 8, "w": 8, "x": 8, "y": 11 }, + "id": 21, + "options": { + "legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom", "showLegend": true }, + "tooltip": { "mode": "multi", "sort": "none" } + }, + "pluginVersion": "10.0.0", + "targets": [ + { "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, "expr": "sum(rate(zcash_net_in_messages[1m]))", "legendFormat": "Inbound", "refId": "A" }, + { "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, "expr": "sum(rate(zcash_net_out_messages[1m]))", "legendFormat": "Outbound", "refId": "B" } + ], + "title": "Message Rate", + "type": "timeseries" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "description": "Distribution of connected peers by client software", + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { "h": 8, "w": 8, "x": 16, "y": 11 }, + "id": 22, + "options": { + "legend": { "displayMode": "table", "placement": "right", "showLegend": true, "values": ["value", "percent"] }, + "pieType": "pie", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "tooltip": { "mode": "single", "sort": "none" } + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "expr": "sum by (user_agent) (zcash_net_peers_connected)", + "legendFormat": "{{user_agent}}", + "refId": "A" + } + ], + "title": "Peer Distribution", + "type": "piechart" + }, + { + "collapsed": false, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 19 }, + "id": 103, + "panels": [], + "title": "Consensus & Mempool", + "type": "row" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "description": "Mempool transaction count and size", + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": false, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] } + }, + "overrides": [ + { "matcher": { "id": "byName", "options": "Transactions" }, "properties": [{ "id": "custom.axisPlacement", "value": "left" }] }, + { "matcher": { "id": "byName", "options": "Size" }, "properties": [{ "id": "custom.axisPlacement", "value": "right" }, { "id": "unit", "value": "bytes" }] } + ] + }, + "gridPos": { "h": 8, "w": 8, "x": 0, "y": 20 }, + "id": 30, + "options": { + "legend": { "calcs": ["lastNotNull", "max"], "displayMode": "table", "placement": "bottom", "showLegend": true }, + "tooltip": { "mode": "multi", "sort": "none" } + }, + "pluginVersion": "10.0.0", + "targets": [ + { "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, "expr": "zcash_mempool_size_transactions", "legendFormat": "Transactions", "refId": "A" }, + { "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, "expr": "zcash_mempool_size_bytes", "legendFormat": "Size", "refId": "B" } + ], + "title": "Mempool Size", + "type": "timeseries" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "description": "Cryptographic proof verification rate (Halo2 + Groth16)", + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "proofs/s", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": false, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { "h": 8, "w": 8, "x": 8, "y": 20 }, + "id": 31, + "options": { + "legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom", "showLegend": true }, + "tooltip": { "mode": "multi", "sort": "desc" } + }, + "pluginVersion": "10.0.0", + "targets": [ + { "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, "expr": "rate(proofs_halo2_verified[1m])", "legendFormat": "Halo2", "refId": "A" }, + { "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, "expr": "rate(proofs_groth16_verified[1m])", "legendFormat": "Groth16", "refId": "B" } + ], + "title": "Proof Verification Rate", + "type": "timeseries" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "description": "Block download and verification rate", + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "blocks/s", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": false, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { "h": 8, "w": 8, "x": 16, "y": 20 }, + "id": 32, + "options": { + "legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom", "showLegend": true }, + "tooltip": { "mode": "multi", "sort": "none" } + }, + "pluginVersion": "10.0.0", + "targets": [ + { "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, "expr": "rate(zcash_chain_verified_block_total[1m])", "legendFormat": "Verified", "refId": "A" }, + { "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, "expr": "rate(sync_downloaded_block_count[1m])", "legendFormat": "Downloaded", "refId": "B" } + ], + "title": "Block Rate", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 28 }, + "id": 104, + "panels": [], + "title": "Infrastructure", + "type": "row" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "description": "RocksDB database disk usage", + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "green", "value": null }, + { "color": "yellow", "value": 80000000000 }, + { "color": "red", "value": 150000000000 } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { "h": 4, "w": 8, "x": 0, "y": 29 }, + "id": 40, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "expr": "zebra_state_rocksdb_total_disk_size_bytes", + "legendFormat": "DB Size", + "refId": "A" + } + ], + "title": "Database Size", + "type": "stat" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "description": "RPC endpoint p99 latency (5m window)", + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "green", "value": null }, + { "color": "yellow", "value": 0.5 }, + { "color": "red", "value": 2 } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { "h": 4, "w": 8, "x": 8, "y": 29 }, + "id": 41, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "textMode": "auto" + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "expr": "histogram_quantile(0.99, sum(rate(rpc_request_duration_seconds_bucket[5m])) by (le))", + "legendFormat": "p99", + "refId": "A" + } + ], + "title": "RPC p99 Latency", + "type": "stat" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "description": "Peer count trend over time with health thresholds", + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": false, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "line" } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "red", "value": null }, + { "color": "yellow", "value": 8 }, + { "color": "green", "value": 20 } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 4, "w": 8, "x": 16, "y": 29 }, + "id": 42, + "options": { + "legend": { "calcs": ["lastNotNull", "min", "max"], "displayMode": "table", "placement": "bottom", "showLegend": true }, + "tooltip": { "mode": "multi", "sort": "none" } + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "expr": "zcash_net_peers", + "legendFormat": "Peers", + "refId": "A" + } + ], + "title": "Peer Count Trend", + "type": "timeseries" + }, + { + "collapsed": true, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 33 }, + "id": 106, + "panels": [ + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "description": "Process CPU usage rate", + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": false, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { "h": 6, "w": 8, "x": 0, "y": 34 }, + "id": 60, + "options": { + "legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom", "showLegend": true }, + "tooltip": { "mode": "multi", "sort": "none" } + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "expr": "rate(process_cpu_seconds_total{job=~\"$job\"}[1m])", + "legendFormat": "CPU", + "refId": "A" + } + ], + "title": "Process CPU Usage", + "type": "timeseries" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "description": "Process memory usage (resident and virtual)", + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": false, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { "h": 6, "w": 8, "x": 8, "y": 34 }, + "id": 61, + "options": { + "legend": { "calcs": ["mean", "max"], "displayMode": "table", "placement": "bottom", "showLegend": true }, + "tooltip": { "mode": "multi", "sort": "none" } + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "expr": "process_resident_memory_bytes{job=~\"$job\"}", + "legendFormat": "Resident", + "refId": "A" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "expr": "process_virtual_memory_bytes{job=~\"$job\"}", + "legendFormat": "Virtual", + "refId": "B" + } + ], + "title": "Process Memory", + "type": "timeseries" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "description": "Open and maximum file descriptors", + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { "legend": false, "tooltip": false, "viz": false }, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": false, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { "mode": "absolute", "steps": [{ "color": "green", "value": null }] }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { "h": 6, "w": 8, "x": 16, "y": 34 }, + "id": 62, + "options": { + "legend": { "calcs": ["lastNotNull", "max"], "displayMode": "table", "placement": "bottom", "showLegend": true }, + "tooltip": { "mode": "multi", "sort": "none" } + }, + "pluginVersion": "10.0.0", + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "expr": "process_open_fds{job=~\"$job\"}", + "legendFormat": "Open", + "refId": "A" + }, + { + "datasource": { "type": "prometheus", "uid": "zebra-prometheus" }, + "expr": "process_max_fds{job=~\"$job\"}", + "legendFormat": "Max", + "refId": "B" + } + ], + "title": "Open File Descriptors", + "type": "timeseries" + } + ], + "title": "System Resources", + "type": "row" + }, + { + "collapsed": false, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 34 }, + "id": 105, + "panels": [], + "title": "Dashboard Links", + "type": "row" + }, + { + "datasource": { "type": "datasource", "uid": "grafana" }, + "gridPos": { "h": 3, "w": 24, "x": 0, "y": 35 }, + "id": 50, + "options": { + "code": { "language": "plaintext", "showLineNumbers": false, "showMiniMap": false }, + "content": "### Detailed Dashboards\n\n| [Syncer](/d/Sl3h19Gnk/syncer) | [Network Health](/d/320aS_dMk/network-health) | [Peers](/d/S29TgUH7k/peers) | [RPC Metrics](/d/zebra-rpc-metrics/zebra-rpc-metrics) | [RPC Tracing](/d/zebra-rpc-tracing/zebra-rpc-tracing) | [Value Pools](/d/zebra-value-pools/zebra-value-pools) | [Mempool](/d/wVXGE6v7z/mempool) | [Database](/d/zebra-rocksdb/rocksdb-database) | [Tx Verification](/d/UXVRR1v7z/transaction-verification) | [Block Verification](/d/rO_Cl5tGz/block-verification) |", + "mode": "markdown" + }, + "pluginVersion": "10.0.0", + "title": "", + "transparent": true, + "type": "text" + } + ], + "refresh": "10s", + "schemaVersion": 38, + "style": "dark", + "tags": ["zebra", "zcash", "overview"], + "templating": { + "list": [ + { + "allValue": null, + "current": { + "selected": true, + "text": ["All"], + "value": ["$__all"] + }, + "datasource": { + "type": "prometheus", + "uid": "zebra-prometheus" + }, + "definition": "label_values(state_finalized_block_height, job)", + "description": "Filter by Prometheus job label", + "hide": 0, + "includeAll": true, + "label": "Job", + "multi": true, + "name": "job", + "options": [], + "query": { + "query": "label_values(state_finalized_block_height, job)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "type": "query" + } + ] + }, + "time": { "from": "now-1h", "to": "now" }, + "timepicker": {}, + "timezone": "", + "title": "Zebra Overview", + "uid": "zebra-overview", + "version": 1, + "weekStart": "" +} diff --git a/observability/grafana/provisioning/dashboards/default.yml b/observability/grafana/provisioning/dashboards/default.yml new file mode 100644 index 0000000..c364c8b --- /dev/null +++ b/observability/grafana/provisioning/dashboards/default.yml @@ -0,0 +1,13 @@ +apiVersion: 1 + +providers: + - name: 'Zebra Dashboards' + orgId: 1 + folder: 'Zebra' + folderUid: 'zebra-dashboards' + type: file + disableDeletion: false + updateIntervalSeconds: 30 + allowUiUpdates: true + options: + path: /var/lib/grafana/dashboards diff --git a/observability/grafana/provisioning/datasources/datasources.yml b/observability/grafana/provisioning/datasources/datasources.yml new file mode 100644 index 0000000..114fcb0 --- /dev/null +++ b/observability/grafana/provisioning/datasources/datasources.yml @@ -0,0 +1,19 @@ +apiVersion: 1 + +datasources: + # Prometheus for metrics (Zebra metrics + Jaeger spanmetrics) + - name: Prometheus + type: prometheus + access: proxy + url: http://prometheus:9090 + isDefault: true + editable: false + uid: zebra-prometheus + + # Jaeger for distributed tracing + - name: Jaeger + type: jaeger + access: proxy + url: http://jaeger:16686 + editable: false + uid: zebra-jaeger diff --git a/observability/jaeger/README.md b/observability/jaeger/README.md new file mode 100644 index 0000000..18d7df2 --- /dev/null +++ b/observability/jaeger/README.md @@ -0,0 +1,344 @@ +# Jaeger Distributed Tracing + +Jaeger shows how requests flow through Zebra, where time is spent, and where errors occur. + +## Why Jaeger? + +While Prometheus metrics show **what** is happening (counters, gauges, histograms), Jaeger traces show **how** it's happening: + +| Metrics (Prometheus) | Traces (Jaeger) | +|---------------------|-----------------| +| "RPC latency P95 is 500ms" | "This specific getblock call spent 400ms in state lookup" | +| "10 requests/second" | "Request X called service A → B → C with these timings" | +| "5% error rate" | "This error started in component Y and propagated to Z" | + +Jaeger is used by other blockchain clients including Lighthouse, Reth, Hyperledger Besu, and Hyperledger Fabric for similar observability needs. + +## Accessing Jaeger + +Open http://localhost:16686 in your browser. + +## Concepts + +### Traces and Spans + +- **Trace**: A complete request journey through the system (e.g., an RPC call from start to finish) +- **Span**: A single operation within a trace (e.g., "verify block", "read from database") +- **Parent-child relationships**: Spans form a tree showing how operations nest + +### Span Kinds + +Jaeger categorizes spans by their role: + +| Kind | Description | Example in Zebra | +|------|-------------|------------------| +| **SERVER** | Entry point handling external requests | JSON-RPC endpoints (`getblock`, `getinfo`) | +| **INTERNAL** | Internal operations within the service | Block verification, state operations | +| **CLIENT** | Outgoing calls to other services | (Not currently used) | + +This distinction is important for the Monitor tab (see below). + +## Monitor Tab (Service Performance Monitoring) + +The Monitor tab provides RED metrics (Rate, Errors, Duration) aggregated from traces. + +### Understanding the Dashboard + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ Service: zebra Span Kind: [Internal ▼] │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ Latency (s) Error rate (%) Request rate (req/s) │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ /\ │ │ │ │ ___ │ │ +│ │ / \ │ │ ────── │ │ / \ │ │ +│ │ / \ │ │ │ │ / \ │ │ +│ └──────────┘ └──────────┘ └──────────┘ │ +│ │ +├─────────────────────────────────────────────────────────────────────┤ +│ Operations metrics │ +│ ┌────────────────────────────────────────────────────────────────┐│ +│ │ Name P95 Latency Request rate Error ││ +│ │ state 10s 10.27 req/s < 0.1% ││ +│ │ connector 4.52s 0.24 req/s < 0.1% ││ +│ │ checkpoint 95.88ms 6.69 req/s < 0.1% ││ +│ │ best_tip_height 4.75ms 58.84 req/s < 0.1% ││ +│ │ download_and_verify 2.32s 0.11 req/s < 0.1% ││ +│ └────────────────────────────────────────────────────────────────┘│ +└─────────────────────────────────────────────────────────────────────┘ +``` + +### Columns Explained + +| Column | Meaning | What to Look For | +|--------|---------|------------------| +| **Name** | Span/operation name | Group of related operations | +| **P95 Latency** | 95th percentile duration | High values indicate slow operations | +| **Request rate** | Operations per second | Throughput of each operation type | +| **Error rate** | Percentage of failures | Any value > 0% needs investigation | +| **Impact** | Relative contribution to overall latency | Focus optimization on high-impact operations | + +### Span Kind Filter + +The "Span Kind" dropdown filters which operations you see: + +- **Internal**: All internal Zebra operations (default view) + - Block verification, state operations, network handling + - High volume during sync + +- **Server**: External-facing request handlers + - JSON-RPC endpoints (`getblock`, `getinfo`, `sendrawtransaction`) + - Use this for RPC performance monitoring + +### Interpreting Common Operations + +| Operation | Description | Normal Latency | +|-----------|-------------|----------------| +| `state` | State database operations | 1-10s during sync | +| `connector` | Peer connection establishment | 1-5s | +| `dial` | Network dial attempts | 1-5s | +| `checkpoint` | Checkpoint verification | 50-200ms | +| `best_tip_height` | Chain tip queries | < 10ms | +| `download_and_verify` | Block download + verification | 1-5s | +| `block_commitment_is_valid_for_chain_history` | Block validation | 50-200ms | +| `rpc_request` | JSON-RPC handler (SERVER span) | Method-dependent | + +## Search Tab + +Use the Search tab to find specific traces. + +### Finding Traces + +1. **Service**: Select `zebra` +2. **Operation**: Choose a specific operation or "all" +3. **Tags**: Filter by attributes (e.g., `rpc.method=getblock`) +4. **Min/Max Duration**: Find slow or fast requests +5. **Limit**: Number of results (default: 20) + +### Useful Search Queries + +**Find slow RPC calls:** +``` +Service: zebra +Operation: rpc_request +Min Duration: 1s +``` + +**Find failed operations:** +``` +Service: zebra +Tags: error=true +``` + +**Find specific RPC method:** +``` +Service: zebra +Tags: rpc.method=getblock +``` + +## Trace Detail View + +Clicking a trace opens the detail view showing: + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ Trace: abc123 (5 spans, 234ms) │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ ├─ rpc_request [SERVER] ─────────────────────────────── 234ms ────┤│ +│ │ rpc.method: getblock ││ +│ │ rpc.system: jsonrpc ││ +│ │ ││ +│ │ ├─ state::read_block ─────────────────────── 180ms ───────────┤││ +│ │ │ block.height: 2000000 │││ +│ │ │ │││ +│ │ │ ├─ db::get ───────────────────── 150ms ──────────────────┤│││ +│ │ │ │ cf: blocks ││││ +│ │ │ └─────────────────────────────────────────────────────────┘│││ +│ │ └─────────────────────────────────────────────────────────────┘││ +│ └─────────────────────────────────────────────────────────────────┘│ +└─────────────────────────────────────────────────────────────────────┘ +``` + +### Reading the Waterfall + +- **Horizontal bars**: Duration of each span +- **Nesting**: Parent-child relationships +- **Colors**: Different services/components +- **Tags**: Attributes attached to each span + +### Span Attributes + +| Attribute | Meaning | +|-----------|---------| +| `otel.kind` | Span type (server, internal) | +| `rpc.method` | JSON-RPC method name | +| `rpc.system` | Protocol (jsonrpc) | +| `otel.status_code` | ERROR on failure | +| `rpc.error_code` | JSON-RPC error code | +| `error.message` | Error description | + +## Debugging with Traces + +### Performance Issues + +1. **Find slow traces**: Use Min Duration filter in Search +2. **Identify bottleneck**: Look for the longest span in the waterfall +3. **Check children**: See if parent is slow due to child operations +4. **Compare traces**: Compare fast vs slow traces for same operation + +### Error Investigation + +1. **Find failed traces**: Search with `error=true` tag +2. **Locate error span**: Look for spans with `otel.status_code=ERROR` +3. **Read error message**: Check `error.message` attribute +4. **Trace propagation**: See how error affected parent operations + +### Example: Debugging Slow RPC + +``` +Problem: getblock calls are slow + +1. Search: + - Service: zebra + - Operation: rpc_request + - Tags: rpc.method=getblock + - Min Duration: 500ms + +2. Open a slow trace + +3. Examine waterfall: + - rpc_request: 800ms total + └─ state::read_block: 750ms ← Most time spent here + └─ db::get: 700ms ← Database is the bottleneck + +4. Conclusion: Database read is slow, possibly due to: + - Disk I/O + - Large block size + - Missing cache +``` + +## RPC Tracing + +Zebra's JSON-RPC endpoints are instrumented with `SPAN_KIND_SERVER` spans, enabling: + +- **Jaeger SPM**: View RPC metrics in the Monitor tab (select "Server" span kind) +- **Per-method analysis**: Filter by `rpc.method` to see specific endpoint performance +- **Error tracking**: Failed RPC calls have `otel.status_code=ERROR` + +### RPC Span Attributes + +Each RPC request includes: + +| Attribute | Example | Description | +|-----------|---------|-------------| +| `otel.kind` | `server` | Marks as server-side handler | +| `rpc.method` | `getblock` | JSON-RPC method name | +| `rpc.system` | `jsonrpc` | Protocol identifier | +| `otel.status_code` | `ERROR` | Present on failure | +| `rpc.error_code` | `-8` | JSON-RPC error code | + +## Compare Tab + +Use Compare to diff two traces: + +1. Select two trace IDs +2. View side-by-side comparison +3. Identify differences in timing or structure + +Useful for: +- Comparing fast vs slow versions of same request +- Before/after optimization comparisons +- Debugging intermittent issues + +## System Architecture Tab + +Shows service dependencies based on trace data: + +- Nodes: Services (currently just `zebra`) +- Edges: Communication between services +- Useful when Zebra calls external services + +## Configuration + +Jaeger v2 configuration is in `config.yaml`: + +```yaml +# Key settings +receivers: + otlp: + protocols: + http: + endpoint: 0.0.0.0:4318 # OTLP HTTP (used by Zebra) + +processors: + batch: {} # Batches spans for efficiency + +connectors: + spanmetrics: # Generates Prometheus metrics from spans + namespace: traces.spanmetrics + +exporters: + prometheus: + endpoint: 0.0.0.0:8889 # Spanmetrics for Prometheus +``` + +### Spanmetrics + +Jaeger automatically generates Prometheus metrics from spans: + +```bash +# View spanmetrics +curl -s http://localhost:8889/metrics | grep traces_spanmetrics +``` + +These power the Monitor tab and can be scraped by Prometheus for Grafana dashboards. + +## Usage Tips + +### During Initial Sync + +- **Reduce sampling** to 1-10% to avoid overwhelming Jaeger +- **Focus on errors** rather than complete traces +- **Use metrics** (Prometheus) for high-level sync progress + +### Steady State Operation + +- **Increase sampling** to 10-50% for better visibility +- **Monitor RPC latency** in the Monitor tab (Server spans) +- **Set up alerts** for high error rates + +### Debugging Sessions + +- **Set sampling to 100%** temporarily +- **Use specific searches** to find relevant traces +- **Compare traces** to identify anomalies +- **Remember to reduce sampling** after debugging + +## Troubleshooting + +### No traces appearing + +1. Ensure Zebra was built with OTel support: `docker compose build --build-arg FEATURES="default-release-binaries opentelemetry" zebra` +2. Check tracing env vars are set: `ZEBRA_TRACING__OPENTELEMETRY_ENDPOINT=http://jaeger:4318` +3. Verify Jaeger health: http://localhost:16686 +4. Check Jaeger logs: `docker compose logs jaeger` + +### Monitor tab shows "No Data" + +1. Ensure spanmetrics connector is configured +2. Wait for traces to accumulate (needs a few minutes) +3. Select the correct Span Kind filter + +### High memory usage + +1. Reduce sampling: `OTEL_TRACES_SAMPLER_ARG=10` +2. Restart Jaeger to clear memory +3. Consider using external storage for production + +## Further Reading + +- [Jaeger Documentation](https://www.jaegertracing.io/docs/) +- [OpenTelemetry Tracing Concepts](https://opentelemetry.io/docs/concepts/signals/traces/) +- [Jaeger v2 Architecture](https://www.jaegertracing.io/docs/2.0/architecture/) diff --git a/observability/jaeger/config.yaml b/observability/jaeger/config.yaml new file mode 100644 index 0000000..21c4f1e --- /dev/null +++ b/observability/jaeger/config.yaml @@ -0,0 +1,86 @@ +# Jaeger v2 Configuration +# Based on OpenTelemetry Collector architecture +# Reference: https://www.jaegertracing.io/docs/2.0/getting-started/ + +receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + http: + endpoint: 0.0.0.0:4318 + +processors: + batch: + timeout: 1s + send_batch_size: 1024 + +connectors: + # Spanmetrics connector generates metrics from spans for Jaeger SPM + # Reference: https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/connector/spanmetricsconnector + spanmetrics: + namespace: span_metrics + metrics_flush_interval: 15s + # Histogram buckets for latency distribution + histogram: + explicit: + buckets: [1ms, 5ms, 10ms, 25ms, 50ms, 100ms, 250ms, 500ms, 1s, 2.5s, 5s, 10s] + dimensions: + - name: rpc.method + - name: rpc.system + - name: otel.status_code + +exporters: + # Jaeger storage (in-memory for development) + jaeger_storage_exporter: + trace_storage: memstore + + # Prometheus metrics endpoint for spanmetrics (used by Jaeger SPM) + prometheus: + endpoint: 0.0.0.0:8889 + namespace: traces + const_labels: + service: zebra + +extensions: + jaeger_storage: + backends: + memstore: + memory: + max_traces: 100000 + # Metrics storage for SPM - reads from Prometheus + metric_backends: + prometheus: + prometheus: + endpoint: http://prometheus:9090 + normalize_calls: true + normalize_duration: true + + jaeger_query: + storage: + traces: memstore + metrics: prometheus + ui: + config_file: "" + + healthcheckv2: + use_v2: true + http: + endpoint: 0.0.0.0:13133 + +service: + extensions: + - jaeger_storage + - jaeger_query + - healthcheckv2 + + pipelines: + traces: + receivers: [otlp] + processors: [batch] + exporters: [jaeger_storage_exporter, spanmetrics] + + # Spanmetrics pipeline - exports to Prometheus + metrics/spanmetrics: + receivers: [spanmetrics] + exporters: [prometheus] diff --git a/observability/prometheus/prometheus.yaml b/observability/prometheus/prometheus.yaml new file mode 100644 index 0000000..9c56994 --- /dev/null +++ b/observability/prometheus/prometheus.yaml @@ -0,0 +1,42 @@ +# Prometheus configuration for Zebra metrics and tracing +# +# IMPORTANT: The scrape_interval affects Grafana dashboard queries. +# The rate() function requires at least 2 data points, so rate windows +# must be > 2x scrape_interval. With 15s scrape_interval: +# - rate(...[1m]) works (4 samples) +# - rate(...[30s]) works (2 samples minimum) +# - rate(...[1s]) does NOT work (insufficient samples) +# +# If you change scrape_interval, update dashboard rate() windows accordingly. + +global: + scrape_interval: 15s + evaluation_interval: 15s + +rule_files: + - /etc/prometheus/rules/*.yml + +alerting: + alertmanagers: + - static_configs: + - targets: ['alertmanager:9093'] + +scrape_configs: + # Zebra node metrics (z3 stack uses z3_zebra container name) + - job_name: "zebra" + scrape_interval: 15s + scrape_timeout: 10s + metrics_path: "/metrics" + static_configs: + - targets: ["zebra:9999"] + labels: + stack: "z3" + + # Jaeger spanmetrics for RPC tracing + # These metrics are generated by the spanmetrics connector in Jaeger + # and provide RED metrics (Rate, Errors, Duration) for RPC calls + - job_name: "jaeger-spanmetrics" + scrape_interval: 15s + scrape_timeout: 10s + static_configs: + - targets: ["jaeger:8889"] diff --git a/observability/prometheus/rules/zebra_alerts.yml b/observability/prometheus/rules/zebra_alerts.yml new file mode 100644 index 0000000..688ea07 --- /dev/null +++ b/observability/prometheus/rules/zebra_alerts.yml @@ -0,0 +1,149 @@ +# Zebra Alerting Rules +# +# These rules are evaluated by Prometheus and sent to AlertManager. +# AlertManager handles routing, grouping, and notifications. +# +# To receive notifications, configure receivers in: +# docker/observability/alertmanager/alertmanager.yml + +groups: + - name: zebra + rules: + # Node is down or metrics endpoint unreachable + - alert: ZebraDown + expr: up{job="zebra"} == 0 + for: 2m + labels: + severity: critical + annotations: + summary: "Zebra node is down" + description: "Zebra metrics endpoint has been unreachable for 2 minutes." + + # Block height not increasing (sync may be stalled) + - alert: ZebraSyncStalled + expr: changes(zcash_chain_verified_block_height[15m]) == 0 + for: 5m + labels: + severity: warning + annotations: + summary: "Block height stalled" + description: "Zebra block height has not increased in the last 15 minutes. Node may be stuck or network may be partitioned." + + # Low peer count (degraded connectivity) + - alert: ZebraLowPeers + expr: zcash_net_peers < 3 + for: 5m + labels: + severity: warning + annotations: + summary: "Low peer count" + description: "Zebra has fewer than 3 peers for 5 minutes. Network connectivity may be degraded." + + # No peers at all (network isolation) + - alert: ZebraNoPeers + expr: zcash_net_peers == 0 + for: 2m + labels: + severity: critical + annotations: + summary: "No peers connected" + description: "Zebra has 0 peers for 2 minutes. Check network connectivity and firewall rules." + + # High error rate + - alert: ZebraHighErrorRate + expr: rate(zebra_errors_total[5m]) > 0.1 + for: 5m + labels: + severity: warning + annotations: + summary: "High error rate" + description: "Zebra is experiencing elevated error rates (> 0.1/s for 5 minutes)." + + # Value Pool Alerts (Monitoring) + - name: zebra-value-pools + rules: + # Critical: Negative value pool would indicate a bug (should never happen - Zebra rejects such blocks) + - alert: ValuePoolNegative + expr: state_finalized_value_pool_transparent < 0 or state_finalized_value_pool_sprout < 0 or state_finalized_value_pool_sapling < 0 or state_finalized_value_pool_orchard < 0 + for: 0m + labels: + severity: critical + annotations: + summary: "Negative value pool detected" + description: "A pool has a negative balance in metrics. Zebra enforces ZIP-209 internally, so this should not occur in normal operation." + + # Warning: Value pool not updating (may indicate sync issues) + - alert: ValuePoolStale + expr: changes(state_finalized_chain_supply_total[15m]) == 0 and state_finalized_block_height > 0 + for: 5m + labels: + severity: warning + annotations: + summary: "Value pool not updating" + description: "Chain supply has not changed in 15 minutes while blocks exist. Sync may be stalled." + + # RPC Alerts + - name: zebra-rpc + rules: + # High RPC latency + - alert: RPCHighLatency + expr: histogram_quantile(0.99, sum(rate(rpc_request_duration_seconds_bucket[5m])) by (le, method)) > 2 + for: 5m + labels: + severity: warning + annotations: + summary: "High RPC latency detected" + description: "RPC method {{ $labels.method }} has p99 latency > 2 seconds for 5 minutes." + + # High RPC error rate + - alert: RPCHighErrorRate + expr: sum(rate(rpc_errors_total[5m])) by (method) / sum(rate(rpc_requests_total[5m])) by (method) > 0.1 + for: 5m + labels: + severity: warning + annotations: + summary: "High RPC error rate" + description: "RPC method {{ $labels.method }} has error rate > 10% for 5 minutes." + + # RPC endpoint overloaded + - alert: RPCOverloaded + expr: rpc_active_requests > 100 + for: 2m + labels: + severity: warning + annotations: + summary: "RPC endpoint overloaded" + description: "More than 100 concurrent RPC requests for 2 minutes. Consider rate limiting." + + # Peer Health Alerts + - name: zebra-peer-health + rules: + # High handshake failure rate + - alert: HandshakeFailureRateHigh + expr: sum(rate(zcash_net_peer_handshake_failures_total[5m])) / (sum(rate(zcash_net_peer_handshake_duration_seconds_count[5m])) + 0.001) > 0.5 + for: 5m + labels: + severity: warning + annotations: + summary: "High peer handshake failure rate" + description: "More than 50% of peer handshakes are failing for 5 minutes. Check network connectivity." + + # Specific failure reason spike (e.g., obsolete version) + - alert: ObsoleteVersionHandshakes + expr: rate(zcash_net_peer_handshake_failures_total{reason="obsolete_version"}[5m]) > 0.1 + for: 5m + labels: + severity: info + annotations: + summary: "Many obsolete version handshakes" + description: "Elevated rate of peer handshake failures due to obsolete protocol versions. May indicate network upgrade in progress." + + # Slow handshakes + - alert: SlowHandshakes + expr: histogram_quantile(0.95, sum(rate(zcash_net_peer_handshake_duration_seconds_bucket{result="success"}[5m])) by (le)) > 10 + for: 5m + labels: + severity: warning + annotations: + summary: "Slow peer handshakes" + description: "p95 handshake duration exceeds 10 seconds. Network latency may be high." From 65cce4c0ec8433eab2d1f06e3db43d605d444d0b Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Fri, 6 Mar 2026 14:10:05 -0300 Subject: [PATCH 36/57] Bump zallet to latest main (757876b) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- zallet | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zallet b/zallet index f0db32d..757876b 160000 --- a/zallet +++ b/zallet @@ -1 +1 @@ -Subproject commit f0db32d23de36b9a8e0c48b4438d22ab076aca58 +Subproject commit 757876b0f9a5ca683fbee459dfc7969657096271 From 9d410918a790b41165efa6e11a04879561cf46c0 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Fri, 6 Mar 2026 17:52:41 -0300 Subject: [PATCH 37/57] add `rpc-router` Co-authored-by: David Campbell --- rpc-router/Cargo.lock | 2715 +++++++++++++++++ rpc-router/Cargo.toml | 23 + rpc-router/Dockerfile | 19 + rpc-router/README.md | 111 + rpc-router/regtest/README.md | 98 + rpc-router/regtest/config/zallet.toml | 42 + rpc-router/regtest/config/zallet_identity.txt | 3 + rpc-router/regtest/docker-compose.yml | 88 + rpc-router/regtest/init.sh | 67 + rpc-router/src/defaults.rs | 22 + rpc-router/src/integration_tests.rs | 285 ++ rpc-router/src/main.rs | 390 +++ rpc-router/src/unit_tests.rs | 160 + 13 files changed, 4023 insertions(+) create mode 100644 rpc-router/Cargo.lock create mode 100644 rpc-router/Cargo.toml create mode 100644 rpc-router/Dockerfile create mode 100644 rpc-router/README.md create mode 100644 rpc-router/regtest/README.md create mode 100644 rpc-router/regtest/config/zallet.toml create mode 100644 rpc-router/regtest/config/zallet_identity.txt create mode 100644 rpc-router/regtest/docker-compose.yml create mode 100755 rpc-router/regtest/init.sh create mode 100644 rpc-router/src/defaults.rs create mode 100644 rpc-router/src/integration_tests.rs create mode 100644 rpc-router/src/main.rs create mode 100644 rpc-router/src/unit_tests.rs diff --git a/rpc-router/Cargo.lock b/rpc-router/Cargo.lock new file mode 100644 index 0000000..61b2ed6 --- /dev/null +++ b/rpc-router/Cargo.lock @@ -0,0 +1,2715 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "async-attributes" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.5.0", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener 5.4.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-object-pool" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "333c456b97c3f2d50604e8b2624253b7f787208cb72eb75e64b0ad11b221652c" +dependencies = [ + "async-std", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel 2.5.0", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener 5.4.1", + "futures-lite", + "rustix", +] + +[[package]] +name = "async-signal" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-std" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" +dependencies = [ + "async-attributes", + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "async-process", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "basic-cookies" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67bd8fd42c16bdb08688243dc5f0cc117a3ca9efeeaba3a345a18a6159ad96f7" +dependencies = [ + "lalrpop", + "lalrpop-util", + "regex", +] + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel 2.5.0", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabffdaee24bd1bf95c5ef7cec31260444317e72ea56c4c91750e8b7ee58d5f1" +dependencies = [ + "log", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.1", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.4.0", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.4.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.4.0", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "httpmock" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ec9586ee0910472dec1a1f0f8acf52f0fdde93aea74d70d4a3107b4be0fd5b" +dependencies = [ + "assert-json-diff", + "async-object-pool", + "async-std", + "async-trait", + "base64 0.21.7", + "basic-cookies", + "crossbeam-utils", + "form_urlencoded", + "futures-util", + "hyper 0.14.32", + "lazy_static", + "levenshtein", + "log", + "regex", + "serde", + "serde_json", + "serde_regex", + "similar", + "tokio", + "url", +] + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http 1.4.0", + "http-body 1.0.1", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http 1.4.0", + "hyper 1.8.1", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.8.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "hyper 1.8.1", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.3", + "system-configuration", + "tokio", + "tower-layer", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "js-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lalrpop" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "pico-args", + "regex", + "regex-syntax", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "levenshtein" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" + +[[package]] +name = "libc" +version = "0.2.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" + +[[package]] +name = "libredox" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +dependencies = [ + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +dependencies = [ + "value-bag", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "h2", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tower 0.5.3", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rpc-router" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64 0.22.1", + "http-body-util", + "httpmock", + "hyper 1.8.1", + "hyper-util", + "reqwest", + "serde", + "serde_json", + "serial_test", + "tokio", + "tower 0.4.13", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scc" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" +dependencies = [ + "sdd", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sdd" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_regex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" +dependencies = [ + "regex", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serial_test" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "911bd979bf1070a3f3aa7b691a3b3e9968f339ceeec89e08c280a8a22207a32f" +dependencies = [ + "futures-executor", + "futures-util", + "log", + "once_cell", + "parking_lot", + "scc", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a7d91949b85b0d2fb687445e448b40d322b6b3e4af6b44a29b21d9a5f33e6d9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.3", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "iri-string", + "pin-project-lite", + "tower 0.5.3", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "value-bag" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/rpc-router/Cargo.toml b/rpc-router/Cargo.toml new file mode 100644 index 0000000..5e39c9a --- /dev/null +++ b/rpc-router/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "rpc-router" +description = "Z3 RPC request router" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0" +base64 = "0.22" +http-body-util = "0.1" +hyper = { version = "1.2", features = ["full"] } +hyper-util = { version = "0.1", features = ["full"] } +reqwest = { version = "0.12", features = ["json"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +tokio = { version = "1.36", features = ["full"] } +tower = { version = "0.4", features = ["util"] } +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } + +[dev-dependencies] +httpmock = "0.7" +serial_test = "3" diff --git a/rpc-router/Dockerfile b/rpc-router/Dockerfile new file mode 100644 index 0000000..50f7b00 --- /dev/null +++ b/rpc-router/Dockerfile @@ -0,0 +1,19 @@ +FROM lukemathwalker/cargo-chef:latest-rust-1.85 AS chef +WORKDIR /app + +FROM chef AS planner +COPY . . +RUN cargo chef prepare --recipe-path recipe.json + +FROM chef AS builder +COPY --from=planner /app/recipe.json recipe.json +# Build dependencies - this is the caching Docker layer! +RUN cargo chef cook --release --recipe-path recipe.json +# Build application +COPY . . +RUN cargo build --release --bin rpc-router + +# We do not need the Rust toolchain to run the binary! +FROM gcr.io/distroless/cc-debian12 AS runtime +COPY --from=builder /app/target/release/rpc-router /usr/local/bin/ +ENTRYPOINT ["/usr/local/bin/rpc-router"] diff --git a/rpc-router/README.md b/rpc-router/README.md new file mode 100644 index 0000000..00c8f4a --- /dev/null +++ b/rpc-router/README.md @@ -0,0 +1,111 @@ +# Z3 RPC Router + +The **Z3 RPC Router** (`rpc-router` crate) provides a single JSON-RPC endpoint on top of the Zebra and Zallet RPC servers. + +It exposes a new port where incoming RPC requests are transparently routed to the appropriate backend: + +- RPC methods belonging to Zebra are forwarded to the Zebra RPC endpoint. +- RPC methods belonging to Zallet are forwarded to the Zallet RPC endpoint. + +From the point of view of a dApp built on top of the Zcash Z3 stack, this means there is a single RPC endpoint to interact with, while all routing happens in the background. + +In addition to request routing, the router also: + +- Calls `rpc.discover` on both Zebra and Zallet at startup +- Builds a merged OpenRPC schema for the Z3 stack +- Exposes the merged schema via its own `rpc.discover` method + +## Developer Usage + +To run the router, Zebra and Zallet must already be running, fully synced, and responsive on known ports **before** starting the router. At startup the router calls `rpc.discover` on both backends to build the merged schema — if either is unreachable the router exits immediately with an error. + +The router accepts this configuration via environment variables, or alternatively you can modify the values directly in main.rs. + +### Running Zebra and Zallet for Development + +The easiest way to get Zebra and Zallet running locally is via the Docker Compose stack at the root of this repository: + +```bash +docker compose up -d zebra zallet +``` + +Wait until both services are healthy, then note the RPC ports from your `.env` file (or the defaults in `docker-compose.yml`) and pass them to the router. + +> **Note:** A `openrpc.py` QA helper that spawned Zebra and Zallet in regtest mode previously existed in the Zebra repository but was removed. It has not been ported to the [zcash/integration-tests](https://github.com/zcash/integration-tests) repository. + +> **Note:** For experimental Z3 regtest mode of the router see [Regtest Environment](regtest/README.md). + +### Running the RPC Router + +```bash +RUST_LOG=info \ +ZEBRA_URL=http://localhost:/ \ +ZALLET_URL=http://localhost:/ \ +cargo run +``` + +Example output: + +```bash +INFO rpc_router: RPC Router listening on 0.0.0.0:8080 +INFO rpc_router: You can use the following playground URL: +INFO rpc_router: https://playground.open-rpc.org/?... +``` + +At this point, the router is listening on `http://localhost:8080`. + +### Querying the Router + +You can use standard JSON-RPC clients (such as `curl`) to call methods exposed by either Zebra or Zallet through the router. + +#### Example: Zebra RPC Call + +```bash +curl --silent -X POST \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"getinfo","params":[],"id":123}' \ + http://127.0.0.1:8080 | jq +``` + +On the router side, you should see: + +```bash +INFO rpc_router: Routing getinfo to Zebra +``` + +#### Example: Zallet RPC Call + +```bash +curl --silent -X POST \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"getwalletinfo","params":[],"id":123}' \ + http://127.0.0.1:8080 | jq +``` + +And on the router logs: + +```bash +INFO rpc_router: Routing getwalletinfo to Zallet +``` + +### OpenRPC Playground + +You can interact with the merged OpenRPC schema using the OpenRPC Playground, pointed at your running router: + +[Playground](https://playground.open-rpc.org/?uiSchema[appBar][ui:title]=Zcash&uiSchema[appBar][ui:logoUrl]=https://z.cash/wp-content/uploads/2023/03/zcash-logo.gif&schemaUrl=http://127.0.0.1:8080&uiSchema[appBar][ui:splitView]=false&uiSchema[appBar][ui:edit]=false&uiSchema[appBar][ui:input]=false&uiSchema[appBar][ui:examplesDropdown]=false&uiSchema[appBar][ui:transports]=false) + +When using the inspector, make sure the target server URL is set to: + +``` +http://localhost:8080/ +``` + +The RPC router automatically sets the required CORS headers, allowing the playground (or other browser-based tools) to call the local endpoint directly. + +By default the `Access-Control-Allow-Origin` header is set to `https://playground.open-rpc.org`. To allow a different origin (e.g. a dApp frontend or any browser client), set the `CORS_ORIGIN` environment variable: + +```bash +CORS_ORIGIN=https://myapp.example.com cargo run +``` + +For local development you can use `CORS_ORIGIN=*` to allow all origins. Avoid using `*` in production as it allows any website to call your local node. diff --git a/rpc-router/regtest/README.md b/rpc-router/regtest/README.md new file mode 100644 index 0000000..e45e56e --- /dev/null +++ b/rpc-router/regtest/README.md @@ -0,0 +1,98 @@ +# RPC Router — Regtest Environment + +Self-contained Docker Compose setup for local end-to-end testing of the rpc-router against real Zebra and Zallet backends in regtest mode. + +**Does not touch the production stack** in the repo root. + +## Prerequisites + +- Docker with the `z3-zallet:local` image built: + ```bash + cd ../../ # repo root + docker compose build zallet + ``` + +## First-time setup + +```bash +cd rpc-router/regtest +./init.sh +``` + +This will: +1. Start Zebra in regtest mode +2. Mine 1 block (activates Orchard at height 1) +3. Initialize the Zallet wallet (`init-wallet-encryption` + `generate-mnemonic`) + +## Start the stack + +```bash +sudo -E docker compose up -d +``` + +Router is available at **http://localhost:8181**. + +## Test routing + +```bash +# Route to Zebra (full node) +curl -s -X POST -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"getblockchaininfo","params":[],"id":1}' \ + http://127.0.0.1:8181 + +# Route to Zallet (wallet) +curl -s -X POST -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"getwalletinfo","params":[],"id":2}' \ + http://127.0.0.1:8181 + +# Merged OpenRPC schema title +curl -s -X POST -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"rpc.discover","params":[],"id":3}' \ + http://127.0.0.1:8181 | grep -o '"title":"[^"]*"' +``` + +## OpenRPC Playground + +Open the playground pointed at your locally running router: + +https://playground.open-rpc.org/?uiSchema[appBar][ui:title]=Zcash&uiSchema[appBar][ui:logoUrl]=https://z.cash/wp-content/uploads/2023/03/zcash-logo.gif&schemaUrl=http://127.0.0.1:8181&uiSchema[appBar][ui:splitView]=false&uiSchema[appBar][ui:edit]=false&uiSchema[appBar][ui:input]=false&uiSchema[appBar][ui:examplesDropdown]=false&uiSchema[appBar][ui:transports]=false + +The playground will call `rpc.discover` on `http://127.0.0.1:8181` to load the live merged schema. + +## Stop and clean up + +```bash +# Stop all containers (keeps volumes/wallet data) +sudo -E docker compose down + +# Stop and delete all volumes (full reset — re-run init.sh afterwards) +sudo -E docker compose down -v +``` + +## Notes + +- Credentials: `zebra` / `zebra` (hardcoded for regtest only) +- Zallet uses regtest nuparams activating all upgrades at block 1 +- The rpc-router Dockerfile is in `rpc-router/` (one level up) + +## Tested environment + +Successfully tested on: + +- **OS**: Linux Mint 21.2 (kernel 5.15.0-171-generic) +- **Rust**: 1.90.0 (rpc-router built locally; Docker image uses `rust-1.85`) +- **Docker Compose**: v5.1.0 (plugin, not standalone `docker-compose`) +- **Zallet image**: `z3-zallet:local` built from submodule at `ae762c05` (Feb 2026) +- **Zebra image**: `zfnd/zebra:3.1.0` + +Expected output for the test commands above: + +**`getwalletinfo`** → routed to Zallet: +```json +{"jsonrpc":"2.0","result":{"walletversion":0,"balance":0.00000000,"unconfirmed_balance":0.00000000,"immature_balance":0.00000000,"shielded_balance":"0.00","shielded_unconfirmed_balance":"0.00","txcount":0,"keypoololdest":0,"keypoolsize":0,"mnemonic_seedfp":"TODO"},"id":1} +``` + +**`getblockchaininfo`** → routed to Zebra (truncated): +```json +{"jsonrpc":"2.0","id":1,"result":{"chain":"test","blocks":1,"headers":1,...,"upgrades":{"5ba81b19":{"name":"Overwinter","activationheight":1,"status":"active"},...}}} +``` diff --git a/rpc-router/regtest/config/zallet.toml b/rpc-router/regtest/config/zallet.toml new file mode 100644 index 0000000..cf11597 --- /dev/null +++ b/rpc-router/regtest/config/zallet.toml @@ -0,0 +1,42 @@ +[builder.limits] +orchard_actions = 250 + +[consensus] +network = "regtest" +regtest_nuparams = [ + "5ba81b19:1", + "76b809bb:1", + "2bb40e60:1", + "f5b9230b:1", + "e9ff75a6:1", +] + +[database] + +[external] + +[features] +as_of_version = "0.0.0" + +[features.deprecated] + +[features.experimental] + +[indexer] +validator_address = "zebra:18232" +validator_user = "zebra" +validator_password = "zebra" + +[keystore] +encryption_identity = "identity.txt" + +[note_management] + +[rpc] +bind = ["0.0.0.0:28232"] +timeout = 30 + +[[rpc.auth]] +user = "zebra" +# Password: "zebra" (from zcash/integration-tests) +pwhash = "eaa27c81df81f655e69bbe84026e733a$bb9f70d53decfeca863cb114f7b0fd5ae050c7ef885c3d5fa73d5bace6c14fea" diff --git a/rpc-router/regtest/config/zallet_identity.txt b/rpc-router/regtest/config/zallet_identity.txt new file mode 100644 index 0000000..558d28b --- /dev/null +++ b/rpc-router/regtest/config/zallet_identity.txt @@ -0,0 +1,3 @@ +# created: 2025-04-22T10:45:29-03:00 +# public key: age1l5d76x6zzsqy90r05a29gysjny7uvh229p5jnrwyqp5dmy39ha2qks3h9f +AGE-SECRET-KEY-1U7FJQWFKZYP7LJFJG9EVM6CG47P5TWP0NU03JE7CZQ6035H2JWDQKCRG65 diff --git a/rpc-router/regtest/docker-compose.yml b/rpc-router/regtest/docker-compose.yml new file mode 100644 index 0000000..3145c24 --- /dev/null +++ b/rpc-router/regtest/docker-compose.yml @@ -0,0 +1,88 @@ +# docker-compose.yml for rpc-router regtest testing +# +# Runs Zebra + Zallet in regtest mode + the rpc-router. +# Completely self-contained; does not touch the production stack. +# +# Usage: +# cd rpc-router/regtest +# ./init.sh # first time: initialize wallet +# docker compose up -d +# # Router available at http://localhost:8181 +# # Test: curl -s -X POST -H "Content-Type: application/json" \ +# # -d '{"jsonrpc":"2.0","method":"getinfo","params":[],"id":1}' \ +# # http://localhost:8181 + +services: + zebra: + image: zfnd/zebra:3.1.0 + container_name: z3_regtest_zebra + restart: unless-stopped + environment: + - RUST_LOG=info + - ZEBRA_NETWORK__NETWORK=Regtest + - ZEBRA_RPC__LISTEN_ADDR=0.0.0.0:18232 + - ZEBRA_RPC__ENABLE_COOKIE_AUTH=false + - ZEBRA_MINING__MINER_ADDRESS=tmSRd1r8gs77Ja67Fw1JcdoXytxsyrLTPJm + - ZEBRA_HEALTH__LISTEN_ADDR=0.0.0.0:8080 + - ZEBRA_HEALTH__ENFORCE_ON_TEST_NETWORKS=false + - ZEBRA_STATE__CACHE_DIR=/home/zebra/.cache/zebra + volumes: + - zebra_regtest_data:/home/zebra/.cache/zebra + ports: + - "18232:18232" + networks: + - regtest_net + healthcheck: + # Regtest has no peers; check RPC port directly + test: ["CMD-SHELL", "curl -sf -X POST -H 'Content-Type: application/json' -d '{\"jsonrpc\":\"2.0\",\"method\":\"getblockchaininfo\",\"params\":[],\"id\":1}' http://127.0.0.1:18232 || exit 1"] + interval: 10s + timeout: 5s + retries: 10 + start_period: 30s + + zallet: + image: z3-zallet:local + container_name: z3_regtest_zallet + restart: unless-stopped + command: ["--datadir", "/var/lib/zallet", "start"] + depends_on: + zebra: + condition: service_healthy + environment: + - RUST_LOG=info + volumes: + - zallet_regtest_data:/var/lib/zallet + - ./config/zallet.toml:/var/lib/zallet/zallet.toml:ro + - ./config/zallet_identity.txt:/var/lib/zallet/identity.txt:ro + ports: + - "28232:28232" + networks: + - regtest_net + + rpc-router: + build: + context: ../ + dockerfile: Dockerfile + container_name: z3_regtest_router + restart: unless-stopped + environment: + - ZEBRA_URL=http://z3_regtest_zebra:18232 + - ZALLET_URL=http://z3_regtest_zallet:28232 + - RPC_USER=zebra + - RPC_PASSWORD=zebra + - LISTEN_PORT=8181 + ports: + - "8181:8181" + depends_on: + zebra: + condition: service_healthy + networks: + - regtest_net + +volumes: + zebra_regtest_data: + zallet_regtest_data: + +networks: + regtest_net: + driver: bridge diff --git a/rpc-router/regtest/init.sh b/rpc-router/regtest/init.sh new file mode 100755 index 0000000..a9b7d1d --- /dev/null +++ b/rpc-router/regtest/init.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +# init.sh - Initialize the regtest wallet for the first time. +# +# Run this once before `docker compose up -d`. +# Safe to re-run: skips steps if already done. +# +# Requirements: +# - Docker with the z3-zallet:local image built (cd ../../ && docker compose build zallet) +# - No running z3_regtest_* containers + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +NETWORK=regtest_regtest_net + +# Use sudo for docker if needed +DOCKER="docker" +if ! docker info > /dev/null 2>&1; then + DOCKER="sudo -E docker" +fi +ZALLET_IMAGE=z3-zallet:local + +cd "$SCRIPT_DIR" + +echo "==> Starting Zebra in regtest mode..." +$DOCKER compose up -d zebra + +echo "==> Waiting for Zebra RPC to be ready..." +until $DOCKER compose exec zebra curl -sf -X POST \ + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"getblockchaininfo","params":[],"id":1}' \ + http://127.0.0.1:18232 > /dev/null 2>&1; do + echo " Zebra not ready yet, retrying..." + sleep 2 +done +echo " Zebra is ready." + +echo "==> Mining 1 block (required for Orchard activation at height 1)..." +$DOCKER compose exec zebra curl -s -u zebra:zebra \ + -X POST -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"generate","params":[1],"id":1}' \ + http://127.0.0.1:18232 | grep -q '"result"' +echo " Block mined." + +echo "==> Running init-wallet-encryption..." +# Remove stale lock file if present (left by a previous interrupted run) +$DOCKER run --rm -v regtest_zallet_regtest_data:/data busybox \ + sh -c 'rm -f /data/.lock' +# Check if wallet already initialized by looking for the wallet database +ALREADY_INIT=$($DOCKER run --rm -v regtest_zallet_regtest_data:/data busybox \ + sh -c 'ls /data/*.sqlite /data/*.age 2>/dev/null | wc -l') +if [ "${ALREADY_INIT:-0}" -gt 0 ]; then + echo " Wallet already initialized, skipping." +else + $DOCKER compose run --rm zallet --datadir /var/lib/zallet init-wallet-encryption + + echo "==> Running generate-mnemonic..." + $DOCKER compose run --rm zallet --datadir /var/lib/zallet generate-mnemonic +fi + +echo "==> Stopping Zebra (will be restarted by docker compose up -d)..." +$DOCKER compose down + +echo "" +echo "✅ Wallet initialized. Now run:" +echo " sudo -E docker compose up -d" +echo " # Router will be available at http://127.0.0.1:8181" diff --git a/rpc-router/src/defaults.rs b/rpc-router/src/defaults.rs new file mode 100644 index 0000000..ec7ff16 --- /dev/null +++ b/rpc-router/src/defaults.rs @@ -0,0 +1,22 @@ +//! Default constants +use std::net::SocketAddr; + +pub(crate) const ZEBRA_URL: &str = "http://127.0.0.1:20251"; + +pub(crate) const ZALLET_URL: &str = "http://127.0.0.1:25251"; + +pub(crate) const ZAINO_URL: &str = "http://zaino:8237"; + +pub(crate) const RPC_USER: &str = "zebra"; + +pub(crate) const RPC_PASSWORD: &str = "zebra"; + +pub(crate) const CORS_ORIGIN: &str = "https://playground.open-rpc.org"; + +pub(crate) const LISTEN_PORT: u16 = 8080; + +pub(crate) const PLAYGROUND_URL: &str = "https://playground.open-rpc.org/?uiSchema[appBar][ui:title]=Zcash&uiSchema[appBar][ui:logoUrl]=https://z.cash/wp-content/uploads/2023/03/zcash-logo.gif&schemaUrl={{addr}}&uiSchema[appBar][ui:splitView]=false&uiSchema[appBar][ui:edit]=false&uiSchema[appBar][ui:input]=false&uiSchema[appBar][ui:examplesDropdown]=false&uiSchema[appBar][ui:transports]=false"; + +pub(crate) fn playground_url(addr: SocketAddr) -> String { + PLAYGROUND_URL.replace("{{addr}}", &addr.to_string()) +} \ No newline at end of file diff --git a/rpc-router/src/integration_tests.rs b/rpc-router/src/integration_tests.rs new file mode 100644 index 0000000..29ada87 --- /dev/null +++ b/rpc-router/src/integration_tests.rs @@ -0,0 +1,285 @@ +use httpmock::prelude::*; +use reqwest::Client; +use serde_json::{json, Value}; +use std::sync::Arc; +use tokio::net::TcpListener; + +use super::*; + +// --- Mock backend helpers --- + +/// Starts a mock Zebra backend that handles rpc.discover and method forwarding. +/// Returns a distinct "zebra-response" result so routing can be verified. +async fn start_zebra_mock() -> MockServer { + let server = MockServer::start_async().await; + + server + .mock_async(|when, then| { + when.method(POST).body_contains("rpc.discover"); + then.status(200).json_body(json!({ + "jsonrpc": "2.0", "id": 1, + "result": { + "openrpc": "1.2.6", + "info": { "title": "Zebra", "version": "1.0.0" }, + "methods": [ + { "name": "getblock", "params": [] }, + { "name": "getinfo", "params": [] } + ], + "components": { "schemas": { "BlockHash": { "type": "string" } } } + } + })); + }) + .await; + + server + .mock_async(|when, then| { + when.method(POST); + then.status(200) + .json_body(json!({ "jsonrpc": "2.0", "id": 1, "result": "zebra-response" })); + }) + .await; + + server +} + +/// Starts a mock Zallet backend that handles rpc.discover and method forwarding. +async fn start_zallet_mock() -> MockServer { + let server = MockServer::start_async().await; + + server + .mock_async(|when, then| { + when.method(POST).body_contains("rpc.discover"); + then.status(200).json_body(json!({ + "jsonrpc": "2.0", "id": 1, + "result": { + "openrpc": "1.2.6", + "info": { "title": "Zallet", "version": "1.0.0" }, + "methods": [ + { "name": "getwalletinfo", "params": [] }, + { "name": "z_sendmany", "params": [] } + ], + "components": { "schemas": { "WalletInfo": { "type": "object" } } } + } + })); + }) + .await; + + server + .mock_async(|when, then| { + when.method(POST); + then.status(200) + .json_body(json!({ "jsonrpc": "2.0", "id": 1, "result": "zallet-response" })); + }) + .await; + + server +} + +/// Starts a mock Zaino backend (generic fallback). +async fn start_zaino_mock() -> MockServer { + let server = MockServer::start_async().await; + + server + .mock_async(|when, then| { + when.method(POST); + then.status(200) + .json_body(json!({ "jsonrpc": "2.0", "id": 1, "result": "zaino-response" })); + }) + .await; + + server +} + +// --- Router startup helper --- + +struct RouterHandle { + pub port: u16, + task: tokio::task::JoinHandle<()>, +} + +impl Drop for RouterHandle { + fn drop(&mut self) { + self.task.abort(); + } +} + +async fn start_router(zebra_url: &str, zallet_url: &str, zaino_url: &str) -> RouterHandle { + let config = Arc::new(Config { + zebra_url: zebra_url.to_string(), + zallet_url: zallet_url.to_string(), + _zaino_url: zaino_url.to_string(), + listen_port: 0, + rpc_user: "zebra".to_string(), + rpc_password: "zebra".to_string(), + cors_origin: "*".to_string(), + }); + + let zebra_schema = call_rpc_discover(&config.zebra_url, &config.rpc_user, &config.rpc_password) + .await + .unwrap()["result"] + .clone(); + let zallet_schema = + call_rpc_discover(&config.zallet_url, &config.rpc_user, &config.rpc_password) + .await + .unwrap()["result"] + .clone(); + let z3 = merge_openrpc_schemas(zebra_schema, zallet_schema).unwrap(); + + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let port = listener.local_addr().unwrap().port(); + + let task = tokio::spawn(async move { + if let Err(e) = run(config, listener, z3).await { + eprintln!("Router error in test: {}", e); + } + }); + + // Let the router start accepting connections. + tokio::time::sleep(std::time::Duration::from_millis(10)).await; + + RouterHandle { port, task } +} + +// --- Tests --- + +#[tokio::test] +async fn test_health_check() { + let zebra = start_zebra_mock().await; + let zallet = start_zallet_mock().await; + let zaino = start_zaino_mock().await; + let router = start_router(&zebra.base_url(), &zallet.base_url(), &zaino.base_url()).await; + + let resp = Client::new() + .get(format!("http://127.0.0.1:{}/health", router.port)) + .send() + .await + .unwrap(); + + assert_eq!(resp.status(), 200); + assert_eq!(resp.text().await.unwrap(), "OK"); +} + +#[tokio::test] +async fn test_non_post_returns_405() { + let zebra = start_zebra_mock().await; + let zallet = start_zallet_mock().await; + let zaino = start_zaino_mock().await; + let router = start_router(&zebra.base_url(), &zallet.base_url(), &zaino.base_url()).await; + + let resp = Client::new() + .get(format!("http://127.0.0.1:{}/", router.port)) + .send() + .await + .unwrap(); + + assert_eq!(resp.status(), 405); +} + +#[tokio::test] +async fn test_cors_preflight_returns_204_with_headers() { + let zebra = start_zebra_mock().await; + let zallet = start_zallet_mock().await; + let zaino = start_zaino_mock().await; + let router = start_router(&zebra.base_url(), &zallet.base_url(), &zaino.base_url()).await; + + let resp = Client::new() + .request( + reqwest::Method::OPTIONS, + format!("http://127.0.0.1:{}/", router.port), + ) + .send() + .await + .unwrap(); + + assert_eq!(resp.status(), 204); + assert!(resp.headers().contains_key("access-control-allow-origin")); + assert!(resp.headers().contains_key("access-control-allow-methods")); +} + +#[tokio::test] +async fn test_rpc_discover_returns_merged_schema() { + let zebra = start_zebra_mock().await; + let zallet = start_zallet_mock().await; + let zaino = start_zaino_mock().await; + let router = start_router(&zebra.base_url(), &zallet.base_url(), &zaino.base_url()).await; + + let resp = Client::new() + .post(format!("http://127.0.0.1:{}/", router.port)) + .json(&json!({ "jsonrpc": "2.0", "id": 1, "method": "rpc.discover", "params": [] })) + .send() + .await + .unwrap(); + + assert_eq!(resp.status(), 200); + + let body: Value = resp.json().await.unwrap(); + let methods = body["methods"].as_array().unwrap(); + let names: Vec<&str> = methods + .iter() + .map(|m| m["name"].as_str().unwrap()) + .collect(); + + assert!(names.contains(&"getblock")); + assert!(names.contains(&"getinfo")); + assert!(names.contains(&"getwalletinfo")); + assert!(names.contains(&"z_sendmany")); +} + +#[tokio::test] +async fn test_zebra_method_routing() { + let zebra = start_zebra_mock().await; + let zallet = start_zallet_mock().await; + let zaino = start_zaino_mock().await; + let router = start_router(&zebra.base_url(), &zallet.base_url(), &zaino.base_url()).await; + + let resp = Client::new() + .post(format!("http://127.0.0.1:{}/", router.port)) + .json(&json!({ "jsonrpc": "2.0", "id": 1, "method": "getblock", "params": [] })) + .send() + .await + .unwrap(); + + assert_eq!(resp.status(), 200); + let body: Value = resp.json().await.unwrap(); + assert_eq!(body["result"], "zebra-response"); +} + +#[tokio::test] +async fn test_zallet_method_routing() { + let zebra = start_zebra_mock().await; + let zallet = start_zallet_mock().await; + let zaino = start_zaino_mock().await; + let router = start_router(&zebra.base_url(), &zallet.base_url(), &zaino.base_url()).await; + + let resp = Client::new() + .post(format!("http://127.0.0.1:{}/", router.port)) + .json(&json!({ "jsonrpc": "2.0", "id": 1, "method": "getwalletinfo", "params": [] })) + .send() + .await + .unwrap(); + + assert_eq!(resp.status(), 200); + let body: Value = resp.json().await.unwrap(); + assert_eq!(body["result"], "zallet-response"); +} + +#[tokio::test] +async fn test_unknown_method_falls_back_to_zebra() { + let zebra = start_zebra_mock().await; + let zallet = start_zallet_mock().await; + let zaino = start_zaino_mock().await; + let router = start_router(&zebra.base_url(), &zallet.base_url(), &zaino.base_url()).await; + + let resp = Client::new() + .post(format!("http://127.0.0.1:{}/", router.port)) + .json( + &json!({ "jsonrpc": "2.0", "id": 1, "method": "z_getaddressforaccount", "params": [] }), + ) + .send() + .await + .unwrap(); + + assert_eq!(resp.status(), 200); + let body: Value = resp.json().await.unwrap(); + assert_eq!(body["result"], "zebra-response"); +} diff --git a/rpc-router/src/main.rs b/rpc-router/src/main.rs new file mode 100644 index 0000000..6ab3e76 --- /dev/null +++ b/rpc-router/src/main.rs @@ -0,0 +1,390 @@ +//! RPC Router for Zebra, Zallet and Zaino. +//! +//! This service listens for JSON-RPC requests and routes them to the appropriate backend +//! based on the method being called. It merges the OpenRPC schemas from Zebra and Zallet +//! to determine which methods belong to which service. +use std::{env, net::SocketAddr, sync::Arc}; + + +use anyhow::Result; +use base64::{engine::general_purpose, Engine as _}; +use http_body_util::{BodyExt, Full}; +use hyper::{ + body::Bytes, + header::{HeaderName, HeaderValue}, + server::conn::http1, + service::service_fn, + Request, Response, StatusCode, Uri, +}; +use hyper_util::{ + client::legacy::Client as HyperClient, + rt::{TokioExecutor, TokioIo}, +}; +use reqwest::Client as ReqwestClient; +use serde::Deserialize; +use serde_json::{json, Value}; +use tokio::net::TcpListener; +use tracing::{error, info, warn}; + +mod defaults; + +#[cfg(test)] +mod unit_tests; + +#[cfg(test)] +mod integration_tests; + +/// Structure to parse incoming JSON-RPC requests. +#[derive(Deserialize, Debug)] +struct RpcRequest { + method: String, +} + +/// Configuration for the RPC router. +#[derive(Clone)] +struct Config { + zebra_url: String, + zallet_url: String, + _zaino_url: String, + rpc_user: String, + rpc_password: String, + cors_origin: String, + listen_port: u16, +} + +impl Config { + fn from_env() -> Self { + Self { + zebra_url: env::var("ZEBRA_URL") + .unwrap_or_else(|_| defaults::ZEBRA_URL.to_string()), + zallet_url: env::var("ZALLET_URL") + .unwrap_or_else(|_| defaults::ZALLET_URL.to_string()), + _zaino_url: env::var("ZAINO_URL").unwrap_or_else(|_| defaults::ZAINO_URL.to_string()), + rpc_user: env::var("RPC_USER").unwrap_or_else(|_| defaults::RPC_USER.to_string()), + rpc_password: env::var("RPC_PASSWORD").unwrap_or_else(|_| defaults::RPC_PASSWORD.to_string()), + cors_origin: env::var("CORS_ORIGIN") + .unwrap_or_else(|_| defaults::CORS_ORIGIN.to_string()), + listen_port: env::var("LISTEN_PORT") + .ok() + .and_then(|p| p.parse().ok()) + .unwrap_or(defaults::LISTEN_PORT), + } + } +} + +/// Merged schema and method lists for routing. +#[derive(Clone)] +struct Z3Schema { + zebra_methods: Vec, + zallet_methods: Vec, + merged: Value, +} + +/// Forwards the incoming request to the specified target URL with basic auth. +async fn forward_request( + req: Request>, + target_url: &str, + rpc_user: &str, + rpc_password: &str, +) -> Result>> { + let client = HyperClient::builder(TokioExecutor::new()).build_http(); + + let uri_string = format!( + "{}{}", + target_url, + req.uri() + .path_and_query() + .map(|x| x.as_str()) + .unwrap_or("/") + ); + let uri: Uri = uri_string.parse()?; + + let (parts, body) = req.into_parts(); + let mut new_req = Request::builder() + .method(parts.method) + .uri(uri) + .version(parts.version); + + // Inject auth header + let auth = general_purpose::STANDARD.encode(format!("{}:{}", rpc_user, rpc_password)); + new_req = new_req.header( + hyper::header::AUTHORIZATION, + hyper::header::HeaderValue::from_str(&format!("Basic {}", auth))?, + ); + + // Copy other headers + for (k, v) in parts.headers { + if let Some(key) = k { + new_req = new_req.header(key, v); + } + } + + let new_req = new_req.body(body)?; + let res = client.request(new_req).await?; + + let (parts, body) = res.into_parts(); + let body_bytes = body.collect().await?.to_bytes(); + let new_res = Response::from_parts(parts, Full::new(body_bytes)); + + Ok(new_res) +} + +/// Adds CORS headers to the response. +fn add_cors_headers(mut resp: Response>, cors_origin: &str) -> Response> { + let headers = resp.headers_mut(); + headers.insert( + HeaderName::from_static("access-control-allow-origin"), + HeaderValue::from_str(cors_origin).unwrap_or(HeaderValue::from_static("*")), + ); + for &(name, value) in &[ + ("access-control-allow-methods", "POST, OPTIONS"), + ("access-control-allow-headers", "Content-Type"), + ("access-control-max-age", "86400"), + ] { + headers.insert( + HeaderName::from_static(name), + HeaderValue::from_static(value), + ); + } + + resp +} + +/// Main request handler. +async fn handler( + req: Request, + config: Arc, + z3: Z3Schema, +) -> Result>> { + // Health check + if req.uri().path() == "/health" { + return Ok(Response::new(Full::new(Bytes::from("OK")))); + } + + // Handle CORS preflight + if req.method() == hyper::Method::OPTIONS { + let resp = add_cors_headers( + Response::builder() + .status(StatusCode::NO_CONTENT) + .body(Full::new(Bytes::new())) + .unwrap(), + &config.cors_origin, + ); + return Ok(resp); + } + + // Only handle POST requests for JSON-RPC + if req.method() != hyper::Method::POST { + return Ok(Response::builder() + .status(StatusCode::METHOD_NOT_ALLOWED) + .body(Full::new(Bytes::from("Method Not Allowed"))) + .unwrap()); + } + + // Buffer the body to parse it + let (parts, body) = req.into_parts(); + let body_bytes = body.collect().await?.to_bytes(); + + // Attempt to parse method from body + let target_url = if let Ok(rpc_req) = serde_json::from_slice::(&body_bytes) { + if rpc_req.method == "rpc.discover" { + info!("Routing rpc.discover to merged schema"); + + return Ok(add_cors_headers( + Response::builder() + .status(StatusCode::OK) + .header(hyper::header::CONTENT_TYPE, "application/json") + .body(Full::new(Bytes::from(serde_json::to_string(&z3.merged)?))) + .expect("z3 merged schema response should be valid"), + &config.cors_origin, + )); + } + + if z3.zebra_methods.iter().any(|m| m["name"] == rpc_req.method) { + info!("Routing {} to Zebra", rpc_req.method); + &config.zebra_url + } else if z3 + .zallet_methods + .iter() + .any(|m| m["name"] == rpc_req.method) + { + info!("Routing {} to Zallet", rpc_req.method); + &config.zallet_url + } else { + warn!( + "Method '{}' not found in Zebra or Zallet schema, falling back to Zebra", + rpc_req.method + ); + &config.zebra_url + } + } else { + warn!("Failed to parse JSON-RPC body, defaulting to Zebra"); + &config.zebra_url + }; + + // Reconstruct request with buffered body + let new_req = Request::from_parts(parts, Full::new(body_bytes)); + + match forward_request(new_req, target_url, &config.rpc_user, &config.rpc_password).await { + Ok(res) => Ok(add_cors_headers(res, &config.cors_origin)), + Err(e) => { + error!("Forwarding error: {}", e); + + Ok(Response::builder() + .status(StatusCode::BAD_GATEWAY) + .body(Full::new(Bytes::from(format!("Bad Gateway: {}", e)))) + .unwrap()) + } + } +} + +/// Calls rpc.discover on the given URL and returns the parsed JSON response. +async fn call_rpc_discover( + url: &str, + rpc_user: &str, + rpc_password: &str, +) -> Result { + let client = ReqwestClient::new(); + + let body = json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "rpc.discover", + "params": [] + }); + + let text = client + .post(url) + .basic_auth(rpc_user, Some(rpc_password)) + .header(reqwest::header::CONTENT_TYPE, "application/json") + .body(body.to_string()) + .send() + .await? + .error_for_status()? + .text() + .await?; + + let resp = serde_json::from_str::(&text)?; + + Ok(resp) +} + +/// Extracts the methods array from the OpenRPC schema. +fn extract_methods_array(schema: &Value) -> Vec { + schema["methods"].as_array().cloned().unwrap_or_default() +} + +/// Annotates each method with its origin server. +fn annotate_methods_with_server(methods: &mut Vec, server_name: &str) { + for m in methods { + if let Some(obj) = m.as_object_mut() { + obj.insert("x-server".to_string(), json!(server_name)); + } else { + warn!( + "Skipping non-object method entry while annotating for {}", + server_name + ); + } + } +} + +/// Merges the components.schemas from the given schema into the combined map. +fn merge_components_schemas(schema: &Value, combined: &mut serde_json::Map) { + if let Some(obj) = schema["components"]["schemas"].as_object() { + for (k, v) in obj { + combined.insert(k.clone(), v.clone()); + } + } +} + +/// Merges the OpenRPC schemas from Zebra and Zallet. +fn merge_openrpc_schemas(zebra: Value, zallet: Value) -> Result { + // Extract method arrays + let mut zebra_methods = extract_methods_array(&zebra); + let mut zallet_methods = extract_methods_array(&zallet); + + // Annotate each method with its origin + annotate_methods_with_server(&mut zebra_methods, "zebra"); + annotate_methods_with_server(&mut zallet_methods, "zallet"); + + // Merge schemas under components.schemas + let mut combined_schemas = serde_json::Map::new(); + merge_components_schemas(&zebra, &mut combined_schemas); + merge_components_schemas(&zallet, &mut combined_schemas); + + let mut combined_methods = Vec::new(); + combined_methods.extend(zebra_methods.clone()); + combined_methods.extend(zallet_methods.clone()); + + // Build final merged schema + let merged = json!({ + "openrpc": zebra["openrpc"].clone(), + "info": { + "title": env!("CARGO_PKG_NAME"), + "description": env!("CARGO_PKG_DESCRIPTION"), + "version": env!("CARGO_PKG_VERSION"), + }, + "servers": [ + { "name": "router", "url": "http://localhost:8080/" }, + ], + "methods": combined_methods, + "components": { + "schemas": combined_schemas + } + }); + + let z3 = Z3Schema { + zebra_methods, + zallet_methods, + merged, + }; + + Ok(z3) +} + +#[tokio::main] +async fn main() -> Result<()> { + tracing_subscriber::fmt::init(); + + let config = Arc::new(Config::from_env()); + let addr = SocketAddr::from(([0, 0, 0, 0], config.listen_port)); + + let zebra_schema = call_rpc_discover(&config.zebra_url, &config.rpc_user, &config.rpc_password) + .await?["result"] + .clone(); + let zallet_schema = + call_rpc_discover(&config.zallet_url, &config.rpc_user, &config.rpc_password).await? + ["result"] + .clone(); + + let z3 = merge_openrpc_schemas(zebra_schema, zallet_schema)?; + + let listener = TcpListener::bind(addr).await?; + info!("RPC Router listening on {}", addr); + info!("You can use the following playground URL:"); + info!("{}", defaults::playground_url(addr)); + + run(config, listener, z3).await +} + +/// Accepts connections and dispatches requests. Extracted for testability. +async fn run(config: Arc, listener: TcpListener, z3: Z3Schema) -> Result<()> { + loop { + let (stream, _) = listener.accept().await?; + let io = TokioIo::new(stream); + let config = config.clone(); + + let z3 = z3.clone(); + tokio::task::spawn(async move { + if let Err(err) = http1::Builder::new() + .serve_connection( + io, + service_fn(move |req| handler(req, config.clone(), z3.clone())), + ) + .await + { + error!("Error serving connection: {:?}", err); + } + }); + } +} diff --git a/rpc-router/src/unit_tests.rs b/rpc-router/src/unit_tests.rs new file mode 100644 index 0000000..ee67ceb --- /dev/null +++ b/rpc-router/src/unit_tests.rs @@ -0,0 +1,160 @@ +use super::*; +use serial_test::serial; + +fn zebra_schema() -> Value { + json!({ + "openrpc": "1.2.6", + "methods": [ + { "name": "getblock", "params": [] }, + { "name": "getinfo", "params": [] } + ], + "components": { + "schemas": { + "BlockHash": { "type": "string" } + } + } + }) +} + +fn zallet_schema() -> Value { + json!({ + "openrpc": "1.2.6", + "methods": [ + { "name": "getwalletinfo", "params": [] }, + { "name": "z_sendmany", "params": [] } + ], + "components": { + "schemas": { + "WalletInfo": { "type": "object" } + } + } + }) +} + +// --- extract_methods_array --- + +#[test] +fn test_extract_methods_array_returns_methods() { + let schema = zebra_schema(); + let methods = extract_methods_array(&schema); + assert_eq!(methods.len(), 2); + assert_eq!(methods[0]["name"], "getblock"); + assert_eq!(methods[1]["name"], "getinfo"); +} + +#[test] +fn test_extract_methods_array_missing_key_returns_empty() { + let schema = json!({ "openrpc": "1.2.6" }); + let methods = extract_methods_array(&schema); + assert!(methods.is_empty()); +} + +// --- annotate_methods_with_server --- + +#[test] +fn test_annotate_methods_sets_x_server() { + let mut methods = extract_methods_array(&zebra_schema()); + annotate_methods_with_server(&mut methods, "zebra"); + for m in &methods { + assert_eq!(m["x-server"], "zebra"); + } +} + +#[test] +fn test_annotate_methods_non_object_entry_does_not_panic() { + // A schema that contains a non-object method entry (e.g. a bare string). + let schema = json!({ "methods": ["not-an-object", { "name": "getblock" }] }); + let mut methods = extract_methods_array(&schema); + // Must not panic — the non-object entry is silently skipped. + annotate_methods_with_server(&mut methods, "zebra"); + assert_eq!(methods[1]["x-server"], "zebra"); +} + +// --- merge_components_schemas --- + +#[test] +fn test_merge_components_schemas_combines_keys() { + let mut combined = serde_json::Map::new(); + merge_components_schemas(&zebra_schema(), &mut combined); + merge_components_schemas(&zallet_schema(), &mut combined); + assert!(combined.contains_key("BlockHash")); + assert!(combined.contains_key("WalletInfo")); +} + +#[test] +fn test_merge_components_schemas_missing_components_is_noop() { + let schema = json!({ "methods": [] }); + let mut combined = serde_json::Map::new(); + merge_components_schemas(&schema, &mut combined); + assert!(combined.is_empty()); +} + +#[test] +fn test_merge_components_schemas_last_write_wins_on_conflict() { + let schema_a = json!({ "components": { "schemas": { "Foo": { "type": "string" } } } }); + let schema_b = json!({ "components": { "schemas": { "Foo": { "type": "integer" } } } }); + let mut combined = serde_json::Map::new(); + merge_components_schemas(&schema_a, &mut combined); + merge_components_schemas(&schema_b, &mut combined); + assert_eq!(combined["Foo"]["type"], "integer"); +} + +// --- merge_openrpc_schemas --- + +#[test] +fn test_merge_openrpc_schemas_combined_method_count() { + let z3 = merge_openrpc_schemas(zebra_schema(), zallet_schema()).unwrap(); + assert_eq!(z3.zebra_methods.len(), 2); + assert_eq!(z3.zallet_methods.len(), 2); + assert_eq!(z3.merged["methods"].as_array().unwrap().len(), 4); +} + +#[test] +fn test_merge_openrpc_schemas_methods_annotated() { + let z3 = merge_openrpc_schemas(zebra_schema(), zallet_schema()).unwrap(); + for m in &z3.zebra_methods { + assert_eq!(m["x-server"], "zebra"); + } + for m in &z3.zallet_methods { + assert_eq!(m["x-server"], "zallet"); + } +} + +#[test] +fn test_merge_openrpc_schemas_components_merged() { + let z3 = merge_openrpc_schemas(zebra_schema(), zallet_schema()).unwrap(); + let schemas = &z3.merged["components"]["schemas"]; + assert!(schemas.get("BlockHash").is_some()); + assert!(schemas.get("WalletInfo").is_some()); +} + +#[test] +fn test_merge_openrpc_schemas_info_fields_present() { + let z3 = merge_openrpc_schemas(zebra_schema(), zallet_schema()).unwrap(); + assert!(z3.merged["info"]["title"].is_string()); + assert!(z3.merged["info"]["version"].is_string()); +} + +// --- Config::from_env --- + +#[test] +#[serial] +fn test_config_from_env_uses_defaults() { + env::remove_var("RPC_USER"); + env::remove_var("RPC_PASSWORD"); + let config = Config::from_env(); + assert_eq!(config.rpc_user, "zebra"); + assert_eq!(config.rpc_password, "zebra"); +} + +#[test] +#[serial] +fn test_config_from_env_reads_rpc_credentials() { + env::set_var("RPC_USER", "alice"); + env::set_var("RPC_PASSWORD", "s3cr3t"); + let config = Config::from_env(); + assert_eq!(config.rpc_user, "alice"); + assert_eq!(config.rpc_password, "s3cr3t"); + env::remove_var("RPC_USER"); + env::remove_var("RPC_PASSWORD"); +} From 4089b0de8ab507f5773c029d2c341a80ee5b8c2b Mon Sep 17 00:00:00 2001 From: DC Date: Tue, 10 Mar 2026 23:03:24 +0000 Subject: [PATCH 38/57] feat: security and usabiity improvements 1) generate zallet_identity when running init.sh for regtest testing, rather than checking into git. 2) clarify tagging of local vs remote images in the main z3 docker-compose.yml 3) bump Zebra to 4.1.0 in the regtest docker-compose.yml. 4) modify init to dynamically generate the age keys required by zallet for regtest mode, and also dynamically generate the pwhash for the rpc user. this is done mostly to reduce noise from automated code scanning tools that search for leaked keys. 5) update README for the rpc-router 6) remove testing zallet-identity.txt age key from git 7) remove default pw salt and hash from zallet.toml --- .gitignore | 3 + docker-compose.yml | 12 ++- rpc-router/regtest/README.md | 19 ++-- rpc-router/regtest/config/zallet.toml | 4 +- rpc-router/regtest/config/zallet_identity.txt | 3 - rpc-router/regtest/docker-compose.yml | 14 ++- rpc-router/regtest/init.sh | 90 ++++++++++++++++++- 7 files changed, 127 insertions(+), 18 deletions(-) delete mode 100644 rpc-router/regtest/config/zallet_identity.txt diff --git a/.gitignore b/.gitignore index 0ee1c67..613838d 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,6 @@ config/tls/* # Un-ignore .gitkeep directly under config !config/.gitkeep + +# Local rpc-router regtest zallet identity +rpc-router/regtest/config/zallet_identity.txt diff --git a/docker-compose.yml b/docker-compose.yml index 6c0446b..798061c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -89,8 +89,18 @@ services: start_period: 60s zallet: - image: electriccoinco/zallet:v0.1.0-alpha.3 + # Default to a local tag for reliable dev/regtest flows. + # To pull and run a remote image instead, set: + # ZALLET_IMAGE=zodlinc/zallet:v0.1.0-alpha.4 + image: ${ZALLET_IMAGE:-z3_zallet:local} platform: ${DOCKER_PLATFORM:-linux/amd64} + build: + context: ./zallet + dockerfile: Dockerfile + target: runtime + tags: + - z3_zallet:local + - zodlinc/zallet:v0.1.0-alpha.4 container_name: z3_zallet restart: unless-stopped user: "1000:1000" diff --git a/rpc-router/regtest/README.md b/rpc-router/regtest/README.md index e45e56e..cd07332 100644 --- a/rpc-router/regtest/README.md +++ b/rpc-router/regtest/README.md @@ -22,7 +22,16 @@ cd rpc-router/regtest This will: 1. Start Zebra in regtest mode 2. Mine 1 block (activates Orchard at height 1) -3. Initialize the Zallet wallet (`init-wallet-encryption` + `generate-mnemonic`) +3. Generate and inject the Zallet `rpc.auth.pwhash` in `config/zallet.toml` +4. Initialize the Zallet wallet (`init-wallet-encryption` + `generate-mnemonic`) + +Optional: override the RPC password used for the hash generation: + +This (default) password which is included in zcash/integration-tests is not checked into the repo to avoid offending the kingphisher and other code scanning gods. + +```bash +RPC_PASSWORD='your-password' ./init.sh +``` ## Start the stack @@ -79,11 +88,11 @@ sudo -E docker compose down -v Successfully tested on: -- **OS**: Linux Mint 21.2 (kernel 5.15.0-171-generic) -- **Rust**: 1.90.0 (rpc-router built locally; Docker image uses `rust-1.85`) +- **OS**: Linux Mint 21.2 (kernel 5.15.0-171-generic). also Debian GNU/Linux 13 (trixie) (kernel 6.12.69+deb13-cloud-amd64) +- **Rust**: 1.90.0 (rpc-router built locally; Docker image uses `rust-1.85`), also 1.94.0 - **Docker Compose**: v5.1.0 (plugin, not standalone `docker-compose`) -- **Zallet image**: `z3-zallet:local` built from submodule at `ae762c05` (Feb 2026) -- **Zebra image**: `zfnd/zebra:3.1.0` +- **Zallet image**: `z3-zallet:local` built from submodule at `ae762c05` (Feb 2026), also built from submodule at `757876b` (March 2026) +- **Zebra image**: `zfnd/zebra:3.1.0` and `zfnd/zebra:4.1.0` Expected output for the test commands above: diff --git a/rpc-router/regtest/config/zallet.toml b/rpc-router/regtest/config/zallet.toml index cf11597..58b2d4b 100644 --- a/rpc-router/regtest/config/zallet.toml +++ b/rpc-router/regtest/config/zallet.toml @@ -39,4 +39,6 @@ timeout = 30 [[rpc.auth]] user = "zebra" # Password: "zebra" (from zcash/integration-tests) -pwhash = "eaa27c81df81f655e69bbe84026e733a$bb9f70d53decfeca863cb114f7b0fd5ae050c7ef885c3d5fa73d5bace6c14fea" +# Generated by ./init.sh from RPC_PASSWORD (default: "zebra"). +# pwhash = "__GENERATED_BY_INIT_SH__" +pwhash = "__GENERATED_BY_INIT_SH__" diff --git a/rpc-router/regtest/config/zallet_identity.txt b/rpc-router/regtest/config/zallet_identity.txt deleted file mode 100644 index 558d28b..0000000 --- a/rpc-router/regtest/config/zallet_identity.txt +++ /dev/null @@ -1,3 +0,0 @@ -# created: 2025-04-22T10:45:29-03:00 -# public key: age1l5d76x6zzsqy90r05a29gysjny7uvh229p5jnrwyqp5dmy39ha2qks3h9f -AGE-SECRET-KEY-1U7FJQWFKZYP7LJFJG9EVM6CG47P5TWP0NU03JE7CZQ6035H2JWDQKCRG65 diff --git a/rpc-router/regtest/docker-compose.yml b/rpc-router/regtest/docker-compose.yml index 3145c24..84d6f98 100644 --- a/rpc-router/regtest/docker-compose.yml +++ b/rpc-router/regtest/docker-compose.yml @@ -14,7 +14,7 @@ services: zebra: - image: zfnd/zebra:3.1.0 + image: zfnd/zebra:4.1.0 container_name: z3_regtest_zebra restart: unless-stopped environment: @@ -41,8 +41,9 @@ services: start_period: 30s zallet: - image: z3-zallet:local + image: z3_zallet:local container_name: z3_regtest_zallet + # zallet image is distroless (no /bin/sh), so CMD-SHELL healthchecks fail. restart: unless-stopped command: ["--datadir", "/var/lib/zallet", "start"] depends_on: @@ -65,9 +66,12 @@ services: dockerfile: Dockerfile container_name: z3_regtest_router restart: unless-stopped + # In Compose networks, service keys are DNS hostnames. + # So `zebra` and `zallet` here resolve to those services on regtest_net, + # regardless of the explicit container_name values above. environment: - - ZEBRA_URL=http://z3_regtest_zebra:18232 - - ZALLET_URL=http://z3_regtest_zallet:28232 + - ZEBRA_URL=http://zebra:18232 + - ZALLET_URL=http://zallet:28232 - RPC_USER=zebra - RPC_PASSWORD=zebra - LISTEN_PORT=8181 @@ -76,6 +80,8 @@ services: depends_on: zebra: condition: service_healthy + zallet: + condition: service_started networks: - regtest_net diff --git a/rpc-router/regtest/init.sh b/rpc-router/regtest/init.sh index a9b7d1d..b59b323 100755 --- a/rpc-router/regtest/init.sh +++ b/rpc-router/regtest/init.sh @@ -5,23 +5,105 @@ # Safe to re-run: skips steps if already done. # # Requirements: -# - Docker with the z3-zallet:local image built (cd ../../ && docker compose build zallet) +# - Docker with the z3_zallet:local image built (cd ../../ && docker compose build zallet) # - No running z3_regtest_* containers set -euo pipefail +log() { + printf '%s\n' "$*" +} + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" NETWORK=regtest_regtest_net +ensure_rage() { + if command -v rage-keygen > /dev/null 2>&1; then + log "rage already installed" + return + fi + + log "rage-keygen is required to generate a local zallet identity." + log "Install it, then re-run this script:" + log " macOS: brew install rage" + log " Debian/Ubuntu: install rage from https://github.com/str4d/rage/releases or provide rage-keygen in PATH" + exit 1 +} + +ensure_local_identity() { + local identity_path="$SCRIPT_DIR/config/zallet_identity.txt" + + if [ -f "$identity_path" ]; then + log "==> Reusing existing local zallet identity..." + return + fi + + ensure_rage + + log "==> Generating local zallet identity..." + mkdir -p "$SCRIPT_DIR/config" + rage-keygen -o "$identity_path" + chmod 600 "$identity_path" +} + +ensure_openssl() { + if command -v openssl > /dev/null 2>&1; then + return + fi + + log "openssl is required to generate a local zallet RPC password hash." + log "Install OpenSSL, then re-run this script." + exit 1 +} + +update_zallet_rpc_pwhash() { + local config_path="$SCRIPT_DIR/config/zallet.toml" + local rpc_password="${RPC_PASSWORD:-zebra}" + local salt + local hash + local pwhash + local tmp + + ensure_openssl + + if [ ! -f "$config_path" ]; then + log "Missing zallet config: $config_path" + exit 1 + fi + + if ! grep -q '^pwhash = "' "$config_path"; then + log "Could not find a pwhash entry in $config_path" + exit 1 + fi + + salt="$(openssl rand -hex 16)" + hash="$(printf '%s' "$rpc_password" | openssl dgst -sha256 -mac HMAC -macopt "key:$salt" | awk '{print $2}')" + + if [ -z "$hash" ]; then + log "Failed to generate zallet RPC password hash" + exit 1 + fi + + pwhash="${salt}\$${hash}" + tmp="$(mktemp "${TMPDIR:-/tmp}/zallet.toml.XXXXXX")" + + sed -E "s|^pwhash = \".*\"$|pwhash = \"${pwhash}\"|" "$config_path" > "$tmp" + mv "$tmp" "$config_path" + + log "==> Updated zallet RPC pwhash in config/zallet.toml" +} + # Use sudo for docker if needed DOCKER="docker" if ! docker info > /dev/null 2>&1; then DOCKER="sudo -E docker" fi -ZALLET_IMAGE=z3-zallet:local cd "$SCRIPT_DIR" +ensure_local_identity +update_zallet_rpc_pwhash + echo "==> Starting Zebra in regtest mode..." $DOCKER compose up -d zebra @@ -44,10 +126,10 @@ echo " Block mined." echo "==> Running init-wallet-encryption..." # Remove stale lock file if present (left by a previous interrupted run) -$DOCKER run --rm -v regtest_zallet_regtest_data:/data busybox \ +$DOCKER run --rm -v zallet_regtest_data:/data busybox \ sh -c 'rm -f /data/.lock' # Check if wallet already initialized by looking for the wallet database -ALREADY_INIT=$($DOCKER run --rm -v regtest_zallet_regtest_data:/data busybox \ +ALREADY_INIT=$($DOCKER run --rm -v zallet_regtest_data:/data busybox \ sh -c 'ls /data/*.sqlite /data/*.age 2>/dev/null | wc -l') if [ "${ALREADY_INIT:-0}" -gt 0 ]; then echo " Wallet already initialized, skipping." From 0565e555c657001439aa0859b2a2901b0147510f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 21 Mar 2026 09:41:49 +0000 Subject: [PATCH 39/57] chore(deps): bump rustls-webpki from 0.103.9 to 0.103.10 in /rpc-router Bumps [rustls-webpki](https://github.com/rustls/webpki) from 0.103.9 to 0.103.10. - [Release notes](https://github.com/rustls/webpki/releases) - [Commits](https://github.com/rustls/webpki/compare/v/0.103.9...v/0.103.10) --- updated-dependencies: - dependency-name: rustls-webpki dependency-version: 0.103.10 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- rpc-router/Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rpc-router/Cargo.lock b/rpc-router/Cargo.lock index 61b2ed6..8fc4fdd 100644 --- a/rpc-router/Cargo.lock +++ b/rpc-router/Cargo.lock @@ -1586,9 +1586,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.9" +version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ "ring", "rustls-pki-types", From 4912e3a47baae829179ac81bfbd32ac4a4232c02 Mon Sep 17 00:00:00 2001 From: Alessio Cimarelli Date: Wed, 25 Mar 2026 09:46:40 +0100 Subject: [PATCH 40/57] Ignore docker compose override file (#16) --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 613838d..7d283e8 100644 --- a/.gitignore +++ b/.gitignore @@ -39,5 +39,8 @@ config/tls/* # Un-ignore .gitkeep directly under config !config/.gitkeep +# Ignore docker compose overrides +docker-compose.override.yml + # Local rpc-router regtest zallet identity rpc-router/regtest/config/zallet_identity.txt From 83e41d7ea6e80a3a03bbaaf8a83d69d0e2a40dfb Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Sat, 28 Mar 2026 14:25:58 -0300 Subject: [PATCH 41/57] refactor(docker): redesign compose, bump applications and add regtest stack (#22) * feat(regtest): add zaino service to regtest stack Zaino serves as the lightwalletd-compatible gRPC interface for light wallet clients (e.g. Zingo). It connects to Zebra's RPC and exposes: - gRPC (port 8137): Compact Block Protocol, direct access for clients - JSON-RPC (port 8237): overlapping with Zebra, available for tooling gRPC is exposed directly and not routed through the rpc-router since it is a different protocol (HTTP/2 + protobuf) from the JSON-RPC router. * refactor(docker): redesign compose architecture with defaults-in-compose pattern Replaces the previous .env-dependent setup with a self-sufficient docker-compose.yml where every variable has a ${VAR:-default} fallback. The stack now works on a fresh clone with zero configuration files. Architecture changes: - All variables in docker-compose.yml use ${VAR:-default} (Mainnet defaults) - .env is gitignored and optional (users create it only to override) - Single .env.example replaces three .env.example.{mainnet,testnet,regtest} files - Regtest uses a compose overlay (docker-compose.regtest.yml) merged via COMPOSE_FILE in .env.regtest, with COMPOSE_PROJECT_NAME for volume isolation - Removed standalone regtest/docker-compose.yml (127 lines of duplicated service definitions); the overlay is ~60 lines of structural differences only - Dissolved regtest/ directory: configs moved to config/regtest/, init script to scripts/regtest-init.sh, docs to docs/regtest.md - Flattened config/zaino/zindexer.toml to config/zaino.toml (1-file directory) - Moved check-zebra-readiness.sh and fix-permissions.sh to scripts/ Docker Compose improvements: - x-common YAML anchor for shared log rotation + security hardening - cap_drop: [ALL] on all services (Zebra adds back CHOWN/SETUID/SETGID/etc. for its setpriv-based entrypoint) - security_opt: [no-new-privileges:true] on all services - Log rotation (50MB x 5 files) prevents unbounded disk growth - stop_grace_period on all services (30s Zebra, 15s others) - start_interval: 5s for faster startup healthcheck detection - Zaino healthcheck upgraded from zainod --version (useless process check) to bash /dev/tcp port check - Image override variables: ZEBRA_IMAGE, ZAINO_IMAGE, ZALLET_IMAGE - Removed env_file from Zallet (doesn't support env var config) and limited Zebra's env_file to optional config-rs passthrough (metrics, OpenTelemetry) - Regtest overlay uses environment: !override on Zaino to switch from cookie auth to username/password auth (Zaino blocks passwords in env vars) - Regtest rpc-router RPC_PASSWORD parameterized via ${RPC_PASSWORD:-zebra} Submodule bumps: - Zebra: bumped to latest main (setpriv replaces gosu, enables cap_drop) - Zaino: bumped to latest dev - Zallet: bumped to latest main Documentation: - README restructured with progressive disclosure (
sections) - Mermaid architecture diagram showing Zallet's embedded Zaino libs - docs/docker-architecture.md: rationale for all compose patterns - docs/regtest.md: regtest workflow, test commands, monitoring setup - All pre-built images used by default; no build step in Quick Start Bugs fixed during validation: - config-rs crashes on empty string env vars (metrics, OpenTelemetry) - macOS openssl dgst output format (awk $2 vs $NF) - Zallet init-wallet-encryption missing --config flag in compose run - Zaino config-rs duplicate field panic (TOML + env var conflict) - ZEBRA_HEALTH__ENFORCE_ON_TEST_NETWORKS was hardcoded, now overridable * fix(docker): correct zaino volume mount, command, and capabilities The zaino service had three issues: - Volume mounted to /home/zaino/.cache/zaino, a path that doesn't exist in the container (user is container_user, not zaino). Data was written to /app/data via symlink but never persisted. Mount to /app/data to match the Dockerfile's published interface. - Command included "zainod" which duplicated the binary invocation since the entrypoint already calls zainod. Remove it so CMD only passes arguments. - Missing cap_add for the entrypoint's privilege-dropping workflow. The x-common anchor drops all capabilities, but zaino's entrypoint needs CHOWN/DAC_OVERRIDE/FOWNER for directory setup and SETUID/SETGID for setpriv, matching zebra's configuration. Fixes #15 * fix(regtest): replace committed pwhash with placeholder The regtest zallet config had a real generated hash committed. Replace with __GENERATED_BY_INIT_SH__ placeholder that regtest-init.sh detects and replaces on first run. The init script now skips hash generation if the placeholder is already replaced, matching the idempotent pattern used for identity generation. * Regtest fixes after PR24 (#25) * fix(regtest): add Nu5 nuparam and auto-generate TLS certs in init script - Add Nu5 (c2d6d0b4) to regtest_nuparams in config/regtest/zallet.toml. Without it, activation_height(Nu5) returns None and the orchard_shardtree migration panics with an unwrap on None. - Remove stale wallet.db in regtest-init.sh before init-wallet-encryption so a previous interrupted run (e.g. from the cookie-mount bug) doesn't leave a schema-mismatched database that causes 'no such table' errors. - Tighten ALREADY_INIT check to look for .age files only (written by generate-mnemonic on successful completion) rather than wallet.db. - Auto-generate the Zaino TLS certificate in regtest-init.sh when missing, so 'docker compose up' no longer fails with a bind-mount path error. --------- Co-authored-by: Gustavo Valverde --- .env | 183 ----- .env.example | 91 +++ .env.regtest | 12 + .gitignore | 28 +- .vscode/settings.json | 3 + README.md | 694 ++++++------------ config/regtest/zaino.toml | 6 + .../config => config/regtest}/zallet.toml | 8 +- config/{zaino/zindexer.toml => zaino.toml} | 0 config/zallet.toml | 3 +- docker-compose.regtest.yml | 73 ++ docker-compose.yml | 141 ++-- docs/docker-architecture.md | 245 +++++++ docs/regtest.md | 162 ++++ rpc-router/README.md | 2 +- rpc-router/regtest/README.md | 107 --- rpc-router/regtest/docker-compose.yml | 94 --- .../check-zebra-readiness.sh | 0 .../fix-permissions.sh | 0 .../init.sh => scripts/regtest-init.sh | 97 ++- zaino | 2 +- zallet | 2 +- zebra | 2 +- 23 files changed, 1001 insertions(+), 954 deletions(-) delete mode 100644 .env create mode 100644 .env.example create mode 100644 .env.regtest create mode 100644 .vscode/settings.json create mode 100644 config/regtest/zaino.toml rename {rpc-router/regtest/config => config/regtest}/zallet.toml (72%) rename config/{zaino/zindexer.toml => zaino.toml} (100%) create mode 100644 docker-compose.regtest.yml create mode 100644 docs/docker-architecture.md create mode 100644 docs/regtest.md delete mode 100644 rpc-router/regtest/README.md delete mode 100644 rpc-router/regtest/docker-compose.yml rename check-zebra-readiness.sh => scripts/check-zebra-readiness.sh (100%) rename fix-permissions.sh => scripts/fix-permissions.sh (100%) rename rpc-router/regtest/init.sh => scripts/regtest-init.sh (51%) diff --git a/.env b/.env deleted file mode 100644 index d2049bd..0000000 --- a/.env +++ /dev/null @@ -1,183 +0,0 @@ -# z3/.env - -# z3/docker-compose.yml Environment Variables - -# ============================================================================= -# VARIABLE HIERARCHY QUICK REFERENCE -# ============================================================================= -# For detailed explanation, see README.md "Understanding the Variable Hierarchy" -# -# Three-tier system to avoid collisions: -# Z3_* - Infrastructure (volumes, ports) - Docker Compose only -# (no prefix) - Shared config (remapped per service) -# ZEBRA_* - Zebra app config (ZEBRA_SECTION__KEY format) -# ZAINO_* - Zaino app config -# ZALLET_* - Zallet app config -# -# ============================================================================= -# Z3 Stack Infrastructure Configuration -# ============================================================================= -# These Z3_* variables control Docker Compose volume mounts and are NEVER -# passed to containers. They avoid collision with service configs (ZEBRA_*, -# ZAINO_*, ZALLET_*). -# -# Default: Docker named volumes (recommended - managed by Docker, no permission issues) -# Advanced: Local directories (see README.md "Advanced: Local Directories" section) -# -# To use local directories: -# 1. Choose appropriate paths for your OS (see README for suggestions) -# 2. Create directories: mkdir -p /your/chosen/path -# 3. Fix permissions: ./fix-permissions.sh /your/chosen/path -# 4. Update variables below with your paths -# -# Security Requirements: -# - Zebra: UID=10001, GID=10001, permissions=700 -# - Zaino: UID=1000, GID=1000, permissions=700 -# - Zallet: UID=65532, GID=65532, permissions=700 -# - Cookie: Keep as Docker volume (recommended) to avoid cross-user issues -# -# WARNING: Never use 755 or 777 permissions - they expose your data! - -# ============================================================================= -# Docker Platform Configuration -# ============================================================================= -# Controls which architecture Docker builds/runs containers for. -# Default: linux/amd64 (x86_64 architecture) -# -# ARM64 Users (Apple Silicon M1/M2/M3, ARM64 Linux): -# Uncomment and set to linux/arm64 for NATIVE builds -# DOCKER_PLATFORM=linux/arm64 - -# Zebra blockchain state directory -# Default: zebra_data (Docker named volume) -Z3_ZEBRA_DATA_PATH=zebra_data - -# Shared cookie authentication directory (used by Zebra and Zaino) -# Default: shared_cookie_volume (Docker named volume) -# NOTE: For local directories, Zebra (10001) writes and Zaino (1000) reads. -# Consider using Docker volume (default) to avoid cross-user permission issues. -# If using local dir, you'll need to set up ACLs or a shared group. -Z3_COOKIE_PATH=shared_cookie_volume - -# Zaino indexer data directory -# Default: zaino_data (Docker named volume) -Z3_ZAINO_DATA_PATH=zaino_data - -# Zallet wallet data directory -# Default: zallet_data (Docker named volume) -Z3_ZALLET_DATA_PATH=zallet_data - -# ============================================================================= -# Common Configuration -# ============================================================================= -# Shared variables used by multiple services, mapped in docker-compose.yml: -# NETWORK_NAME → ZEBRA_NETWORK__NETWORK, ZAINO_NETWORK -# ENABLE_COOKIE_AUTH → ZEBRA_RPC__ENABLE_COOKIE_AUTH -# COOKIE_AUTH_FILE_DIR → ZEBRA_RPC__COOKIE_DIR, ZAINO_VALIDATOR_SETTINGS__VALIDATOR_COOKIE_PATH - -# Network name for all services (e.g., Mainnet, Testnet, Regtest) -NETWORK_NAME=Mainnet -# Globally enables RPC cookie authentication -ENABLE_COOKIE_AUTH=true -# In-container directory for the .cookie authentication file -COOKIE_AUTH_FILE_DIR=/var/run/auth - -# ============================================================================= -# Zebra Configuration -# ============================================================================= -# Zebra logging (will be mapped to RUST_LOG in container) -Z3_ZEBRA_RUST_LOG=info -# Zebra tracing filter (config-rs format: ZEBRA_TRACING__FILTER) -ZEBRA_TRACING__FILTER=info -# Zebra RPC listen address (config-rs format: ZEBRA_RPC__LISTEN_ADDR) -ZEBRA_RPC__LISTEN_ADDR=0.0.0.0:18232 -# Zebra state cache directory (config-rs format: ZEBRA_STATE__CACHE_DIR) -ZEBRA_STATE__CACHE_DIR=/home/zebra/.cache/zebra -# Zebra health endpoint configuration -ZEBRA_HEALTH__LISTEN_ADDR=0.0.0.0:8080 -ZEBRA_HEALTH__MIN_CONNECTED_PEERS=1 -ZEBRA_HEALTH__READY_MAX_BLOCKS_BEHIND=2 -ZEBRA_HEALTH__ENFORCE_ON_TEST_NETWORKS=false -# Infrastructure: Zebra RPC port (used in Docker Compose port mappings and service discovery) -Z3_ZEBRA_RPC_PORT=18232 -# Infrastructure: Zebra host RPC port (for external access to Zebra) -Z3_ZEBRA_HOST_RPC_PORT=18232 -# Infrastructure: Zebra host health port (for external access to health endpoints) -Z3_ZEBRA_HOST_HEALTH_PORT=8080 - -# ============================================================================= -# Zaino Configuration -# ============================================================================= -# Zaino Rust log level -ZAINO_RUST_LOG=info,reqwest=warn,hyper_util=warn -# Zaino's internal gRPC port. Zallet connects to this port on the 'zaino' service hostname. -ZAINO_GRPC_PORT=8137 -# Enable/disable Zaino's JSON-RPC service -ZAINO_JSON_RPC_ENABLE=false -# Zaino internal JSON-RPC port -ZAINO_JSON_RPC_PORT=8237 -# Zaino gRPC TLS (true/false) -ZAINO_GRPC_TLS_ENABLE=true # Set to true to enforce security policies -# Zaino host gRPC port (for external access to Zaino gRPC) -ZAINO_HOST_GRPC_PORT=8137 -# Zaino host JSON-RPC port (for external access to Zaino JSON-RPC) -ZAINO_HOST_JSONRPC_PORT=8237 -# In-container FHS-compliant paths for Zaino TLS configs -# These are the paths Zaino application expects for its cert and key -ZAINO_GRPC_TLS_CERT_PATH=/var/run/zaino/tls/zaino.crt -ZAINO_GRPC_TLS_KEY_PATH=/var/run/zaino/tls/zaino.key -# For Zaino healthcheck with TLS (adjust curl_opt as needed for your cert) -ZAINO_GRPC_TLS_ENABLE_SCHEME_SUFFIX=s -ZAINO_GRPC_TLS_ENABLE_CURL_OPT=-k # -k allows insecure for self-signed, use --cacert for prod -# Option to disable Zaino's local DB features (skips FinalisedState sync) -ZAINO_NO_DB=true -# Zaino validator username for Zebra RPC -ZAINO_VALIDATOR_USER=__cookie__ -# Zaino Rust backtrace setting -ZAINO_RUST_BACKTRACE=full -# Zaino application internal config path -ZAINO_CONF_PATH=/home/zaino/.config/zaino/zindexer.toml -# Zaino application internal data directory -ZAINO_DATA_DIR=/home/zaino/.cache/zaino - -# ============================================================================= -# Zallet Configuration -# ============================================================================= -# Zallet Rust log level -ZALLET_RUST_LOG=info,hyper_util=warn,reqwest=warn -# Zallet internal RPC port -ZALLET_RPC_PORT=28232 -# Zallet host RPC port (for external access to Zallet RPC) -ZALLET_HOST_RPC_PORT=28232 -# Zallet application internal config path -ZALLET_CONF_PATH=/etc/zallet/zallet.toml -# Zallet application internal data directory -ZALLET_DATA_DIR=/home/zallet/.data -# Example path for a CA certificate file that Zallet might use to trust Zaino's gRPC TLS certificate. -# If Zaino uses a self-signed certificate or a certificate from a private CA, Zallet would need to be -# configured to trust it. The actual environment variable name and mechanism depend on Zallet's implementation. -# ZALLET_INDEXER_CA_PATH=/path/to/trusted/zaino_ca.crt - -# ============================================================================= -# Monitoring Configuration (--profile monitoring) -# ============================================================================= -# Enable monitoring with: docker compose --profile monitoring up -d -# -# To enable Zebra metrics, uncomment this variable: -ZEBRA_METRICS__ENDPOINT_ADDR=0.0.0.0:9999 -# -# To enable OpenTelemetry tracing (Jaeger), build Zebra with OTel support: -# docker compose build --build-arg FEATURES="default-release-binaries opentelemetry" zebra -# Then set the tracing endpoint: -# ZEBRA_TRACING__OPENTELEMETRY_ENDPOINT=http://jaeger:4318 -# ZEBRA_TRACING__OPENTELEMETRY_SERVICE_NAME=zebra -# ZEBRA_TRACING__OPENTELEMETRY_SAMPLE_PERCENT=100 -# -# Service ports (defaults shown, customize if needed): -# GRAFANA_PORT=3000 -# PROMETHEUS_PORT=9094 -# JAEGER_UI_PORT=16686 -# ALERTMANAGER_PORT=9093 -# -# Grafana admin password (default: admin, prompted to change on first login): -# GRAFANA_ADMIN_PASSWORD=your_secure_password diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e4ff8f9 --- /dev/null +++ b/.env.example @@ -0,0 +1,91 @@ +# Z3 Stack — Environment Variable Overrides +# +# All defaults are in docker-compose.yml. This file is OPTIONAL. +# Create .env and uncomment only the variables you want to override. +# +# Quick reference — switching networks: +# +# Mainnet (default): docker compose up -d +# Testnet: create .env with NETWORK_NAME=Testnet +# Regtest: docker compose --env-file .env.regtest up -d +# +# Variable Mainnet Testnet Regtest +# ───────────────────────────────────────────────────────────────── +# NETWORK_NAME Mainnet Testnet Regtest +# ENABLE_COOKIE_AUTH true true false +# ZEBRA_HEALTH__MIN_CONNECTED_PEERS 1 1 0 +# + +# ============================================================================= +# Network +# ============================================================================= +# NETWORK_NAME=Mainnet +# ENABLE_COOKIE_AUTH=true +# ZEBRA_HEALTH__MIN_CONNECTED_PEERS=1 + +# ============================================================================= +# Platform (ARM64 users: uncomment for native builds) +# ============================================================================= +# DOCKER_PLATFORM=linux/arm64 + +# ============================================================================= +# Image Overrides (defaults are pinned versions in docker-compose.yml) +# ============================================================================= +# ZEBRA_IMAGE=zfnd/zebra:4.2.0 +# ZAINO_IMAGE=ghcr.io/zcashfoundation/zaino:sha-0164cab +# ZALLET_IMAGE=electriccoinco/zallet:v0.1.0-alpha.3 +# ZEBRA_BUILD_FEATURES=default-release-binaries + +# ============================================================================= +# Data Paths (default: Docker named volumes) +# ============================================================================= +# Z3_ZEBRA_DATA_PATH=zebra_data +# Z3_ZAINO_DATA_PATH=zaino_data +# Z3_ZALLET_DATA_PATH=zallet_data +# Z3_COOKIE_PATH=shared_cookie_volume + +# ============================================================================= +# Port Mappings +# ============================================================================= +# Z3_ZEBRA_HOST_RPC_PORT=18232 +# Z3_ZEBRA_HOST_HEALTH_PORT=8080 +# ZAINO_HOST_GRPC_PORT=8137 +# ZAINO_HOST_JSONRPC_PORT=8237 +# ZALLET_HOST_RPC_PORT=28232 + +# ============================================================================= +# Internal Container Ports (change only for advanced multi-instance setups) +# Note: internal ports must also match bind addresses in config/*.toml files +# ============================================================================= +# Z3_ZEBRA_RPC_PORT=18232 +# ZAINO_GRPC_PORT=8137 +# ZAINO_JSON_RPC_PORT=8237 +# ZALLET_RPC_PORT=28232 + +# ============================================================================= +# Log Levels +# ============================================================================= +# Z3_ZEBRA_RUST_LOG=info +# ZEBRA_TRACING__FILTER=info +# ZAINO_RUST_LOG=info,reqwest=warn,hyper_util=warn +# ZAINO_RUST_BACKTRACE=full +# ZALLET_RUST_LOG=info,hyper_util=warn,reqwest=warn + +# ============================================================================= +# Monitoring (enable with: docker compose --profile monitoring up -d) +# ============================================================================= +# ZEBRA_METRICS__ENDPOINT_ADDR=0.0.0.0:9999 +# GRAFANA_PORT=3000 +# GRAFANA_ADMIN_PASSWORD=admin +# PROMETHEUS_PORT=9094 +# JAEGER_UI_PORT=16686 +# ALERTMANAGER_PORT=9093 +# JAEGER_OTLP_GRPC_PORT=4317 +# JAEGER_OTLP_HTTP_PORT=4318 +# JAEGER_SPANMETRICS_PORT=8889 +# +# OpenTelemetry tracing (requires Zebra built with opentelemetry feature): +# docker compose build --build-arg FEATURES="default-release-binaries opentelemetry" zebra +# ZEBRA_TRACING__OPENTELEMETRY_ENDPOINT=http://jaeger:4318 +# ZEBRA_TRACING__OPENTELEMETRY_SERVICE_NAME=zebra +# ZEBRA_TRACING__OPENTELEMETRY_SAMPLE_PERCENT=100 diff --git a/.env.regtest b/.env.regtest new file mode 100644 index 0000000..5c2c3f0 --- /dev/null +++ b/.env.regtest @@ -0,0 +1,12 @@ +# Regtest environment — used with: docker compose --env-file .env.regtest up -d +# +# COMPOSE_FILE merges the regtest overlay on top of the base compose. +# COMPOSE_PROJECT_NAME isolates volumes/networks from mainnet/testnet. + +COMPOSE_PROJECT_NAME=z3-regtest +COMPOSE_FILE=docker-compose.yml:docker-compose.regtest.yml + +NETWORK_NAME=Regtest +ENABLE_COOKIE_AUTH=false +ZEBRA_HEALTH__MIN_CONNECTED_PEERS=0 +ZEBRA_MINING__MINER_ADDRESS=tmSRd1r8gs77Ja67Fw1JcdoXytxsyrLTPJm diff --git a/.gitignore b/.gitignore index 7d283e8..41cb6b1 100644 --- a/.gitignore +++ b/.gitignore @@ -19,28 +19,24 @@ target/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ -# Ignore all contents of the 'config' directory recursively +# ============================================================================= +# Config directory — ignore generated secrets, track templates +# ============================================================================= config/** +!config/.gitkeep !config/zallet.toml - -# Un-ignore the 'tls' subdirectory itself, so we can look inside it +!config/zaino.toml +# TLS directory structure (certs are generated locally) !config/tls/ - -# Then ignore everything inside 'tls' config/tls/* - -# Then un-ignore the .gitkeep file within 'tls' !config/tls/.gitkeep +# Regtest configs (tracked templates; identity file is generated by scripts/regtest-init.sh) +!config/regtest/ +!config/regtest/zaino.toml +!config/regtest/zallet.toml -# Zaino config (required by config-rs) -!config/zaino/ -!config/zaino/zindexer.toml - -# Un-ignore .gitkeep directly under config -!config/.gitkeep +# Runtime environment overrides (see .env.example for available variables) +.env # Ignore docker compose overrides docker-compose.override.yml - -# Local rpc-router regtest zallet identity -rpc-router/regtest/config/zallet_identity.txt diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ef0a046 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "yaml.customTags": ["!override sequence", "!reset sequence"] +} \ No newline at end of file diff --git a/README.md b/README.md index 145a68b..42f626a 100644 --- a/README.md +++ b/README.md @@ -1,619 +1,401 @@ -# Z3 - Unified Zcash Stack +# Z3 — Unified Zcash Stack -A modern, modular Zcash software stack combining Zebra, Zaino, and Zallet to replace the legacy `zcashd`. - -## Table of Contents - -- [Quick Start](#quick-start) -- [Understanding the Architecture](#understanding-the-architecture) -- [Docker Images](#docker-images) -- [Prerequisites](#prerequisites) -- [System Requirements](#system-requirements) -- [Setup](#setup) -- [Running the Stack](#running-the-stack) -- [Stopping the Stack](#stopping-the-stack) -- [Data Storage & Volumes](#data-storage--volumes) -- [Interacting with Services](#interacting-with-services) -- [Configuration Guide](#configuration-guide) -- [Health and Readiness Checks](#health-and-readiness-checks) - ---- +Three services replacing the legacy `zcashd`: **Zebra** (full node), **Zaino** (indexer), and **Zallet** (wallet). Orchestrated via Docker Compose with sensible defaults that work out of the box. ## Quick Start -> [!IMPORTANT] -> **First time running Z3?** You must sync Zebra before starting the other services. This takes **24-72 hours for mainnet** or **2-12 hours for testnet**. There is no way around this initial sync. -> -> **Already have synced Zebra data?** You can start all services immediately. - -### First Time Setup (No Existing Data) - ```bash -# 1. Clone and generate required files git clone https://github.com/ZcashFoundation/z3 && cd z3 -git submodule update --init --recursive + +# Generate required credentials openssl req -x509 -newkey rsa:4096 -keyout config/tls/zaino.key -out config/tls/zaino.crt \ -sha256 -days 365 -nodes -subj "/CN=localhost" \ -addext "subjectAltName=DNS:localhost,DNS:zaino,IP:127.0.0.1" rage-keygen -o config/zallet_identity.txt -# 2. Build Zaino and Zallet (required - no pre-built images available) -docker compose build zaino zallet - -# 3. Review configuration -# - config/zallet.toml: set network = "main" or "test" -# - .env: review defaults (usually no changes needed) - -# 4. Start ONLY Zebra first +# Start Zebra first — it must sync before other services can start +# Mainnet: 24-72 hours | Testnet: 2-12 hours docker compose up -d zebra -# 5. Wait for Zebra to sync (this takes hours/days) -./check-zebra-readiness.sh -# Or manually: curl http://localhost:8080/ready (returns "ok" when synced) +# Monitor sync (returns "ok" when ready) +curl http://localhost:8080/ready -# 6. Once Zebra is synced, start the remaining services +# Once Zebra is synced, start the full stack docker compose up -d ``` -> [!NOTE] -> The `check-zebra-readiness.sh` script polls Zebra's readiness endpoint and notifies you when sync is complete. You can safely close your terminal during sync and check back later. +Pre-built images for all 3 services are pulled automatically. No build step or submodule init needed. -### With Existing Synced Data +> [!WARNING] +> The TLS certificate and identity file must exist before running any `docker compose` command. If `config/tls/zaino.crt` or `config/tls/zaino.key` are missing, Compose will fail with a file-not-found error. -If you have previously synced Zebra data (or are mounting existing blockchain state): +> [!IMPORTANT] +> Zebra must sync the blockchain before Zaino and Zallet can start. Running `docker compose up -d` on a fresh install without syncing Zebra first will cause the other services to fail repeatedly. Start Zebra alone, wait for sync, then start the rest. -```bash -# Build if not already built -docker compose build zaino zallet +**Already have synced Zebra data?** Start everything immediately: -# All services can start immediately +```bash docker compose up -d - -# Verify all services are healthy -docker compose ps +docker compose ps # verify all healthy ``` > [!TIP] -> **ARM64 Users (Apple Silicon):** Set `DOCKER_PLATFORM=linux/arm64` in `.env` for native builds. This reduces build time from ~50 minutes to ~3 minutes. - ---- - -## Understanding the Architecture - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ Z3 Stack │ -├─────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ -│ │ Zebra │◄────────│ Zaino │ │ Zallet │ │ -│ │ (node) │ │ (index) │ │(wallet) │ │ -│ └────┬────┘ └─────────┘ └────┬────┘ │ -│ │ │ │ -│ │ ┌─────────────┐ │ │ -│ └──────────────│ Embedded │◄─────────┘ │ -│ │ Zaino libs │ │ -│ └─────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────┘ -``` - -> [!NOTE] -> **Zallet embeds Zaino libraries internally.** It connects directly to Zebra's JSON-RPC, not to the standalone Zaino service. The Zaino container in this stack is for external gRPC clients (like Zingo wallet) and for testing the indexer independently. - -**Service Roles:** -- **Zebra** - Full node that syncs and validates the Zcash blockchain -- **Zaino** - Standalone indexer providing gRPC interface for light wallets -- **Zallet** - Wallet service with embedded indexer that talks directly to Zebra +> **Apple Silicon users:** Create `.env` with `DOCKER_PLATFORM=linux/arm64` for native builds. -## Docker Images +## Deployment Modes -### Image Sources +The stack supports 3 network modes. Mainnet runs with zero configuration; testnet and regtest require a few overrides. -| Service | Image | Source | -|---------|-------|--------| -| **Zebra** | `zfnd/zebra:4.1.0` | [ZcashFoundation/zebra](https://github.com/ZcashFoundation/zebra) | -| **Zaino** | `ghcr.io/zcashfoundation/zaino:sha-1871eba` | [zingolabs/zaino](https://github.com/zingolabs/zaino) | -| **Zallet** | `electriccoinco/zallet:v0.1.0-alpha.3` | [zcash/wallet](https://github.com/zcash/wallet) | +| Mode | Command | Use case | +|------|---------|----------| +| **Mainnet** | `docker compose up -d` | Production, syncs the live Zcash blockchain | +| **Testnet** | Create `.env` with `NETWORK_NAME=Testnet` | Testing against the public test network | +| **Regtest** | `docker compose --env-file .env.regtest up -d` | Local development, instant blocks, no sync wait | -### Building Local Images (Optional) +### Running Testnet -To build from local submodules instead of using pre-built images: +Create a `.env` file with the variables that differ from mainnet: ```bash -# Initialize submodules -git submodule update --init --recursive - -# Build all services locally -docker compose build +echo "NETWORK_NAME=Testnet" > .env ``` -> [!NOTE] -> The submodules in this repository are pinned to tested, compatible commits if you prefer to build locally. - -## Prerequisites - -Before you begin, ensure you have the following installed: - -* **Docker Engine:** [Install Docker](https://docs.docker.com/engine/install/) -* **Docker Compose:** (Usually included with Docker Desktop, or [install separately](https://docs.docker.com/compose/install/)) -* **Docker Permissions (Linux):** You may need to run Docker commands with `sudo`, or add your user to the `docker` group. See [Docker's post-installation steps](https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user) for details. Note that the `docker` group grants root-level privileges. -* **rage:** For generating the Zallet identity file. Install from [str4d/rage releases](https://github.com/str4d/rage/releases) or build from source. -* **Git:** For cloning the repositories and submodules. - -## System Requirements - -Running the full Z3 stack (Zebra + Zaino + Zallet) requires substantial hardware resources due to blockchain synchronization and indexing. - -### Minimum Specifications - -- **CPU:** 2 cores (4+ cores strongly recommended) -- **RAM:** 4 GB for Zebra; 8+ GB recommended for full stack -- **Disk Space:** - - Mainnet: 300 GB (blockchain state) - - Testnet: 30 GB (blockchain state) - - Additional space for Zaino indexer database (requirements under determination) - - SSD strongly recommended for sync performance -- **Network:** Reliable internet connection - - Initial sync download: ~300 GB for mainnet - - Ongoing bandwidth: 10 MB - 10 GB per day - -### Recommended Specifications +Update `config/zallet.toml` to set `network = "test"` in the `[consensus]` section, then start the stack normally. -- **CPU:** 4+ cores -- **RAM:** 16+ GB -- **Disk Space:** 500+ GB with room for blockchain growth -- **Network:** 100+ Mbps connection with ~300 GB/month bandwidth +### Running Regtest -### Sync Time Expectations +Regtest uses a compose overlay (`docker-compose.regtest.yml`) that adds the rpc-router service, disables TLS, and adjusts healthchecks for a peerless network. Volumes are automatically isolated via `COMPOSE_PROJECT_NAME=z3-regtest`. -- **Mainnet:** 24-72 hours on recommended hardware -- **Testnet:** 2-12 hours (currently ~3.1M blocks) -- **Cached/Resumed:** Minutes (if using existing Zebra state) +First-time setup (**required** before starting the stack): -Sync time varies based on CPU speed, disk I/O (SSD vs HDD), and network bandwidth. +```bash +# Initialize wallet, generate RPC auth, and mine the first block +./scripts/regtest-init.sh -**Note:** These specifications are based on [Zebra's official requirements](https://zebra.zfnd.org/user/requirements.html). Zaino indexer adds additional resource overhead; specific requirements are under determination. Running all three services together requires resources beyond Zebra alone. +# Start the full regtest stack +docker compose --env-file .env.regtest up -d +``` -## Setup +The init script generates the Zallet RPC password hash, starts Zebra, mines the first block, and initializes the Zallet wallet. It is safe to re-run — it skips steps that are already done. Subsequent runs only need the last command. -1. **Clone the Repository:** +See [docs/regtest.md](docs/regtest.md) for test commands (curl, grpcurl) and the full workflow reference. - Clone the `z3` repository: +### Monitoring Stack - ```bash - git clone https://github.com/ZcashFoundation/z3 - cd z3 - ``` +Prometheus, Grafana, Jaeger, and AlertManager are available behind a Docker Compose profile. Zebra metrics must be explicitly enabled for Prometheus to have data to scrape. - **Using Pre-Built Images (Recommended):** Submodules are not required. Skip to step 2. +Add to your `.env` (or `.env.regtest` for regtest): - **Building Locally (Optional):** Initialize submodules to build from source: +```bash +ZEBRA_METRICS__ENDPOINT_ADDR=0.0.0.0:9999 +``` - ```bash - git submodule update --init --recursive - ``` +Then start with the monitoring profile: -2. **Platform Configuration (Apple Silicon / ARM64):** +```bash +# Mainnet/Testnet +docker compose --profile monitoring up -d - **ARM64 users**: Enable native builds for dramatically faster performance. +# Regtest +docker compose --env-file .env.regtest --profile monitoring up -d +``` - Z3 defaults to AMD64 (x86_64) for development consistency. On ARM64 systems (Apple Silicon M1/M2/M3 or ARM64 Linux), this uses emulation which is **very slow**: - - AMD64 emulation: ~50 minutes to build Zebra - - Native ARM64: ~2-3 minutes to build Zebra +Access Grafana at `http://localhost:3000` (admin/admin), Prometheus at `http://localhost:9094`, and Jaeger at `http://localhost:16686`. - **To enable native ARM64 builds:** +## Architecture - Edit `.env` and uncomment the `DOCKER_PLATFORM` line: +```mermaid +graph LR + subgraph Z3["Z3 Stack"] + Zebra["Zebra
(full node)"] + Zaino["Zaino
(indexer)"] - ```bash - # In .env file, change this: - # DOCKER_PLATFORM=linux/arm64 + subgraph Zallet["Zallet (wallet)"] + EmbeddedZaino["Embedded
Zaino libs"] + end + end - # To this: - DOCKER_PLATFORM=linux/arm64 - ``` + Zebra -->|JSON-RPC :18232| Zaino + Zebra -->|JSON-RPC :18232| EmbeddedZaino + Zaino -->|gRPC :8137| LightClients["Light wallet
clients"] +``` - Or set it directly in your shell: +**Zebra** syncs and validates the Zcash blockchain. **Zaino** provides a lightwalletd-compatible gRPC interface for light wallet clients like Zingo. **Zallet** embeds Zaino's indexer libraries internally and connects directly to Zebra's JSON-RPC; it does not use the standalone Zaino service. - ```bash - echo "DOCKER_PLATFORM=linux/arm64" >> .env - ``` +All images can be overridden via environment variables (`ZEBRA_IMAGE`, `ZAINO_IMAGE`, `ZALLET_IMAGE`). See `.env.example` for all available options. - **Intel/AMD users**: No action needed. Default AMD64 settings work optimally. +| Service | Default Image | Source | +|---------|---------------|--------| +| Zebra | `zfnd/zebra:4.2.0` | [ZcashFoundation/zebra](https://github.com/ZcashFoundation/zebra) | +| Zaino | `ghcr.io/zcashfoundation/zaino:sha-0164cab` | [zingolabs/zaino](https://github.com/zingolabs/zaino) | +| Zallet | `electriccoinco/zallet:v0.1.0-alpha.3` | [zcash/wallet](https://github.com/zcash/wallet) | -3. **Required Files:** +## Prerequisites - You'll need to generate these files in the `config/` directory: - - `config/tls/zaino.crt` and `config/tls/zaino.key` - Zaino TLS certificates - - `config/zallet_identity.txt` - Zallet encryption key - - `config/zallet.toml` - Zallet configuration (provided, review and customize) +- [Docker Engine](https://docs.docker.com/engine/install/) with [Docker Compose](https://docs.docker.com/compose/install/) (v2.24.0+) +- [rage](https://github.com/str4d/rage/releases) for generating Zallet encryption keys +- Git for cloning the repository -4. **Generate Zaino TLS Certificates:** +> [!NOTE] +> **Linux users** may need `sudo` for Docker commands, or add your user to the `docker` group. See [Docker's post-installation steps](https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user). - ```bash - openssl req -x509 -newkey rsa:4096 -keyout config/tls/zaino.key -out config/tls/zaino.crt -sha256 -days 365 -nodes -subj "/CN=localhost" -addext "subjectAltName = DNS:localhost,IP:127.0.0.1" - ``` +## Service Endpoints - This creates a self-signed certificate valid for 365 days. For production, use certificates from a trusted CA. +Once running, services are available at: -5. **Generate Zallet Identity File:** +| Service | Endpoint | Default Port | +|---------|----------|-------------| +| Zebra RPC | `http://localhost:18232` | `Z3_ZEBRA_HOST_RPC_PORT` | +| Zebra Health | `http://localhost:8080/ready` | `Z3_ZEBRA_HOST_HEALTH_PORT` | +| Zaino gRPC | `localhost:8137` | `ZAINO_HOST_GRPC_PORT` | +| Zaino JSON-RPC | `http://localhost:8237` | `ZAINO_HOST_JSONRPC_PORT` | +| Zallet RPC | `http://localhost:28232` | `ZALLET_HOST_RPC_PORT` | - ```bash - rage-keygen -o config/zallet_identity.txt - ``` +## Stopping the Stack - **Securely back up this file and the public key** (printed to terminal). +```bash +docker compose down # stop containers, keep data +docker compose down -v # stop and delete all volumes (full reset) +``` -6. **Review Zallet Configuration:** +--- - Review `config/zallet.toml` and update the network setting: - - For mainnet: `network = "main"` in `[consensus]` section - - For testnet: `network = "test"` in `[consensus]` section +## Reference - See [Configuration Guide](#configuration-guide) for details on Zallet's architecture and config requirements. +The sections below cover setup details, configuration, and operational topics. Expand the section you need. -7. **Review Environment Variables:** +
+System Requirements - A comprehensive `.env` file is provided with sensible defaults. Review and customize as needed: - - `NETWORK_NAME` - Set to `Mainnet` or `Testnet` - - Log levels for each service (defaults to `info` with warning filters) - - Port mappings (defaults work for most setups) +### Minimum - See [Configuration Guide](#configuration-guide) for the complete variable hierarchy and customization options. +- **CPU:** 2 cores (4+ recommended) +- **RAM:** 4 GB for Zebra alone; 8+ GB for the full stack +- **Disk:** Mainnet ~300 GB, Testnet ~30 GB (SSD strongly recommended) +- **Network:** Reliable internet; initial mainnet sync downloads ~300 GB -## Running the Stack +### Recommended -> [!WARNING] -> **Why can't I just run `docker compose up`?** -> -> Docker Compose healthchecks have timeout limits that cannot accommodate blockchain sync times (hours to days). If you run `docker compose up` on a fresh install, Zaino and Zallet will repeatedly fail waiting for Zebra to sync. -> -> **Solution:** Start Zebra alone first, wait for sync, then start everything else. +- **CPU:** 4+ cores +- **RAM:** 16+ GB +- **Disk:** 500+ GB with room for blockchain growth +- **Network:** 100+ Mbps, ~300 GB/month bandwidth -### First Time (Fresh Sync Required) +### Sync Times -```bash -# Step 1: Start only Zebra -docker compose up -d zebra +| Network | First sync | With existing data | +|---------|-----------|-------------------| +| Mainnet | 24-72 hours | Minutes | +| Testnet | 2-12 hours | Minutes | -# Step 2: Monitor sync progress (choose one method) -./check-zebra-readiness.sh # Recommended: script waits and notifies -docker compose logs -f zebra # Watch logs -curl http://localhost:8080/ready # Manual check (returns "ok" when synced) +Based on [Zebra's official requirements](https://zebra.zfnd.org/user/requirements.html). Zaino adds additional resource overhead; specific requirements are under determination. -# Step 3: Once synced, start all services -docker compose up -d -``` +
-> [!NOTE] -> **Sync times:** -> - Mainnet: 24-72 hours (depends on hardware/network) -> - Testnet: 2-12 hours -> -> You can close your terminal during sync. Zebra runs in the background. +
+Setup Details -### Returning User (Existing Data) +### Submodules -If Zebra has previously synced (data persists in Docker volumes): +Pre-built images are used by default. To build from source instead: ```bash -docker compose up -d -docker compose ps # Verify all healthy +git submodule update --init --recursive +docker compose build ``` -### Development Mode +### TLS Certificates (Required) -For local development when you need services running during sync: +Zaino's gRPC endpoint uses TLS. Generate a self-signed certificate: ```bash -cp docker-compose.override.yml.example docker-compose.override.yml -docker compose up -d +openssl req -x509 -newkey rsa:4096 \ + -keyout config/tls/zaino.key -out config/tls/zaino.crt \ + -sha256 -days 365 -nodes -subj "/CN=localhost" \ + -addext "subjectAltName=DNS:localhost,DNS:zaino,IP:127.0.0.1" ``` -> [!CAUTION] -> Development mode uses `/healthy` instead of `/ready`. Services will start but may error until Zebra catches up. Not for production use. +For production, use certificates from a trusted CA. -## Stopping the Stack - -To stop the services and remove the containers: +### Zallet Identity File (Required) ```bash -docker compose down -``` - -To also remove the data volumes (⚠️ **deletes all blockchain data, indexer database, wallet database**): - -```bash -docker compose down -v +rage-keygen -o config/zallet_identity.txt ``` -## Data Storage & Volumes - -The Z3 stack stores blockchain data, indexer state, and wallet data in Docker volumes. You can choose between Docker-managed volumes (default) or local directories. - -### Default: Docker Named Volumes (Recommended) - -By default, the stack uses Docker named volumes which are managed by Docker: +Back up this file and the public key printed to the terminal. -- `zebra_data`: Zebra blockchain state (~300GB+ for mainnet, ~30GB for testnet) -- `zaino_data`: Zaino indexer database -- `zallet_data`: Zallet wallet data -- `shared_cookie_volume`: RPC authentication cookies +### Zallet Configuration -**Advantages:** -- No permission issues -- Automatic management by Docker -- Better performance on macOS/Windows +Review `config/zallet.toml` and set the network in the `[consensus]` section: -### Advanced: Local Directories +- Mainnet: `network = "main"` +- Testnet: `network = "test"` -For advanced use cases (backups, external SSDs, shared storage), you can bind local directories instead of using Docker-managed volumes. +Zallet embeds Zaino's indexer libraries and connects directly to Zebra's JSON-RPC endpoint. -**Important:** Choose directory locations appropriate for your operating system and requirements: -- Linux: `/mnt/data/z3`, `/var/lib/z3`, or user home directories -- macOS: `/Volumes/ExternalDrive/z3`, `~/Library/Application Support/z3`, or user Documents -- Windows (WSL): `/mnt/c/Z3Data` or native Windows paths if using Docker Desktop +Critical requirements for `config/zallet.toml`: -#### Setup Steps +- `validator_address` must point to `zebra:18232` (Zebra's JSON-RPC), not `zaino:8137` +- All TOML sections must be present: `[builder]`, `[consensus]`, `[database]`, `[external]`, `[features]`, `[indexer]`, `[keystore]`, `[note_management]`, `[rpc]` +- Cookie authentication must be configured in both TOML and mounted as a volume -1. **Create your directories** in your chosen location: - ```bash - mkdir -p /your/chosen/path/zebra-state - mkdir -p /your/chosen/path/zaino-data - mkdir -p /your/chosen/path/zallet-data - ``` +### Platform Configuration (ARM64) -2. **Fix permissions** using the provided utility: - ```bash - ./fix-permissions.sh zebra /your/chosen/path/zebra-state - ./fix-permissions.sh zaino /your/chosen/path/zaino-data - ./fix-permissions.sh zallet /your/chosen/path/zallet-data - ``` +Z3 defaults to AMD64 for consistency. On Apple Silicon or ARM64 Linux, emulation makes builds ~15x slower. Enable native builds: - Note: Keep the cookie directory as a Docker volume (recommended) to avoid cross-user permission issues. +```bash +echo "DOCKER_PLATFORM=linux/arm64" >> .env +``` -3. **Update `.env` file** with your paths: - ```bash - Z3_ZEBRA_DATA_PATH=/your/chosen/path/zebra-state - Z3_ZAINO_DATA_PATH=/your/chosen/path/zaino-data - Z3_ZALLET_DATA_PATH=/your/chosen/path/zallet-data - # Z3_COOKIE_PATH=shared_cookie_volume # Keep as Docker volume - ``` +
-4. **Restart the stack**: - ```bash - docker compose down - docker compose up -d - ``` +
+Configuration Reference -#### Security Requirements +### Defaults-in-Compose -Each service runs as a specific non-root user with distinct UIDs/GIDs: +Every variable in `docker-compose.yml` has a default via `${VAR:-default}`. The stack works with zero configuration files. Create `.env` only to override specific values. -- **Zebra**: UID=10001, GID=10001, permissions 700 -- **Zaino**: UID=1000, GID=1000, permissions 700 -- **Zallet**: UID=65532, GID=65532, permissions 700 +Precedence (highest wins): -**Critical:** Local directories must have correct ownership and secure permissions: -- Use `fix-permissions.sh` to set ownership automatically -- Permissions must be 700 (owner only) or 750 (owner + group read) -- **Never use 755 or 777** - these expose your blockchain data and wallet to other users +1. Shell environment variables +2. `.env` file values +3. Compose file defaults -## Configuration Guide +### Variable Naming -This section explains how the Z3 stack is configured and how to customize it for your needs. +Variables follow a 3-tier naming system to avoid collisions: -### Configuration Overview +| Prefix | Scope | Example | +|--------|-------|---------| +| `Z3_*` | Infrastructure (volumes, ports); never passed to containers | `Z3_ZEBRA_DATA_PATH` | +| Unprefixed | Shared config, remapped per service in compose | `NETWORK_NAME`, `ENABLE_COOKIE_AUTH` | +| `ZEBRA_*`, `ZAINO_*`, `ZALLET_*` | Service-specific application config | `ZEBRA_TRACING__FILTER` | -The Z3 stack uses a layered configuration approach: +### Common Overrides -1. **Service Defaults** - Built-in defaults for each service -2. **Environment Variables** (`.env`) - Runtime configuration and customization -3. **Configuration Files** - Required for specific services (Zallet, Zaino TLS) -4. **Docker Compose Remapping** - Transforms variables for service-specific formats +```bash +# Network +NETWORK_NAME=Testnet -### Variable Hierarchy +# Log levels +Z3_ZEBRA_RUST_LOG=debug +ZAINO_RUST_LOG=debug -The Z3 stack uses a **three-tier variable naming system** to avoid collisions: +# Ports +Z3_ZEBRA_HOST_RPC_PORT=28232 +ZAINO_HOST_GRPC_PORT=9137 -**1. Z3_* Variables (Infrastructure)** -- Purpose: Docker-level configuration (volume paths, port mappings, service discovery) -- Scope: Used only in `docker-compose.yml`, never passed directly to containers -- Examples: `Z3_ZEBRA_DATA_PATH`, `Z3_ZEBRA_RPC_PORT`, `Z3_ZEBRA_RUST_LOG` -- Why: Prevents collision with service configuration variables +# Images +ZEBRA_IMAGE=zfnd/zebra:5.0.0 +``` -**2. Shared Variables (Common Configuration)** -- Purpose: Settings used by multiple services -- Scope: Remapped in `docker-compose.yml` to service-specific names -- Examples: - - `NETWORK_NAME` → `ZEBRA_NETWORK__NETWORK`, `ZAINO_NETWORK`, `ZALLET_NETWORK` - - `ENABLE_COOKIE_AUTH` → `ZEBRA_RPC__ENABLE_COOKIE_AUTH`, `ZAINO_VALIDATOR_COOKIE_AUTH` - - `COOKIE_AUTH_FILE_DIR` → Mapped to cookie paths for each service +See `.env.example` for all available variables. -**3. Service Configuration Variables (Application Config)** -- Purpose: Service-specific configuration passed to applications -- Scope: Passed via `env_file` in `docker-compose.yml` -- Formats: - - **Zebra**: `ZEBRA_*` (config-rs format: `ZEBRA_SECTION__KEY` with `__` separator) - - **Zaino**: `ZAINO_*` - - **Zallet**: `ZALLET_*` +
-### Configuration Approaches by Service +
+Data Storage and Volumes -**Zebra:** -- **Method**: Pure environment variables -- **Format**: `ZEBRA_SECTION__KEY` (e.g., `ZEBRA_RPC__LISTEN_ADDR`) -- **Files**: None required (uses environment variables only) +### Docker Named Volumes (Default) -**Zaino:** -- **Method**: Pure environment variables -- **Format**: `ZAINO_*` (e.g., `ZAINO_GRPC_PORT`) -- **Files**: TLS certificates (`config/tls/zaino.crt`, `config/tls/zaino.key`) - -**Zallet:** -- **Method**: Hybrid (TOML file + environment variables) -- **Format**: `ZALLET_*` for runtime parameters (e.g., `ZALLET_RUST_LOG`) -- **Files**: - - `config/zallet.toml` - Core configuration (required) - - `config/zallet_identity.txt` - Encryption key (required) - -### Zallet's Architecture - -Zallet differs from Zebra and Zaino in key ways: +The stack uses Docker-managed named volumes by default: -**Embedded Zaino Indexer:** -- Zallet embeds **Zaino's indexer libraries** (`zaino-fetch`, `zaino-state`, `zaino-proto`) as dependencies -- This embedded indexer connects directly to **Zebra's JSON-RPC** endpoint to fetch blockchain data -- Zallet does **NOT** connect to the standalone Zaino gRPC/JSON-RPC service (which is for other light clients) +| Volume | Contents | +|--------|----------| +| `zebra_data` | Blockchain state (~300 GB mainnet, ~30 GB testnet) | +| `zaino_data` | Indexer database | +| `zallet_data` | Wallet database | +| `shared_cookie_volume` | RPC authentication cookies | -**Service Connectivity:** -``` -Zebra (JSON-RPC :18232) - ├─→ Zaino Service (standalone indexer for gRPC clients like Zingo) - └─→ Zallet (uses embedded Zaino indexer libraries) -``` +### Local Directories (Advanced) -**Critical Configuration Requirements:** -1. `config/zallet.toml` must exist with all required sections (even if empty) -2. `validator_address` must point to `zebra:18232` (Zebra's JSON-RPC), **NOT** `zaino:8137` -3. All TOML sections must be present: `[builder]`, `[consensus]`, `[database]`, `[external]`, `[features]`, `[indexer]`, `[keystore]`, `[note_management]`, `[rpc]` -4. Cookie authentication must be configured in both TOML and mounted as a volume +For backups, external SSDs, or shared storage, override volume paths in `.env`: -### Common Customizations - -**Change Network (Mainnet/Testnet):** ```bash -# In .env: -NETWORK_NAME=Mainnet # or Testnet - -# In config/zallet.toml: -[consensus] -network = "main" # or "test" +Z3_ZEBRA_DATA_PATH=/mnt/ssd/zebra-state +Z3_ZAINO_DATA_PATH=/mnt/ssd/zaino-data +Z3_ZALLET_DATA_PATH=/mnt/ssd/zallet-data ``` -**Adjust Log Levels:** -```bash -# In .env: -Z3_ZEBRA_RUST_LOG=info -ZAINO_RUST_LOG=info,reqwest=warn,hyper_util=warn -ZALLET_RUST_LOG=info,hyper_util=warn,reqwest=warn - -# For debugging, use: -ZAINO_RUST_LOG=debug -``` +Fix permissions before starting: -**Change Ports:** ```bash -# In .env: -Z3_ZEBRA_HOST_RPC_PORT=18232 -ZAINO_HOST_GRPC_PORT=8137 -ZALLET_HOST_RPC_PORT=28232 +./scripts/fix-permissions.sh zebra /mnt/ssd/zebra-state +./scripts/fix-permissions.sh zaino /mnt/ssd/zaino-data +./scripts/fix-permissions.sh zallet /mnt/ssd/zallet-data ``` -**Environment Variable Precedence:** +Each service runs as a specific non-root user. Directories must have correct ownership (set by the script) and 700 permissions. Never use 755 or 777. -Docker Compose applies variables in this order (later overrides earlier): -1. Dockerfile defaults -2. `.env` file substitution (e.g., `${VARIABLE}`) -3. `env_file` section -4. `environment` section -5. Shell environment variables (if exported) +
-**Important**: If you export a variable in your shell, it will override the `.env` file. Use `unset VARIABLE` to remove shell variables. +
+Health Checks and Sync Strategy -## Health and Readiness Checks +### Two-Phase Deployment -Zebra provides two HTTP endpoints for monitoring service health: +Zebra's blockchain sync takes hours to days. Docker Compose healthcheck timeouts cannot accommodate this, so the stack uses a two-phase approach: -### `/healthy` - Liveness Check -- **Returns 200**: Zebra is running and has minimum connected peers (configurable, default: 1) -- **Returns 503**: Not enough peer connections -- **Use for**: Docker healthchecks, liveness monitoring, restart decisions -- **Works during**: Initial sync, normal operation -- **Endpoint**: `http://localhost:${Z3_ZEBRA_HOST_HEALTH_PORT:-8080}/healthy` +1. Start Zebra alone (`docker compose up -d zebra`) +2. Wait for sync (`curl http://localhost:8080/ready` returns "ok") +3. Start the full stack (`docker compose up -d`) -### `/ready` - Readiness Check -- **Returns 200**: Zebra is synced near the network tip (within configured blocks, default: 2) -- **Returns 503**: Still syncing or lagging behind network tip -- **Use for**: Production traffic routing, manual verification before use -- **Fails during**: Fresh sync (can take 24+ hours for mainnet) -- **Endpoint**: `http://localhost:${Z3_ZEBRA_HOST_HEALTH_PORT:-8080}/ready` +### Health Endpoints -### Service Dependency Strategy +Zebra exposes 2 endpoints on port 8080: -The Z3 stack uses **readiness-based dependencies** to prevent service hangs: +| Endpoint | Returns 200 when | Use for | +|----------|-------------------|---------| +| `/healthy` | Has minimum peer connections | Liveness monitoring, restart decisions | +| `/ready` | Synced within 2 blocks of tip | Production readiness, dependency gating | + +### Service Dependency Chain ``` -Zebra (/ready - synced near tip) - → Zaino (gRPC responding) +Zebra (/ready — synced near tip) + → Zaino (gRPC port responding) → Zallet (RPC responding) ``` -**Why this approach:** -- **Zaino requires Zebra to be near the network tip** - if Zebra is still syncing, Zaino will hang internally waiting -- **Two-phase deployment** separates initial sync from normal operation -- **Docker Compose healthcheck** verifies Zebra is synced before starting dependent services - -**What each healthcheck tests:** -- `zebra`: `/ready` - Synced near network tip (within 2 blocks, configurable) -- `zaino`: gRPC server responding - Ready to index blocks -- `zallet`: RPC server responding - Ready for wallet operations +The default compose configuration gates Zaino and Zallet on Zebra's `/ready` endpoint. For development, copy `docker-compose.override.yml.example` to `docker-compose.override.yml` to switch to `/healthy` (allows services to start during sync, but they may error until Zebra catches up). -**Deployment modes:** - -| Mode | When to use | Zebra healthcheck | Behavior | -|------|-------------|-------------------|----------| -| **Production** (default) | Mainnet, production testnet | `/ready` | Two-phase: sync Zebra first, then start stack | -| **Development** (override) | Local dev, quick testing | `/healthy` | Start all services immediately (may have delays) | +| Mode | Zebra healthcheck | Behavior | +|------|-------------------|----------| +| **Production** (default) | `/ready` | Two-phase: sync Zebra first, then start stack | +| **Development** (override) | `/healthy` | Start immediately; services may error during sync | ### Monitoring Sync Progress -During Phase 1 (Zebra sync), monitor progress: - ```bash -# Check readiness (returns "ok" when synced near tip) -curl http://localhost:8080/ready - -# Monitor sync progress via logs -docker compose logs -f zebra - -# Check current status -docker compose ps zebra +curl http://localhost:8080/ready # "ok" when synced +docker compose logs -f zebra # watch logs +./scripts/check-zebra-readiness.sh # polls until synced, prints status every 30s ``` -**What to expect:** -- Zebra shows `healthy (starting)` while syncing (during 90-second grace period) +What to expect during sync: +- Zebra shows `healthy (starting)` while syncing (during the 90-second grace period) - Once synced, `/ready` returns `ok` and Zebra shows `healthy` -- Zaino and Zallet remain in `waiting` state until dependencies are healthy +- Zaino and Zallet remain in `waiting` state until Zebra is ready -### Configuration Options +### Tuning Health Checks -**Skip sync wait for development** (`.env`): ```bash -# Make /ready always return 200 on testnet (even during sync) -ZEBRA_HEALTH__ENFORCE_ON_TEST_NETWORKS=false # Default: false - -# When set to true, testnet behaves like mainnet (strict readiness check) -``` - -**Adjust readiness threshold** (`.env`): -```bash -# How many blocks behind network tip is acceptable (default: 2) +# Adjust how many blocks behind the tip is acceptable (default: 2) ZEBRA_HEALTH__READY_MAX_BLOCKS_BEHIND=2 -# Minimum peer connections for /healthy (default: 1) +# Minimum peer connections for /healthy (default: 1, set 0 for regtest) ZEBRA_HEALTH__MIN_CONNECTED_PEERS=1 -``` -## Interacting with Services +# Make /ready always return 200 on testnet even during sync (default: false) +ZEBRA_HEALTH__ENFORCE_ON_TEST_NETWORKS=false +``` -Once the stack is running, services can be accessed via their exposed ports: +
-* **Zebra RPC:** `http://localhost:${Z3_ZEBRA_HOST_RPC_PORT:-18232}` (default: Testnet `http://localhost:18232`) -* **Zebra Health:** `http://localhost:${Z3_ZEBRA_HOST_HEALTH_PORT:-8080}/healthy` and `/ready` -* **Zaino gRPC:** `localhost:${ZAINO_HOST_GRPC_PORT:-8137}` (default: `localhost:8137`) -* **Zaino JSON-RPC:** `http://localhost:${ZAINO_HOST_JSONRPC_PORT:-8237}` (default: `http://localhost:8237`, if enabled) -* **Zallet RPC:** `http://localhost:${ZALLET_HOST_RPC_PORT:-28232}` (default: `http://localhost:28232`) +## Further Reading -Refer to the individual component documentation for RPC API details. \ No newline at end of file +- [docs/docker-architecture.md](docs/docker-architecture.md): Design decisions, Compose patterns, and rationale behind the stack's configuration +- [docs/regtest.md](docs/regtest.md): Regtest environment setup, test commands (curl, grpcurl), and workflow reference +- [.env.example](.env.example): All available environment variable overrides diff --git a/config/regtest/zaino.toml b/config/regtest/zaino.toml new file mode 100644 index 0000000..1b218ae --- /dev/null +++ b/config/regtest/zaino.toml @@ -0,0 +1,6 @@ +# Zaino config for regtest — auth via username/password (no cookie auth) +backend = "fetch" + +[validator_settings] +validator_user = "zebra" +validator_password = "zebra" diff --git a/rpc-router/regtest/config/zallet.toml b/config/regtest/zallet.toml similarity index 72% rename from rpc-router/regtest/config/zallet.toml rename to config/regtest/zallet.toml index 58b2d4b..8e0f42b 100644 --- a/rpc-router/regtest/config/zallet.toml +++ b/config/regtest/zallet.toml @@ -9,6 +9,7 @@ regtest_nuparams = [ "2bb40e60:1", "f5b9230b:1", "e9ff75a6:1", + "c2d6d0b4:1", ] [database] @@ -28,7 +29,7 @@ validator_user = "zebra" validator_password = "zebra" [keystore] -encryption_identity = "identity.txt" +encryption_identity = "/etc/zallet/identity.txt" [note_management] @@ -38,7 +39,6 @@ timeout = 30 [[rpc.auth]] user = "zebra" -# Password: "zebra" (from zcash/integration-tests) -# Generated by ./init.sh from RPC_PASSWORD (default: "zebra"). -# pwhash = "__GENERATED_BY_INIT_SH__" +# Generated by scripts/regtest-init.sh from RPC_PASSWORD (default: "zebra"). +# Run ./scripts/regtest-init.sh to generate after cloning. pwhash = "__GENERATED_BY_INIT_SH__" diff --git a/config/zaino/zindexer.toml b/config/zaino.toml similarity index 100% rename from config/zaino/zindexer.toml rename to config/zaino.toml diff --git a/config/zallet.toml b/config/zallet.toml index 7c0f338..0ad39cd 100644 --- a/config/zallet.toml +++ b/config/zallet.toml @@ -45,7 +45,6 @@ encryption_identity = "/etc/zallet/identity.txt" # Note management - using defaults [rpc] -# RPC server configuration -# Bind address is set via ZALLET_RPC_BIND environment variable in docker-compose +# Bind port must match ZALLET_RPC_PORT in .env if overridden (both must change together) bind = ["0.0.0.0:28232"] timeout = 30 diff --git a/docker-compose.regtest.yml b/docker-compose.regtest.yml new file mode 100644 index 0000000..1b9f782 --- /dev/null +++ b/docker-compose.regtest.yml @@ -0,0 +1,73 @@ +# Regtest overlay — merged on top of docker-compose.yml +# +# Usage: docker compose --env-file .env.regtest up -d +# Or: ./scripts/regtest-init.sh (first time) +# +# This file only contains regtest-specific structural differences. +# All other config (network, ports, volumes) comes from .env.regtest +# overriding the defaults in the base docker-compose.yml. + +services: + zebra: + environment: + ZEBRA_MINING__MINER_ADDRESS: ${ZEBRA_MINING__MINER_ADDRESS:-} + # Regtest has no peers — check RPC directly instead of /ready + healthcheck: + test: ["CMD-SHELL", "curl -sf -X POST -H 'Content-Type: application/json' -d '{\"jsonrpc\":\"2.0\",\"method\":\"getblockchaininfo\",\"params\":[],\"id\":1}' http://127.0.0.1:18232 || exit 1"] + interval: 10s + timeout: 5s + retries: 10 + start_period: 30s + + zaino: + # Regtest uses username/password auth (set in config/regtest/zaino.toml), not cookie auth. + # Override the full environment to avoid inheriting the cookie path from the base. + environment: !override + RUST_LOG: ${ZAINO_RUST_LOG:-info} + ZAINO_NETWORK: Regtest + ZAINO_VALIDATOR_SETTINGS__VALIDATOR_JSONRPC_LISTEN_ADDRESS: zebra:${Z3_ZEBRA_RPC_PORT:-18232} + ZAINO_GRPC_SETTINGS__LISTEN_ADDRESS: 0.0.0.0:${ZAINO_GRPC_PORT:-8137} + ZAINO_JSON_SERVER_SETTINGS__JSON_RPC_LISTEN_ADDRESS: 0.0.0.0:${ZAINO_JSON_RPC_PORT:-8237} + ZAINO_GRPC_SETTINGS__TLS__CERT_PATH: ${ZAINO_GRPC_TLS_CERT_PATH:-/var/run/zaino/tls/zaino.crt} + ZAINO_GRPC_SETTINGS__TLS__KEY_PATH: ${ZAINO_GRPC_TLS_KEY_PATH:-/var/run/zaino/tls/zaino.key} + # Override config file with regtest-specific version + volumes: + - ./config/regtest/zaino.toml:/etc/zaino/zindexer.toml:ro + + zallet: + # Regtest uses username/password auth, not cookie auth. + # Override the full volumes list to avoid inheriting the cookie volume mount + # from the base compose file (which fails on distroless images where /var/run + # is not a directory). + volumes: !override + - ${Z3_ZALLET_DATA_PATH:-zallet_data}:/var/lib/zallet + - ./config/regtest/zallet.toml:/etc/zallet/zallet.toml:ro + - ./config/regtest/zallet_identity.txt:/etc/zallet/identity.txt:ro + + rpc-router: + build: + context: ./rpc-router + dockerfile: Dockerfile + restart: unless-stopped + logging: + driver: json-file + options: + max-size: "50m" + max-file: "5" + cap_drop: [ALL] + security_opt: [no-new-privileges:true] + environment: + ZEBRA_URL: http://zebra:18232 + ZALLET_URL: http://zallet:28232 + RPC_USER: ${RPC_USER:-zebra} + RPC_PASSWORD: ${RPC_PASSWORD:-zebra} + LISTEN_PORT: "8181" + ports: + - "8181:8181" + depends_on: + zebra: + condition: service_healthy + zallet: + condition: service_started + networks: + - z3_net diff --git a/docker-compose.yml b/docker-compose.yml index 798061c..1dddd60 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,21 @@ +# Shared defaults applied to all services via <<: *common +# - logging: rotates logs (50MB x 5 files) to prevent unbounded disk growth +# - cap_drop: removes all Linux capabilities (services run as non-root) +# - security_opt: prevents privilege escalation via setuid binaries +# See docs/docker-architecture.md for rationale. +x-common: &common + logging: + driver: json-file + options: + max-size: "50m" + max-file: "5" + cap_drop: [ALL] + security_opt: [no-new-privileges:true] + services: zebra: - # Run 'docker compose build' to build locally from ./zebra submodule instead - # For OpenTelemetry tracing: docker compose build --build-arg FEATURES="default-release-binaries opentelemetry" zebra - image: zfnd/zebra:4.1.0 + image: ${ZEBRA_IMAGE:-zfnd/zebra:4.2.0} + platform: ${DOCKER_PLATFORM:-linux/amd64} build: context: ./zebra dockerfile: docker/Dockerfile @@ -11,88 +24,96 @@ services: FEATURES: ${ZEBRA_BUILD_FEATURES:-default-release-binaries} container_name: z3_zebra restart: unless-stopped + stop_grace_period: 30s + <<: *common + # Zebra's entrypoint runs mkdir/chown before dropping privileges via setpriv. + # These operations need capabilities that cap_drop: [ALL] removes, so we add them back. + cap_add: [CHOWN, DAC_OVERRIDE, FOWNER, SETUID, SETGID] + # Optional ZEBRA_* vars (metrics, OpenTelemetry) pass through from .env when set. + # They must NOT be listed in environment: with empty defaults because config-rs + # treats empty strings as values and fails to parse them (e.g., "" as a socket address). env_file: - - ./.env + - path: ./.env + required: false environment: - - RUST_LOG=${Z3_ZEBRA_RUST_LOG} - - ZEBRA_NETWORK__NETWORK=${NETWORK_NAME} - - ZEBRA_RPC__ENABLE_COOKIE_AUTH=${ENABLE_COOKIE_AUTH} - - ZEBRA_RPC__COOKIE_DIR=${COOKIE_AUTH_FILE_DIR} + RUST_LOG: ${Z3_ZEBRA_RUST_LOG:-info} + ZEBRA_NETWORK__NETWORK: ${NETWORK_NAME:-Mainnet} + ZEBRA_RPC__LISTEN_ADDR: 0.0.0.0:${Z3_ZEBRA_RPC_PORT:-18232} + ZEBRA_RPC__ENABLE_COOKIE_AUTH: ${ENABLE_COOKIE_AUTH:-true} + ZEBRA_RPC__COOKIE_DIR: ${COOKIE_AUTH_FILE_DIR:-/var/run/auth} + ZEBRA_TRACING__FILTER: ${ZEBRA_TRACING__FILTER:-info} + ZEBRA_STATE__CACHE_DIR: /home/zebra/.cache/zebra + ZEBRA_HEALTH__LISTEN_ADDR: 0.0.0.0:8080 + ZEBRA_HEALTH__MIN_CONNECTED_PEERS: ${ZEBRA_HEALTH__MIN_CONNECTED_PEERS:-1} + ZEBRA_HEALTH__READY_MAX_BLOCKS_BEHIND: ${ZEBRA_HEALTH__READY_MAX_BLOCKS_BEHIND:-2} + ZEBRA_HEALTH__ENFORCE_ON_TEST_NETWORKS: ${ZEBRA_HEALTH__ENFORCE_ON_TEST_NETWORKS:-false} volumes: - # Blockchain state (defaults to named volume 'zebra_data') - - ${Z3_ZEBRA_DATA_PATH}:/home/zebra/.cache/zebra - # Cookie authentication shared with Zaino and Zallet - - ${Z3_COOKIE_PATH}:${COOKIE_AUTH_FILE_DIR} + - ${Z3_ZEBRA_DATA_PATH:-zebra_data}:/home/zebra/.cache/zebra + - ${Z3_COOKIE_PATH:-shared_cookie_volume}:${COOKIE_AUTH_FILE_DIR:-/var/run/auth} ports: - - "${Z3_ZEBRA_HOST_RPC_PORT}:${Z3_ZEBRA_RPC_PORT}" - - "${Z3_ZEBRA_HOST_HEALTH_PORT}:8080" + - "${Z3_ZEBRA_HOST_RPC_PORT:-18232}:${Z3_ZEBRA_RPC_PORT:-18232}" + - "${Z3_ZEBRA_HOST_HEALTH_PORT:-8080}:8080" networks: - z3_net healthcheck: - # Verifies Zebra is synced near network tip (default: within 2 blocks) - # For fresh sync: run 'docker compose up -d zebra' first, wait for sync, - # then run 'docker compose up -d' to start full stack test: ["CMD-SHELL", "curl -sf http://127.0.0.1:8080/ready || exit 1"] interval: 30s timeout: 10s retries: 3 start_period: 90s + start_interval: 5s zaino: - # No official image from zingolabs yet - built from zingolabs/zaino:dev - image: ghcr.io/zcashfoundation/zaino:sha-1871eba + image: ${ZAINO_IMAGE:-ghcr.io/zcashfoundation/zaino:sha-0164cab} platform: ${DOCKER_PLATFORM:-linux/amd64} build: context: ./zaino dockerfile: Dockerfile container_name: z3_zaino restart: unless-stopped - command: ["zainod", "--config", "/etc/zaino/zindexer.toml"] + stop_grace_period: 15s + <<: *common + # Zaino's entrypoint runs mkdir/chown before dropping privileges via setpriv. + # These operations need capabilities that cap_drop: [ALL] removes, so we add them back. + cap_add: [CHOWN, DAC_OVERRIDE, FOWNER, SETUID, SETGID] + command: ["--config", "/etc/zaino/zindexer.toml"] depends_on: zebra: condition: service_healthy environment: - - RUST_LOG=${ZAINO_RUST_LOG} - - RUST_BACKTRACE=${ZAINO_RUST_BACKTRACE} - - ZAINO_NETWORK=${NETWORK_NAME} - - ZAINO_VALIDATOR_SETTINGS__VALIDATOR_JSONRPC_LISTEN_ADDRESS=zebra:${Z3_ZEBRA_RPC_PORT} - - ZAINO_VALIDATOR_SETTINGS__VALIDATOR_COOKIE_PATH=${COOKIE_AUTH_FILE_DIR}/.cookie - # gRPC server - - ZAINO_GRPC_SETTINGS__LISTEN_ADDRESS=0.0.0.0:${ZAINO_GRPC_PORT} - # JSON-RPC server - - ZAINO_JSON_SERVER_SETTINGS__JSON_RPC_LISTEN_ADDRESS=0.0.0.0:${ZAINO_JSON_RPC_PORT} - # TLS configuration - - ZAINO_GRPC_SETTINGS__TLS__CERT_PATH=${ZAINO_GRPC_TLS_CERT_PATH} - - ZAINO_GRPC_SETTINGS__TLS__KEY_PATH=${ZAINO_GRPC_TLS_KEY_PATH} + RUST_LOG: ${ZAINO_RUST_LOG:-info,reqwest=warn,hyper_util=warn} + RUST_BACKTRACE: ${ZAINO_RUST_BACKTRACE:-full} + ZAINO_NETWORK: ${NETWORK_NAME:-Mainnet} + ZAINO_VALIDATOR_SETTINGS__VALIDATOR_JSONRPC_LISTEN_ADDRESS: zebra:${Z3_ZEBRA_RPC_PORT:-18232} + ZAINO_VALIDATOR_SETTINGS__VALIDATOR_COOKIE_PATH: ${COOKIE_AUTH_FILE_DIR:-/var/run/auth}/.cookie + ZAINO_GRPC_SETTINGS__LISTEN_ADDRESS: 0.0.0.0:${ZAINO_GRPC_PORT:-8137} + ZAINO_JSON_SERVER_SETTINGS__JSON_RPC_LISTEN_ADDRESS: 0.0.0.0:${ZAINO_JSON_RPC_PORT:-8237} + ZAINO_GRPC_SETTINGS__TLS__CERT_PATH: ${ZAINO_GRPC_TLS_CERT_PATH:-/var/run/zaino/tls/zaino.crt} + ZAINO_GRPC_SETTINGS__TLS__KEY_PATH: ${ZAINO_GRPC_TLS_KEY_PATH:-/var/run/zaino/tls/zaino.key} volumes: - # Indexer state (defaults to named volume 'zaino_data') - - ${Z3_ZAINO_DATA_PATH}:/home/zaino/.cache/zaino - # Cookie authentication - - ${Z3_COOKIE_PATH}:${COOKIE_AUTH_FILE_DIR}:ro - - ./config/zaino/zindexer.toml:/etc/zaino/zindexer.toml:ro + - ${Z3_ZAINO_DATA_PATH:-zaino_data}:/app/data + - ${Z3_COOKIE_PATH:-shared_cookie_volume}:${COOKIE_AUTH_FILE_DIR:-/var/run/auth}:ro + - ./config/zaino.toml:/etc/zaino/zindexer.toml:ro configs: - source: zaino_tls_cert - target: ${ZAINO_GRPC_TLS_CERT_PATH} + target: ${ZAINO_GRPC_TLS_CERT_PATH:-/var/run/zaino/tls/zaino.crt} - source: zaino_tls_key - target: ${ZAINO_GRPC_TLS_KEY_PATH} + target: ${ZAINO_GRPC_TLS_KEY_PATH:-/var/run/zaino/tls/zaino.key} ports: - - "${ZAINO_HOST_GRPC_PORT}:${ZAINO_GRPC_PORT}" - - "${ZAINO_HOST_JSONRPC_PORT}:${ZAINO_JSON_RPC_PORT}" + - "${ZAINO_HOST_GRPC_PORT:-8137}:${ZAINO_GRPC_PORT:-8137}" + - "${ZAINO_HOST_JSONRPC_PORT:-8237}:${ZAINO_JSON_RPC_PORT:-8237}" networks: - z3_net healthcheck: - # Process check (gRPC cannot be directly curled) - test: ["CMD-SHELL", "/usr/local/bin/zainod --version >/dev/null 2>&1 || exit 1"] + test: ["CMD-SHELL", "bash -c 'echo > /dev/tcp/127.0.0.1/8137' 2>/dev/null || exit 1"] interval: 30s timeout: 5s retries: 3 start_period: 60s + start_interval: 5s zallet: - # Default to a local tag for reliable dev/regtest flows. - # To pull and run a remote image instead, set: - # ZALLET_IMAGE=zodlinc/zallet:v0.1.0-alpha.4 - image: ${ZALLET_IMAGE:-z3_zallet:local} + image: ${ZALLET_IMAGE:-electriccoinco/zallet:v0.1.0-alpha.3} platform: ${DOCKER_PLATFORM:-linux/amd64} build: context: ./zallet @@ -100,30 +121,24 @@ services: target: runtime tags: - z3_zallet:local - - zodlinc/zallet:v0.1.0-alpha.4 container_name: z3_zallet restart: unless-stopped + stop_grace_period: 15s + <<: *common user: "1000:1000" command: ["--datadir", "/var/lib/zallet", "--config", "/etc/zallet/zallet.toml", "start"] depends_on: zebra: condition: service_healthy - env_file: - - ./.env environment: - - RUST_LOG=${ZALLET_RUST_LOG} - # Zallet configuration is handled via config/zallet.toml - # Environment variable overrides are currently not supported by Zallet + RUST_LOG: ${ZALLET_RUST_LOG:-info,hyper_util=warn,reqwest=warn} volumes: - # Wallet database (defaults to named volume 'zallet_data') - - ${Z3_ZALLET_DATA_PATH}:/var/lib/zallet - # Cookie authentication for Zebra access - - ${Z3_COOKIE_PATH}:${COOKIE_AUTH_FILE_DIR}:ro - # Configuration files (mounted outside datadir to avoid permission conflicts) + - ${Z3_ZALLET_DATA_PATH:-zallet_data}:/var/lib/zallet + - ${Z3_COOKIE_PATH:-shared_cookie_volume}:${COOKIE_AUTH_FILE_DIR:-/var/run/auth}:ro - ./config/zallet.toml:/etc/zallet/zallet.toml:ro - ./config/zallet_identity.txt:/etc/zallet/identity.txt:ro ports: - - "${ZALLET_HOST_RPC_PORT}:${ZALLET_RPC_PORT}" + - "${ZALLET_HOST_RPC_PORT:-28232}:${ZALLET_RPC_PORT:-28232}" networks: - z3_net # No healthcheck: distroless image has no shell/curl @@ -143,6 +158,7 @@ services: container_name: z3_jaeger profiles: [monitoring] restart: unless-stopped + <<: *common volumes: - ./observability/jaeger/config.yaml:/etc/jaeger/config.yaml:ro command: @@ -165,6 +181,7 @@ services: container_name: z3_prometheus profiles: [monitoring] restart: unless-stopped + <<: *common volumes: - prometheus_data:/prometheus - ./observability/prometheus/rules:/etc/prometheus/rules:ro @@ -194,13 +211,14 @@ services: container_name: z3_grafana profiles: [monitoring] restart: unless-stopped + <<: *common volumes: - ./observability/grafana/dashboards:/var/lib/grafana/dashboards:ro - ./observability/grafana/provisioning:/etc/grafana/provisioning:ro - grafana_data:/var/lib/grafana environment: - - GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH=/var/lib/grafana/dashboards/zebra_overview.json - - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin} + GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH: /var/lib/grafana/dashboards/zebra_overview.json + GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:-admin} ports: - "${GRAFANA_PORT:-3000}:3000" networks: @@ -222,6 +240,7 @@ services: container_name: z3_alertmanager profiles: [monitoring] restart: unless-stopped + <<: *common volumes: - ./observability/alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro - alertmanager_data:/alertmanager diff --git a/docs/docker-architecture.md b/docs/docker-architecture.md new file mode 100644 index 0000000..38863f2 --- /dev/null +++ b/docs/docker-architecture.md @@ -0,0 +1,245 @@ +# Docker Compose Architecture + +This document explains the architectural decisions, patterns, and modern Docker Compose features used in the Z3 stack. It serves as a reference for contributors and operators who need to understand *why* things are structured the way they are. + +## Overview + +```text +docker-compose.yml Base stack (Zebra + Zaino + Zallet + monitoring) +docker-compose.regtest.yml Regtest overlay (structural differences only) +.env.example Reference for all overridable variables +.env User overrides (gitignored, optional) +.env.regtest Regtest configuration (tracked) +config/ All service configs (mainnet defaults + regtest/ subdirectory) +scripts/ Operational scripts (regtest-init, check-zebra-readiness, etc.) +``` + +The core principle: **`docker-compose.yml` is self-sufficient**. Every variable uses `${VAR:-default}` syntax, so `docker compose up` works on a fresh clone with zero configuration files. The `.env` file is purely optional; users create it only when they want to override a default. + +## Defaults-in-Compose Pattern + +### How it works + +Every variable reference in `docker-compose.yml` includes a default value: + +```yaml +image: ${ZEBRA_IMAGE:-zfnd/zebra:4.2.0} +environment: + ZEBRA_NETWORK__NETWORK: ${NETWORK_NAME:-Mainnet} +volumes: + - ${Z3_ZEBRA_DATA_PATH:-zebra_data}:/home/zebra/.cache/zebra +ports: + - "${Z3_ZEBRA_HOST_RPC_PORT:-18232}:${Z3_ZEBRA_RPC_PORT:-18232}" +``` + +Docker Compose resolves `${VAR:-default}` as: use `VAR` if set and non-empty, otherwise use `default`. Values come from (highest precedence first): + +1. Shell environment variables (`ZEBRA_IMAGE=custom docker compose up`) +2. `.env` file in the project root +3. The `:-default` fallback in the compose file + +### Why not a tracked `.env` with all defaults? + +The previous approach kept a 183-line `.env` file tracked in git with all values populated. This had problems: + +- **Mandatory dependency**: `docker compose up` failed without the file +- **Duplication**: Three per-network copies (mainnet/testnet/regtest) with 95% identical content +- **Drift**: Changes to defaults required updating multiple files +- **Merge conflicts**: Users who modified `.env` locally hit conflicts on every pull + +With defaults in the compose file, none of these problems exist. A Testnet user creates a 1-line `.env`: + +```env +NETWORK_NAME=Testnet +``` + +Everything else inherits from the compose defaults. + +## Multi-Environment Support + +### `COMPOSE_PROJECT_NAME` — Volume and Network Isolation + +Docker Compose automatically prefixes all resources (volumes, networks) with the project name. This gives each environment its own isolated storage without parameterizing volume names: + +| Environment | Project name | Volume on disk | +|------------|-------------|----------------| +| Mainnet | `z3` (default, from directory) | `z3_zebra_data` | +| Regtest | `z3-regtest` (from `.env.regtest`) | `z3-regtest_zebra_data` | + +The same `zebra_data` volume name in the compose file creates different actual volumes depending on the project name. No naming tricks needed. + +### `COMPOSE_FILE` — Automatic Overlay Loading + +`.env.regtest` includes: + +```env +COMPOSE_FILE=docker-compose.yml:docker-compose.regtest.yml +``` + +When you run `docker compose --env-file .env.regtest up`, Compose automatically loads both files and merges them. The colon-separated list is processed left to right; later files override earlier ones. + +This means the regtest overlay only needs to contain *structural differences* from the base (different healthchecks, additional services, authentication changes). Everything else is inherited. + +### Compose File Merge Rules + +When the regtest overlay defines the same service as the base, attributes merge as follows: + +| Attribute type | Merge behavior | +|----------------|---------------| +| Scalars (`image`, `command`, `container_name`) | Override replaces | +| `environment` | Merge by key name; override wins on conflict | +| `volumes` (service-level) | Merge by mount target path; same target = override wins | +| `ports` | Append | +| `healthcheck` | Override replaces entirely | +| `build.args` | Merge by key name; override wins on conflict | + +Example: the base mounts `./config/zaino.toml` at `/etc/zaino/zindexer.toml`. The regtest overlay mounts `./config/regtest/zaino.toml` at the same target path. Because the target matches, the overlay's mount replaces the base's; no duplication, no conflict. + +### `!override` YAML Tag — Replacing Attributes in Overlays + +Standard YAML merge can add and overwrite keys but cannot *remove* them. Docker Compose v2.24+ added the `!override` tag to solve this: + +```yaml +# In docker-compose.regtest.yml — replaces Zaino's entire environment block, +# removing the cookie auth path that the base compose sets +services: + zaino: + environment: !override + RUST_LOG: info + ZAINO_NETWORK: Regtest + # ... only regtest-relevant vars, no cookie path +``` + +`!override` fully replaces the attribute instead of merging. The regtest overlay uses this on Zaino's `environment` to switch from cookie-based authentication (base compose) to username/password authentication (credentials in `config/regtest/zaino.toml`). + +A related tag, `!reset`, clears an attribute to its default value (e.g., `ports: !reset []` empties the port list instead of appending). + +Both tags require Docker Compose v2.24.0 or later. + +## Extension Fields and YAML Anchors + +### `x-common` — Shared Service Configuration + +```yaml +x-common: &common + logging: + driver: json-file + options: + max-size: "50m" + max-file: "5" + cap_drop: [ALL] + security_opt: [no-new-privileges:true] +``` + +Services reference this with `<<: *common`, which merges all keys from the anchor into the service definition. This ensures consistent log rotation and security hardening across all services without repeating the configuration. + +Top-level keys starting with `x-` are *extension fields*; Docker Compose ignores them during processing but they serve as anchor sources for YAML reuse. + +### Zebra's `setpriv` Entrypoint + +Zebra's Docker entrypoint starts as root, runs `mkdir` and `chown` to set up mounted volume directories, then uses `setpriv` (part of `util-linux`, included in Debian trixie) to drop to a non-root user. These pre-privilege-drop operations need capabilities that `cap_drop: [ALL]` removes, so Zebra adds back only the 5 it needs: + +```yaml +cap_add: [CHOWN, DAC_OVERRIDE, FOWNER, SETUID, SETGID] +``` + +Zaino and Zallet run as non-root from the start and work with `cap_drop: [ALL]` alone. + +## Healthchecks + +### `start_interval` — Two-Speed Healthchecks + +Docker Engine 25.0+ supports `start_interval`, which checks more frequently during startup then backs off: + +```yaml +healthcheck: + test: ["CMD-SHELL", "curl -sf http://127.0.0.1:8080/ready || exit 1"] + interval: 30s # steady-state: every 30s + start_interval: 5s # during startup: every 5s + start_period: 90s # grace period before failures count +``` + +During the `start_period`, the check runs every `start_interval` (5s). After the first success or after `start_period` expires, it switches to `interval` (30s). This means a service that becomes ready in 10 seconds is detected in ~15 seconds instead of waiting up to 120 seconds. + +### Zaino — Port Check Instead of Process Check + +Zaino's image (`debian:bookworm-slim`) doesn't include `curl` or `netcat`. The healthcheck uses bash's built-in TCP socket capability: + +```yaml +test: ["CMD-SHELL", "bash -c 'echo > /dev/tcp/127.0.0.1/8137' 2>/dev/null || exit 1"] +``` + +This verifies the gRPC port is actually accepting connections. The previous check (`zainod --version`) only confirmed the binary existed on disk; it would pass even if the service had crashed after startup. + +### Regtest Zebra — RPC Check Instead of `/ready` + +The base compose uses Zebra's `/ready` endpoint, which verifies the node is synced near the network tip. In regtest mode there are no peers and no network tip to sync to, so `/ready` would never succeed. The regtest overlay replaces this with a direct RPC call (`getblockchaininfo`) that confirms the RPC server is responding. + +### Development Override + +`docker-compose.override.yml.example` provides a ready-made override that switches Zebra's healthcheck from `/ready` to `/healthy`, allowing dependent services to start during sync. Copy it to `docker-compose.override.yml` (gitignored) for local development. + +## Security Hardening + +### `cap_drop: [ALL]` + +Linux containers receive ~14 capabilities by default (including `CHOWN`, `DAC_OVERRIDE`, `NET_RAW`). Most applications don't need any of them. `cap_drop: [ALL]` removes all capabilities, reducing the attack surface if a container is compromised. + +### `security_opt: [no-new-privileges:true]` + +Prevents processes inside the container from gaining additional privileges through setuid binaries or capability inheritance. Even if an attacker writes a setuid binary into a writable tmpfs, it won't escalate privileges. + +### Log Rotation + +Without `max-size` and `max-file`, Docker's default `json-file` log driver grows logs unbounded. For a blockchain node running 24/7, this will eventually fill the disk. The `x-common` anchor configures 50MB per log file with 5 rotated files (250MB max per service). + +## Image Override Variables + +All service images are overridable via environment variables: + +```yaml +image: ${ZEBRA_IMAGE:-zfnd/zebra:4.2.0} +image: ${ZAINO_IMAGE:-ghcr.io/zcashfoundation/zaino:sha-0164cab} +image: ${ZALLET_IMAGE:-electriccoinco/zallet:v0.1.0-alpha.3} +``` + +This allows operators to: + +- Pin to a specific version or digest for reproducibility +- Test a pre-release candidate without editing the compose file +- Use a private registry mirror in air-gapped environments +- Run CI with custom-built images via shell variables + +## Environment Variable Strategy + +### Explicit Mapping via `environment:` + +All services declare their environment variables explicitly in the `environment:` block. This prevents unintended leakage of variables between services. Zallet does not support environment variable configuration at all, so it only receives `RUST_LOG`. + +### Zebra's `env_file` Exception + +Zebra is the only service that also uses `env_file: [{path: ./.env, required: false}]`. This exists because Zebra uses config-rs, which auto-reads any `ZEBRA_*` environment variable. Optional config-rs variables like `ZEBRA_METRICS__ENDPOINT_ADDR` and `ZEBRA_TRACING__OPENTELEMETRY_*` cannot be listed in the explicit `environment:` block with empty defaults, because config-rs treats empty strings as values and crashes when parsing `""` as a socket address or integer. + +The `env_file` passthrough allows these optional variables to reach Zebra only when the user explicitly sets them in `.env`. When `.env` doesn't exist (the `required: false` case), Zebra receives only the explicit `environment:` variables and uses its built-in defaults for everything else. + +Non-ZEBRA variables (ZAINO_*, Z3_*, etc.) that leak through `env_file` are harmless because config-rs ignores variables that don't match its configured prefix. + +## Regtest Overlay Constraints + +### Zaino Authentication + +The base compose configures Zaino with cookie-based authentication (shared cookie volume with Zebra). Regtest disables cookie auth (`ENABLE_COOKIE_AUTH=false`), so the regtest overlay uses `environment: !override` on Zaino to replace the full environment block, removing the cookie path and all variables that are not needed in regtest. + +Regtest instead uses username/password authentication configured in `config/regtest/zaino.toml`. These credentials cannot be set via environment variables because Zaino blocks sensitive keys (containing "password") in env vars for security reasons. + +### Config File vs Environment Variable Conflicts + +Zaino uses config-rs, which merges values from both TOML config files and environment variables. If the same field is set in both places, config-rs panics with a "duplicate field" error. The regtest zaino config (`config/regtest/zaino.toml`) must only contain settings that are NOT set via environment variables. Currently it contains only `backend` and the auth credentials. + +### `docker compose run` and the `--config` Flag + +When using `docker compose run` to execute one-off commands (e.g., wallet initialization), the arguments replace the service's `command` from the compose file. This means the `--config /etc/zallet/zallet.toml` flag from the base service definition is not inherited. The init script must pass `--config` explicitly in every `compose run` invocation. + +## `stop_grace_period` + +When Docker sends `SIGTERM` to stop a container, it waits 10 seconds by default before sending `SIGKILL`. Blockchain nodes may need more time to flush state to disk. Zebra gets 30 seconds; other services get 15 seconds. This prevents potential state corruption during planned shutdowns. diff --git a/docs/regtest.md b/docs/regtest.md new file mode 100644 index 0000000..848ef4d --- /dev/null +++ b/docs/regtest.md @@ -0,0 +1,162 @@ +# Z3 Regtest Environment + +Local end-to-end testing of the full Z3 stack (Zebra, Zaino, Zallet, and the rpc-router) in regtest mode. + +Uses the base `docker-compose.yml` with `docker-compose.regtest.yml` overlay and `.env.regtest` for regtest-specific configuration. Volumes are isolated via `COMPOSE_PROJECT_NAME=z3-regtest`, so regtest data never conflicts with mainnet or testnet. + +## Prerequisites + +- Docker with [Docker Compose](https://docs.docker.com/compose/install/) (v2.24.0+) +- [rage](https://github.com/str4d/rage/releases) for generating Zallet encryption keys +- TLS certificates generated (see Quick Start in the main [README](../README.md)) +- For gRPC testing: [grpcurl](https://github.com/fullstorydev/grpcurl) and the zaino submodule initialized (`git submodule update --init zaino`) + +## First-time setup + +From the repo root: + +```bash +./scripts/regtest-init.sh +``` + +This will: + +1. Generate a Zallet encryption identity (if not already present) +2. Generate and inject the Zallet RPC password hash in `config/regtest/zallet.toml` +3. Start Zebra in regtest mode +4. Mine 1 block (activates Orchard at height 1) +5. Initialize the Zallet wallet (`init-wallet-encryption` + `generate-mnemonic`) + +Optionally override the RPC password (default is `zebra`): + +```bash +RPC_PASSWORD='your-password' ./scripts/regtest-init.sh +``` + +## Start the stack + +From the repo root: + +```bash +docker compose --env-file .env.regtest up -d +``` + +Zebra, Zaino, and Zallet use pre-built images. The rpc-router builds from source on first run (takes a few minutes; subsequent runs use the Docker layer cache). + +> [!NOTE] +> Regtest uses the same host ports as mainnet/testnet. If other Z3 services are running, stop them first (`docker compose down`) or override port variables in `.env.regtest`. + +| Service | Endpoint | Description | +|---------|----------|-------------| +| rpc-router | http://localhost:8181 | JSON-RPC router (Zebra + Zallet) | +| Zaino gRPC | https://localhost:8137 | lightwalletd-compatible gRPC (TLS) | +| Zebra RPC | http://localhost:18232 | Direct Zebra JSON-RPC | +| Zallet RPC | http://localhost:28232 | Direct Zallet JSON-RPC | + +## Test routing + +These commands go through the rpc-router, which forwards to Zebra or Zallet based on the method: + +```bash +# Route to Zebra (full node) +curl -s -X POST -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"getblockchaininfo","params":[],"id":1}' \ + http://127.0.0.1:8181 + +# Route to Zallet (wallet) +curl -s -X POST -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"getwalletinfo","params":[],"id":2}' \ + http://127.0.0.1:8181 + +# Merged OpenRPC schema +curl -s -X POST -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"rpc.discover","params":[],"id":3}' \ + http://127.0.0.1:8181 | grep -o '"title":"[^"]*"' +``` + +## Test Zaino gRPC + +Zaino exposes the [lightwalletd-compatible gRPC protocol](https://github.com/zcash/lightwalletd/blob/master/walletrpc/service.proto) on port 8137 with TLS. The `--insecure` flag tells grpcurl to accept the self-signed certificate. + +Initialize the zaino submodule if you haven't already (needed for the proto files): + +```bash +git submodule update --init zaino +``` + +Test with `GetLightdInfo` (from the repo root): + +```bash +grpcurl -insecure \ + -import-path zaino/zaino-proto/proto \ + -proto service.proto \ + 127.0.0.1:8137 \ + cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLightdInfo +``` + +Get the latest block height: + +```bash +grpcurl -insecure \ + -import-path zaino/zaino-proto/proto \ + -proto service.proto \ + -d '{}' \ + 127.0.0.1:8137 \ + cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLatestBlock +``` + +## OpenRPC Playground + +Open the playground pointed at your locally running router: + + + +The playground calls `rpc.discover` on `http://127.0.0.1:8181` to load the live merged schema. + +## Stop and clean up + +```bash +# Stop containers (keeps volumes/wallet data) +docker compose --env-file .env.regtest down + +# Full reset (deletes all regtest data; re-run scripts/regtest-init.sh afterwards) +docker compose --env-file .env.regtest down -v +``` + +## Expected output + +**`getblockchaininfo`** (routed to Zebra, truncated): + +```json +{"jsonrpc":"2.0","id":1,"result":{"chain":"test","blocks":1,"headers":1,...,"upgrades":{"5ba81b19":{"name":"Overwinter","activationheight":1,"status":"active"},...}}} +``` + +**`getwalletinfo`** (routed to Zallet): + +```json +{"jsonrpc":"2.0","result":{"walletversion":0,"balance":0.00000000,"unconfirmed_balance":0.00000000,"immature_balance":0.00000000,"shielded_balance":"0.00","shielded_unconfirmed_balance":"0.00","txcount":0,"keypoololdest":0,"keypoolsize":0,"mnemonic_seedfp":"TODO"},"id":1} +``` + +## Monitoring in regtest + +To enable monitoring, add the metrics endpoint to `.env.regtest`: + +```bash +ZEBRA_METRICS__ENDPOINT_ADDR=0.0.0.0:9999 +``` + +Then start with both flags: + +```bash +docker compose --env-file .env.regtest --profile monitoring up -d +``` + +The port (9999) must match the Prometheus scrape target configured in `observability/prometheus/prometheus.yaml`. + +## Notes + +- Credentials: `zebra` / `zebra` (hardcoded for regtest only) +- Zallet uses regtest nuparams activating all upgrades at block 1 +- Zaino uses username/password auth in regtest (not cookie auth) +- Zaino gRPC uses TLS with the same self-signed certificate as mainnet/testnet +- The rpc-router source is in `rpc-router/`; it is built automatically on first `docker compose up` diff --git a/rpc-router/README.md b/rpc-router/README.md index 00c8f4a..752a866 100644 --- a/rpc-router/README.md +++ b/rpc-router/README.md @@ -33,7 +33,7 @@ Wait until both services are healthy, then note the RPC ports from your `.env` f > **Note:** A `openrpc.py` QA helper that spawned Zebra and Zallet in regtest mode previously existed in the Zebra repository but was removed. It has not been ported to the [zcash/integration-tests](https://github.com/zcash/integration-tests) repository. -> **Note:** For experimental Z3 regtest mode of the router see [Regtest Environment](regtest/README.md). +> **Note:** For experimental Z3 regtest mode of the router see [Regtest Environment](../docs/regtest.md). ### Running the RPC Router diff --git a/rpc-router/regtest/README.md b/rpc-router/regtest/README.md deleted file mode 100644 index cd07332..0000000 --- a/rpc-router/regtest/README.md +++ /dev/null @@ -1,107 +0,0 @@ -# RPC Router — Regtest Environment - -Self-contained Docker Compose setup for local end-to-end testing of the rpc-router against real Zebra and Zallet backends in regtest mode. - -**Does not touch the production stack** in the repo root. - -## Prerequisites - -- Docker with the `z3-zallet:local` image built: - ```bash - cd ../../ # repo root - docker compose build zallet - ``` - -## First-time setup - -```bash -cd rpc-router/regtest -./init.sh -``` - -This will: -1. Start Zebra in regtest mode -2. Mine 1 block (activates Orchard at height 1) -3. Generate and inject the Zallet `rpc.auth.pwhash` in `config/zallet.toml` -4. Initialize the Zallet wallet (`init-wallet-encryption` + `generate-mnemonic`) - -Optional: override the RPC password used for the hash generation: - -This (default) password which is included in zcash/integration-tests is not checked into the repo to avoid offending the kingphisher and other code scanning gods. - -```bash -RPC_PASSWORD='your-password' ./init.sh -``` - -## Start the stack - -```bash -sudo -E docker compose up -d -``` - -Router is available at **http://localhost:8181**. - -## Test routing - -```bash -# Route to Zebra (full node) -curl -s -X POST -H "Content-Type: application/json" \ - -d '{"jsonrpc":"2.0","method":"getblockchaininfo","params":[],"id":1}' \ - http://127.0.0.1:8181 - -# Route to Zallet (wallet) -curl -s -X POST -H "Content-Type: application/json" \ - -d '{"jsonrpc":"2.0","method":"getwalletinfo","params":[],"id":2}' \ - http://127.0.0.1:8181 - -# Merged OpenRPC schema title -curl -s -X POST -H "Content-Type: application/json" \ - -d '{"jsonrpc":"2.0","method":"rpc.discover","params":[],"id":3}' \ - http://127.0.0.1:8181 | grep -o '"title":"[^"]*"' -``` - -## OpenRPC Playground - -Open the playground pointed at your locally running router: - -https://playground.open-rpc.org/?uiSchema[appBar][ui:title]=Zcash&uiSchema[appBar][ui:logoUrl]=https://z.cash/wp-content/uploads/2023/03/zcash-logo.gif&schemaUrl=http://127.0.0.1:8181&uiSchema[appBar][ui:splitView]=false&uiSchema[appBar][ui:edit]=false&uiSchema[appBar][ui:input]=false&uiSchema[appBar][ui:examplesDropdown]=false&uiSchema[appBar][ui:transports]=false - -The playground will call `rpc.discover` on `http://127.0.0.1:8181` to load the live merged schema. - -## Stop and clean up - -```bash -# Stop all containers (keeps volumes/wallet data) -sudo -E docker compose down - -# Stop and delete all volumes (full reset — re-run init.sh afterwards) -sudo -E docker compose down -v -``` - -## Notes - -- Credentials: `zebra` / `zebra` (hardcoded for regtest only) -- Zallet uses regtest nuparams activating all upgrades at block 1 -- The rpc-router Dockerfile is in `rpc-router/` (one level up) - -## Tested environment - -Successfully tested on: - -- **OS**: Linux Mint 21.2 (kernel 5.15.0-171-generic). also Debian GNU/Linux 13 (trixie) (kernel 6.12.69+deb13-cloud-amd64) -- **Rust**: 1.90.0 (rpc-router built locally; Docker image uses `rust-1.85`), also 1.94.0 -- **Docker Compose**: v5.1.0 (plugin, not standalone `docker-compose`) -- **Zallet image**: `z3-zallet:local` built from submodule at `ae762c05` (Feb 2026), also built from submodule at `757876b` (March 2026) -- **Zebra image**: `zfnd/zebra:3.1.0` and `zfnd/zebra:4.1.0` - -Expected output for the test commands above: - -**`getwalletinfo`** → routed to Zallet: -```json -{"jsonrpc":"2.0","result":{"walletversion":0,"balance":0.00000000,"unconfirmed_balance":0.00000000,"immature_balance":0.00000000,"shielded_balance":"0.00","shielded_unconfirmed_balance":"0.00","txcount":0,"keypoololdest":0,"keypoolsize":0,"mnemonic_seedfp":"TODO"},"id":1} -``` - -**`getblockchaininfo`** → routed to Zebra (truncated): -```json -{"jsonrpc":"2.0","id":1,"result":{"chain":"test","blocks":1,"headers":1,...,"upgrades":{"5ba81b19":{"name":"Overwinter","activationheight":1,"status":"active"},...}}} -``` diff --git a/rpc-router/regtest/docker-compose.yml b/rpc-router/regtest/docker-compose.yml deleted file mode 100644 index 84d6f98..0000000 --- a/rpc-router/regtest/docker-compose.yml +++ /dev/null @@ -1,94 +0,0 @@ -# docker-compose.yml for rpc-router regtest testing -# -# Runs Zebra + Zallet in regtest mode + the rpc-router. -# Completely self-contained; does not touch the production stack. -# -# Usage: -# cd rpc-router/regtest -# ./init.sh # first time: initialize wallet -# docker compose up -d -# # Router available at http://localhost:8181 -# # Test: curl -s -X POST -H "Content-Type: application/json" \ -# # -d '{"jsonrpc":"2.0","method":"getinfo","params":[],"id":1}' \ -# # http://localhost:8181 - -services: - zebra: - image: zfnd/zebra:4.1.0 - container_name: z3_regtest_zebra - restart: unless-stopped - environment: - - RUST_LOG=info - - ZEBRA_NETWORK__NETWORK=Regtest - - ZEBRA_RPC__LISTEN_ADDR=0.0.0.0:18232 - - ZEBRA_RPC__ENABLE_COOKIE_AUTH=false - - ZEBRA_MINING__MINER_ADDRESS=tmSRd1r8gs77Ja67Fw1JcdoXytxsyrLTPJm - - ZEBRA_HEALTH__LISTEN_ADDR=0.0.0.0:8080 - - ZEBRA_HEALTH__ENFORCE_ON_TEST_NETWORKS=false - - ZEBRA_STATE__CACHE_DIR=/home/zebra/.cache/zebra - volumes: - - zebra_regtest_data:/home/zebra/.cache/zebra - ports: - - "18232:18232" - networks: - - regtest_net - healthcheck: - # Regtest has no peers; check RPC port directly - test: ["CMD-SHELL", "curl -sf -X POST -H 'Content-Type: application/json' -d '{\"jsonrpc\":\"2.0\",\"method\":\"getblockchaininfo\",\"params\":[],\"id\":1}' http://127.0.0.1:18232 || exit 1"] - interval: 10s - timeout: 5s - retries: 10 - start_period: 30s - - zallet: - image: z3_zallet:local - container_name: z3_regtest_zallet - # zallet image is distroless (no /bin/sh), so CMD-SHELL healthchecks fail. - restart: unless-stopped - command: ["--datadir", "/var/lib/zallet", "start"] - depends_on: - zebra: - condition: service_healthy - environment: - - RUST_LOG=info - volumes: - - zallet_regtest_data:/var/lib/zallet - - ./config/zallet.toml:/var/lib/zallet/zallet.toml:ro - - ./config/zallet_identity.txt:/var/lib/zallet/identity.txt:ro - ports: - - "28232:28232" - networks: - - regtest_net - - rpc-router: - build: - context: ../ - dockerfile: Dockerfile - container_name: z3_regtest_router - restart: unless-stopped - # In Compose networks, service keys are DNS hostnames. - # So `zebra` and `zallet` here resolve to those services on regtest_net, - # regardless of the explicit container_name values above. - environment: - - ZEBRA_URL=http://zebra:18232 - - ZALLET_URL=http://zallet:28232 - - RPC_USER=zebra - - RPC_PASSWORD=zebra - - LISTEN_PORT=8181 - ports: - - "8181:8181" - depends_on: - zebra: - condition: service_healthy - zallet: - condition: service_started - networks: - - regtest_net - -volumes: - zebra_regtest_data: - zallet_regtest_data: - -networks: - regtest_net: - driver: bridge diff --git a/check-zebra-readiness.sh b/scripts/check-zebra-readiness.sh similarity index 100% rename from check-zebra-readiness.sh rename to scripts/check-zebra-readiness.sh diff --git a/fix-permissions.sh b/scripts/fix-permissions.sh similarity index 100% rename from fix-permissions.sh rename to scripts/fix-permissions.sh diff --git a/rpc-router/regtest/init.sh b/scripts/regtest-init.sh similarity index 51% rename from rpc-router/regtest/init.sh rename to scripts/regtest-init.sh index b59b323..4b30c41 100755 --- a/rpc-router/regtest/init.sh +++ b/scripts/regtest-init.sh @@ -1,22 +1,26 @@ #!/usr/bin/env bash # init.sh - Initialize the regtest wallet for the first time. # -# Run this once before `docker compose up -d`. +# Run this once before starting the regtest stack. # Safe to re-run: skips steps if already done. # # Requirements: -# - Docker with the z3_zallet:local image built (cd ../../ && docker compose build zallet) +# - Docker with Docker Compose v2.24.0+ +# - rage-keygen (for first-time identity generation) +# - openssl (for TLS certificate and RPC password hash generation) # - No running z3_regtest_* containers set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +ENV_FILE="$REPO_ROOT/.env.regtest" +COMPOSE="docker compose --env-file $ENV_FILE" + log() { printf '%s\n' "$*" } -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -NETWORK=regtest_regtest_net - ensure_rage() { if command -v rage-keygen > /dev/null 2>&1; then log "rage already installed" @@ -31,7 +35,7 @@ ensure_rage() { } ensure_local_identity() { - local identity_path="$SCRIPT_DIR/config/zallet_identity.txt" + local identity_path="$REPO_ROOT/config/regtest/zallet_identity.txt" if [ -f "$identity_path" ]; then log "==> Reusing existing local zallet identity..." @@ -41,11 +45,31 @@ ensure_local_identity() { ensure_rage log "==> Generating local zallet identity..." - mkdir -p "$SCRIPT_DIR/config" + mkdir -p "$REPO_ROOT/config/regtest" rage-keygen -o "$identity_path" chmod 600 "$identity_path" } +ensure_tls_certs() { + local cert_path="$REPO_ROOT/config/tls/zaino.crt" + local key_path="$REPO_ROOT/config/tls/zaino.key" + + if [ -f "$cert_path" ] && [ -f "$key_path" ]; then + log "==> Reusing existing TLS certificates..." + return + fi + + ensure_openssl + + log "==> Generating self-signed TLS certificate for Zaino..." + mkdir -p "$REPO_ROOT/config/tls" + openssl req -x509 -newkey rsa:4096 \ + -keyout "$key_path" -out "$cert_path" \ + -sha256 -days 365 -nodes -subj "/CN=localhost" \ + -addext "subjectAltName=DNS:localhost,DNS:zaino,IP:127.0.0.1" 2>/dev/null + log " TLS certificate written to config/tls/zaino.crt" +} + ensure_openssl() { if command -v openssl > /dev/null 2>&1; then return @@ -57,15 +81,14 @@ ensure_openssl() { } update_zallet_rpc_pwhash() { - local config_path="$SCRIPT_DIR/config/zallet.toml" + local config_path="$REPO_ROOT/config/regtest/zallet.toml" local rpc_password="${RPC_PASSWORD:-zebra}" + local placeholder="__GENERATED_BY_INIT_SH__" local salt local hash local pwhash local tmp - ensure_openssl - if [ ! -f "$config_path" ]; then log "Missing zallet config: $config_path" exit 1 @@ -76,8 +99,16 @@ update_zallet_rpc_pwhash() { exit 1 fi + # Skip if already generated (not the placeholder) + if ! grep -q "pwhash = \"${placeholder}\"" "$config_path"; then + log "==> Zallet RPC pwhash already generated, skipping." + return + fi + + ensure_openssl + salt="$(openssl rand -hex 16)" - hash="$(printf '%s' "$rpc_password" | openssl dgst -sha256 -mac HMAC -macopt "key:$salt" | awk '{print $2}')" + hash="$(printf '%s' "$rpc_password" | openssl dgst -sha256 -mac HMAC -macopt "key:$salt" | awk '{print $NF}')" if [ -z "$hash" ]; then log "Failed to generate zallet RPC password hash" @@ -90,25 +121,30 @@ update_zallet_rpc_pwhash() { sed -E "s|^pwhash = \".*\"$|pwhash = \"${pwhash}\"|" "$config_path" > "$tmp" mv "$tmp" "$config_path" - log "==> Updated zallet RPC pwhash in config/zallet.toml" + log "==> Generated zallet RPC pwhash in config/regtest/zallet.toml" } # Use sudo for docker if needed DOCKER="docker" if ! docker info > /dev/null 2>&1; then DOCKER="sudo -E docker" + COMPOSE="sudo -E $COMPOSE" fi -cd "$SCRIPT_DIR" +cd "$REPO_ROOT" ensure_local_identity +ensure_tls_certs update_zallet_rpc_pwhash +# Clean up any leftover containers from previous runs +$COMPOSE down --remove-orphans 2>/dev/null || true + echo "==> Starting Zebra in regtest mode..." -$DOCKER compose up -d zebra +$COMPOSE up -d zebra echo "==> Waiting for Zebra RPC to be ready..." -until $DOCKER compose exec zebra curl -sf -X POST \ +until curl -sf -X POST \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"getblockchaininfo","params":[],"id":1}' \ http://127.0.0.1:18232 > /dev/null 2>&1; do @@ -118,32 +154,39 @@ done echo " Zebra is ready." echo "==> Mining 1 block (required for Orchard activation at height 1)..." -$DOCKER compose exec zebra curl -s -u zebra:zebra \ +curl -s -u zebra:zebra \ -X POST -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"generate","params":[1],"id":1}' \ http://127.0.0.1:18232 | grep -q '"result"' echo " Block mined." +# Volume name includes the project prefix from COMPOSE_PROJECT_NAME +VOLUME_PREFIX="z3-regtest" + echo "==> Running init-wallet-encryption..." -# Remove stale lock file if present (left by a previous interrupted run) -$DOCKER run --rm -v zallet_regtest_data:/data busybox \ - sh -c 'rm -f /data/.lock' -# Check if wallet already initialized by looking for the wallet database -ALREADY_INIT=$($DOCKER run --rm -v zallet_regtest_data:/data busybox \ - sh -c 'ls /data/*.sqlite /data/*.age 2>/dev/null | wc -l') +# Remove stale lock file and wallet database if present (left by a previous +# interrupted run). wallet.db will be recreated with the correct schema by +# init-wallet-encryption; leaving a stale one causes a schema mismatch error. +$DOCKER run --rm -v "${VOLUME_PREFIX}_zallet_data:/data" busybox \ + sh -c 'rm -f /data/.lock /data/wallet.db' +# Check if wallet already initialized: generate-mnemonic stores an age-encrypted +# file; if one exists the full init sequence has already completed successfully. +ALREADY_INIT=$($DOCKER run --rm -v "${VOLUME_PREFIX}_zallet_data:/data" busybox \ + sh -c 'ls /data/*.age 2>/dev/null | wc -l') if [ "${ALREADY_INIT:-0}" -gt 0 ]; then echo " Wallet already initialized, skipping." else - $DOCKER compose run --rm zallet --datadir /var/lib/zallet init-wallet-encryption + $COMPOSE run --rm zallet --datadir /var/lib/zallet --config /etc/zallet/zallet.toml init-wallet-encryption echo "==> Running generate-mnemonic..." - $DOCKER compose run --rm zallet --datadir /var/lib/zallet generate-mnemonic + $COMPOSE run --rm zallet --datadir /var/lib/zallet --config /etc/zallet/zallet.toml generate-mnemonic fi echo "==> Stopping Zebra (will be restarted by docker compose up -d)..." -$DOCKER compose down +$COMPOSE down echo "" -echo "✅ Wallet initialized. Now run:" -echo " sudo -E docker compose up -d" +echo "Wallet initialized. Now run:" +echo " docker compose --env-file .env.regtest up -d" echo " # Router will be available at http://127.0.0.1:8181" +echo " # Zaino gRPC will be available at 127.0.0.1:8137" diff --git a/zaino b/zaino index 49e5241..93a9495 160000 --- a/zaino +++ b/zaino @@ -1 +1 @@ -Subproject commit 49e5241def007353033900eaefc84de5f6c13fcc +Subproject commit 93a9495336e7ee6f28ab1b02d1959a23b459f035 diff --git a/zallet b/zallet index 757876b..cb9a477 160000 --- a/zallet +++ b/zallet @@ -1 +1 @@ -Subproject commit 757876b0f9a5ca683fbee459dfc7969657096271 +Subproject commit cb9a4778a483dcd06cc739afb6bb85a081fdf687 diff --git a/zebra b/zebra index e04845f..60a7124 160000 --- a/zebra +++ b/zebra @@ -1 +1 @@ -Subproject commit e04845f3e4a0976031f35c6564aa56ea4d722371 +Subproject commit 60a7124ad1f07a6475d73f76eee423c554453189 From fc113936a5353996bd19f3aee2970f1d5b995159 Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Tue, 7 Apr 2026 11:58:42 +0100 Subject: [PATCH 42/57] fix(docker): bump submodules and images, fix zaino start command (#29) - Bump Zebra to v4.3.0 - Bump Zaino to zingolabs/zaino@dev (0.2.0, Zebra 4.3.0 compatible) - Bump Zallet to zcash/wallet@main - Fix zaino command: add missing `start` subcommand (fixes #28) - Rebuild zaino CI image (sha-83e41d7) Closes #28 --- .env.example | 4 ++-- README.md | 4 ++-- docker-compose.yml | 9 ++++++--- zaino | 2 +- zallet | 2 +- zebra | 2 +- 6 files changed, 13 insertions(+), 10 deletions(-) diff --git a/.env.example b/.env.example index e4ff8f9..108c8de 100644 --- a/.env.example +++ b/.env.example @@ -31,8 +31,8 @@ # ============================================================================= # Image Overrides (defaults are pinned versions in docker-compose.yml) # ============================================================================= -# ZEBRA_IMAGE=zfnd/zebra:4.2.0 -# ZAINO_IMAGE=ghcr.io/zcashfoundation/zaino:sha-0164cab +# ZEBRA_IMAGE=zfnd/zebra:4.3.0 +# ZAINO_IMAGE=ghcr.io/zcashfoundation/zaino:sha-83e41d7 # ZALLET_IMAGE=electriccoinco/zallet:v0.1.0-alpha.3 # ZEBRA_BUILD_FEATURES=default-release-binaries diff --git a/README.md b/README.md index 42f626a..c5c3c97 100644 --- a/README.md +++ b/README.md @@ -126,8 +126,8 @@ All images can be overridden via environment variables (`ZEBRA_IMAGE`, `ZAINO_IM | Service | Default Image | Source | |---------|---------------|--------| -| Zebra | `zfnd/zebra:4.2.0` | [ZcashFoundation/zebra](https://github.com/ZcashFoundation/zebra) | -| Zaino | `ghcr.io/zcashfoundation/zaino:sha-0164cab` | [zingolabs/zaino](https://github.com/zingolabs/zaino) | +| Zebra | `zfnd/zebra:4.3.0` | [ZcashFoundation/zebra](https://github.com/ZcashFoundation/zebra) | +| Zaino | `ghcr.io/zcashfoundation/zaino:sha-83e41d7` | [zingolabs/zaino](https://github.com/zingolabs/zaino) | | Zallet | `electriccoinco/zallet:v0.1.0-alpha.3` | [zcash/wallet](https://github.com/zcash/wallet) | ## Prerequisites diff --git a/docker-compose.yml b/docker-compose.yml index 1dddd60..d5f4842 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,7 +14,7 @@ x-common: &common services: zebra: - image: ${ZEBRA_IMAGE:-zfnd/zebra:4.2.0} + image: ${ZEBRA_IMAGE:-zfnd/zebra:4.3.0} platform: ${DOCKER_PLATFORM:-linux/amd64} build: context: ./zebra @@ -64,7 +64,10 @@ services: start_interval: 5s zaino: - image: ${ZAINO_IMAGE:-ghcr.io/zcashfoundation/zaino:sha-0164cab} + # Image built by z3 CI from zingolabs/zaino@dev branch. + # The sha-* tag is a z3 commit hash, not a zaino one. + # Upstream: https://github.com/zingolabs/zaino (zainod 0.2.0 / Zebra 4.3.0 compatible) + image: ${ZAINO_IMAGE:-ghcr.io/zcashfoundation/zaino:sha-83e41d7} platform: ${DOCKER_PLATFORM:-linux/amd64} build: context: ./zaino @@ -76,7 +79,7 @@ services: # Zaino's entrypoint runs mkdir/chown before dropping privileges via setpriv. # These operations need capabilities that cap_drop: [ALL] removes, so we add them back. cap_add: [CHOWN, DAC_OVERRIDE, FOWNER, SETUID, SETGID] - command: ["--config", "/etc/zaino/zindexer.toml"] + command: ["start", "--config", "/etc/zaino/zindexer.toml"] depends_on: zebra: condition: service_healthy diff --git a/zaino b/zaino index 93a9495..ba87fb0 160000 --- a/zaino +++ b/zaino @@ -1 +1 @@ -Subproject commit 93a9495336e7ee6f28ab1b02d1959a23b459f035 +Subproject commit ba87fb02250593ab27afbeb35866c4baa64e7d80 diff --git a/zallet b/zallet index cb9a477..b7b080f 160000 --- a/zallet +++ b/zallet @@ -1 +1 @@ -Subproject commit cb9a4778a483dcd06cc739afb6bb85a081fdf687 +Subproject commit b7b080fe95f9dc72a5faac07773dae03fa1c1f15 diff --git a/zebra b/zebra index 60a7124..92a4e55 160000 --- a/zebra +++ b/zebra @@ -1 +1 @@ -Subproject commit 60a7124ad1f07a6475d73f76eee423c554453189 +Subproject commit 92a4e55f9e702468dbb0d8ec57df3e0c3fdd4ea9 From 141ec4f41f937500950f58c6abcc94ddf6953616 Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Tue, 7 Apr 2026 17:07:27 +0100 Subject: [PATCH 43/57] ci: add PR validation, smoke tests, and workflow security hardening (#30) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ci: add PR validation, smoke tests, and workflow security hardening - Add CI workflow: compose config validation + service startup smoke tests on every PR - Add integration test workflow: regtest stack end-to-end validation on dev push - Add zizmor workflow: GitHub Actions security scanning (SARIF → Security tab) - Add Dependabot for GitHub Actions (monthly, with cooldown) - Pin all actions to commit SHAs and update to latest versions - Harden all workflows: persist-credentials: false, permissions: {}t The smoke tests would have directly caught #28 (missing zaino start subcommand) --- .github/dependabot.yml | 11 ++ .github/workflows/ci.yaml | 101 ++++++++++++++++++ .github/workflows/sub-build-docker-image.yaml | 14 +-- .github/workflows/zizmor.yaml | 33 ++++++ README.md | 2 +- 5 files changed, 153 insertions(+), 8 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yaml create mode 100644 .github/workflows/zizmor.yaml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..40975a8 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: monthly + groups: + github-actions: + patterns: ["*"] + cooldown: + default-days: 7 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..3614865 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,101 @@ +name: CI + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +on: + pull_request: + push: + branches: [dev] + +permissions: {} + +jobs: + smoke-test: + name: Service smoke tests + runs-on: ubuntu-latest + env: + COMPOSE_PROJECT_NAME: z3-smoke + COMPOSE_FILE: docker-compose.yml:docker-compose.regtest.yml + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Generate TLS certs and regtest configs + run: | + mkdir -p config/tls config/regtest + openssl req -x509 -newkey rsa:2048 \ + -keyout config/tls/zaino.key -out config/tls/zaino.crt \ + -sha256 -days 1 -nodes -subj "/CN=localhost" 2>/dev/null + chmod 644 config/tls/zaino.key config/tls/zaino.crt + echo "# dummy identity for CI" > config/regtest/zallet_identity.txt + SALT=$(openssl rand -hex 16) + HASH=$(printf 'zebra' | openssl dgst -sha256 -mac HMAC -macopt "key:$SALT" | awk '{print $NF}') + sed -i "s|__GENERATED_BY_INIT_SH__|${SALT}\$${HASH}|" config/regtest/zallet.toml + + - name: Validate compose configs + run: | + docker compose config --quiet + docker compose --env-file .env.regtest config --quiet + + - name: Start Zebra (regtest) + run: | + docker compose --env-file .env.regtest up -d zebra + echo "Waiting for Zebra RPC..." + for i in $(seq 1 60); do + if curl -sf -X POST -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"getblockchaininfo","params":[],"id":1}' \ + http://127.0.0.1:18232 > /dev/null 2>&1; then + echo "Zebra is ready" + break + fi + if [ "$i" -eq 60 ]; then echo "Zebra failed to start" && exit 1; fi + sleep 2 + done + + - name: Verify Zebra is serving RPC + run: | + curl -sf -X POST -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"getblockchaininfo","params":[],"id":1}' \ + http://127.0.0.1:18232 | jq . + + - name: Mine block 1 (required for Zaino sync) + run: | + curl -sf -u zebra:zebra -X POST -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"generate","params":[1],"id":1}' \ + http://127.0.0.1:18232 | jq . + + - name: Start Zaino and verify JSON-RPC proxy + run: | + docker compose --env-file .env.regtest up -d zaino + echo "Waiting for Zaino JSON-RPC..." + for i in $(seq 1 30); do + if RESULT=$(curl -sf -X POST -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"getblockchaininfo","params":[],"id":1}' \ + http://127.0.0.1:8237 2>/dev/null); then + echo "Zaino is ready" + echo "$RESULT" | jq . + break + fi + if [ "$i" -eq 30 ]; then + echo "Zaino failed to start" + docker compose --env-file .env.regtest logs zaino --tail 20 + exit 1 + fi + sleep 2 + done + + - name: Verify Zallet accepts compose command + run: docker compose --env-file .env.regtest run --rm --no-deps zallet --help + + - name: Collect logs on failure + if: failure() + run: | + echo "=== Zebra logs ===" + docker compose --env-file .env.regtest logs zebra --tail 50 2>&1 || true + echo "=== Zaino logs ===" + docker compose --env-file .env.regtest logs zaino --tail 50 2>&1 || true + echo "=== Container status ===" + docker compose --env-file .env.regtest ps 2>&1 || true diff --git a/.github/workflows/sub-build-docker-image.yaml b/.github/workflows/sub-build-docker-image.yaml index cb4483f..dacb503 100644 --- a/.github/workflows/sub-build-docker-image.yaml +++ b/.github/workflows/sub-build-docker-image.yaml @@ -74,27 +74,27 @@ jobs: steps: - name: Checkout ${{ inputs.repository }} - uses: actions/checkout@v4.2.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: ${{ inputs.repository }} ref: ${{ inputs.ref }} persist-credentials: false - name: Checkout Z3 - uses: actions/checkout@v4.1.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: path: z3 persist-credentials: false - name: Inject slug/short variables - uses: rlespinasse/github-slug-action@v5.1.0 + uses: rlespinasse/github-slug-action@9e7def61550737ba68c62d34a32dd31792e3f429 # v5.5.0 with: short-length: 7 # Automatic tag management and OCI Image Format Specification for labels - name: Docker meta id: meta - uses: docker/metadata-action@v5.7.0 + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 with: # list of Docker images to use as base name for tags # We only publish images to DockerHub if a release is not a pre-release @@ -116,7 +116,7 @@ jobs: type=sha,event=branch - name: Login to GitHub Container Registry - uses: docker/login-action@v3.4.0 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ghcr.io username: ${{ github.actor }} @@ -125,12 +125,12 @@ jobs: # Setup Docker Buildx to use Docker Build Cloud - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@v3.10.0 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 # Build and push image to GitHub Container Registry - name: Build & push id: docker_build - uses: docker/build-push-action@v6.15.0 + uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 with: target: ${{ inputs.dockerfile_target }} context: . diff --git a/.github/workflows/zizmor.yaml b/.github/workflows/zizmor.yaml new file mode 100644 index 0000000..581dfb7 --- /dev/null +++ b/.github/workflows/zizmor.yaml @@ -0,0 +1,33 @@ +name: GitHub Actions security analysis + +on: + push: + branches: [dev] + paths: + - '.github/workflows/**' + pull_request: + paths: + - '.github/workflows/**' + +permissions: {} + +jobs: + zizmor: + name: zizmor + runs-on: ubuntu-latest + permissions: + security-events: write + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + - name: Run zizmor + run: uvx zizmor --format sarif . > results.sarif + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Upload SARIF + uses: github/codeql-action/upload-sarif@5c8a8a642e79153f5d047b10ec1cba1d1cc65699 # v3.35.1 + with: + sarif_file: results.sarif + category: zizmor diff --git a/README.md b/README.md index c5c3c97..aa4f389 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Update `config/zallet.toml` to set `network = "test"` in the `[consensus]` secti ### Running Regtest -Regtest uses a compose overlay (`docker-compose.regtest.yml`) that adds the rpc-router service, disables TLS, and adjusts healthchecks for a peerless network. Volumes are automatically isolated via `COMPOSE_PROJECT_NAME=z3-regtest`. +Regtest uses a compose overlay (`docker-compose.regtest.yml`) that adds the rpc-router service, switches from cookie auth to username/password auth, and adjusts healthchecks for a peerless network. Volumes are automatically isolated via `COMPOSE_PROJECT_NAME=z3-regtest`. First-time setup (**required** before starting the stack): From 95d5968f5b26627aa1b782455d4ad0824412d33f Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Tue, 7 Apr 2026 17:21:44 +0100 Subject: [PATCH 44/57] fix: gitignore mutable zallet configs to prevent git pull conflicts Users following the README modify config/zallet.toml (network setting) and regtest-init.sh modifies config/regtest/zallet.toml (pwhash). These tracked files caused conflicts on git pull. - Ship .default templates (tracked) alongside gitignored live copies - regtest-init.sh and CI seed from .default automatically - zaino configs stay tracked (never modified by users/scripts) --- .github/workflows/ci.yaml | 4 +++- .gitignore | 8 ++++---- README.md | 5 +++-- config/regtest/{zallet.toml => zallet.toml.default} | 0 config/{zallet.toml => zallet.toml.default} | 0 scripts/regtest-init.sh | 10 ++++++++++ 6 files changed, 20 insertions(+), 7 deletions(-) rename config/regtest/{zallet.toml => zallet.toml.default} (100%) rename config/{zallet.toml => zallet.toml.default} (100%) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3614865..dc0cfea 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -25,7 +25,9 @@ jobs: - name: Generate TLS certs and regtest configs run: | - mkdir -p config/tls config/regtest + cp -n config/zallet.toml.default config/zallet.toml + cp -n config/regtest/zallet.toml.default config/regtest/zallet.toml + mkdir -p config/tls openssl req -x509 -newkey rsa:2048 \ -keyout config/tls/zaino.key -out config/tls/zaino.crt \ -sha256 -days 1 -nodes -subj "/CN=localhost" 2>/dev/null diff --git a/.gitignore b/.gitignore index 41cb6b1..1abc5e0 100644 --- a/.gitignore +++ b/.gitignore @@ -20,20 +20,20 @@ target/ #.idea/ # ============================================================================= -# Config directory — ignore generated secrets, track templates +# Config directory — track .default templates, ignore live copies and secrets # ============================================================================= config/** !config/.gitkeep -!config/zallet.toml +!config/zallet.toml.default !config/zaino.toml # TLS directory structure (certs are generated locally) !config/tls/ config/tls/* !config/tls/.gitkeep -# Regtest configs (tracked templates; identity file is generated by scripts/regtest-init.sh) +# Regtest configs !config/regtest/ !config/regtest/zaino.toml -!config/regtest/zallet.toml +!config/regtest/zallet.toml.default # Runtime environment overrides (see .env.example for available variables) .env diff --git a/README.md b/README.md index aa4f389..1c55229 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,8 @@ Three services replacing the legacy `zcashd`: **Zebra** (full node), **Zaino** ( ```bash git clone https://github.com/ZcashFoundation/z3 && cd z3 -# Generate required credentials +# Seed config files and generate required credentials +cp -n config/zallet.toml.default config/zallet.toml openssl req -x509 -newkey rsa:4096 -keyout config/tls/zaino.key -out config/tls/zaino.crt \ -sha256 -days 365 -nodes -subj "/CN=localhost" \ -addext "subjectAltName=DNS:localhost,DNS:zaino,IP:127.0.0.1" @@ -27,7 +28,7 @@ docker compose up -d Pre-built images for all 3 services are pulled automatically. No build step or submodule init needed. > [!WARNING] -> The TLS certificate and identity file must exist before running any `docker compose` command. If `config/tls/zaino.crt` or `config/tls/zaino.key` are missing, Compose will fail with a file-not-found error. +> The TLS certificate, identity file, and zallet config must exist before running any `docker compose` command. Run the setup steps above first — if any file is missing, Compose will fail. > [!IMPORTANT] > Zebra must sync the blockchain before Zaino and Zallet can start. Running `docker compose up -d` on a fresh install without syncing Zebra first will cause the other services to fail repeatedly. Start Zebra alone, wait for sync, then start the rest. diff --git a/config/regtest/zallet.toml b/config/regtest/zallet.toml.default similarity index 100% rename from config/regtest/zallet.toml rename to config/regtest/zallet.toml.default diff --git a/config/zallet.toml b/config/zallet.toml.default similarity index 100% rename from config/zallet.toml rename to config/zallet.toml.default diff --git a/scripts/regtest-init.sh b/scripts/regtest-init.sh index 4b30c41..f2f49a8 100755 --- a/scripts/regtest-init.sh +++ b/scripts/regtest-init.sh @@ -80,6 +80,15 @@ ensure_openssl() { exit 1 } +seed_configs() { + for name in config/zallet.toml config/regtest/zallet.toml; do + if [ ! -f "$REPO_ROOT/$name" ] && [ -f "$REPO_ROOT/$name.default" ]; then + cp "$REPO_ROOT/$name.default" "$REPO_ROOT/$name" + log "==> Seeded $name from $name.default" + fi + done +} + update_zallet_rpc_pwhash() { local config_path="$REPO_ROOT/config/regtest/zallet.toml" local rpc_password="${RPC_PASSWORD:-zebra}" @@ -133,6 +142,7 @@ fi cd "$REPO_ROOT" +seed_configs ensure_local_identity ensure_tls_certs update_zallet_rpc_pwhash From 7e8a261eea592a97418d299ee10845d194bfed29 Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Thu, 23 Apr 2026 14:48:22 +0100 Subject: [PATCH 45/57] feat: add optional zcashd compose profile --- .env.example | 26 ++++++++++++++++++++++- .gitignore | 3 +++ README.md | 28 +++++++++++++++++++++---- docker-compose.regtest.yml | 25 ++++++++++++++++++++++ docker-compose.yml | 41 +++++++++++++++++++++++++++++++++++++ docs/docker-architecture.md | 7 ++++--- docs/regtest.md | 22 ++++++++++++++++++++ 7 files changed, 144 insertions(+), 8 deletions(-) diff --git a/.env.example b/.env.example index 108c8de..1bc1437 100644 --- a/.env.example +++ b/.env.example @@ -8,6 +8,7 @@ # Mainnet (default): docker compose up -d # Testnet: create .env with NETWORK_NAME=Testnet # Regtest: docker compose --env-file .env.regtest up -d +# zcashd comparator: add --profile zcashd to include optional zcashd # # Variable Mainnet Testnet Regtest # ───────────────────────────────────────────────────────────────── @@ -24,9 +25,11 @@ # ZEBRA_HEALTH__MIN_CONNECTED_PEERS=1 # ============================================================================= -# Platform (ARM64 users: uncomment for native builds) +# Platform (ARM64 users: uncomment for native Z3 service images) # ============================================================================= # DOCKER_PLATFORM=linux/arm64 +# The official zcashd image is linux/amd64; only change this for a custom arm64 image. +# ZCASHD_DOCKER_PLATFORM=linux/amd64 # ============================================================================= # Image Overrides (defaults are pinned versions in docker-compose.yml) @@ -34,6 +37,7 @@ # ZEBRA_IMAGE=zfnd/zebra:4.3.0 # ZAINO_IMAGE=ghcr.io/zcashfoundation/zaino:sha-83e41d7 # ZALLET_IMAGE=electriccoinco/zallet:v0.1.0-alpha.3 +# ZCASHD_IMAGE=electriccoinco/zcashd:latest # ZEBRA_BUILD_FEATURES=default-release-binaries # ============================================================================= @@ -42,6 +46,8 @@ # Z3_ZEBRA_DATA_PATH=zebra_data # Z3_ZAINO_DATA_PATH=zaino_data # Z3_ZALLET_DATA_PATH=zallet_data +# Z3_ZCASHD_DATA_PATH=zcashd_data +# Z3_ZCASHD_PARAMS_PATH=zcashd_params # Z3_COOKIE_PATH=shared_cookie_volume # ============================================================================= @@ -52,6 +58,8 @@ # ZAINO_HOST_GRPC_PORT=8137 # ZAINO_HOST_JSONRPC_PORT=8237 # ZALLET_HOST_RPC_PORT=28232 +# ZCASHD_HOST_RPC_BIND=127.0.0.1 +# ZCASHD_HOST_RPC_PORT=38232 # ============================================================================= # Internal Container Ports (change only for advanced multi-instance setups) @@ -61,6 +69,7 @@ # ZAINO_GRPC_PORT=8137 # ZAINO_JSON_RPC_PORT=8237 # ZALLET_RPC_PORT=28232 +# ZCASHD_RPC_PORT=38232 # ============================================================================= # Log Levels @@ -71,6 +80,21 @@ # ZAINO_RUST_BACKTRACE=full # ZALLET_RUST_LOG=info,hyper_util=warn,reqwest=warn +# ============================================================================= +# Optional zcashd Comparator (enable with: docker compose --profile zcashd up -d zcashd) +# ============================================================================= +# ZCASHD_RPCUSER=zebra +# ZCASHD_RPCPASSWORD=zebra +# ZCASHD_RPC_ALLOWIP=0.0.0.0/0 +# Regtest activation overrides for custom comparator runs. +# Defaults keep zcashd aligned with the standard Z3 regtest stack. +# ZCASHD_OVERWINTER_ACTIVATION_HEIGHT=1 +# ZCASHD_SAPLING_ACTIVATION_HEIGHT=1 +# ZCASHD_BLOSSOM_ACTIVATION_HEIGHT=1 +# ZCASHD_HEARTWOOD_ACTIVATION_HEIGHT=1 +# ZCASHD_CANOPY_ACTIVATION_HEIGHT=1 +# ZCASHD_NU5_ACTIVATION_HEIGHT=1 + # ============================================================================= # Monitoring (enable with: docker compose --profile monitoring up -d) # ============================================================================= diff --git a/.gitignore b/.gitignore index 41cb6b1..f8b1c27 100644 --- a/.gitignore +++ b/.gitignore @@ -38,5 +38,8 @@ config/tls/* # Runtime environment overrides (see .env.example for available variables) .env +# Local comparator state +.tmp/ + # Ignore docker compose overrides docker-compose.override.yml diff --git a/README.md b/README.md index aa4f389..5b5efe1 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ docker compose ps # verify all healthy ``` > [!TIP] -> **Apple Silicon users:** Create `.env` with `DOCKER_PLATFORM=linux/arm64` for native builds. +> **Apple Silicon users:** Set `DOCKER_PLATFORM=linux/arm64` for native Z3 services. The optional zcashd profile has a separate platform setting. ## Deployment Modes @@ -80,6 +80,20 @@ The init script generates the Zallet RPC password hash, starts Zebra, mines the See [docs/regtest.md](docs/regtest.md) for test commands (curl, grpcurl) and the full workflow reference. +### Optional zcashd Comparator + +`zcashd` is available behind an opt-in Compose profile for local comparison tests. It is not part of the default Z3 stack, uses separate data and params volumes, exposes RPC on `http://localhost:38232`, and starts with public P2P disabled (`-listen=0 -connect=0`). + +```bash +# Mainnet/testnet-style comparator +docker compose --profile zcashd up -d zcashd + +# Regtest comparator with the regtest overlay +docker compose --env-file .env.regtest --profile zcashd up -d zcashd +``` + +The default image is the official `electriccoinco/zcashd:latest` image. Default RPC credentials are `zebra` / `zebra`; override them with `ZCASHD_RPCUSER` and `ZCASHD_RPCPASSWORD`. For custom regtest runs, zcashd activation heights can be overridden with `ZCASHD_*_ACTIVATION_HEIGHT` variables; use a separate `Z3_ZCASHD_DATA_PATH` when changing them. + ### Monitoring Stack Prometheus, Grafana, Jaeger, and AlertManager are available behind a Docker Compose profile. Zebra metrics must be explicitly enabled for Prometheus to have data to scrape. @@ -122,13 +136,14 @@ graph LR **Zebra** syncs and validates the Zcash blockchain. **Zaino** provides a lightwalletd-compatible gRPC interface for light wallet clients like Zingo. **Zallet** embeds Zaino's indexer libraries internally and connects directly to Zebra's JSON-RPC; it does not use the standalone Zaino service. -All images can be overridden via environment variables (`ZEBRA_IMAGE`, `ZAINO_IMAGE`, `ZALLET_IMAGE`). See `.env.example` for all available options. +All images can be overridden via environment variables (`ZEBRA_IMAGE`, `ZAINO_IMAGE`, `ZALLET_IMAGE`, `ZCASHD_IMAGE`). See `.env.example` for all available options. | Service | Default Image | Source | |---------|---------------|--------| | Zebra | `zfnd/zebra:4.3.0` | [ZcashFoundation/zebra](https://github.com/ZcashFoundation/zebra) | | Zaino | `ghcr.io/zcashfoundation/zaino:sha-83e41d7` | [zingolabs/zaino](https://github.com/zingolabs/zaino) | | Zallet | `electriccoinco/zallet:v0.1.0-alpha.3` | [zcash/wallet](https://github.com/zcash/wallet) | +| zcashd | `electriccoinco/zcashd:latest` | [zcash/zcash](https://github.com/zcash/zcash) | ## Prerequisites @@ -150,6 +165,7 @@ Once running, services are available at: | Zaino gRPC | `localhost:8137` | `ZAINO_HOST_GRPC_PORT` | | Zaino JSON-RPC | `http://localhost:8237` | `ZAINO_HOST_JSONRPC_PORT` | | Zallet RPC | `http://localhost:28232` | `ZALLET_HOST_RPC_PORT` | +| zcashd RPC | `http://localhost:38232` | `ZCASHD_HOST_RPC_PORT` | ## Stopping the Stack @@ -242,12 +258,14 @@ Critical requirements for `config/zallet.toml`: ### Platform Configuration (ARM64) -Z3 defaults to AMD64 for consistency. On Apple Silicon or ARM64 Linux, emulation makes builds ~15x slower. Enable native builds: +Z3 defaults to AMD64 for consistency. On Apple Silicon or ARM64 Linux, enable native Zebra, Zaino, and Zallet images: ```bash echo "DOCKER_PLATFORM=linux/arm64" >> .env ``` +The optional zcashd service has a separate `ZCASHD_DOCKER_PLATFORM` setting. Keep the default `linux/amd64` when using `electriccoinco/zcashd:latest`; Docker Desktop runs it through emulation on Apple Silicon. Only set `ZCASHD_DOCKER_PLATFORM=linux/arm64` when `ZCASHD_IMAGE` points to an arm64-capable image. +
@@ -307,6 +325,8 @@ The stack uses Docker-managed named volumes by default: | `zebra_data` | Blockchain state (~300 GB mainnet, ~30 GB testnet) | | `zaino_data` | Indexer database | | `zallet_data` | Wallet database | +| `zcashd_data` | Optional zcashd comparator chain state | +| `zcashd_params` | Optional zcashd comparator params cache | | `shared_cookie_volume` | RPC authentication cookies | ### Local Directories (Advanced) @@ -327,7 +347,7 @@ Fix permissions before starting: ./scripts/fix-permissions.sh zallet /mnt/ssd/zallet-data ``` -Each service runs as a specific non-root user. Directories must have correct ownership (set by the script) and 700 permissions. Never use 755 or 777. +Zebra, Zaino, and Zallet each run as a specific non-root user. Directories must have correct ownership (set by the script) and 700 permissions. Never use 755 or 777.
diff --git a/docker-compose.regtest.yml b/docker-compose.regtest.yml index 1b9f782..3e431fa 100644 --- a/docker-compose.regtest.yml +++ b/docker-compose.regtest.yml @@ -44,6 +44,31 @@ services: - ./config/regtest/zallet.toml:/etc/zallet/zallet.toml:ro - ./config/regtest/zallet_identity.txt:/etc/zallet/identity.txt:ro + zcashd: + # Optional comparator for local compatibility checks. Keep it isolated + # from P2P in regtest. Activation heights default to config/regtest/zallet.toml + # but can be overridden for custom regtest runs. + command: + - -regtest + - -i-am-aware-zcashd-will-be-replaced-by-zebrad-and-zallet-in-2025=1 + - -listen=0 + - -dnsseed=0 + - -dns=0 + - -connect=0 + - -server=1 + - -nuparams=5ba81b19:${ZCASHD_OVERWINTER_ACTIVATION_HEIGHT:-1} + - -nuparams=76b809bb:${ZCASHD_SAPLING_ACTIVATION_HEIGHT:-1} + - -nuparams=2bb40e60:${ZCASHD_BLOSSOM_ACTIVATION_HEIGHT:-1} + - -nuparams=f5b9230b:${ZCASHD_HEARTWOOD_ACTIVATION_HEIGHT:-1} + - -nuparams=e9ff75a6:${ZCASHD_CANOPY_ACTIVATION_HEIGHT:-1} + - -nuparams=c2d6d0b4:${ZCASHD_NU5_ACTIVATION_HEIGHT:-1} + healthcheck: + test: ["CMD-SHELL", "zcash-cli -regtest -rpcconnect=127.0.0.1 -rpcport=$${ZCASHD_RPCPORT} -rpcuser=$${ZCASHD_RPCUSER} -rpcpassword=$${ZCASHD_RPCPASSWORD} getblockchaininfo >/dev/null"] + interval: 10s + timeout: 5s + retries: 10 + start_period: 60s + rpc-router: build: context: ./rpc-router diff --git a/docker-compose.yml b/docker-compose.yml index d5f4842..0f2923f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -146,6 +146,45 @@ services: - z3_net # No healthcheck: distroless image has no shell/curl + zcashd: + # Optional zcashd node. It is intentionally isolated from the + # public P2P network unless the operator overrides the command. + profiles: [zcashd] + image: ${ZCASHD_IMAGE:-electriccoinco/zcashd:latest} + # The official zcashd image currently publishes linux/amd64 only. + platform: ${ZCASHD_DOCKER_PLATFORM:-linux/amd64} + container_name: z3_zcashd + restart: unless-stopped + stop_grace_period: 30s + <<: *common + user: "2001:2001" + command: + - -i-am-aware-zcashd-will-be-replaced-by-zebrad-and-zallet-in-2025=1 + - -listen=0 + - -dnsseed=0 + - -dns=0 + - -connect=0 + - -server=1 + environment: + ZCASHD_RPCUSER: ${ZCASHD_RPCUSER:-zebra} + ZCASHD_RPCPASSWORD: ${ZCASHD_RPCPASSWORD:-zebra} + ZCASHD_RPCBIND: 0.0.0.0 + ZCASHD_RPCPORT: ${ZCASHD_RPC_PORT:-38232} + ZCASHD_ALLOWIP: ${ZCASHD_RPC_ALLOWIP:-0.0.0.0/0} + volumes: + - ${Z3_ZCASHD_DATA_PATH:-zcashd_data}:/srv/zcashd/.zcash + - ${Z3_ZCASHD_PARAMS_PATH:-zcashd_params}:/srv/zcashd/.zcash-params + ports: + - "${ZCASHD_HOST_RPC_BIND:-127.0.0.1}:${ZCASHD_HOST_RPC_PORT:-38232}:${ZCASHD_RPC_PORT:-38232}" + networks: + - z3_net + healthcheck: + test: ["CMD-SHELL", "zcash-cli -rpcconnect=127.0.0.1 -rpcport=$${ZCASHD_RPCPORT} -rpcuser=$${ZCASHD_RPCUSER} -rpcpassword=$${ZCASHD_RPCPASSWORD} getblockchaininfo >/dev/null"] + interval: 10s + timeout: 5s + retries: 10 + start_period: 60s + # ============================================================================= # Monitoring Stack (enabled with --profile monitoring) # ============================================================================= @@ -262,6 +301,8 @@ services: volumes: zebra_data: + zcashd_data: + zcashd_params: zaino_data: zallet_data: shared_cookie_volume: diff --git a/docs/docker-architecture.md b/docs/docker-architecture.md index 38863f2..6e8986d 100644 --- a/docs/docker-architecture.md +++ b/docs/docker-architecture.md @@ -5,7 +5,7 @@ This document explains the architectural decisions, patterns, and modern Docker ## Overview ```text -docker-compose.yml Base stack (Zebra + Zaino + Zallet + monitoring) +docker-compose.yml Base stack (Zebra + Zaino + Zallet + optional profiles) docker-compose.regtest.yml Regtest overlay (structural differences only) .env.example Reference for all overridable variables .env User overrides (gitignored, optional) @@ -198,9 +198,10 @@ Without `max-size` and `max-file`, Docker's default `json-file` log driver grows All service images are overridable via environment variables: ```yaml -image: ${ZEBRA_IMAGE:-zfnd/zebra:4.2.0} -image: ${ZAINO_IMAGE:-ghcr.io/zcashfoundation/zaino:sha-0164cab} +image: ${ZEBRA_IMAGE:-zfnd/zebra:4.3.0} +image: ${ZAINO_IMAGE:-ghcr.io/zcashfoundation/zaino:sha-83e41d7} image: ${ZALLET_IMAGE:-electriccoinco/zallet:v0.1.0-alpha.3} +image: ${ZCASHD_IMAGE:-electriccoinco/zcashd:latest} ``` This allows operators to: diff --git a/docs/regtest.md b/docs/regtest.md index 848ef4d..bf907bb 100644 --- a/docs/regtest.md +++ b/docs/regtest.md @@ -52,6 +52,27 @@ Zebra, Zaino, and Zallet use pre-built images. The rpc-router builds from source | Zaino gRPC | https://localhost:8137 | lightwalletd-compatible gRPC (TLS) | | Zebra RPC | http://localhost:18232 | Direct Zebra JSON-RPC | | Zallet RPC | http://localhost:28232 | Direct Zallet JSON-RPC | +| zcashd RPC | http://localhost:38232 | Optional zcashd comparator (`--profile zcashd`) | + +## Optional zcashd comparator + +For local compatibility checks against zcashd, start the profiled zcashd service: + +```bash +docker compose --env-file .env.regtest --profile zcashd up -d zcashd +``` + +The regtest overlay starts zcashd with public P2P disabled (`-listen=0 -connect=0`) and, by default, the same NU activation heights used by Zallet. It uses separate Docker volumes (`zcashd_data`, `zcashd_params`) and default RPC credentials `zebra` / `zebra`. See the [README platform section](../README.md#platform-configuration-arm64) for arm64 notes. + +For comparator runs that need a specific upgrade era, override the zcashd activation heights and use a separate data volume: + +```bash +Z3_ZCASHD_DATA_PATH=./.tmp/zcashd-canopy-data \ +ZCASHD_NU5_ACTIVATION_HEIGHT=100 \ +docker compose --env-file .env.regtest --profile zcashd up -d --force-recreate zcashd +``` + +This keeps the default Z3 regtest state separate from comparator state and allows V4/Canopy fixtures before NU5 activation. ## Test routing @@ -159,4 +180,5 @@ The port (9999) must match the Prometheus scrape target configured in `observabi - Zallet uses regtest nuparams activating all upgrades at block 1 - Zaino uses username/password auth in regtest (not cookie auth) - Zaino gRPC uses TLS with the same self-signed certificate as mainnet/testnet +- zcashd is optional and only starts when the `zcashd` profile is enabled - The rpc-router source is in `rpc-router/`; it is built automatically on first `docker compose up` From b7bdc034c3418f31d18acc3b68d0212757b922d1 Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Thu, 23 Apr 2026 14:51:56 +0100 Subject: [PATCH 46/57] chore: update zebra image default to 4.3.1 --- .env.example | 2 +- README.md | 2 +- docker-compose.yml | 2 +- docs/docker-architecture.md | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.env.example b/.env.example index 1bc1437..b76e81b 100644 --- a/.env.example +++ b/.env.example @@ -34,7 +34,7 @@ # ============================================================================= # Image Overrides (defaults are pinned versions in docker-compose.yml) # ============================================================================= -# ZEBRA_IMAGE=zfnd/zebra:4.3.0 +# ZEBRA_IMAGE=zfnd/zebra:4.3.1 # ZAINO_IMAGE=ghcr.io/zcashfoundation/zaino:sha-83e41d7 # ZALLET_IMAGE=electriccoinco/zallet:v0.1.0-alpha.3 # ZCASHD_IMAGE=electriccoinco/zcashd:latest diff --git a/README.md b/README.md index 5b5efe1..b909b85 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ All images can be overridden via environment variables (`ZEBRA_IMAGE`, `ZAINO_IM | Service | Default Image | Source | |---------|---------------|--------| -| Zebra | `zfnd/zebra:4.3.0` | [ZcashFoundation/zebra](https://github.com/ZcashFoundation/zebra) | +| Zebra | `zfnd/zebra:4.3.1` | [ZcashFoundation/zebra](https://github.com/ZcashFoundation/zebra) | | Zaino | `ghcr.io/zcashfoundation/zaino:sha-83e41d7` | [zingolabs/zaino](https://github.com/zingolabs/zaino) | | Zallet | `electriccoinco/zallet:v0.1.0-alpha.3` | [zcash/wallet](https://github.com/zcash/wallet) | | zcashd | `electriccoinco/zcashd:latest` | [zcash/zcash](https://github.com/zcash/zcash) | diff --git a/docker-compose.yml b/docker-compose.yml index 0f2923f..5db43c3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,7 +14,7 @@ x-common: &common services: zebra: - image: ${ZEBRA_IMAGE:-zfnd/zebra:4.3.0} + image: ${ZEBRA_IMAGE:-zfnd/zebra:4.3.1} platform: ${DOCKER_PLATFORM:-linux/amd64} build: context: ./zebra diff --git a/docs/docker-architecture.md b/docs/docker-architecture.md index 6e8986d..b8a5b31 100644 --- a/docs/docker-architecture.md +++ b/docs/docker-architecture.md @@ -23,7 +23,7 @@ The core principle: **`docker-compose.yml` is self-sufficient**. Every variable Every variable reference in `docker-compose.yml` includes a default value: ```yaml -image: ${ZEBRA_IMAGE:-zfnd/zebra:4.2.0} +image: ${ZEBRA_IMAGE:-zfnd/zebra:4.3.1} environment: ZEBRA_NETWORK__NETWORK: ${NETWORK_NAME:-Mainnet} volumes: @@ -198,7 +198,7 @@ Without `max-size` and `max-file`, Docker's default `json-file` log driver grows All service images are overridable via environment variables: ```yaml -image: ${ZEBRA_IMAGE:-zfnd/zebra:4.3.0} +image: ${ZEBRA_IMAGE:-zfnd/zebra:4.3.1} image: ${ZAINO_IMAGE:-ghcr.io/zcashfoundation/zaino:sha-83e41d7} image: ${ZALLET_IMAGE:-electriccoinco/zallet:v0.1.0-alpha.3} image: ${ZCASHD_IMAGE:-electriccoinco/zcashd:latest} From b0f4f4365c91d255c1e3109849dbf924a3b41e8f Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Sat, 25 Apr 2026 08:46:19 +0100 Subject: [PATCH 47/57] fix: align zcashd profile with v6.12.1 --- .env.example | 5 ++--- .github/workflows/ci.yaml | 28 ++++++++++++++++++++++++++-- README.md | 13 +++++++------ docker-compose.regtest.yml | 10 +++++++++- docker-compose.yml | 20 +++++++++++++------- docs/docker-architecture.md | 2 +- docs/regtest.md | 2 +- scripts/fix-permissions.sh | 15 ++++++++++++++- 8 files changed, 73 insertions(+), 22 deletions(-) diff --git a/.env.example b/.env.example index b76e81b..2c2b9db 100644 --- a/.env.example +++ b/.env.example @@ -28,7 +28,7 @@ # Platform (ARM64 users: uncomment for native Z3 service images) # ============================================================================= # DOCKER_PLATFORM=linux/arm64 -# The official zcashd image is linux/amd64; only change this for a custom arm64 image. +# The default zcashd image is linux/amd64; only change this for a custom arm64 image. # ZCASHD_DOCKER_PLATFORM=linux/amd64 # ============================================================================= @@ -37,7 +37,7 @@ # ZEBRA_IMAGE=zfnd/zebra:4.3.1 # ZAINO_IMAGE=ghcr.io/zcashfoundation/zaino:sha-83e41d7 # ZALLET_IMAGE=electriccoinco/zallet:v0.1.0-alpha.3 -# ZCASHD_IMAGE=electriccoinco/zcashd:latest +# ZCASHD_IMAGE=zodlinc/zcashd:v6.12.1 # ZEBRA_BUILD_FEATURES=default-release-binaries # ============================================================================= @@ -47,7 +47,6 @@ # Z3_ZAINO_DATA_PATH=zaino_data # Z3_ZALLET_DATA_PATH=zallet_data # Z3_ZCASHD_DATA_PATH=zcashd_data -# Z3_ZCASHD_PARAMS_PATH=zcashd_params # Z3_COOKIE_PATH=shared_cookie_volume # ============================================================================= diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3614865..c73bbc2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -28,7 +28,8 @@ jobs: mkdir -p config/tls config/regtest openssl req -x509 -newkey rsa:2048 \ -keyout config/tls/zaino.key -out config/tls/zaino.crt \ - -sha256 -days 1 -nodes -subj "/CN=localhost" 2>/dev/null + -sha256 -days 1 -nodes -subj "/CN=localhost" \ + -addext "subjectAltName=DNS:localhost,DNS:zaino,IP:127.0.0.1" 2>/dev/null chmod 644 config/tls/zaino.key config/tls/zaino.crt echo "# dummy identity for CI" > config/regtest/zallet_identity.txt SALT=$(openssl rand -hex 16) @@ -39,6 +40,7 @@ jobs: run: | docker compose config --quiet docker compose --env-file .env.regtest config --quiet + docker compose --env-file .env.regtest --profile zcashd config --quiet - name: Start Zebra (regtest) run: | @@ -87,6 +89,26 @@ jobs: sleep 2 done + - name: Start zcashd comparator and verify RPC + run: | + docker compose --env-file .env.regtest --profile zcashd up -d zcashd + echo "Waiting for zcashd RPC..." + for i in $(seq 1 30); do + if RESULT=$(curl -sf -u zebra:zebra -X POST -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"getblockchaininfo","params":[],"id":1}' \ + http://127.0.0.1:38232 2>/dev/null); then + echo "zcashd is ready" + echo "$RESULT" | jq . + break + fi + if [ "$i" -eq 30 ]; then + echo "zcashd failed to start" + docker compose --env-file .env.regtest --profile zcashd logs zcashd --tail 50 + exit 1 + fi + sleep 2 + done + - name: Verify Zallet accepts compose command run: docker compose --env-file .env.regtest run --rm --no-deps zallet --help @@ -97,5 +119,7 @@ jobs: docker compose --env-file .env.regtest logs zebra --tail 50 2>&1 || true echo "=== Zaino logs ===" docker compose --env-file .env.regtest logs zaino --tail 50 2>&1 || true + echo "=== zcashd logs ===" + docker compose --env-file .env.regtest --profile zcashd logs zcashd --tail 50 2>&1 || true echo "=== Container status ===" - docker compose --env-file .env.regtest ps 2>&1 || true + docker compose --env-file .env.regtest --profile zcashd ps 2>&1 || true diff --git a/README.md b/README.md index b909b85..5b86533 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ See [docs/regtest.md](docs/regtest.md) for test commands (curl, grpcurl) and the ### Optional zcashd Comparator -`zcashd` is available behind an opt-in Compose profile for local comparison tests. It is not part of the default Z3 stack, uses separate data and params volumes, exposes RPC on `http://localhost:38232`, and starts with public P2P disabled (`-listen=0 -connect=0`). +`zcashd` is available behind an opt-in Compose profile for local comparison tests. It is not part of the default Z3 stack, uses a separate data volume, exposes RPC on `http://localhost:38232`, and starts with public P2P disabled (`-listen=0 -connect=0`). ```bash # Mainnet/testnet-style comparator @@ -92,7 +92,7 @@ docker compose --profile zcashd up -d zcashd docker compose --env-file .env.regtest --profile zcashd up -d zcashd ``` -The default image is the official `electriccoinco/zcashd:latest` image. Default RPC credentials are `zebra` / `zebra`; override them with `ZCASHD_RPCUSER` and `ZCASHD_RPCPASSWORD`. For custom regtest runs, zcashd activation heights can be overridden with `ZCASHD_*_ACTIVATION_HEIGHT` variables; use a separate `Z3_ZCASHD_DATA_PATH` when changing them. +The default image is `zodlinc/zcashd:v6.12.1`. Default RPC credentials are `zebra` / `zebra`; override them with `ZCASHD_RPCUSER` and `ZCASHD_RPCPASSWORD`. For custom regtest runs, zcashd activation heights can be overridden with `ZCASHD_*_ACTIVATION_HEIGHT` variables; use a separate `Z3_ZCASHD_DATA_PATH` when changing them. ### Monitoring Stack @@ -143,7 +143,7 @@ All images can be overridden via environment variables (`ZEBRA_IMAGE`, `ZAINO_IM | Zebra | `zfnd/zebra:4.3.1` | [ZcashFoundation/zebra](https://github.com/ZcashFoundation/zebra) | | Zaino | `ghcr.io/zcashfoundation/zaino:sha-83e41d7` | [zingolabs/zaino](https://github.com/zingolabs/zaino) | | Zallet | `electriccoinco/zallet:v0.1.0-alpha.3` | [zcash/wallet](https://github.com/zcash/wallet) | -| zcashd | `electriccoinco/zcashd:latest` | [zcash/zcash](https://github.com/zcash/zcash) | +| zcashd | `zodlinc/zcashd:v6.12.1` | [zcash/zcash](https://github.com/zcash/zcash) | ## Prerequisites @@ -264,7 +264,7 @@ Z3 defaults to AMD64 for consistency. On Apple Silicon or ARM64 Linux, enable na echo "DOCKER_PLATFORM=linux/arm64" >> .env ``` -The optional zcashd service has a separate `ZCASHD_DOCKER_PLATFORM` setting. Keep the default `linux/amd64` when using `electriccoinco/zcashd:latest`; Docker Desktop runs it through emulation on Apple Silicon. Only set `ZCASHD_DOCKER_PLATFORM=linux/arm64` when `ZCASHD_IMAGE` points to an arm64-capable image. +The optional zcashd service has a separate `ZCASHD_DOCKER_PLATFORM` setting. Keep the default `linux/amd64` when using `zodlinc/zcashd:v6.12.1`; Docker Desktop runs it through emulation on Apple Silicon. Only set `ZCASHD_DOCKER_PLATFORM=linux/arm64` when `ZCASHD_IMAGE` points to an arm64-capable image. @@ -326,7 +326,6 @@ The stack uses Docker-managed named volumes by default: | `zaino_data` | Indexer database | | `zallet_data` | Wallet database | | `zcashd_data` | Optional zcashd comparator chain state | -| `zcashd_params` | Optional zcashd comparator params cache | | `shared_cookie_volume` | RPC authentication cookies | ### Local Directories (Advanced) @@ -337,6 +336,7 @@ For backups, external SSDs, or shared storage, override volume paths in `.env`: Z3_ZEBRA_DATA_PATH=/mnt/ssd/zebra-state Z3_ZAINO_DATA_PATH=/mnt/ssd/zaino-data Z3_ZALLET_DATA_PATH=/mnt/ssd/zallet-data +Z3_ZCASHD_DATA_PATH=/mnt/ssd/zcashd-data ``` Fix permissions before starting: @@ -345,9 +345,10 @@ Fix permissions before starting: ./scripts/fix-permissions.sh zebra /mnt/ssd/zebra-state ./scripts/fix-permissions.sh zaino /mnt/ssd/zaino-data ./scripts/fix-permissions.sh zallet /mnt/ssd/zallet-data +./scripts/fix-permissions.sh zcashd /mnt/ssd/zcashd-data ``` -Zebra, Zaino, and Zallet each run as a specific non-root user. Directories must have correct ownership (set by the script) and 700 permissions. Never use 755 or 777. +Zebra, Zaino, Zallet, and zcashd each run as a specific non-root user. Directories must have correct ownership (set by the script) and 700 permissions. Never use 755 or 777. diff --git a/docker-compose.regtest.yml b/docker-compose.regtest.yml index 3e431fa..27f6c54 100644 --- a/docker-compose.regtest.yml +++ b/docker-compose.regtest.yml @@ -49,6 +49,9 @@ services: # from P2P in regtest. Activation heights default to config/regtest/zallet.toml # but can be overridden for custom regtest runs. command: + - zcashd + - -conf=/dev/null + - -printtoconsole - -regtest - -i-am-aware-zcashd-will-be-replaced-by-zebrad-and-zallet-in-2025=1 - -listen=0 @@ -56,6 +59,11 @@ services: - -dns=0 - -connect=0 - -server=1 + - -rpcuser=${ZCASHD_RPCUSER:-zebra} + - -rpcpassword=${ZCASHD_RPCPASSWORD:-zebra} + - -rpcbind=0.0.0.0 + - -rpcport=${ZCASHD_RPC_PORT:-38232} + - -rpcallowip=${ZCASHD_RPC_ALLOWIP:-0.0.0.0/0} - -nuparams=5ba81b19:${ZCASHD_OVERWINTER_ACTIVATION_HEIGHT:-1} - -nuparams=76b809bb:${ZCASHD_SAPLING_ACTIVATION_HEIGHT:-1} - -nuparams=2bb40e60:${ZCASHD_BLOSSOM_ACTIVATION_HEIGHT:-1} @@ -63,7 +71,7 @@ services: - -nuparams=e9ff75a6:${ZCASHD_CANOPY_ACTIVATION_HEIGHT:-1} - -nuparams=c2d6d0b4:${ZCASHD_NU5_ACTIVATION_HEIGHT:-1} healthcheck: - test: ["CMD-SHELL", "zcash-cli -regtest -rpcconnect=127.0.0.1 -rpcport=$${ZCASHD_RPCPORT} -rpcuser=$${ZCASHD_RPCUSER} -rpcpassword=$${ZCASHD_RPCPASSWORD} getblockchaininfo >/dev/null"] + test: ["CMD-SHELL", "zcash-cli -conf=/dev/null -regtest -rpcconnect=127.0.0.1 -rpcport=$${ZCASHD_RPCPORT} -rpcuser=$${ZCASHD_RPCUSER} -rpcpassword=$${ZCASHD_RPCPASSWORD} getblockchaininfo >/dev/null"] interval: 10s timeout: 5s retries: 10 diff --git a/docker-compose.yml b/docker-compose.yml index 5db43c3..a00a95f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -150,21 +150,29 @@ services: # Optional zcashd node. It is intentionally isolated from the # public P2P network unless the operator overrides the command. profiles: [zcashd] - image: ${ZCASHD_IMAGE:-electriccoinco/zcashd:latest} - # The official zcashd image currently publishes linux/amd64 only. + image: ${ZCASHD_IMAGE:-zodlinc/zcashd:v6.12.1} + # The default zcashd image currently publishes linux/amd64 only. platform: ${ZCASHD_DOCKER_PLATFORM:-linux/amd64} container_name: z3_zcashd restart: unless-stopped stop_grace_period: 30s <<: *common - user: "2001:2001" + user: "999:999" command: + - zcashd + - -conf=/dev/null + - -printtoconsole - -i-am-aware-zcashd-will-be-replaced-by-zebrad-and-zallet-in-2025=1 - -listen=0 - -dnsseed=0 - -dns=0 - -connect=0 - -server=1 + - -rpcuser=${ZCASHD_RPCUSER:-zebra} + - -rpcpassword=${ZCASHD_RPCPASSWORD:-zebra} + - -rpcbind=0.0.0.0 + - -rpcport=${ZCASHD_RPC_PORT:-38232} + - -rpcallowip=${ZCASHD_RPC_ALLOWIP:-0.0.0.0/0} environment: ZCASHD_RPCUSER: ${ZCASHD_RPCUSER:-zebra} ZCASHD_RPCPASSWORD: ${ZCASHD_RPCPASSWORD:-zebra} @@ -172,14 +180,13 @@ services: ZCASHD_RPCPORT: ${ZCASHD_RPC_PORT:-38232} ZCASHD_ALLOWIP: ${ZCASHD_RPC_ALLOWIP:-0.0.0.0/0} volumes: - - ${Z3_ZCASHD_DATA_PATH:-zcashd_data}:/srv/zcashd/.zcash - - ${Z3_ZCASHD_PARAMS_PATH:-zcashd_params}:/srv/zcashd/.zcash-params + - ${Z3_ZCASHD_DATA_PATH:-zcashd_data}:/home/zcash/.zcash ports: - "${ZCASHD_HOST_RPC_BIND:-127.0.0.1}:${ZCASHD_HOST_RPC_PORT:-38232}:${ZCASHD_RPC_PORT:-38232}" networks: - z3_net healthcheck: - test: ["CMD-SHELL", "zcash-cli -rpcconnect=127.0.0.1 -rpcport=$${ZCASHD_RPCPORT} -rpcuser=$${ZCASHD_RPCUSER} -rpcpassword=$${ZCASHD_RPCPASSWORD} getblockchaininfo >/dev/null"] + test: ["CMD-SHELL", "zcash-cli -conf=/dev/null -rpcconnect=127.0.0.1 -rpcport=$${ZCASHD_RPCPORT} -rpcuser=$${ZCASHD_RPCUSER} -rpcpassword=$${ZCASHD_RPCPASSWORD} getblockchaininfo >/dev/null"] interval: 10s timeout: 5s retries: 10 @@ -302,7 +309,6 @@ services: volumes: zebra_data: zcashd_data: - zcashd_params: zaino_data: zallet_data: shared_cookie_volume: diff --git a/docs/docker-architecture.md b/docs/docker-architecture.md index b8a5b31..2ad3faf 100644 --- a/docs/docker-architecture.md +++ b/docs/docker-architecture.md @@ -201,7 +201,7 @@ All service images are overridable via environment variables: image: ${ZEBRA_IMAGE:-zfnd/zebra:4.3.1} image: ${ZAINO_IMAGE:-ghcr.io/zcashfoundation/zaino:sha-83e41d7} image: ${ZALLET_IMAGE:-electriccoinco/zallet:v0.1.0-alpha.3} -image: ${ZCASHD_IMAGE:-electriccoinco/zcashd:latest} +image: ${ZCASHD_IMAGE:-zodlinc/zcashd:v6.12.1} ``` This allows operators to: diff --git a/docs/regtest.md b/docs/regtest.md index bf907bb..e5cbeac 100644 --- a/docs/regtest.md +++ b/docs/regtest.md @@ -62,7 +62,7 @@ For local compatibility checks against zcashd, start the profiled zcashd service docker compose --env-file .env.regtest --profile zcashd up -d zcashd ``` -The regtest overlay starts zcashd with public P2P disabled (`-listen=0 -connect=0`) and, by default, the same NU activation heights used by Zallet. It uses separate Docker volumes (`zcashd_data`, `zcashd_params`) and default RPC credentials `zebra` / `zebra`. See the [README platform section](../README.md#platform-configuration-arm64) for arm64 notes. +The regtest overlay starts zcashd with public P2P disabled (`-listen=0 -connect=0`) and, by default, the same NU activation heights used by Zallet. It uses a separate Docker volume (`zcashd_data`) and default RPC credentials `zebra` / `zebra`. See the [README platform section](../README.md#platform-configuration-arm64) for arm64 notes. For comparator runs that need a specific upgrade era, override the zcashd activation heights and use a separate data volume: diff --git a/scripts/fix-permissions.sh b/scripts/fix-permissions.sh index fcc17d0..77ea499 100755 --- a/scripts/fix-permissions.sh +++ b/scripts/fix-permissions.sh @@ -6,12 +6,13 @@ # Usage: # ./fix-permissions.sh # -# Services: zebra, zaino, zallet, cookie +# Services: zebra, zaino, zallet, zcashd, cookie # # Examples: # ./fix-permissions.sh zebra /mnt/ssd/zebra-state # ./fix-permissions.sh zaino /home/user/data/zaino # ./fix-permissions.sh zallet ~/Documents/zallet-data +# ./fix-permissions.sh zcashd /mnt/ssd/zcashd-data # ./fix-permissions.sh cookie /var/lib/z3/cookies # @@ -30,6 +31,8 @@ ZAINO_UID=1000 ZAINO_GID=1000 ZALLET_UID=65532 ZALLET_GID=65532 +ZCASHD_UID=999 +ZCASHD_GID=999 # Show usage usage() { @@ -39,12 +42,14 @@ usage() { echo " zebra - Zebra blockchain state (UID:GID 10001:10001, perms 700)" echo " zaino - Zaino indexer data (UID:GID 1000:1000, perms 700)" echo " zallet - Zallet wallet data (UID:GID 65532:65532, perms 700)" + echo " zcashd - Optional zcashd comparator data (UID:GID 999:999, perms 700)" echo " cookie - Shared cookie directory (UID:GID 10001:10001, perms 750)" echo "" echo "Examples:" echo " $0 zebra /mnt/ssd/zebra-state" echo " $0 zaino /home/user/data/zaino" echo " $0 zallet ~/Documents/zallet-data" + echo " $0 zcashd /mnt/ssd/zcashd-data" exit 1 } @@ -73,6 +78,11 @@ case "$SERVICE" in OWNER_GID=$ZALLET_GID PERMS=700 ;; + zcashd) + OWNER_UID=$ZCASHD_UID + OWNER_GID=$ZCASHD_GID + PERMS=700 + ;; cookie) OWNER_UID=$ZEBRA_UID OWNER_GID=$ZEBRA_GID @@ -131,6 +141,9 @@ case "$SERVICE" in zallet) echo " Z3_ZALLET_DATA_PATH=${DIR_PATH}" ;; + zcashd) + echo " Z3_ZCASHD_DATA_PATH=${DIR_PATH}" + ;; cookie) echo " Z3_COOKIE_PATH=${DIR_PATH}" ;; From 567a9b40adea08f6a336270f2d7f8dd30cf81954 Mon Sep 17 00:00:00 2001 From: DC Date: Sat, 30 May 2026 11:23:30 -0600 Subject: [PATCH 48/57] ci: switch push triggers from dev to main The repo default branch was renamed dev -> main (issue #26). Update the three workflows that pin push triggers to the old branch name so they fire on the new default. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/build-z3-images.yaml | 2 +- .github/workflows/ci.yaml | 2 +- .github/workflows/zizmor.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-z3-images.yaml b/.github/workflows/build-z3-images.yaml index 07ce902..9861894 100644 --- a/.github/workflows/build-z3-images.yaml +++ b/.github/workflows/build-z3-images.yaml @@ -14,7 +14,7 @@ on: default: false push: branches: - - dev + - main paths: - '.github/workflows/build-z3-images.yaml' diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c73bbc2..ee157b1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -7,7 +7,7 @@ concurrency: on: pull_request: push: - branches: [dev] + branches: [main] permissions: {} diff --git a/.github/workflows/zizmor.yaml b/.github/workflows/zizmor.yaml index 581dfb7..81223bd 100644 --- a/.github/workflows/zizmor.yaml +++ b/.github/workflows/zizmor.yaml @@ -2,7 +2,7 @@ name: GitHub Actions security analysis on: push: - branches: [dev] + branches: [main] paths: - '.github/workflows/**' pull_request: From e9b93e64a7e6f6242f538977149cbc7b81d44a00 Mon Sep 17 00:00:00 2001 From: DC Date: Sat, 30 May 2026 12:00:55 -0600 Subject: [PATCH 49/57] ci: pass RUST_VERSION build-arg when building zaino MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit zingolabs/zaino's Dockerfile (on dev) declares RUST_VERSION as a build-arg with no default by design — the canonical source is rust-toolchain.toml. Our reusable build workflow only passed SHORT_SHA, so the FROM stage expanded to "rust:-bookworm" and Docker rejected it with "invalid reference format". Add an opt-in `read_rust_version` input to sub-build-docker-image.yaml that reads the channel from rust-toolchain.toml in the just-checked-out upstream repo, validates it as x.y or x.y.z (matching upstream's get-rust-version.sh), and appends it to build-args. The zaino caller opts in; zebra and zallet are unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/build-z3-images.yaml | 1 + .github/workflows/sub-build-docker-image.yaml | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/.github/workflows/build-z3-images.yaml b/.github/workflows/build-z3-images.yaml index 9861894..0f5ff4e 100644 --- a/.github/workflows/build-z3-images.yaml +++ b/.github/workflows/build-z3-images.yaml @@ -58,6 +58,7 @@ jobs: rust_backtrace: full rust_lib_backtrace: full rust_log: info + read_rust_version: true build-zallet: name: Build zallet Docker diff --git a/.github/workflows/sub-build-docker-image.yaml b/.github/workflows/sub-build-docker-image.yaml index dacb503..8c086df 100644 --- a/.github/workflows/sub-build-docker-image.yaml +++ b/.github/workflows/sub-build-docker-image.yaml @@ -45,6 +45,11 @@ on: required: false type: boolean default: false + read_rust_version: + description: "Read RUST_VERSION from rust-toolchain.toml in the checked-out upstream repo and pass it as a build-arg" + required: false + type: boolean + default: false outputs: image_digest: @@ -80,6 +85,28 @@ jobs: ref: ${{ inputs.ref }} persist-credentials: false + - name: Read RUST_VERSION from rust-toolchain.toml + if: ${{ inputs.read_rust_version }} + id: rust_version + run: | + set -euo pipefail + if [[ ! -f rust-toolchain.toml ]]; then + echo "rust-toolchain.toml not found in ${{ inputs.repository }}@${{ inputs.ref }}" >&2 + exit 1 + fi + channel=$(grep -E '^[[:space:]]*channel[[:space:]]*=' rust-toolchain.toml \ + | head -n1 \ + | sed -E 's/^[[:space:]]*channel[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/') + if [[ -z "$channel" ]]; then + echo "no [toolchain].channel found in rust-toolchain.toml" >&2 + exit 1 + fi + if [[ ! "$channel" =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then + echo "channel '$channel' is not a concrete numeric version (x.y or x.y.z)" >&2 + exit 1 + fi + echo "rust_version=$channel" >> "$GITHUB_OUTPUT" + - name: Checkout Z3 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -139,6 +166,7 @@ jobs: labels: ${{ steps.meta.outputs.labels }} build-args: | SHORT_SHA=${{ env.GITHUB_SHA_SHORT }} + ${{ inputs.read_rust_version && format('RUST_VERSION={0}', steps.rust_version.outputs.rust_version) || '' }} push: true # It's recommended to build images with max-level provenance attestations # https://docs.docker.com/build/ci/github-actions/attestations/ From eeb5e038c307c0f4fb0d891bda97c57f4340fdc5 Mon Sep 17 00:00:00 2001 From: DC Date: Sat, 30 May 2026 12:05:23 -0600 Subject: [PATCH 50/57] ci: pass inputs as env to silence zizmor template-injection Zizmor flagged the previous commit's `Read RUST_VERSION` step for expanding `${{ inputs.repository }}` and `${{ inputs.ref }}` directly into the shell `run:` body. Move both into `env:` so they're consumed as ordinary shell variables, removing the template-expansion sink. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/sub-build-docker-image.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/sub-build-docker-image.yaml b/.github/workflows/sub-build-docker-image.yaml index 8c086df..4ddcbad 100644 --- a/.github/workflows/sub-build-docker-image.yaml +++ b/.github/workflows/sub-build-docker-image.yaml @@ -88,10 +88,13 @@ jobs: - name: Read RUST_VERSION from rust-toolchain.toml if: ${{ inputs.read_rust_version }} id: rust_version + env: + UPSTREAM_REPO: ${{ inputs.repository }} + UPSTREAM_REF: ${{ inputs.ref }} run: | set -euo pipefail if [[ ! -f rust-toolchain.toml ]]; then - echo "rust-toolchain.toml not found in ${{ inputs.repository }}@${{ inputs.ref }}" >&2 + echo "rust-toolchain.toml not found in ${UPSTREAM_REPO}@${UPSTREAM_REF}" >&2 exit 1 fi channel=$(grep -E '^[[:space:]]*channel[[:space:]]*=' rust-toolchain.toml \ From 6c081fd0a65ee46f3c4b4943acb66a96442e1b1d Mon Sep 17 00:00:00 2001 From: DC Date: Sat, 30 May 2026 12:59:58 -0600 Subject: [PATCH 51/57] ci: add cargo test job for rpc-router MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rpc-router has 13 unit tests and 7 integration tests that were not exercised by CI — the existing smoke-test job only builds the binary and validates the docker compose stack. This blind spot meant Cargo.lock bumps (including transitive openssl / rustls-webpki updates) had no automated runtime signal. Add a sibling job that runs `cargo test --locked --all-targets` against rpc-router on Rust 1.85, matching the toolchain pinned in rpc-router/Dockerfile. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9302a2a..6d61b43 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -125,3 +125,20 @@ jobs: docker compose --env-file .env.regtest --profile zcashd logs zcashd --tail 50 2>&1 || true echo "=== Container status ===" docker compose --env-file .env.regtest --profile zcashd ps 2>&1 || true + + rpc-router-tests: + name: rpc-router cargo test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Install Rust toolchain + run: | + rustup toolchain install 1.85 --profile minimal --no-self-update + rustup default 1.85 + + - name: cargo test + working-directory: rpc-router + run: cargo test --locked --all-targets From 4b9072a44de2b001a4ad662985e05cfce951cb92 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 30 May 2026 13:12:07 -0600 Subject: [PATCH 52/57] chore(deps): bump rustls-webpki from 0.103.10 to 0.103.13 in /rpc-router (#34) Bumps [rustls-webpki](https://github.com/rustls/webpki) from 0.103.10 to 0.103.13. - [Release notes](https://github.com/rustls/webpki/releases) - [Commits](https://github.com/rustls/webpki/compare/v/0.103.10...v/0.103.13) --- updated-dependencies: - dependency-name: rustls-webpki dependency-version: 0.103.13 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- rpc-router/Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rpc-router/Cargo.lock b/rpc-router/Cargo.lock index 8fc4fdd..ad77d80 100644 --- a/rpc-router/Cargo.lock +++ b/rpc-router/Cargo.lock @@ -1586,9 +1586,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.10" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "ring", "rustls-pki-types", From 45ec0c3ed784ab6b2c610bd05f6e77862cd6061c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 30 May 2026 13:17:36 -0600 Subject: [PATCH 53/57] chore(deps): bump openssl from 0.10.75 to 0.10.80 in /rpc-router (#38) Bumps [openssl](https://github.com/rust-openssl/rust-openssl) from 0.10.75 to 0.10.80. - [Release notes](https://github.com/rust-openssl/rust-openssl/releases) - [Commits](https://github.com/rust-openssl/rust-openssl/compare/openssl-v0.10.75...openssl-v0.10.80) --- updated-dependencies: - dependency-name: openssl dependency-version: 0.10.80 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- rpc-router/Cargo.lock | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/rpc-router/Cargo.lock b/rpc-router/Cargo.lock index ad77d80..a78c6b6 100644 --- a/rpc-router/Cargo.lock +++ b/rpc-router/Cargo.lock @@ -1212,15 +1212,14 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl" -version = "0.10.75" +version = "0.10.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" dependencies = [ "bitflags", "cfg-if", "foreign-types", "libc", - "once_cell", "openssl-macros", "openssl-sys", ] @@ -1244,9 +1243,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.111" +version = "0.9.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" dependencies = [ "cc", "libc", From 97e7b91b69d5b8948dd5ca059124d5204e3b3806 Mon Sep 17 00:00:00 2001 From: DC Date: Thu, 4 Jun 2026 07:16:04 -0600 Subject: [PATCH 54/57] chore: bump default zebra to latest (v5.0.0) (#41) * chore(docker): bump default zebra image to latest Zebra v5.0.0 was released and tagged `latest`. Switch the docker-compose default and its mirrored references in .env.example, README, and docker-architecture.md from `zfnd/zebra:4.3.1` to `zfnd/zebra:latest` so fresh stacks track upstream Zebra releases without an .env override. Operators who need a pinned version can still set ZEBRA_IMAGE in .env (see the README override example). Note: zaino is pinned to a Zebra-4.3-compatible image (see comment at docker-compose.yml:69); the new zebra default may surface RPC-compat issues that are out of scope for this commit. Co-Authored-By: Claude Opus 4.7 (1M context) * chore(submodule): bump zebra to v5.0.0 Move the zebra/ submodule pointer from v4.3.0 to the v5.0.0 release tag so the build-from-source path (`docker compose build`) produces a v5.0.0 image consistent with the new default in the previous commit. Co-Authored-By: Claude Opus 4.7 (1M context) --------- Co-authored-by: Claude Opus 4.7 (1M context) --- .env.example | 2 +- README.md | 2 +- docker-compose.yml | 2 +- docs/docker-architecture.md | 4 ++-- zebra | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index 2c2b9db..26c735b 100644 --- a/.env.example +++ b/.env.example @@ -34,7 +34,7 @@ # ============================================================================= # Image Overrides (defaults are pinned versions in docker-compose.yml) # ============================================================================= -# ZEBRA_IMAGE=zfnd/zebra:4.3.1 +# ZEBRA_IMAGE=zfnd/zebra:latest # ZAINO_IMAGE=ghcr.io/zcashfoundation/zaino:sha-83e41d7 # ZALLET_IMAGE=electriccoinco/zallet:v0.1.0-alpha.3 # ZCASHD_IMAGE=zodlinc/zcashd:v6.12.1 diff --git a/README.md b/README.md index 2a36382..cea7445 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ All images can be overridden via environment variables (`ZEBRA_IMAGE`, `ZAINO_IM | Service | Default Image | Source | |---------|---------------|--------| -| Zebra | `zfnd/zebra:4.3.1` | [ZcashFoundation/zebra](https://github.com/ZcashFoundation/zebra) | +| Zebra | `zfnd/zebra:latest` | [ZcashFoundation/zebra](https://github.com/ZcashFoundation/zebra) | | Zaino | `ghcr.io/zcashfoundation/zaino:sha-83e41d7` | [zingolabs/zaino](https://github.com/zingolabs/zaino) | | Zallet | `electriccoinco/zallet:v0.1.0-alpha.3` | [zcash/wallet](https://github.com/zcash/wallet) | | zcashd | `zodlinc/zcashd:v6.12.1` | [zcash/zcash](https://github.com/zcash/zcash) | diff --git a/docker-compose.yml b/docker-compose.yml index a00a95f..ed9be09 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,7 +14,7 @@ x-common: &common services: zebra: - image: ${ZEBRA_IMAGE:-zfnd/zebra:4.3.1} + image: ${ZEBRA_IMAGE:-zfnd/zebra:latest} platform: ${DOCKER_PLATFORM:-linux/amd64} build: context: ./zebra diff --git a/docs/docker-architecture.md b/docs/docker-architecture.md index 2ad3faf..08d33de 100644 --- a/docs/docker-architecture.md +++ b/docs/docker-architecture.md @@ -23,7 +23,7 @@ The core principle: **`docker-compose.yml` is self-sufficient**. Every variable Every variable reference in `docker-compose.yml` includes a default value: ```yaml -image: ${ZEBRA_IMAGE:-zfnd/zebra:4.3.1} +image: ${ZEBRA_IMAGE:-zfnd/zebra:latest} environment: ZEBRA_NETWORK__NETWORK: ${NETWORK_NAME:-Mainnet} volumes: @@ -198,7 +198,7 @@ Without `max-size` and `max-file`, Docker's default `json-file` log driver grows All service images are overridable via environment variables: ```yaml -image: ${ZEBRA_IMAGE:-zfnd/zebra:4.3.1} +image: ${ZEBRA_IMAGE:-zfnd/zebra:latest} image: ${ZAINO_IMAGE:-ghcr.io/zcashfoundation/zaino:sha-83e41d7} image: ${ZALLET_IMAGE:-electriccoinco/zallet:v0.1.0-alpha.3} image: ${ZCASHD_IMAGE:-zodlinc/zcashd:v6.12.1} diff --git a/zebra b/zebra index 92a4e55..1e6519e 160000 --- a/zebra +++ b/zebra @@ -1 +1 @@ -Subproject commit 92a4e55f9e702468dbb0d8ec57df3e0c3fdd4ea9 +Subproject commit 1e6519ea91e2d3035c20aadd4d9a40dcac2eed3a From dd84312b15d7e868fce18255cb0a656ce1e0371d Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Wed, 10 Jun 2026 12:32:30 -0700 Subject: [PATCH 55/57] feat: platform contract, per-network Compose projects, and integration guides (#43) * feat: introduce platform contract with versioned identifiers and CI validators Adds z3-contract.yaml as the machine-readable inventory of every stable identifier downstream consumers depend on: networks, Compose project names, external network names, volume names, in-network DNS, the per-network port matrix with profile gating, healthcheck transport and endpoint per service, the env-var schema with namespace tags, and the RPC auth mode per network. z3-contract.schema.json constrains the YAML's shape so consumers in any language can validate before integrating. Three CI gates enforce the surface on every PR: - validate-contract.py: rendered Compose output matches the contracted port matrix, volumes, healthcheck transport, and scrape target. - validate-contract-parity.py: env-var parity across compose files, .env.example, and the contract YAML (with ecosystem_vars unioned). - jsonschema: YAML validates against the JSON Schema. The contract carries its own SemVer version (contract_version), separate from the repo's release version. Bumping it is a breaking change to downstream integrators, not to operators. docs/contract.md walks the file- ownership model and the full surface. * feat: reshape Compose stack into per-network projects with auto-loaded overrides Mainnet, testnet, and regtest now run as three Compose projects (z3-mainnet, z3-testnet, z3-regtest) produced from one base compose plus a per-network overlay. Host ports for services without an upstream per-network convention use a +10000 offset relative to mainnet. The three networks coexist on one host without binding collisions, including under the monitoring and zcashd profiles. Per-host operator overrides live in docker-compose..override.yml, gitignored. scripts/setup-network.sh copies an empty placeholder from the tracked .example if the live file is missing, so testnet and regtest auto-load the override without erroring on a fresh clone. The same pattern covers per-network Zallet, Zaino, and the regtest Zebra TOML configs and the Zallet identity: each tracked as .example, copied on first run, edited freely without git-pull conflicts. Regtest's Zebra config (config/regtest/zebra.toml.example) activates NU5/NU6 at the heights Zaino's regtest defaults expect, so the stack boots end-to-end on a fresh clone instead of stalling at Canopy. Image defaults bumped: Zebra to 5.0.0 (multi-arch; runs native arm64 on Apple Silicon), Zaino moved to zingodevops/zainod with a semver tag instead of a commit SHA, zcashd to v6.20.0. All pins remain ${VAR:-tag} fallbacks that operators override per-pin via Z3_*_IMAGE. scripts/fix-permissions.sh corrects the bind-mount uid:gid map for Zallet and renames the printed env var for the Zebra service to Z3_CHAIN_DATA_PATH to match the compose. scripts/regtest-init.sh mines two blocks (NU5 activation height). scripts/check-zebra-readiness.sh prints the correct --env-file in its success message per health port. * docs: add integration archetypes and rewrite operator docs around the contract Downstream services have three documented attachment archetypes under docs/integrations/: - compose-peer: a Docker service in the same logical stack attaches to the external network and cookie volume by name. - host-side-pointer: a host process connects via published host ports and reads the cookie out of the named volume. - lightwalletd-client: a wallet or scanner dials Zaino's gRPC port over TLS. Each guide carries mainnet/testnet/regtest port tables, working code snippets in Rust and TypeScript, and the regtest auth divergence called out (rpc_auth.mode: username_password, rpc-router default credentials, direct-Zebra credentials path) so a consumer can target either auth mode. README, FAQ, regtest, and docker-architecture docs rewritten to align with the per-network project model, the override convention, and the multi-arch Zebra default. Observability READMEs reflect the monitoring profile port matrix per network. * fix: unblock CI and apply PR #43 review feedback The "Contract validation (all networks)" job and a fresh `git clone && docker compose up` both failed because .env.testnet and .env.regtest listed a gitignored override file in COMPOSE_FILE. Drop it from COMPOSE_FILE; per-host overrides are now opt-in via -f or an operator-local append. Remove the baggage reviewers flagged: - Zaino runs the upstream -no-tls image; the self-signed cert, the configs block, and config/tls are gone. Intra-container gRPC is plaintext, and edge TLS belongs at a reverse proxy. - Compose no longer pins the json-file logging driver, so the operator's daemon default (journald and others) wins; daemon.json rotation is documented. - The dead Zebra indexer port (8230) is removed; nothing in the stack used it. - Regtest zcashd nuparams are hardcoded and the six activation-height vars dropped. Publish Zebra p2p on mainnet and testnet for inbound peers (regtest stays peerless). Keep image pins for consensus safety and add a Renovate config that raises bump PRs. regtest-init now requires Compose v2 with a clear message, and the contract validator gained a bidirectional no-drift check. Restructure the docs around two audiences with progressive disclosure: operators (the three commands, production readiness, data and backup) and developers and testers (multi-network behavior, integration, the contract). The platform contract is no longer in the operator path, which is what confused the reviewer. Delete the stale memory-bank and data scratch directories. Cookie auth removal and rpc-router promotion are deferred to #44. * fix(ci): stage regtest zebra.toml for the regtest smoke job The regtest overlay bind-mounts config/regtest/zebra.toml into Zebra, but the smoke job's staging step only copied the zallet and zaino templates. With the source file missing, Docker mounted an empty directory and Zebra exited with "configuration file not found". The job had never run before (it was skipped behind the failing contract-validation gate), so the gap was latent until that gate started passing. * fix: correct zcashd comparator user and datadir for the zodlinc image The zcashd service still carried `user: "999:999"` and a `/home/zcash/.zcash` data mount from a previous image. The current zodlinc/zcashd image runs as the zcash user (uid 2001) with its datadir under /srv/zcashd, so the entrypoint (forced to uid 999) hit "touch: cannot touch '.zcash/zcash.conf': Permission denied" and crash-looped. Run as 2001 and mount the data volume at /srv/zcashd, which the image owns as zcash, so a fresh named volume inherits writable ownership. fix-permissions.sh now chowns to 2001 to match. The regtest smoke job surfaced this once the contract-validation gate stopped skipping it. * refactor: remove the zcashd comparator The optional zcashd profile is gone: the service, the zcashd Compose profile, the ZCASHD_* / Z3_ZCASHD_* env vars, the data volume, the contract entries (DNS, volume, port, healthcheck, profile, image platform), the regtest smoke step, and the unused zcashd submodule. docker-compose.testnet.yml held only the zcashd testnet command, so it is deleted too; .env.testnet now loads just the base compose and selects the network with Z3_NETWORK. zcashd is deprecating (no releases past v6.20.0) and both PR reviewers questioned keeping it. It was also broken with the current zodlinc/zcashd image: that image's entrypoint builds zcashd's invocation from ZCASHD_* env vars and appends the compose command after it, so the -regtest and -nuparams flags never applied and the comparator crash-looped. Reworking the stack around that interface for a deprecating component is not worth it. The stack is now Zebra + Zaino + Zallet, plus the regtest-only rpc-router and the monitoring profile. * fix(regtest): align NU6 and NU6.1 activation heights across Zebra, Zaino, Zallet Addresses the PR #43 review comments on the regtest network-upgrade list. The three components disagreed: Zaino's hardcoded regtest anchor (ZEBRAD_DEFAULT_ACTIVATION_HEIGHTS) activates NU6 at height 2 and NU6.1 at 1000, but Zebra declared only NU6=2 and Zallet's regtest_nuparams stopped at NU5. A regtest chain past height 1000 would desync the wallet's view of the chain. Add NU6 (c8e71055) to Zallet at height 2, and NU6.1 (4dec4df0) to both Zebra and Zallet at height 1000, matching Zaino. NU6.2 stays out on purpose: neither Zaino 0.4.0-rc.2 nor Zallet's zcash_protocol 0.7.2 defines it, so a 5437f330 entry would fail to parse; it waits until all three pinned versions know it. Verified at runtime: regtest boots with the new config (Zebra parses the quoted "NU6.1" = 1000 key) and Zaino reads the chain Zebra produces. --- .clinerules | 119 ----- .env.example | 175 +++---- .env.mainnet | 12 + .env.regtest | 46 +- .env.testnet | 45 ++ .github/workflows/ci.yaml | 185 ++++++-- .gitignore | 33 +- .gitmodules | 3 - .vscode/settings.json | 7 +- README.md | 443 +++++++++--------- config/mainnet/zaino.toml.example | 5 + config/mainnet/zallet.toml.example | 49 ++ .../{zaino.toml => zaino.toml.example} | 0 config/regtest/zallet.toml.default | 44 -- config/regtest/zallet.toml.example | 54 +++ config/regtest/zebra.toml.example | 22 + config/testnet/zaino.toml.example | 5 + config/testnet/zallet.toml.example | 49 ++ config/tls/.gitkeep | 0 config/zaino.toml | 1 - config/zallet.toml.default | 50 -- docker-compose.regtest.yml | 113 ++--- docker-compose.yml | 299 ++++++------ docs/contract.md | 182 +++++++ docs/data/ecosystem_map.md | 92 ---- docs/data/rpc_mapping.md | 67 --- docs/data/zcashd_RPC_methods.md | 71 --- ...cation_team_rpc_method_support_9apr2025.md | 129 ----- .../zebra_adoption_tracking/analyze_nodes.sh | 36 -- docs/docker-architecture.md | 246 ++++++---- docs/faq.md | 195 ++++++++ docs/integrations/README.md | 35 ++ docs/integrations/compose-peer.md | 137 ++++++ docs/integrations/host-side-pointer.md | 108 +++++ docs/integrations/lightwalletd-client.md | 101 ++++ docs/memory-bank/activeContext.md | 91 ---- docs/memory-bank/productContext.md | 59 --- docs/memory-bank/progress.md | 91 ---- docs/memory-bank/projectbrief.md | 37 -- docs/memory-bank/systemPatterns.md | 42 -- docs/memory-bank/techContext.md | 305 ------------ docs/memory-bank/z3_component_interplay.md | 112 ----- docs/memory-bank/z3_docker.md | 135 ------ docs/regtest.md | 72 +-- observability/README.md | 92 ++-- observability/jaeger/README.md | 20 +- renovate.json | 25 + scripts/check-zebra-readiness.sh | 45 +- scripts/fix-permissions.sh | 35 +- scripts/regtest-init.sh | 147 +++--- scripts/setup-network.sh | 86 ++++ scripts/validate-contract-parity.py | 128 +++++ scripts/validate-contract.py | 317 +++++++++++++ z3-contract.schema.json | 204 ++++++++ z3-contract.yaml | 254 ++++++++++ zcashd | 1 - 56 files changed, 3054 insertions(+), 2402 deletions(-) delete mode 100644 .clinerules create mode 100644 .env.mainnet create mode 100644 .env.testnet create mode 100644 config/mainnet/zaino.toml.example create mode 100644 config/mainnet/zallet.toml.example rename config/regtest/{zaino.toml => zaino.toml.example} (100%) delete mode 100644 config/regtest/zallet.toml.default create mode 100644 config/regtest/zallet.toml.example create mode 100644 config/regtest/zebra.toml.example create mode 100644 config/testnet/zaino.toml.example create mode 100644 config/testnet/zallet.toml.example delete mode 100644 config/tls/.gitkeep delete mode 100644 config/zaino.toml delete mode 100644 config/zallet.toml.default create mode 100644 docs/contract.md delete mode 100644 docs/data/ecosystem_map.md delete mode 100644 docs/data/rpc_mapping.md delete mode 100644 docs/data/zcashd_RPC_methods.md delete mode 100644 docs/data/zcashd_deprecation_team_rpc_method_support_9apr2025.md delete mode 100755 docs/data/zebra_adoption_tracking/analyze_nodes.sh create mode 100644 docs/faq.md create mode 100644 docs/integrations/README.md create mode 100644 docs/integrations/compose-peer.md create mode 100644 docs/integrations/host-side-pointer.md create mode 100644 docs/integrations/lightwalletd-client.md delete mode 100644 docs/memory-bank/activeContext.md delete mode 100644 docs/memory-bank/productContext.md delete mode 100644 docs/memory-bank/progress.md delete mode 100644 docs/memory-bank/projectbrief.md delete mode 100644 docs/memory-bank/systemPatterns.md delete mode 100644 docs/memory-bank/techContext.md delete mode 100644 docs/memory-bank/z3_component_interplay.md delete mode 100644 docs/memory-bank/z3_docker.md create mode 100644 renovate.json create mode 100755 scripts/setup-network.sh create mode 100755 scripts/validate-contract-parity.py create mode 100755 scripts/validate-contract.py create mode 100644 z3-contract.schema.json create mode 100644 z3-contract.yaml delete mode 160000 zcashd diff --git a/.clinerules b/.clinerules deleted file mode 100644 index 5443202..0000000 --- a/.clinerules +++ /dev/null @@ -1,119 +0,0 @@ -# Cline's Memory Bank - -I am Cline, an expert software engineer with a unique characteristic: my memory resets completely between sessions. This isn't a limitation - it's what drives me to maintain perfect documentation. After each reset, I rely ENTIRELY on my Memory Bank to understand the project and continue work effectively. I MUST read ALL memory bank files at the start of EVERY task - this is not optional. - -## Memory Bank Structure - -The Memory Bank consists of core files and optional context files, all in Markdown format. Files build upon each other in a clear hierarchy: - -flowchart TD - PB[projectbrief.md] --> PC[productContext.md] - PB --> SP[systemPatterns.md] - PB --> TC[techContext.md] - - PC --> AC[activeContext.md] - SP --> AC - TC --> AC - - AC --> P[progress.md] - -### Core Files (Required) -1. `projectbrief.md` - - Foundation document that shapes all other files - - Created at project start if it doesn't exist - - Defines core requirements and goals - - Source of truth for project scope - -2. `productContext.md` - - Why this project exists - - Problems it solves - - How it should work - - User experience goals - -3. `activeContext.md` - - Current work focus - - Recent changes - - Next steps - - Active decisions and considerations - - Important patterns and preferences - - Learnings and project insights - -4. `systemPatterns.md` - - System architecture - - Key technical decisions - - Design patterns in use - - Component relationships - - Critical implementation paths - -5. `techContext.md` - - Technologies used - - Development setup - - Technical constraints - - Dependencies - - Tool usage patterns - -6. `progress.md` - - What works - - What's left to build - - Current status - - Known issues - - Evolution of project decisions - -7. `z3_docker.md` - - Documents what we know so far about how to best Dockerize the Z3 components in a single Compose file - -### Additional Context -Create additional files/folders within memory-bank/ when they help organize: -- Complex feature documentation -- Integration specifications -- API documentation -- Testing strategies -- Deployment procedures - -## Core Workflows - -### Plan Mode -flowchart TD - Start[Start] --> ReadFiles[Read Memory Bank] - ReadFiles --> CheckFiles{Files Complete?} - - CheckFiles -->|No| Plan[Create Plan] - Plan --> Document[Document in Chat] - - CheckFiles -->|Yes| Verify[Verify Context] - Verify --> Strategy[Develop Strategy] - Strategy --> Present[Present Approach] - -### Act Mode -flowchart TD - Start[Start] --> Context[Check Memory Bank] - Context --> Update[Update Documentation] - Update --> Execute[Execute Task] - Execute --> Document[Document Changes] - -## Documentation Updates - -Memory Bank updates occur when: -1. Discovering new project patterns -2. After implementing significant changes -3. When user requests with **update memory bank** (MUST review ALL files) -4. When context needs clarification - -flowchart TD - Start[Update Process] - - subgraph Process - P1[Review ALL Files] - P2[Document Current State] - P3[Clarify Next Steps] - P4[Document Insights & Patterns] - - P1 --> P2 --> P3 --> P4 - end - - Start --> Process - -Note: When triggered by **update memory bank**, I MUST review every memory bank file, even if some don't require updates. Focus particularly on activeContext.md and progress.md as they track current state. - -REMEMBER: After every memory reset, I begin completely fresh. The Memory Bank is my only link to previous work. It must be maintained with precision and clarity, as my effectiveness depends entirely on its accuracy. - diff --git a/.env.example b/.env.example index 26c735b..da4673f 100644 --- a/.env.example +++ b/.env.example @@ -1,114 +1,95 @@ -# Z3 Stack — Environment Variable Overrides +# Z3 environment variable reference. # -# All defaults are in docker-compose.yml. This file is OPTIONAL. -# Create .env and uncomment only the variables you want to override. +# Pick a network and start the stack: +# docker compose --env-file .env.mainnet up -d +# docker compose --env-file .env.testnet up -d +# docker compose --env-file .env.regtest up -d # -# Quick reference — switching networks: -# -# Mainnet (default): docker compose up -d -# Testnet: create .env with NETWORK_NAME=Testnet -# Regtest: docker compose --env-file .env.regtest up -d -# zcashd comparator: add --profile zcashd to include optional zcashd -# -# Variable Mainnet Testnet Regtest -# ───────────────────────────────────────────────────────────────── -# NETWORK_NAME Mainnet Testnet Regtest -# ENABLE_COOKIE_AUTH true true false -# ZEBRA_HEALTH__MIN_CONNECTED_PEERS 1 1 0 +# Every line below is a commented-out override with its default value. Copy +# this file to .env (or any file you load with --env-file) and uncomment only +# the lines you want to change. See z3-contract.yaml for the env-var schema. # +# Naming convention: Z3_* variables configure stack-level settings such as +# host ports, image pins, and volume paths. Native service variables +# (ZEBRA_*, ZAINO_*, GF_*) pass through unchanged. -# ============================================================================= -# Network -# ============================================================================= -# NETWORK_NAME=Mainnet -# ENABLE_COOKIE_AUTH=true -# ZEBRA_HEALTH__MIN_CONNECTED_PEERS=1 +# Network selection (set by the .env. files; rarely overridden) +# COMPOSE_PROJECT_NAME=z3-mainnet +# Z3_NETWORK=Mainnet +# Z3_CONFIG_DIR=./config/mainnet -# ============================================================================= -# Platform (ARM64 users: uncomment for native Z3 service images) -# ============================================================================= -# DOCKER_PLATFORM=linux/arm64 -# The default zcashd image is linux/amd64; only change this for a custom arm64 image. -# ZCASHD_DOCKER_PLATFORM=linux/amd64 +# Image pins. Defaults live in docker-compose.yml as ${VAR:-tag} fallbacks; +# override per pin here to test a pre-release or pin a digest. +# Z3_ZEBRA_IMAGE=zfnd/zebra:5.0.0 +# Z3_ZAINO_IMAGE=zingodevops/zainod:0.4.0-rc.2 +# Z3_ZALLET_IMAGE=electriccoinco/zallet:v0.1.0-alpha.3 +# Z3_ZEBRA_BUILD_FEATURES=default-release-binaries -# ============================================================================= -# Image Overrides (defaults are pinned versions in docker-compose.yml) -# ============================================================================= -# ZEBRA_IMAGE=zfnd/zebra:latest -# ZAINO_IMAGE=ghcr.io/zcashfoundation/zaino:sha-83e41d7 -# ZALLET_IMAGE=electriccoinco/zallet:v0.1.0-alpha.3 -# ZCASHD_IMAGE=zodlinc/zcashd:v6.12.1 -# ZEBRA_BUILD_FEATURES=default-release-binaries - -# ============================================================================= -# Data Paths (default: Docker named volumes) -# ============================================================================= -# Z3_ZEBRA_DATA_PATH=zebra_data -# Z3_ZAINO_DATA_PATH=zaino_data -# Z3_ZALLET_DATA_PATH=zallet_data -# Z3_ZCASHD_DATA_PATH=zcashd_data -# Z3_COOKIE_PATH=shared_cookie_volume +# Platform pin. Zebra is multi-arch and selects the host arch automatically. +# Zaino and Zallet are pinned to amd64 in compose because their upstream +# images publish amd64 only; setting DOCKER_PLATFORM=linux/arm64 with the +# ./zaino and ./zallet submodules initialized builds them native arm64. +# DOCKER_PLATFORM=linux/arm64 -# ============================================================================= -# Port Mappings -# ============================================================================= -# Z3_ZEBRA_HOST_RPC_PORT=18232 +# Host ports (mainnet defaults; .env.testnet and .env.regtest override them) +# Z3_ZEBRA_HOST_RPC_PORT=8232 +# Z3_ZEBRA_HOST_P2P_PORT=8233 # Z3_ZEBRA_HOST_HEALTH_PORT=8080 -# ZAINO_HOST_GRPC_PORT=8137 -# ZAINO_HOST_JSONRPC_PORT=8237 -# ZALLET_HOST_RPC_PORT=28232 -# ZCASHD_HOST_RPC_BIND=127.0.0.1 -# ZCASHD_HOST_RPC_PORT=38232 +# Z3_ZAINO_HOST_GRPC_PORT=8137 +# Z3_ZAINO_HOST_JSON_RPC_PORT=8237 +# Z3_ZALLET_HOST_RPC_PORT=28232 + +# Zebra container ports (vary per network) +# Z3_ZEBRA_RPC_PORT=8232 +# Z3_ZEBRA_P2P_PORT=8233 -# ============================================================================= -# Internal Container Ports (change only for advanced multi-instance setups) -# Note: internal ports must also match bind addresses in config/*.toml files -# ============================================================================= -# Z3_ZEBRA_RPC_PORT=18232 -# ZAINO_GRPC_PORT=8137 -# ZAINO_JSON_RPC_PORT=8237 -# ZALLET_RPC_PORT=28232 -# ZCASHD_RPC_PORT=38232 +# Data paths (default: Docker named volumes; override to a bind-mount path) +# Z3_CHAIN_DATA_PATH=/mnt/ssd/zebra-state +# Z3_ZAINO_DATA_PATH=/mnt/ssd/zaino-data +# Z3_ZALLET_DATA_PATH=/mnt/ssd/zallet-data +# Z3_COOKIE_PATH=cookie -# ============================================================================= -# Log Levels -# ============================================================================= +# Log levels. +# Per-service Z3__RUST_LOG wins; falls back to global RUST_LOG; falls +# back to a sensible per-service default. Setting `RUST_LOG=debug` in your +# shell or .env flips the whole stack to debug. # Z3_ZEBRA_RUST_LOG=info -# ZEBRA_TRACING__FILTER=info -# ZAINO_RUST_LOG=info,reqwest=warn,hyper_util=warn -# ZAINO_RUST_BACKTRACE=full -# ZALLET_RUST_LOG=info,hyper_util=warn,reqwest=warn +# Z3_ZAINO_RUST_LOG=info,reqwest=warn,hyper_util=warn +# Z3_ZALLET_RUST_LOG=info,hyper_util=warn,reqwest=warn +# RUST_BACKTRACE=full # Zaino reads this directly; default is full + +# RPC router (regtest only) +# Z3_REGTEST_RPC_ROUTER_USER=zebra +# Z3_REGTEST_RPC_ROUTER_PASSWORD=zebra +# Z3_REGTEST_RPC_ROUTER_HOST_PORT=8181 -# ============================================================================= -# Optional zcashd Comparator (enable with: docker compose --profile zcashd up -d zcashd) -# ============================================================================= -# ZCASHD_RPCUSER=zebra -# ZCASHD_RPCPASSWORD=zebra -# ZCASHD_RPC_ALLOWIP=0.0.0.0/0 -# Regtest activation overrides for custom comparator runs. -# Defaults keep zcashd aligned with the standard Z3 regtest stack. -# ZCASHD_OVERWINTER_ACTIVATION_HEIGHT=1 -# ZCASHD_SAPLING_ACTIVATION_HEIGHT=1 -# ZCASHD_BLOSSOM_ACTIVATION_HEIGHT=1 -# ZCASHD_HEARTWOOD_ACTIVATION_HEIGHT=1 -# ZCASHD_CANOPY_ACTIVATION_HEIGHT=1 -# ZCASHD_NU5_ACTIVATION_HEIGHT=1 +# Zebra configuration overrides (native config-rs) +# Advertise a reachable address to peers when behind NAT or a firewall, so +# inbound p2p works (forward the published p2p host port to this host). +# ZEBRA_NETWORK__EXTERNAL_ADDR=203.0.113.10:8233 +# ZEBRA_RPC__ENABLE_COOKIE_AUTH=true +# ZEBRA_HEALTH__MIN_CONNECTED_PEERS=1 +# ZEBRA_HEALTH__READY_MAX_BLOCKS_BEHIND=2 +# ZEBRA_HEALTH__ENFORCE_ON_TEST_NETWORKS=false +# ZEBRA_TRACING__FILTER=info +# Miner address shape depends on the network: tm* for regtest/testnet, +# t1*/t3*/u1* for mainnet. Required for regtest mining. +# ZEBRA_MINING__MINER_ADDRESS=tmSRd1r8gs77Ja67Fw1JcdoXytxsyrLTPJm -# ============================================================================= -# Monitoring (enable with: docker compose --profile monitoring up -d) -# ============================================================================= +# Monitoring (enable with --profile monitoring; Zebra metrics are on by default) +# Z3_PROMETHEUS_PORT=9094 +# Z3_GRAFANA_PORT=3000 +# GF_SECURITY_ADMIN_PASSWORD=admin +# Z3_JAEGER_UI_PORT=16686 +# Z3_ALERTMANAGER_PORT=9093 +# Z3_JAEGER_OTLP_GRPC_PORT=4317 +# Z3_JAEGER_OTLP_HTTP_PORT=4318 +# Z3_JAEGER_SPANMETRICS_PORT=8889 # ZEBRA_METRICS__ENDPOINT_ADDR=0.0.0.0:9999 -# GRAFANA_PORT=3000 -# GRAFANA_ADMIN_PASSWORD=admin -# PROMETHEUS_PORT=9094 -# JAEGER_UI_PORT=16686 -# ALERTMANAGER_PORT=9093 -# JAEGER_OTLP_GRPC_PORT=4317 -# JAEGER_OTLP_HTTP_PORT=4318 -# JAEGER_SPANMETRICS_PORT=8889 -# -# OpenTelemetry tracing (requires Zebra built with opentelemetry feature): -# docker compose build --build-arg FEATURES="default-release-binaries opentelemetry" zebra + +# OpenTelemetry tracing. The monitoring profile starts Jaeger, but Zebra only +# exports spans when these vars are set. The default Zebra build features include +# opentelemetry; keep that feature if you override Z3_ZEBRA_BUILD_FEATURES. # ZEBRA_TRACING__OPENTELEMETRY_ENDPOINT=http://jaeger:4318 # ZEBRA_TRACING__OPENTELEMETRY_SERVICE_NAME=zebra # ZEBRA_TRACING__OPENTELEMETRY_SAMPLE_PERCENT=100 diff --git a/.env.mainnet b/.env.mainnet new file mode 100644 index 0000000..b074d37 --- /dev/null +++ b/.env.mainnet @@ -0,0 +1,12 @@ +# Z3 mainnet environment. +# +# Usage: +# docker compose --env-file .env.mainnet up -d +# +# Sets the Compose project name, network selection, and config directory for +# the mainnet stack. docker-compose.yml defaults are already mainnet-shaped +# (Zebra ports 8232 RPC and 8233 p2p), so no port overrides are needed here. + +COMPOSE_PROJECT_NAME=z3-mainnet +Z3_NETWORK=Mainnet +Z3_CONFIG_DIR=./config/mainnet diff --git a/.env.regtest b/.env.regtest index 5c2c3f0..f458b4b 100644 --- a/.env.regtest +++ b/.env.regtest @@ -1,12 +1,48 @@ -# Regtest environment — used with: docker compose --env-file .env.regtest up -d +# Z3 regtest environment. # -# COMPOSE_FILE merges the regtest overlay on top of the base compose. -# COMPOSE_PROJECT_NAME isolates volumes/networks from mainnet/testnet. +# Usage: +# docker compose --env-file .env.regtest up -d +# ./scripts/regtest-init.sh (first time, to generate keys + mine block 1) +# +# COMPOSE_FILE merges the regtest overlay on top of the base compose. It does +# NOT reference an override file, so a fresh clone boots without running setup. +# Per-host overrides are opt-in: create docker-compose.regtest.override.yml and +# append it to COMPOSE_FILE (or pass it with -f) only if you need one. +# Regtest inherits Zebra's testnet container-port defaults. Host ports are +# explicit and globally unique so all three networks can coexist. +# Cookie auth is disabled; Zaino and Zallet authenticate to Zebra via +# username/password configured in config/regtest/*.toml. COMPOSE_PROJECT_NAME=z3-regtest COMPOSE_FILE=docker-compose.yml:docker-compose.regtest.yml -NETWORK_NAME=Regtest -ENABLE_COOKIE_AUTH=false +Z3_NETWORK=Regtest +Z3_CONFIG_DIR=./config/regtest + +ZEBRA_RPC__ENABLE_COOKIE_AUTH=false ZEBRA_HEALTH__MIN_CONNECTED_PEERS=0 ZEBRA_MINING__MINER_ADDRESS=tmSRd1r8gs77Ja67Fw1JcdoXytxsyrLTPJm + +# Zebra container ports (regtest inherits testnet defaults) +Z3_ZEBRA_RPC_PORT=18232 + +# Zebra host ports +Z3_ZEBRA_HOST_RPC_PORT=29232 +Z3_ZEBRA_HOST_HEALTH_PORT=28080 + +# Zaino / Zallet host ports +Z3_ZAINO_HOST_GRPC_PORT=28137 +Z3_ZAINO_HOST_JSON_RPC_PORT=28237 +Z3_ZALLET_HOST_RPC_PORT=50232 + +# rpc-router host port (regtest-only unified Zebra+Zallet JSON-RPC) +Z3_REGTEST_RPC_ROUTER_HOST_PORT=8181 + +# Monitoring (only used with --profile monitoring) +Z3_PROMETHEUS_PORT=29094 +Z3_GRAFANA_PORT=23000 +Z3_JAEGER_UI_PORT=36686 +Z3_JAEGER_OTLP_GRPC_PORT=25317 +Z3_JAEGER_OTLP_HTTP_PORT=25318 +Z3_JAEGER_SPANMETRICS_PORT=28889 +Z3_ALERTMANAGER_PORT=29093 diff --git a/.env.testnet b/.env.testnet new file mode 100644 index 0000000..c3dffb7 --- /dev/null +++ b/.env.testnet @@ -0,0 +1,45 @@ +# Z3 testnet environment. +# +# Usage: +# docker compose --env-file .env.testnet up -d +# +# Container ports follow Zebra's testnet defaults (18232 RPC, 18233 p2p). +# Host ports for services without a per-network upstream convention (Zaino, +# Zallet, metrics) use a +10000 offset relative to mainnet so testnet and +# mainnet can run concurrently on the same host without port collisions. +# +# Testnet uses the base compose with the testnet values below; there is no +# testnet overlay. Per-host overrides are opt-in: create +# docker-compose.testnet.override.yml and load it explicitly with +# `-f docker-compose.yml -f docker-compose.testnet.override.yml`, or append it +# to COMPOSE_FILE in your operator-local .env. + +COMPOSE_PROJECT_NAME=z3-testnet +COMPOSE_FILE=docker-compose.yml +Z3_NETWORK=Testnet +Z3_CONFIG_DIR=./config/testnet + +ZEBRA_HEALTH__ENFORCE_ON_TEST_NETWORKS=true + +# Zebra container ports (testnet defaults from zebra-chain/parameters/network.rs) +Z3_ZEBRA_RPC_PORT=18232 +Z3_ZEBRA_P2P_PORT=18233 + +# Zebra host ports (match container; testnet ports do not collide with mainnet's) +Z3_ZEBRA_HOST_RPC_PORT=18232 +Z3_ZEBRA_HOST_P2P_PORT=18233 +Z3_ZEBRA_HOST_HEALTH_PORT=18080 + +# Zaino / Zallet host ports +Z3_ZAINO_HOST_GRPC_PORT=18137 +Z3_ZAINO_HOST_JSON_RPC_PORT=18237 +Z3_ZALLET_HOST_RPC_PORT=40232 + +# Monitoring (only used with --profile monitoring) +Z3_PROMETHEUS_PORT=19094 +Z3_GRAFANA_PORT=13000 +Z3_JAEGER_UI_PORT=26686 +Z3_JAEGER_OTLP_GRPC_PORT=15317 +Z3_JAEGER_OTLP_HTTP_PORT=15318 +Z3_JAEGER_SPANMETRICS_PORT=18889 +Z3_ALERTMANAGER_PORT=19093 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6d61b43..5c85560 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -12,46 +12,88 @@ on: permissions: {} jobs: - smoke-test: - name: Service smoke tests + contract-validation: + name: Contract validation (all networks) runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Stage local config files for compose render + # The live config//{zallet,zaino}.toml files and identity files + # are gitignored, but docker compose config validates bind-mount source + # existence. Copy templates into place and touch identity stubs. + run: | + for net in mainnet testnet regtest; do + cp config/$net/zallet.toml.example config/$net/zallet.toml + cp config/$net/zaino.toml.example config/$net/zaino.toml + touch config/$net/zallet_identity.txt + done + + - name: Validate compose for every network + run: | + docker compose --env-file .env.mainnet config --quiet + docker compose --env-file .env.testnet config --quiet + docker compose --env-file .env.regtest config --quiet + docker compose --env-file .env.mainnet --profile monitoring config --quiet + docker compose --env-file .env.testnet --profile monitoring config --quiet + docker compose --env-file .env.regtest --profile monitoring config --quiet + + - name: Install Python deps (contract parser + JSON Schema validator) + run: python3 -m pip install --quiet pyyaml jsonschema + + - name: Validate z3-contract.yaml against z3-contract.schema.json + run: | + python3 -c ' + import json, sys, yaml, jsonschema + contract = yaml.safe_load(open("z3-contract.yaml")) + schema = json.load(open("z3-contract.schema.json")) + jsonschema.validate(contract, schema) + print("PASS: z3-contract.yaml validates against z3-contract.schema.json") + ' + + - name: Validate platform contract against resolved compose + run: ./scripts/validate-contract.py + + - name: Validate contract inventory parity (env vars across compose and .env.example) + run: ./scripts/validate-contract-parity.py + + regtest-smoke: + name: Regtest smoke test + runs-on: ubuntu-latest + needs: contract-validation + # External identifiers for the regtest stack. Keep aligned with + # z3-contract.yaml networks.regtest.{external_network,volumes.cookie}. + # Values are literal constants, not user input — safe in run: blocks. env: - COMPOSE_PROJECT_NAME: z3-smoke - COMPOSE_FILE: docker-compose.yml:docker-compose.regtest.yml + COOKIE_VOLUME: z3-regtest-cookie + EXTERNAL_NETWORK: z3-regtest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - name: Generate TLS certs and regtest configs + - name: Stage local files (live TOMLs from templates, identity) + # Mirrors the file setup step without requiring rage in CI. The sed + # command patches the live gitignored copy, not the tracked template. run: | - cp -n config/zallet.toml.default config/zallet.toml - cp -n config/regtest/zallet.toml.default config/regtest/zallet.toml - mkdir -p config/tls - openssl req -x509 -newkey rsa:2048 \ - -keyout config/tls/zaino.key -out config/tls/zaino.crt \ - -sha256 -days 1 -nodes -subj "/CN=localhost" \ - -addext "subjectAltName=DNS:localhost,DNS:zaino,IP:127.0.0.1" 2>/dev/null - chmod 644 config/tls/zaino.key config/tls/zaino.crt + cp config/regtest/zallet.toml.example config/regtest/zallet.toml + cp config/regtest/zaino.toml.example config/regtest/zaino.toml + cp config/regtest/zebra.toml.example config/regtest/zebra.toml echo "# dummy identity for CI" > config/regtest/zallet_identity.txt SALT=$(openssl rand -hex 16) HASH=$(printf 'zebra' | openssl dgst -sha256 -mac HMAC -macopt "key:$SALT" | awk '{print $NF}') sed -i "s|__GENERATED_BY_INIT_SH__|${SALT}\$${HASH}|" config/regtest/zallet.toml - - name: Validate compose configs - run: | - docker compose config --quiet - docker compose --env-file .env.regtest config --quiet - docker compose --env-file .env.regtest --profile zcashd config --quiet - - name: Start Zebra (regtest) run: | docker compose --env-file .env.regtest up -d zebra - echo "Waiting for Zebra RPC..." + echo "Waiting for Zebra RPC on host port 29232..." for i in $(seq 1 60); do if curl -sf -X POST -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"getblockchaininfo","params":[],"id":1}' \ - http://127.0.0.1:18232 > /dev/null 2>&1; then + http://127.0.0.1:29232 > /dev/null 2>&1; then echo "Zebra is ready" break fi @@ -63,22 +105,22 @@ jobs: run: | curl -sf -X POST -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"getblockchaininfo","params":[],"id":1}' \ - http://127.0.0.1:18232 | jq . + http://127.0.0.1:29232 | jq . - name: Mine block 1 (required for Zaino sync) run: | curl -sf -u zebra:zebra -X POST -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"generate","params":[1],"id":1}' \ - http://127.0.0.1:18232 | jq . + http://127.0.0.1:29232 | jq . - name: Start Zaino and verify JSON-RPC proxy run: | docker compose --env-file .env.regtest up -d zaino - echo "Waiting for Zaino JSON-RPC..." + echo "Waiting for Zaino JSON-RPC on host port 28237..." for i in $(seq 1 30); do if RESULT=$(curl -sf -X POST -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"getblockchaininfo","params":[],"id":1}' \ - http://127.0.0.1:8237 2>/dev/null); then + http://127.0.0.1:28237 2>/dev/null); then echo "Zaino is ready" echo "$RESULT" | jq . break @@ -91,40 +133,87 @@ jobs: sleep 2 done - - name: Start zcashd comparator and verify RPC + - name: Verify Zallet accepts compose command + run: docker compose --env-file .env.regtest run --rm --no-deps zallet --help + + - name: Verify the cookie volume is attachable + # Regtest disables cookie auth (ZEBRA_RPC__ENABLE_COOKIE_AUTH=false) so + # Zebra does not write /auth/.cookie. This check only verifies that the + # published volume can be mounted. + run: | + docker run --rm -v "$COOKIE_VOLUME":/auth:ro alpine \ + sh -c 'test -d /auth && echo "Cookie volume mounted read-only at /auth"' + + - name: Verify the external network is attachable + run: | + docker run --rm --network "$EXTERNAL_NETWORK" alpine \ + sh -c 'apk add --no-cache bind-tools >/dev/null 2>&1 && nslookup zebra && nslookup zaino' + + - name: Collect logs on failure + if: failure() + run: | + echo "=== Container status ===" + docker compose --env-file .env.regtest ps 2>&1 || true + echo "=== Zebra logs ===" + docker compose --env-file .env.regtest logs zebra --tail 50 2>&1 || true + echo "=== Zaino logs ===" + docker compose --env-file .env.regtest logs zaino --tail 50 2>&1 || true + + cookie-permission-smoke: + # Regtest disables cookie auth, so the regtest smoke job does not exercise + # the cookie path. This job boots testnet Zebra and checks that the cookie + # file is readable by service runtime users. + name: Cookie permission smoke (testnet) + runs-on: ubuntu-latest + needs: contract-validation + # External cookie volume for the testnet stack. Keep aligned with + # z3-contract.yaml networks.testnet.volumes.cookie. + env: + COOKIE_VOLUME: z3-testnet-cookie + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Stage operator files for testnet boot run: | - docker compose --env-file .env.regtest --profile zcashd up -d zcashd - echo "Waiting for zcashd RPC..." + cp config/testnet/zallet.toml.example config/testnet/zallet.toml + cp config/testnet/zaino.toml.example config/testnet/zaino.toml + echo "# dummy identity for CI" > config/testnet/zallet_identity.txt + + - name: Start testnet zebra and cookie-permissions sidecar + # The sidecar chmods the cookie 0644 so services that mount the shared + # volume can read it. + run: | + docker compose --env-file .env.testnet up -d zebra cookie-permissions + echo "Waiting up to 60s for Zebra to write the cookie file..." for i in $(seq 1 30); do - if RESULT=$(curl -sf -u zebra:zebra -X POST -H "Content-Type: application/json" \ - -d '{"jsonrpc":"2.0","method":"getblockchaininfo","params":[],"id":1}' \ - http://127.0.0.1:38232 2>/dev/null); then - echo "zcashd is ready" - echo "$RESULT" | jq . + if docker run --rm -v "$COOKIE_VOLUME":/auth:ro alpine test -f /auth/.cookie 2>/dev/null; then + echo "Cookie file present after ~$((i*2))s" break fi if [ "$i" -eq 30 ]; then - echo "zcashd failed to start" - docker compose --env-file .env.regtest --profile zcashd logs zcashd --tail 50 + echo "Cookie file never appeared" + docker compose --env-file .env.testnet logs zebra --tail 50 exit 1 fi sleep 2 done + # Give the sidecar one polling cycle to chmod the cookie. + sleep 6 - - name: Verify Zallet accepts compose command - run: docker compose --env-file .env.regtest run --rm --no-deps zallet --help - - - name: Collect logs on failure - if: failure() + - name: Assert cookie file is readable by both Zaino (uid 1000) and Zallet (uid 1000) + # Test the runtime UIDs used by the services that read the shared + # cookie volume. run: | - echo "=== Zebra logs ===" - docker compose --env-file .env.regtest logs zebra --tail 50 2>&1 || true - echo "=== Zaino logs ===" - docker compose --env-file .env.regtest logs zaino --tail 50 2>&1 || true - echo "=== zcashd logs ===" - docker compose --env-file .env.regtest --profile zcashd logs zcashd --tail 50 2>&1 || true - echo "=== Container status ===" - docker compose --env-file .env.regtest --profile zcashd ps 2>&1 || true + docker run --rm --user 1000:1000 -v "$COOKIE_VOLUME":/auth:ro alpine \ + sh -c 'cat /auth/.cookie >/dev/null && echo "uid 1000 read OK"' + docker run --rm --user 10001:10001 -v "$COOKIE_VOLUME":/auth:ro alpine \ + sh -c 'cat /auth/.cookie >/dev/null && echo "uid 10001 read OK"' + + - name: Tear down + if: always() + run: docker compose --env-file .env.testnet down -v --remove-orphans 2>&1 || true rpc-router-tests: name: rpc-router cargo test diff --git a/.gitignore b/.gitignore index 8b6c56e..951fd23 100644 --- a/.gitignore +++ b/.gitignore @@ -19,21 +19,22 @@ target/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ -# ============================================================================= -# Config directory — track .default templates, ignore live copies and secrets +# Config directory: tracked defaults live in .example files; active config, +# identity files, and TLS keys are local and gitignored. # ============================================================================= config/** !config/.gitkeep -!config/zallet.toml.default -!config/zaino.toml -# TLS directory structure (certs are generated locally) -!config/tls/ -config/tls/* -!config/tls/.gitkeep -# Regtest configs +# Per-network: track only .example templates; live .toml files are local +!config/mainnet/ +!config/mainnet/zaino.toml.example +!config/mainnet/zallet.toml.example +!config/testnet/ +!config/testnet/zaino.toml.example +!config/testnet/zallet.toml.example !config/regtest/ -!config/regtest/zaino.toml -!config/regtest/zallet.toml.default +!config/regtest/zaino.toml.example +!config/regtest/zallet.toml.example +!config/regtest/zebra.toml.example # Runtime environment overrides (see .env.example for available variables) .env @@ -41,5 +42,13 @@ config/tls/* # Local comparator state .tmp/ -# Ignore docker compose overrides +# Mainnet auto-loads docker-compose.override.yml when present (Compose's native +# behavior; absent is not an error). Per-network overrides are opt-in via -f or +# COMPOSE_FILE, so they are gitignored too if an operator creates one. docker-compose.override.yml +docker-compose.testnet.override.yml +docker-compose.regtest.override.yml + +# Python bytecode caches (from scripts/validate-contract*.py) +__pycache__/ +*.pyc diff --git a/.gitmodules b/.gitmodules index 0175cc1..3d7a3e1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,6 +7,3 @@ [submodule "zallet"] path = zallet url = https://github.com/zcash/wallet -[submodule "zcashd"] - path = zcashd - url = https://github.com/zcash/zcash.git diff --git a/.vscode/settings.json b/.vscode/settings.json index ef0a046..f2f2775 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,8 @@ { - "yaml.customTags": ["!override sequence", "!reset sequence"] + "yaml.customTags": [ + "!override sequence", + "!override mapping", + "!reset sequence", + "!reset mapping" + ] } \ No newline at end of file diff --git a/README.md b/README.md index cea7445..033a535 100644 --- a/README.md +++ b/README.md @@ -1,127 +1,142 @@ -# Z3 — Unified Zcash Stack +# Z3: a Zcash node platform -Three services replacing the legacy `zcashd`: **Zebra** (full node), **Zaino** (indexer), and **Zallet** (wallet). Orchestrated via Docker Compose with sensible defaults that work out of the box. +Z3 runs **Zebra** (full node), **Zaino** (indexer), and **Zallet** (wallet) together with Docker Compose, on mainnet, testnet, or a local regtest network. -## Quick Start +Two kinds of people use Z3, and this README is split for them: -```bash -git clone https://github.com/ZcashFoundation/z3 && cd z3 +- **Operators** run the stack as infrastructure. Start at [Choose your network](#choose-your-network); everything you need to run Z3 is in that half. +- **Developers, testers, and researchers** build or test against Z3, often running several networks at once. Start at [Building and testing against Z3](#building-and-testing-against-z3). -# Seed config files and generate required credentials -cp -n config/zallet.toml.default config/zallet.toml -openssl req -x509 -newkey rsa:4096 -keyout config/tls/zaino.key -out config/tls/zaino.crt \ - -sha256 -days 365 -nodes -subj "/CN=localhost" \ - -addext "subjectAltName=DNS:localhost,DNS:zaino,IP:127.0.0.1" -rage-keygen -o config/zallet_identity.txt +## Prerequisites -# Start Zebra first — it must sync before other services can start -# Mainnet: 24-72 hours | Testnet: 2-12 hours -docker compose up -d zebra +- [Docker Engine](https://docs.docker.com/engine/install/) with [Docker Compose](https://docs.docker.com/compose/install/) (v2.24.4+) +- [rage](https://github.com/str4d/rage/releases) for generating Zallet encryption keys (`brew install rage` on macOS, or download a release) +- Git, to clone this repository +- `openssl`, only for regtest (it hashes the regtest wallet RPC password); pre-installed on macOS and most Linux distros -# Monitor sync (returns "ok" when ready) -curl http://localhost:8080/ready +## For operators -# Once Zebra is synced, start the full stack -docker compose up -d -``` +### Choose your network -Pre-built images for all 3 services are pulled automatically. No build step or submodule init needed. +Z3 runs as one of three independent Compose projects. Pick by what you are doing: -> [!WARNING] -> The TLS certificate, identity file, and zallet config must exist before running any `docker compose` command. Run the setup steps above first — if any file is missing, Compose will fail. +| Network | Use it for | First sync | Real funds | +|---------|-----------|------------|------------| +| **mainnet** | Production: the real Zcash chain | 24-72 hours | Yes | +| **testnet** | Staging against the public test network | 2-12 hours | No (test ZEC) | +| **regtest** | Local practice and development: instant blocks, no peers, no sync | Seconds | No | -> [!IMPORTANT] -> Zebra must sync the blockchain before Zaino and Zallet can start. Running `docker compose up -d` on a fresh install without syncing Zebra first will cause the other services to fail repeatedly. Start Zebra alone, wait for sync, then start the rest. +All three can run at once on one host; each gets its own ports and volumes. New to Z3? Start with **regtest** to watch the whole stack come up in seconds, then move to mainnet. -**Already have synced Zebra data?** Start everything immediately: +### Quick start -```bash -docker compose up -d -docker compose ps # verify all healthy -``` +Each network has a one-time setup step and a start step. -> [!TIP] -> **Apple Silicon users:** Set `DOCKER_PLATFORM=linux/arm64` for native Z3 services. The optional zcashd profile has a separate platform setting. +**Mainnet (production).** Zebra must sync the whole chain before Zaino and Zallet can serve clients, so the boot is two-phase: start Zebra, wait for the sync, then start the rest. -## Deployment Modes +```bash +git clone https://github.com/ZcashFoundation/z3 && cd z3 -The stack supports 3 network modes. Mainnet runs with zero configuration; testnet and regtest require a few overrides. +# 1. One-time setup: creates local config files and the Zallet wallet identity. +./scripts/setup-network.sh mainnet -| Mode | Command | Use case | -|------|---------|----------| -| **Mainnet** | `docker compose up -d` | Production, syncs the live Zcash blockchain | -| **Testnet** | Create `.env` with `NETWORK_NAME=Testnet` | Testing against the public test network | -| **Regtest** | `docker compose --env-file .env.regtest up -d` | Local development, instant blocks, no sync wait | +# 2. Start Zebra and wait for it to sync. The poller exits when Zebra is ready. +docker compose --env-file .env.mainnet up -d zebra +./scripts/check-zebra-readiness.sh -### Running Testnet +# 3. Start Zaino + Zallet once Zebra is synced. +docker compose --env-file .env.mainnet up -d +``` -Create a `.env` file with the variables that differ from mainnet: +Images are pulled automatically; no build step or submodule init is needed. -```bash -echo "NETWORK_NAME=Testnet" > .env -``` +> [!IMPORTANT] +> Running step 3 before Zebra reaches `/ready` makes Zaino and Zallet restart-loop until the sync catches up. The poller in step 2 exits only when Zebra is synced. -Update `config/zallet.toml` to set `network = "test"` in the `[consensus]` section, then start the stack normally. +Your edits to the per-network config under `config//` stay local and survive `git pull`. -### Running Regtest +**Testnet (public test network).** The same two-phase flow; pass the testnet health port to the poller: -Regtest uses a compose overlay (`docker-compose.regtest.yml`) that adds the rpc-router service, switches from cookie auth to username/password auth, and adjusts healthchecks for a peerless network. Volumes are automatically isolated via `COMPOSE_PROJECT_NAME=z3-regtest`. +```bash +./scripts/setup-network.sh testnet +docker compose --env-file .env.testnet up -d zebra +./scripts/check-zebra-readiness.sh 18080 +docker compose --env-file .env.testnet up -d +``` -First-time setup (**required** before starting the stack): +**Regtest (local practice and development).** Regtest mines blocks on demand with no peers, so it boots in seconds. One command does setup, starts Zebra, and mines the activation blocks: ```bash -# Initialize wallet, generate RPC auth, and mine the first block ./scripts/regtest-init.sh - -# Start the full regtest stack docker compose --env-file .env.regtest up -d ``` -The init script generates the Zallet RPC password hash, starts Zebra, mines the first block, and initializes the Zallet wallet. It is safe to re-run — it skips steps that are already done. Subsequent runs only need the last command. +After the first run, the `up -d` line alone is enough. See [docs/regtest.md](docs/regtest.md) for test commands and the full workflow. -See [docs/regtest.md](docs/regtest.md) for test commands (curl, grpcurl) and the full workflow reference. +### Where your data lives -### Optional zcashd Comparator +Each network keeps its chain state in a Docker named volume called `z3--chain`, which lands under Docker's data root (on Linux, `/var/lib/docker/volumes/`). Mainnet is roughly **300 GB**; size the disk before you start. -`zcashd` is available behind an opt-in Compose profile for local comparison tests. It is not part of the default Z3 stack, uses a separate data volume, exposes RPC on `http://localhost:38232`, and starts with public P2P disabled (`-listen=0 -connect=0`). +- **Find the path:** `docker volume inspect z3-mainnet-chain -f '{{.Mountpoint}}'` +- **Put it on another disk:** set `Z3_CHAIN_DATA_PATH=/mnt/ssd/zebra-state` and run `./scripts/fix-permissions.sh zebra /mnt/ssd/zebra-state` before the first start. +- **Back up the wallet:** the only data worth backing up is the wallet, and it needs **two** pieces kept together: the `z3--zallet` volume **and** `config//zallet_identity.txt` (the age key the wallet database is encrypted with). One without the other cannot be restored. Chain state is re-syncable and the cookie is regenerated, so neither needs backup. +- **Stop vs wipe:** `docker compose --env-file .env. down` stops the stack and keeps every volume; adding `-v` (`down -v`) deletes them, which means a full re-sync. -```bash -# Mainnet/testnet-style comparator -docker compose --profile zcashd up -d zcashd +The volume table and bind-mount details are in the [Reference](#reference) section. -# Regtest comparator with the regtest overlay -docker compose --env-file .env.regtest --profile zcashd up -d zcashd -``` +### Running in production -The default image is `zodlinc/zcashd:v6.12.1`. Default RPC credentials are `zebra` / `zebra`; override them with `ZCASHD_RPCUSER` and `ZCASHD_RPCPASSWORD`. For custom regtest runs, zcashd activation heights can be overridden with `ZCASHD_*_ACTIVATION_HEIGHT` variables; use a separate `Z3_ZCASHD_DATA_PATH` when changing them. +Z3 ships production-shaped defaults, but a few choices are yours to make before running mainnet for real: -### Monitoring Stack +- **Pick where the chain lives.** The default named volume sits under `/var/lib/docker` (~300 GB on mainnet). To use a dedicated disk, set `Z3_CHAIN_DATA_PATH` and run `fix-permissions.sh` before the first start (see [Where your data lives](#where-your-data-lives)). +- **Plan the wallet backup.** Keep the `z3--zallet` volume and `config//zallet_identity.txt` together; nothing else needs backup. +- **Set a log rotation policy.** Z3 does not pin a logging driver, so containers use your Docker daemon default. Add size limits in `/etc/docker/daemon.json` (see the [FAQ](docs/faq.md)); otherwise logs grow unbounded on a 24/7 node. +- **Decide p2p exposure.** Mainnet and testnet publish Zebra's p2p port for inbound peers. Behind NAT or a firewall, set `ZEBRA_NETWORK__EXTERNAL_ADDR` to the address peers should dial. Regtest is peerless and publishes no p2p. +- **Bound resources on a shared host.** No CPU or memory limits are set by default: right for a dedicated node, easy to get wrong on a shared box. Add `deploy.resources.limits` in an override file if you need them. -Prometheus, Grafana, Jaeger, and AlertManager are available behind a Docker Compose profile. Zebra metrics must be explicitly enabled for Prometheus to have data to scrape. +Z3 ships safe defaults: pinned image versions (no surprise upgrades), non-root containers with Linux capabilities dropped, health checks that hold the wallet back until the node is synced, and automatic restart. Upgrades stay deliberate: bump the version pin in a reviewed change, or set `Z3__IMAGE`. -Add to your `.env` (or `.env.regtest` for regtest): +### Monitoring + +Prometheus, Grafana, Jaeger, and AlertManager ship behind a Compose profile. Zebra's metrics are on by default at the in-network `zebra:9999` scrape target. ```bash -ZEBRA_METRICS__ENDPOINT_ADDR=0.0.0.0:9999 +docker compose --env-file .env. --profile monitoring up -d ``` -Then start with the monitoring profile: +Default UI host ports are globally unique across the three networks (mainnet Grafana `3000`, testnet `13000`, regtest `23000`, and so on). Each is overridable: `Z3_GRAFANA_PORT`, `Z3_PROMETHEUS_PORT`, `Z3_ALERTMANAGER_PORT`, `Z3_JAEGER_UI_PORT`. -```bash -# Mainnet/Testnet -docker compose --profile monitoring up -d +### Stopping the stack -# Regtest -docker compose --env-file .env.regtest --profile monitoring up -d +```bash +docker compose --env-file .env.mainnet down # stop containers, keep data +docker compose --env-file .env.mainnet down -v # stop and delete all volumes (full reset) ``` -Access Grafana at `http://localhost:3000` (admin/admin), Prometheus at `http://localhost:9094`, and Jaeger at `http://localhost:16686`. +## Building and testing against Z3 + +You do not need this section to operate Z3. It covers running several networks for test scenarios, how the networks differ, the published service endpoints, and attaching your own services. + +### How the networks differ + +Operators usually run one network; testers often run several and need to know where they diverge from a deployment standpoint: -## Architecture +| Aspect | mainnet | testnet | regtest | +|--------|---------|---------|---------| +| Peers / p2p | Real peers; p2p published (`8233`) | Test peers; p2p published (`18233`) | Peerless; no p2p | +| Blocks | From the network | From the network | Mined on demand (`generate`) | +| Sync wait before use | Hours to days | Hours | None | +| RPC auth | Cookie file | Cookie file | Username / password | +| In-network Zebra RPC | `zebra:8232` | `zebra:18232` | `zebra:18232` | +| Coexists on one host with | testnet, regtest | mainnet, regtest | mainnet, testnet | + +Mainnet pairs cleanly with either other network on one host. Testnet and regtest reuse Zebra's testnet container ports, so they differ only by published host port (the env files keep those unique). The rpc-router, a unified Zebra+Zallet JSON-RPC endpoint, runs only on regtest today. + +### Architecture ```mermaid graph LR - subgraph Z3["Z3 Stack"] + subgraph Z3["Z3 (per network)"] Zebra["Zebra
(full node)"] Zaino["Zaino
(indexer)"] @@ -130,59 +145,61 @@ graph LR end end - Zebra -->|JSON-RPC :18232| Zaino - Zebra -->|JSON-RPC :18232| EmbeddedZaino - Zaino -->|gRPC :8137| LightClients["Light wallet
clients"] + Zebra -->|JSON-RPC| Zaino + Zebra -->|JSON-RPC| EmbeddedZaino + Zaino -->|gRPC| LightClients["Light wallet
clients"] ``` -**Zebra** syncs and validates the Zcash blockchain. **Zaino** provides a lightwalletd-compatible gRPC interface for light wallet clients like Zingo. **Zallet** embeds Zaino's indexer libraries internally and connects directly to Zebra's JSON-RPC; it does not use the standalone Zaino service. - -All images can be overridden via environment variables (`ZEBRA_IMAGE`, `ZAINO_IMAGE`, `ZALLET_IMAGE`, `ZCASHD_IMAGE`). See `.env.example` for all available options. +**Zebra** syncs and validates the Zcash blockchain. **Zaino** provides a lightwalletd-compatible gRPC interface for light wallet clients. **Zallet** embeds Zaino's indexer libraries internally and connects directly to Zebra's JSON-RPC; it does not use the standalone Zaino service. -| Service | Default Image | Source | -|---------|---------------|--------| -| Zebra | `zfnd/zebra:latest` | [ZcashFoundation/zebra](https://github.com/ZcashFoundation/zebra) | -| Zaino | `ghcr.io/zcashfoundation/zaino:sha-83e41d7` | [zingolabs/zaino](https://github.com/zingolabs/zaino) | -| Zallet | `electriccoinco/zallet:v0.1.0-alpha.3` | [zcash/wallet](https://github.com/zcash/wallet) | -| zcashd | `zodlinc/zcashd:v6.12.1` | [zcash/zcash](https://github.com/zcash/zcash) | +Image pins live as `${VAR:-tag}` defaults in `docker-compose.yml`; override any pin with `Z3_ZEBRA_IMAGE`, `Z3_ZAINO_IMAGE`, or `Z3_ZALLET_IMAGE`. Upstream sources: [Zebra](https://github.com/ZcashFoundation/zebra), [Zaino](https://github.com/zingolabs/zaino), [Zallet](https://github.com/zcash/wallet). -## Prerequisites +### Service endpoints -- [Docker Engine](https://docs.docker.com/engine/install/) with [Docker Compose](https://docs.docker.com/compose/install/) (v2.24.0+) -- [rage](https://github.com/str4d/rage/releases) for generating Zallet encryption keys -- Git for cloning the repository +Published host ports are chosen per `.env.` so all networks coexist on one host. The full per-network matrix lives in [`z3-contract.yaml`](z3-contract.yaml). -> [!NOTE] -> **Linux users** may need `sudo` for Docker commands, or add your user to the `docker` group. See [Docker's post-installation steps](https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user). +| Service | Endpoint shape | Env var | +|---------|----------------|---------| +| Zebra RPC | `http://localhost:` | `Z3_ZEBRA_HOST_RPC_PORT` | +| Zebra p2p (inbound peers) | `localhost:` | `Z3_ZEBRA_HOST_P2P_PORT` | +| Zebra health | `http://localhost:/ready` | `Z3_ZEBRA_HOST_HEALTH_PORT` | +| Zaino gRPC (plaintext, no TLS) | `localhost:` | `Z3_ZAINO_HOST_GRPC_PORT` | +| Zaino JSON-RPC | `http://localhost:` | `Z3_ZAINO_HOST_JSON_RPC_PORT` | +| Zallet RPC | `http://localhost:` | `Z3_ZALLET_HOST_RPC_PORT` | -## Service Endpoints +Inside the network, services resolve by DNS name (`zebra`, `zaino`, `zallet`) on Zebra's per-network container ports. -Once running, services are available at: +### Attaching your own services -| Service | Endpoint | Default Port | -|---------|----------|-------------| -| Zebra RPC | `http://localhost:18232` | `Z3_ZEBRA_HOST_RPC_PORT` | -| Zebra Health | `http://localhost:8080/ready` | `Z3_ZEBRA_HOST_HEALTH_PORT` | -| Zaino gRPC | `localhost:8137` | `ZAINO_HOST_GRPC_PORT` | -| Zaino JSON-RPC | `http://localhost:8237` | `ZAINO_HOST_JSONRPC_PORT` | -| Zallet RPC | `http://localhost:28232` | `ZALLET_HOST_RPC_PORT` | -| zcashd RPC | `http://localhost:38232` | `ZCASHD_HOST_RPC_PORT` | +> Operators can skip this. It exists for downstream services, wallets, and tooling that attach to a running Z3 stack, and for agents that consume the API programmatically. -## Stopping the Stack +Z3 publishes a stable set of identifiers (network names, volume names, ports, and auth surfaces) so your service can attach by name and keep working across networks. A mainnet or testnet peer attaches over the external network and reads the RPC cookie from the shared volume: -```bash -docker compose down # stop containers, keep data -docker compose down -v # stop and delete all volumes (full reset) +```yaml +networks: + z3: + external: true + name: z3-testnet +volumes: + z3-cookie: + external: true + name: z3-testnet-cookie +services: + my-app: + networks: [z3] + volumes: + - z3-cookie:/var/run/auth:ro + environment: + ZEBRA_RPC_URL: http://zebra:18232 + ZEBRA_COOKIE_PATH: /var/run/auth/.cookie ``` ---- +Regtest uses username/password instead of cookie auth; do not mount `z3-regtest-cookie` expecting a readable cookie. For the three attachment patterns (Compose peer, host-side pointer, lightwalletd client), see [docs/integrations/](docs/integrations/). For the full identifier inventory and stability promise, see [docs/contract.md](docs/contract.md) and [`z3-contract.yaml`](z3-contract.yaml). ## Reference -The sections below cover setup details, configuration, and operational topics. Expand the section you need. -
-System Requirements +System requirements ### Minimum @@ -198,146 +215,134 @@ The sections below cover setup details, configuration, and operational topics. E - **Disk:** 500+ GB with room for blockchain growth - **Network:** 100+ Mbps, ~300 GB/month bandwidth -### Sync Times +### Sync times | Network | First sync | With existing data | |---------|-----------|-------------------| | Mainnet | 24-72 hours | Minutes | | Testnet | 2-12 hours | Minutes | -Based on [Zebra's official requirements](https://zebra.zfnd.org/user/requirements.html). Zaino adds additional resource overhead; specific requirements are under determination. +Based on [Zebra's official requirements](https://zebra.zfnd.org/user/requirements.html).
-Setup Details +Setup details ### Submodules -Pre-built images are used by default. To build from source instead: +Pre-built images are used by default. To build from source: ```bash git submodule update --init --recursive -docker compose build +docker compose --env-file .env.mainnet build ``` -### TLS Certificates (Required) +### First-run setup (`setup-network.sh`) -Zaino's gRPC endpoint uses TLS. Generate a self-signed certificate: +`./scripts/setup-network.sh ` is idempotent and does everything needed before the first `docker compose up`: -```bash -openssl req -x509 -newkey rsa:4096 \ - -keyout config/tls/zaino.key -out config/tls/zaino.crt \ - -sha256 -days 365 -nodes -subj "/CN=localhost" \ - -addext "subjectAltName=DNS:localhost,DNS:zaino,IP:127.0.0.1" -``` - -For production, use certificates from a trusted CA. +- Copies `config//zallet.toml.example` -> `config//zallet.toml` (local, gitignored) +- Copies `config//zaino.toml.example` → `config//zaino.toml` (same) +- Generates `config//zallet_identity.txt` via `rage-keygen` if missing -### Zallet Identity File (Required) - -```bash -rage-keygen -o config/zallet_identity.txt -``` +Subsequent runs print which steps were skipped. Back up `zallet_identity.txt` together with the `z3--zallet` volume; without the identity file the wallet database cannot be decrypted. -Back up this file and the public key printed to the terminal. +### Per-network Zallet config -### Zallet Configuration +Zallet config lives at `config//zallet.toml` (local, gitignored). The tracked `.example` template carries the default. The `[indexer]` section points at the per-network Zebra RPC port (8232 mainnet, 18232 testnet, 18232 regtest). Edit your live `.toml` freely; pulls won't conflict. -Review `config/zallet.toml` and set the network in the `[consensus]` section: +To compare your copy against a refreshed template after `git pull`: -- Mainnet: `network = "main"` -- Testnet: `network = "test"` - -Zallet embeds Zaino's indexer libraries and connects directly to Zebra's JSON-RPC endpoint. - -Critical requirements for `config/zallet.toml`: +```bash +diff config/mainnet/zallet.toml config/mainnet/zallet.toml.example +``` -- `validator_address` must point to `zebra:18232` (Zebra's JSON-RPC), not `zaino:8137` -- All TOML sections must be present: `[builder]`, `[consensus]`, `[database]`, `[external]`, `[features]`, `[indexer]`, `[keystore]`, `[note_management]`, `[rpc]` -- Cookie authentication must be configured in both TOML and mounted as a volume +### Platform configuration (ARM64) -### Platform Configuration (ARM64) +Zebra is multi-arch; Docker picks the host's native arch automatically, no override needed. Zaino and Zallet are pinned to `linux/amd64` because their upstream images publish amd64 only. On Apple Silicon those two run under emulation by default; the workload is light enough that this rarely matters. -Z3 defaults to AMD64 for consistency. On Apple Silicon or ARM64 Linux, enable native Zebra, Zaino, and Zallet images: +To run Zaino and Zallet natively on arm64, build them locally from the submodules: ```bash -echo "DOCKER_PLATFORM=linux/arm64" >> .env +git submodule update --init --recursive +DOCKER_PLATFORM=linux/arm64 docker compose --env-file .env.mainnet build zaino zallet +docker compose --env-file .env.mainnet up -d ``` -The optional zcashd service has a separate `ZCASHD_DOCKER_PLATFORM` setting. Keep the default `linux/amd64` when using `zodlinc/zcashd:v6.12.1`; Docker Desktop runs it through emulation on Apple Silicon. Only set `ZCASHD_DOCKER_PLATFORM=linux/arm64` when `ZCASHD_IMAGE` points to an arm64-capable image. -
-Configuration Reference +Configuration reference -### Defaults-in-Compose +### Defaults in compose -Every variable in `docker-compose.yml` has a default via `${VAR:-default}`. The stack works with zero configuration files. Create `.env` only to override specific values. +Every variable in `docker-compose.yml` has a default via `${VAR:-default}`. The stack works with zero configuration files; the per-network env files override only what differs from mainnet. Precedence (highest wins): 1. Shell environment variables -2. `.env` file values -3. Compose file defaults +2. `--env-file ` arguments +3. `.env` file values (auto-loaded) +4. Compose file defaults -### Variable Naming +### Variable naming -Variables follow a 3-tier naming system to avoid collisions: +Two namespaces keep stack-level settings separate from service-native settings: -| Prefix | Scope | Example | -|--------|-------|---------| -| `Z3_*` | Infrastructure (volumes, ports); never passed to containers | `Z3_ZEBRA_DATA_PATH` | -| Unprefixed | Shared config, remapped per service in compose | `NETWORK_NAME`, `ENABLE_COOKIE_AUTH` | -| `ZEBRA_*`, `ZAINO_*`, `ZALLET_*` | Service-specific application config | `ZEBRA_TRACING__FILTER` | +| Namespace | Scope | Examples | +|-----------|-------|----------| +| `Z3_*` | Stack-level settings: port matrix, image pins, volume paths, per-service log split, monitoring port matrix | `Z3_NETWORK`, `Z3_ZEBRA_HOST_RPC_PORT`, `Z3_ZEBRA_IMAGE`, `Z3_ZEBRA_RUST_LOG` | +| `ZEBRA_*` / `ZAINO_*` | Service-native config-rs vars (double-underscore is config-rs nesting) | `ZEBRA_RPC__ENABLE_COOKIE_AUTH`, `ZEBRA_HEALTH__MIN_CONNECTED_PEERS` | +| `DOCKER_PLATFORM`, `COMPOSE_*`, `RUST_LOG` | Ecosystem / shell standards | `DOCKER_PLATFORM=linux/arm64` | -### Common Overrides +z3 sets the service-internal vars (`ZEBRA_RPC__LISTEN_ADDR`, `ZAINO_VALIDATOR_SETTINGS__*`, etc.) inside the compose `environment:` blocks based on the public knobs above. Operators should not set those directly. -```bash -# Network -NETWORK_NAME=Testnet +### Common overrides -# Log levels +```bash +# Per-service log levels Z3_ZEBRA_RUST_LOG=debug -ZAINO_RUST_LOG=debug +Z3_ZAINO_RUST_LOG=debug + +# Pin a different image version +Z3_ZEBRA_IMAGE=zfnd/zebra:5.0.0 -# Ports -Z3_ZEBRA_HOST_RPC_PORT=28232 -ZAINO_HOST_GRPC_PORT=9137 +# Move chain state to an external SSD +Z3_CHAIN_DATA_PATH=/mnt/ssd/zebra-state -# Images -ZEBRA_IMAGE=zfnd/zebra:5.0.0 +# Disable Zebra cookie auth (advanced; native Zebra config-rs var) +ZEBRA_RPC__ENABLE_COOKIE_AUTH=false ``` -See `.env.example` for all available variables. +When using the documented `--env-file .env.` commands, put these in the shell environment or pass `.env` as a second `--env-file`; Compose does not auto-load `.env` in that mode. See `.env.example` for the full reference.
-Data Storage and Volumes +Data storage and volumes -### Docker Named Volumes (Default) +### Docker named volumes (default) -The stack uses Docker-managed named volumes by default: +Z3 declares each volume with an explicit `name:` so the external Docker identifier is `${COMPOSE_PROJECT_NAME}-`. Volume contents per network: -| Volume | Contents | +| Suffix | Contents | |--------|----------| -| `zebra_data` | Blockchain state (~300 GB mainnet, ~30 GB testnet) | -| `zaino_data` | Indexer database | -| `zallet_data` | Wallet database | -| `zcashd_data` | Optional zcashd comparator chain state | -| `shared_cookie_volume` | RPC authentication cookies | +| `chain` | Zebra blockchain state (~300 GB mainnet, ~30 GB testnet) | +| `zaino` | Zaino indexer database | +| `zallet` | Zallet wallet database (contains keys) | +| `cookie` | RPC authentication cookie for mainnet/testnet (regtest disables cookie auth) | -### Local Directories (Advanced) +Example concrete names: `z3-mainnet-chain`, `z3-testnet-cookie`, `z3-regtest-zallet`. + +### Local directories (advanced) For backups, external SSDs, or shared storage, override volume paths in `.env`: ```bash -Z3_ZEBRA_DATA_PATH=/mnt/ssd/zebra-state +Z3_CHAIN_DATA_PATH=/mnt/ssd/zebra-state Z3_ZAINO_DATA_PATH=/mnt/ssd/zaino-data Z3_ZALLET_DATA_PATH=/mnt/ssd/zallet-data -Z3_ZCASHD_DATA_PATH=/mnt/ssd/zcashd-data ``` Fix permissions before starting: @@ -346,78 +351,72 @@ Fix permissions before starting: ./scripts/fix-permissions.sh zebra /mnt/ssd/zebra-state ./scripts/fix-permissions.sh zaino /mnt/ssd/zaino-data ./scripts/fix-permissions.sh zallet /mnt/ssd/zallet-data -./scripts/fix-permissions.sh zcashd /mnt/ssd/zcashd-data ``` -Zebra, Zaino, Zallet, and zcashd each run as a specific non-root user. Directories must have correct ownership (set by the script) and 700 permissions. Never use 755 or 777. +Zebra, Zaino, and Zallet each run as a specific non-root user. Directories must have correct ownership (set by the script) and `700` permissions. Never use `755` or `777`.
-Health Checks and Sync Strategy +Health checks and sync strategy -### Two-Phase Deployment +### Two-phase deployment Zebra's blockchain sync takes hours to days. Docker Compose healthcheck timeouts cannot accommodate this, so the stack uses a two-phase approach: -1. Start Zebra alone (`docker compose up -d zebra`) -2. Wait for sync (`curl http://localhost:8080/ready` returns "ok") -3. Start the full stack (`docker compose up -d`) +1. Start Zebra alone: `docker compose --env-file .env.mainnet up -d zebra` +2. Wait for sync: `curl http://localhost:8080/ready` returns `ok` +3. Start the full stack: `docker compose --env-file .env.mainnet up -d` -### Health Endpoints +### Health endpoints -Zebra exposes 2 endpoints on port 8080: +Zebra exposes two endpoints on its health port: | Endpoint | Returns 200 when | Use for | |----------|-------------------|---------| -| `/healthy` | Has minimum peer connections | Liveness monitoring, restart decisions | +| `/healthy` | Minimum peer connections present | Liveness monitoring, restart decisions | | `/ready` | Synced within 2 blocks of tip | Production readiness, dependency gating | -### Service Dependency Chain +### Service dependency chain ``` -Zebra (/ready — synced near tip) - → Zaino (gRPC port responding) - → Zallet (RPC responding) +Zebra (/ready: synced near tip) + -> Cookie permissions (.cookie readable on cookie-auth networks) + -> Zaino (gRPC port responding) + -> Zallet (RPC responding) ``` -The default compose configuration gates Zaino and Zallet on Zebra's `/ready` endpoint. For development, copy `docker-compose.override.yml.example` to `docker-compose.override.yml` to switch to `/healthy` (allows services to start during sync, but they may error until Zebra catches up). - -| Mode | Zebra healthcheck | Behavior | -|------|-------------------|----------| -| **Production** (default) | `/ready` | Two-phase: sync Zebra first, then start stack | -| **Development** (override) | `/healthy` | Start immediately; services may error during sync | +The default compose gates Zaino and Zallet on Zebra's `/ready` endpoint and on the cookie-permissions sidecar when cookie auth is enabled. For development, copy `docker-compose.override.yml.example` to `docker-compose.override.yml` to switch Zebra gating to `/healthy` (allows services to start during sync, but they may error until Zebra catches up). -### Monitoring Sync Progress +### Monitoring sync progress ```bash -curl http://localhost:8080/ready # "ok" when synced -docker compose logs -f zebra # watch logs -./scripts/check-zebra-readiness.sh # polls until synced, prints status every 30s +curl http://localhost:8080/ready # "ok" when synced +docker compose --env-file .env.mainnet logs -f zebra +./scripts/check-zebra-readiness.sh # polls until synced, prints status every 30s ``` -What to expect during sync: -- Zebra shows `healthy (starting)` while syncing (during the 90-second grace period) -- Once synced, `/ready` returns `ok` and Zebra shows `healthy` -- Zaino and Zallet remain in `waiting` state until Zebra is ready +### Tuning health checks -### Tuning Health Checks +Three Zebra healthcheck thresholds are operator-tunable. Defaults live in `docker-compose.yml`; override in `.env`: ```bash -# Adjust how many blocks behind the tip is acceptable (default: 2) -ZEBRA_HEALTH__READY_MAX_BLOCKS_BEHIND=2 +# How far behind tip /ready tolerates (raise during catch-up syncs) +ZEBRA_HEALTH__READY_MAX_BLOCKS_BEHIND=10 -# Minimum peer connections for /healthy (default: 1, set 0 for regtest) -ZEBRA_HEALTH__MIN_CONNECTED_PEERS=1 +# Minimum peers required for /healthy (set 0 for regtest) +ZEBRA_HEALTH__MIN_CONNECTED_PEERS=3 -# Make /ready always return 200 on testnet even during sync (default: false) -ZEBRA_HEALTH__ENFORCE_ON_TEST_NETWORKS=false +# Make /ready always return 200 on testnet even during sync +ZEBRA_HEALTH__ENFORCE_ON_TEST_NETWORKS=true ```
-## Further Reading +## Further reading -- [docs/docker-architecture.md](docs/docker-architecture.md): Design decisions, Compose patterns, and rationale behind the stack's configuration -- [docs/regtest.md](docs/regtest.md): Regtest environment setup, test commands (curl, grpcurl), and workflow reference -- [.env.example](.env.example): All available environment variable overrides +- **Operators:** [docs/faq.md](docs/faq.md) (the "For operators" section) +- **Developers and testers:** [docs/faq.md](docs/faq.md) (the "For developers and testers" section), [docs/regtest.md](docs/regtest.md), [docs/integrations/](docs/integrations/) +- **Integrators and agents:** [docs/contract.md](docs/contract.md), [`z3-contract.yaml`](z3-contract.yaml) +- **Internals:** [docs/docker-architecture.md](docs/docker-architecture.md): Compose patterns, overlay merge rules, security hardening rationale +- **Every env var:** [.env.example](.env.example) diff --git a/config/mainnet/zaino.toml.example b/config/mainnet/zaino.toml.example new file mode 100644 index 0000000..7babd6d --- /dev/null +++ b/config/mainnet/zaino.toml.example @@ -0,0 +1,5 @@ +# Zaino configuration: Mainnet. +# +# All runtime settings come from environment variables in docker-compose.yml. +# This file exists so the compose mount has a target; do not add values here +# unless an operator-specific override is needed that env vars cannot express. diff --git a/config/mainnet/zallet.toml.example b/config/mainnet/zallet.toml.example new file mode 100644 index 0000000..cee7340 --- /dev/null +++ b/config/mainnet/zallet.toml.example @@ -0,0 +1,49 @@ +# Zallet configuration: Mainnet. +# +# Zallet embeds Zaino's indexer libraries and connects directly to Zebra's +# JSON-RPC endpoint. It does NOT use the standalone Zaino service (which +# serves gRPC to external light-wallet clients). +# +# For full configuration options: `zallet example-config`. + +[builder] +# Transaction builder: defaults. + +[builder.limits] +# Transaction limits: defaults. + +[consensus] +network = "main" + +[database] +# Wallet database at /wallet.db. + +[external] +# External settings: defaults. + +[features] +as_of_version = "0.1.0-alpha.3" + +[features.deprecated] + +[features.experimental] + +[indexer] +# Mainnet Zebra RPC port: 8232 (Zebra's canonical mainnet port). The service +# DNS name `zebra` resolves on the z3-mainnet network. +validator_address = "zebra:8232" +validator_cookie_path = "/var/run/auth/.cookie" + +[keystore] +# Age encryption identity file, mounted by docker-compose from +# ${Z3_CONFIG_DIR}/zallet_identity.txt. +encryption_identity = "/etc/zallet/identity.txt" + +[note_management] +# Note management: defaults. + +[rpc] +# Zallet RPC listen address inside the container. +# Default port 28232; for a different host port set Z3_ZALLET_HOST_RPC_PORT. +bind = ["0.0.0.0:28232"] +timeout = 30 diff --git a/config/regtest/zaino.toml b/config/regtest/zaino.toml.example similarity index 100% rename from config/regtest/zaino.toml rename to config/regtest/zaino.toml.example diff --git a/config/regtest/zallet.toml.default b/config/regtest/zallet.toml.default deleted file mode 100644 index 8e0f42b..0000000 --- a/config/regtest/zallet.toml.default +++ /dev/null @@ -1,44 +0,0 @@ -[builder.limits] -orchard_actions = 250 - -[consensus] -network = "regtest" -regtest_nuparams = [ - "5ba81b19:1", - "76b809bb:1", - "2bb40e60:1", - "f5b9230b:1", - "e9ff75a6:1", - "c2d6d0b4:1", -] - -[database] - -[external] - -[features] -as_of_version = "0.0.0" - -[features.deprecated] - -[features.experimental] - -[indexer] -validator_address = "zebra:18232" -validator_user = "zebra" -validator_password = "zebra" - -[keystore] -encryption_identity = "/etc/zallet/identity.txt" - -[note_management] - -[rpc] -bind = ["0.0.0.0:28232"] -timeout = 30 - -[[rpc.auth]] -user = "zebra" -# Generated by scripts/regtest-init.sh from RPC_PASSWORD (default: "zebra"). -# Run ./scripts/regtest-init.sh to generate after cloning. -pwhash = "__GENERATED_BY_INIT_SH__" diff --git a/config/regtest/zallet.toml.example b/config/regtest/zallet.toml.example new file mode 100644 index 0000000..a24770b --- /dev/null +++ b/config/regtest/zallet.toml.example @@ -0,0 +1,54 @@ +[builder.limits] +orchard_actions = 250 + +[consensus] +network = "regtest" +# NU activation heights must match Zebra's regtest config in +# config/regtest/zebra.toml and Zaino's hardcoded ActivationHeights +# (Canopy at 1, NU5 at 2, NU6 at 2, NU6.1 at 1000). Diverging in either +# direction breaks the wallet's view of the chain. NU6.2 is intentionally +# absent: zcash_protocol 0.7.2 (this Zallet's dep) has no NU6.2 branch id, +# so a "5437f330:..." entry would fail to parse ("Unknown consensus branch ID"). +regtest_nuparams = [ + "5ba81b19:1", # Overwinter + "76b809bb:1", # Sapling + "2bb40e60:1", # Blossom + "f5b9230b:1", # Heartwood + "e9ff75a6:1", # Canopy + "c2d6d0b4:2", # NU5 (Orchard); matches Zaino's hardcoded expectation + "c8e71055:2", # NU6; matches Zaino's hardcoded NU6=2 and Zebra's NU6=2 + "4dec4df0:1000", # NU6.1; matches Zaino's hardcoded NU6.1=1000 +] + +[database] + +[external] + +[features] +as_of_version = "0.1.0-alpha.3" + +[features.deprecated] + +[features.experimental] + +[indexer] +validator_address = "zebra:18232" +validator_user = "zebra" +validator_password = "zebra" + +[keystore] +encryption_identity = "/etc/zallet/identity.txt" + +[note_management] + +[rpc] +# Zallet RPC listen address inside the container. +# Default port 28232; for a different host port set Z3_ZALLET_HOST_RPC_PORT. +bind = ["0.0.0.0:28232"] +timeout = 30 + +[[rpc.auth]] +user = "zebra" +# Generated by scripts/regtest-init.sh from RPC_PASSWORD (default: "zebra"). +# Run ./scripts/regtest-init.sh to generate after cloning. +pwhash = "__GENERATED_BY_INIT_SH__" diff --git a/config/regtest/zebra.toml.example b/config/regtest/zebra.toml.example new file mode 100644 index 0000000..2181d9f --- /dev/null +++ b/config/regtest/zebra.toml.example @@ -0,0 +1,22 @@ +# Zebra regtest configuration. `scripts/setup-network.sh regtest` copies this +# template into a live gitignored config file on first run. +# +# Zebra's default regtest stops at Canopy. Activating NU5, NU6, and NU6.1 +# here matches Zaino's hardcoded regtest ActivationHeights +# (ZEBRAD_DEFAULT_ACTIVATION_HEIGHTS: NU5=2, NU6=2, NU6.1=1000) so its sync +# loop can read the chain Zebra produces. zebrad reads ~/.config/zebrad.toml +# automatically. NU6.1 must be 1000 (not 2) to match Zaino's chain view. + +[network] +network = "Regtest" + +[network.testnet_parameters.activation_heights] +BeforeOverwinter = 1 +Overwinter = 1 +Sapling = 1 +Blossom = 1 +Heartwood = 1 +Canopy = 1 +NU5 = 2 +NU6 = 2 +"NU6.1" = 1000 diff --git a/config/testnet/zaino.toml.example b/config/testnet/zaino.toml.example new file mode 100644 index 0000000..4f77757 --- /dev/null +++ b/config/testnet/zaino.toml.example @@ -0,0 +1,5 @@ +# Zaino configuration: Testnet. +# +# All runtime settings come from environment variables in docker-compose.yml. +# This file exists so the compose mount has a target; do not add values here +# unless an operator-specific override is needed that env vars cannot express. diff --git a/config/testnet/zallet.toml.example b/config/testnet/zallet.toml.example new file mode 100644 index 0000000..5018f0c --- /dev/null +++ b/config/testnet/zallet.toml.example @@ -0,0 +1,49 @@ +# Zallet configuration: Testnet. +# +# Zallet embeds Zaino's indexer libraries and connects directly to Zebra's +# JSON-RPC endpoint. It does NOT use the standalone Zaino service (which +# serves gRPC to external light-wallet clients). +# +# For full configuration options: `zallet example-config`. + +[builder] +# Transaction builder: defaults. + +[builder.limits] +# Transaction limits: defaults. + +[consensus] +network = "test" + +[database] +# Wallet database at /wallet.db. + +[external] +# External settings: defaults. + +[features] +as_of_version = "0.1.0-alpha.3" + +[features.deprecated] + +[features.experimental] + +[indexer] +# Testnet Zebra RPC port: 18232 (Zebra's canonical testnet port). The service +# DNS name `zebra` resolves on the z3-testnet network. +validator_address = "zebra:18232" +validator_cookie_path = "/var/run/auth/.cookie" + +[keystore] +# Age encryption identity file, mounted by docker-compose from +# ${Z3_CONFIG_DIR}/zallet_identity.txt. +encryption_identity = "/etc/zallet/identity.txt" + +[note_management] +# Note management: defaults. + +[rpc] +# Zallet RPC listen address inside the container. +# Default port 28232; for a different host port set Z3_ZALLET_HOST_RPC_PORT. +bind = ["0.0.0.0:28232"] +timeout = 30 diff --git a/config/tls/.gitkeep b/config/tls/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/config/zaino.toml b/config/zaino.toml deleted file mode 100644 index d4c0bc7..0000000 --- a/config/zaino.toml +++ /dev/null @@ -1 +0,0 @@ -# Minimal Zaino config - most settings come from environment variables diff --git a/config/zallet.toml.default b/config/zallet.toml.default deleted file mode 100644 index 0ad39cd..0000000 --- a/config/zallet.toml.default +++ /dev/null @@ -1,50 +0,0 @@ -# Zallet Configuration for Z3 Stack -# -# Zallet embeds Zaino's indexer libraries and connects to Zebra's JSON-RPC endpoint. -# It does NOT connect to the standalone Zaino service (which is for gRPC clients). -# For full configuration options, run: zallet example-config - -[builder] -# Transaction builder settings - using defaults - -[builder.limits] -# Transaction limits - using defaults - -[consensus] -# Network must match NETWORK_NAME in .env (Mainnet/Testnet) -network = "main" - -[database] -# Wallet database - using defaults (wallet.db in datadir) - -[external] -# External settings - using defaults - -[features] -as_of_version = "0.1.0-alpha.3" - -[features.deprecated] -# No deprecated features enabled - -[features.experimental] -# No experimental features enabled - -[indexer] -# Zallet's embedded Zaino indexer libraries connect to Zebra's JSON-RPC endpoint -# to fetch blockchain data. The validator_address MUST point to Zebra (not the -# standalone Zaino service). Service name 'zebra' and port from Z3_ZEBRA_RPC_PORT in .env -validator_address = "zebra:18232" -# Cookie authentication path (matches Zebra's ENABLE_COOKIE_AUTH=true) -validator_cookie_path = "/var/run/auth/.cookie" - -[keystore] -# Age encryption identity file (mounted from ./config/zallet_identity.txt) -encryption_identity = "/etc/zallet/identity.txt" - -[note_management] -# Note management - using defaults - -[rpc] -# Bind port must match ZALLET_RPC_PORT in .env if overridden (both must change together) -bind = ["0.0.0.0:28232"] -timeout = 30 diff --git a/docker-compose.regtest.yml b/docker-compose.regtest.yml index 27f6c54..e7c68a4 100644 --- a/docker-compose.regtest.yml +++ b/docker-compose.regtest.yml @@ -1,106 +1,79 @@ -# Regtest overlay — merged on top of docker-compose.yml +# Regtest overlay merged on top of docker-compose.yml. # -# Usage: docker compose --env-file .env.regtest up -d -# Or: ./scripts/regtest-init.sh (first time) +# Usage: +# docker compose --env-file .env.regtest up -d +# ./scripts/regtest-init.sh (first time, to generate keys + mine block 1) # -# This file only contains regtest-specific structural differences. -# All other config (network, ports, volumes) comes from .env.regtest -# overriding the defaults in the base docker-compose.yml. +# Only contains regtest-specific structural overrides. Network selection, +# port matrix, project name, and config dir come from .env.regtest. services: zebra: + # ZEBRA_MINING__MINER_ADDRESS is required for regtest mining. The :? guard + # fails the compose substitution with a clear message if the var is unset, + # rather than passing an empty string that crashes Zebra at startup + # (config-rs treats empty strings as values; ZcashAddress::from_str("") + # is a parse error). environment: - ZEBRA_MINING__MINER_ADDRESS: ${ZEBRA_MINING__MINER_ADDRESS:-} - # Regtest has no peers — check RPC directly instead of /ready + ZEBRA_MINING__MINER_ADDRESS: ${ZEBRA_MINING__MINER_ADDRESS:?must be set in .env.regtest or shell environment} + # Zebra's default regtest stops at Canopy; this config activates NU5/NU6 + # at the heights Zaino's regtest defaults expect. zebrad reads + # ~/.config/zebrad.toml automatically. + volumes: + - ${Z3_CONFIG_DIR:-./config/regtest}/zebra.toml:/home/zebra/.config/zebrad.toml:ro + # Regtest is peerless, so p2p is not published (its container port also + # reuses testnet's 18233). Re-list the published ports without p2p. + ports: !override + - "${Z3_ZEBRA_HOST_RPC_PORT:-29232}:${Z3_ZEBRA_RPC_PORT:-18232}" + - "${Z3_ZEBRA_HOST_HEALTH_PORT:-28080}:8080" + # Regtest has no peers; check RPC directly instead of /ready. healthcheck: - test: ["CMD-SHELL", "curl -sf -X POST -H 'Content-Type: application/json' -d '{\"jsonrpc\":\"2.0\",\"method\":\"getblockchaininfo\",\"params\":[],\"id\":1}' http://127.0.0.1:18232 || exit 1"] + test: ["CMD-SHELL", "curl -sf -X POST -H 'Content-Type: application/json' -d '{\"jsonrpc\":\"2.0\",\"method\":\"getblockchaininfo\",\"params\":[],\"id\":1}' http://127.0.0.1:${Z3_ZEBRA_RPC_PORT:-18232} || exit 1"] interval: 10s timeout: 5s retries: 10 start_period: 30s zaino: - # Regtest uses username/password auth (set in config/regtest/zaino.toml), not cookie auth. - # Override the full environment to avoid inheriting the cookie path from the base. + # Regtest uses username/password auth (set in config/regtest/zaino.toml), + # not cookie auth. Override the full environment to drop the cookie path + # inherited from the base. environment: !override - RUST_LOG: ${ZAINO_RUST_LOG:-info} + RUST_LOG: ${Z3_ZAINO_RUST_LOG:-${RUST_LOG:-info}} ZAINO_NETWORK: Regtest ZAINO_VALIDATOR_SETTINGS__VALIDATOR_JSONRPC_LISTEN_ADDRESS: zebra:${Z3_ZEBRA_RPC_PORT:-18232} - ZAINO_GRPC_SETTINGS__LISTEN_ADDRESS: 0.0.0.0:${ZAINO_GRPC_PORT:-8137} - ZAINO_JSON_SERVER_SETTINGS__JSON_RPC_LISTEN_ADDRESS: 0.0.0.0:${ZAINO_JSON_RPC_PORT:-8237} - ZAINO_GRPC_SETTINGS__TLS__CERT_PATH: ${ZAINO_GRPC_TLS_CERT_PATH:-/var/run/zaino/tls/zaino.crt} - ZAINO_GRPC_SETTINGS__TLS__KEY_PATH: ${ZAINO_GRPC_TLS_KEY_PATH:-/var/run/zaino/tls/zaino.key} - # Override config file with regtest-specific version - volumes: - - ./config/regtest/zaino.toml:/etc/zaino/zindexer.toml:ro + ZAINO_GRPC_SETTINGS__LISTEN_ADDRESS: 0.0.0.0:8137 + ZAINO_JSON_SERVER_SETTINGS__JSON_RPC_LISTEN_ADDRESS: 0.0.0.0:8237 + volumes: !override + - ${Z3_ZAINO_DATA_PATH:-zaino}:/app/data + - ${Z3_CONFIG_DIR:-./config/regtest}/zaino.toml:/etc/zaino/zindexer.toml:ro zallet: - # Regtest uses username/password auth, not cookie auth. - # Override the full volumes list to avoid inheriting the cookie volume mount - # from the base compose file (which fails on distroless images where /var/run - # is not a directory). + # Regtest uses username/password auth. Override the volumes list to drop + # the cookie volume mount (the distroless image lacks a shell to create + # /var/run dirs at runtime). volumes: !override - - ${Z3_ZALLET_DATA_PATH:-zallet_data}:/var/lib/zallet - - ./config/regtest/zallet.toml:/etc/zallet/zallet.toml:ro - - ./config/regtest/zallet_identity.txt:/etc/zallet/identity.txt:ro - - zcashd: - # Optional comparator for local compatibility checks. Keep it isolated - # from P2P in regtest. Activation heights default to config/regtest/zallet.toml - # but can be overridden for custom regtest runs. - command: - - zcashd - - -conf=/dev/null - - -printtoconsole - - -regtest - - -i-am-aware-zcashd-will-be-replaced-by-zebrad-and-zallet-in-2025=1 - - -listen=0 - - -dnsseed=0 - - -dns=0 - - -connect=0 - - -server=1 - - -rpcuser=${ZCASHD_RPCUSER:-zebra} - - -rpcpassword=${ZCASHD_RPCPASSWORD:-zebra} - - -rpcbind=0.0.0.0 - - -rpcport=${ZCASHD_RPC_PORT:-38232} - - -rpcallowip=${ZCASHD_RPC_ALLOWIP:-0.0.0.0/0} - - -nuparams=5ba81b19:${ZCASHD_OVERWINTER_ACTIVATION_HEIGHT:-1} - - -nuparams=76b809bb:${ZCASHD_SAPLING_ACTIVATION_HEIGHT:-1} - - -nuparams=2bb40e60:${ZCASHD_BLOSSOM_ACTIVATION_HEIGHT:-1} - - -nuparams=f5b9230b:${ZCASHD_HEARTWOOD_ACTIVATION_HEIGHT:-1} - - -nuparams=e9ff75a6:${ZCASHD_CANOPY_ACTIVATION_HEIGHT:-1} - - -nuparams=c2d6d0b4:${ZCASHD_NU5_ACTIVATION_HEIGHT:-1} - healthcheck: - test: ["CMD-SHELL", "zcash-cli -conf=/dev/null -regtest -rpcconnect=127.0.0.1 -rpcport=$${ZCASHD_RPCPORT} -rpcuser=$${ZCASHD_RPCUSER} -rpcpassword=$${ZCASHD_RPCPASSWORD} getblockchaininfo >/dev/null"] - interval: 10s - timeout: 5s - retries: 10 - start_period: 60s + - ${Z3_ZALLET_DATA_PATH:-zallet}:/var/lib/zallet + - ${Z3_CONFIG_DIR:-./config/regtest}/zallet.toml:/etc/zallet/zallet.toml:ro + - ${Z3_CONFIG_DIR:-./config/regtest}/zallet_identity.txt:/etc/zallet/identity.txt:ro rpc-router: build: context: ./rpc-router dockerfile: Dockerfile restart: unless-stopped - logging: - driver: json-file - options: - max-size: "50m" - max-file: "5" cap_drop: [ALL] security_opt: [no-new-privileges:true] environment: - ZEBRA_URL: http://zebra:18232 + ZEBRA_URL: http://zebra:${Z3_ZEBRA_RPC_PORT:-18232} ZALLET_URL: http://zallet:28232 - RPC_USER: ${RPC_USER:-zebra} - RPC_PASSWORD: ${RPC_PASSWORD:-zebra} + RPC_USER: ${Z3_REGTEST_RPC_ROUTER_USER:-zebra} + RPC_PASSWORD: ${Z3_REGTEST_RPC_ROUTER_PASSWORD:-zebra} LISTEN_PORT: "8181" ports: - - "8181:8181" + - "${Z3_REGTEST_RPC_ROUTER_HOST_PORT:-8181}:8181" depends_on: zebra: condition: service_healthy zallet: condition: service_started - networks: - - z3_net diff --git a/docker-compose.yml b/docker-compose.yml index ed9be09..d00ec2d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,60 +1,81 @@ -# Shared defaults applied to all services via <<: *common -# - logging: rotates logs (50MB x 5 files) to prevent unbounded disk growth -# - cap_drop: removes all Linux capabilities (services run as non-root) -# - security_opt: prevents privilege escalation via setuid binaries -# See docs/docker-architecture.md for rationale. +# Z3: a Zcash node platform. +# +# Run a network with: +# docker compose --env-file .env.mainnet up -d +# docker compose --env-file .env.testnet up -d +# docker compose --env-file .env.regtest up -d +# +# Default invocation (no --env-file) brings up mainnet. +# Operator-specific overrides go in .env (auto-loaded, gitignored). +# +# Public contract identifiers (network, volume, port names) are defined in +# z3-contract.yaml. Image pins below have inline fallbacks and can be +# overridden per service via Z3_*_IMAGE. Downstream services attach via the +# documented network and cookie volume; see docs/integrations/. +# +# Shared defaults applied to every service via <<: *common: +# cap_drop removes all Linux capabilities (services run as non-root) +# security_opt prevents privilege escalation via setuid binaries +# +# Logging is deliberately not set here. Pinning a driver in Compose overrides +# the operator's Docker daemon default (journald, local, a remote collector). +# To bound log growth, set a rotating default in /etc/docker/daemon.json; see +# docs/faq.md ("Why doesn't z3 set a logging driver"). + +name: z3-mainnet + x-common: &common - logging: - driver: json-file - options: - max-size: "50m" - max-file: "5" cap_drop: [ALL] security_opt: [no-new-privileges:true] services: zebra: - image: ${ZEBRA_IMAGE:-zfnd/zebra:latest} - platform: ${DOCKER_PLATFORM:-linux/amd64} + image: ${Z3_ZEBRA_IMAGE:-zfnd/zebra:5.0.0} + # Zebra is multi-arch; Docker selects the host's native variant. + # DOCKER_PLATFORM forces a specific arch for cross-architecture testing. + platform: ${DOCKER_PLATFORM:-} build: context: ./zebra dockerfile: docker/Dockerfile target: runtime args: - FEATURES: ${ZEBRA_BUILD_FEATURES:-default-release-binaries} - container_name: z3_zebra + FEATURES: ${Z3_ZEBRA_BUILD_FEATURES:-default-release-binaries} restart: unless-stopped stop_grace_period: 30s <<: *common # Zebra's entrypoint runs mkdir/chown before dropping privileges via setpriv. - # These operations need capabilities that cap_drop: [ALL] removes, so we add them back. + # Those operations need capabilities that cap_drop: [ALL] removes. cap_add: [CHOWN, DAC_OVERRIDE, FOWNER, SETUID, SETGID] - # Optional ZEBRA_* vars (metrics, OpenTelemetry) pass through from .env when set. - # They must NOT be listed in environment: with empty defaults because config-rs - # treats empty strings as values and fails to parse them (e.g., "" as a socket address). + # Optional operator overrides (e.g., OpenTelemetry endpoints) load from .env. + # config-rs treats empty strings as values, so empty-defaulted vars cannot + # appear in the explicit environment: block; they must be omitted instead. env_file: - path: ./.env required: false environment: - RUST_LOG: ${Z3_ZEBRA_RUST_LOG:-info} - ZEBRA_NETWORK__NETWORK: ${NETWORK_NAME:-Mainnet} - ZEBRA_RPC__LISTEN_ADDR: 0.0.0.0:${Z3_ZEBRA_RPC_PORT:-18232} - ZEBRA_RPC__ENABLE_COOKIE_AUTH: ${ENABLE_COOKIE_AUTH:-true} - ZEBRA_RPC__COOKIE_DIR: ${COOKIE_AUTH_FILE_DIR:-/var/run/auth} + RUST_LOG: ${Z3_ZEBRA_RUST_LOG:-${RUST_LOG:-info}} + ZEBRA_NETWORK__NETWORK: ${Z3_NETWORK:-Mainnet} + ZEBRA_RPC__LISTEN_ADDR: 0.0.0.0:${Z3_ZEBRA_RPC_PORT:-8232} + ZEBRA_RPC__ENABLE_COOKIE_AUTH: ${ZEBRA_RPC__ENABLE_COOKIE_AUTH:-true} + ZEBRA_RPC__COOKIE_DIR: /var/run/auth ZEBRA_TRACING__FILTER: ${ZEBRA_TRACING__FILTER:-info} ZEBRA_STATE__CACHE_DIR: /home/zebra/.cache/zebra ZEBRA_HEALTH__LISTEN_ADDR: 0.0.0.0:8080 ZEBRA_HEALTH__MIN_CONNECTED_PEERS: ${ZEBRA_HEALTH__MIN_CONNECTED_PEERS:-1} ZEBRA_HEALTH__READY_MAX_BLOCKS_BEHIND: ${ZEBRA_HEALTH__READY_MAX_BLOCKS_BEHIND:-2} ZEBRA_HEALTH__ENFORCE_ON_TEST_NETWORKS: ${ZEBRA_HEALTH__ENFORCE_ON_TEST_NETWORKS:-false} + ZEBRA_METRICS__ENDPOINT_ADDR: ${ZEBRA_METRICS__ENDPOINT_ADDR:-0.0.0.0:9999} volumes: - - ${Z3_ZEBRA_DATA_PATH:-zebra_data}:/home/zebra/.cache/zebra - - ${Z3_COOKIE_PATH:-shared_cookie_volume}:${COOKIE_AUTH_FILE_DIR:-/var/run/auth} + - ${Z3_CHAIN_DATA_PATH:-chain}:/home/zebra/.cache/zebra + - ${Z3_COOKIE_PATH:-cookie}:/var/run/auth + # p2p (8233 mainnet / 18233 testnet) is published so the node accepts + # inbound peer connections; without it Zebra makes only outbound peers. + # Behind NAT or a firewall, also set ZEBRA_NETWORK__EXTERNAL_ADDR to the + # address peers should dial. Regtest is peerless and omits this (see overlay). ports: - - "${Z3_ZEBRA_HOST_RPC_PORT:-18232}:${Z3_ZEBRA_RPC_PORT:-18232}" + - "${Z3_ZEBRA_HOST_RPC_PORT:-8232}:${Z3_ZEBRA_RPC_PORT:-8232}" + - "${Z3_ZEBRA_HOST_P2P_PORT:-8233}:${Z3_ZEBRA_P2P_PORT:-8233}" - "${Z3_ZEBRA_HOST_HEALTH_PORT:-8080}:8080" - networks: - - z3_net healthcheck: test: ["CMD-SHELL", "curl -sf http://127.0.0.1:8080/ready || exit 1"] interval: 30s @@ -63,50 +84,78 @@ services: start_period: 90s start_interval: 5s + # Sidecar: makes Zebra's RPC cookie readable inside the shared auth volume. + # Zebra writes /var/run/auth/.cookie with mode 0600 owned by uid 10001; + # Zaino, Zallet, and attached services may run as different users. + cookie-permissions: + image: alpine:3 + restart: unless-stopped + <<: *common + # FOWNER is needed to chmod Zebra's cookie after cap_drop: [ALL]. + cap_add: [FOWNER] + environment: + ZEBRA_RPC__ENABLE_COOKIE_AUTH: ${ZEBRA_RPC__ENABLE_COOKIE_AUTH:-true} + volumes: + - ${Z3_COOKIE_PATH:-cookie}:/var/run/auth + command: + - sh + - -c + - | + while true; do + if [ -f /var/run/auth/.cookie ]; then + chmod 0644 /var/run/auth/.cookie 2>/dev/null + fi + sleep 5 + done + depends_on: + zebra: + condition: service_started + healthcheck: + test: ["CMD-SHELL", "if [ \"$${ZEBRA_RPC__ENABLE_COOKIE_AUTH:-true}\" = \"false\" ]; then exit 0; fi; test -r /var/run/auth/.cookie"] + interval: 5s + timeout: 2s + retries: 12 + start_period: 5s + zaino: - # Image built by z3 CI from zingolabs/zaino@dev branch. - # The sha-* tag is a z3 commit hash, not a zaino one. - # Upstream: https://github.com/zingolabs/zaino (zainod 0.2.0 / Zebra 4.3.0 compatible) - image: ${ZAINO_IMAGE:-ghcr.io/zcashfoundation/zaino:sha-83e41d7} + # The -no-tls tag compiles out Zaino's "TLS required on a non-private bind" + # guard, so intra-container gRPC on 0.0.0.0:8137 runs as plaintext h2c. + # NO_TLS=true below makes a local arm64 build match that pin. Terminate + # edge TLS at a reverse proxy if Zaino is exposed beyond the host. + image: ${Z3_ZAINO_IMAGE:-zingodevops/zainod:0.4.0-rc.2-no-tls} + # Zaino publishes linux/amd64 only; arm64 hosts run under emulation + # unless the operator builds from the ./zaino submodule. platform: ${DOCKER_PLATFORM:-linux/amd64} build: context: ./zaino dockerfile: Dockerfile - container_name: z3_zaino + args: + NO_TLS: "true" restart: unless-stopped stop_grace_period: 15s <<: *common - # Zaino's entrypoint runs mkdir/chown before dropping privileges via setpriv. - # These operations need capabilities that cap_drop: [ALL] removes, so we add them back. cap_add: [CHOWN, DAC_OVERRIDE, FOWNER, SETUID, SETGID] command: ["start", "--config", "/etc/zaino/zindexer.toml"] depends_on: zebra: condition: service_healthy + cookie-permissions: + condition: service_healthy environment: - RUST_LOG: ${ZAINO_RUST_LOG:-info,reqwest=warn,hyper_util=warn} - RUST_BACKTRACE: ${ZAINO_RUST_BACKTRACE:-full} - ZAINO_NETWORK: ${NETWORK_NAME:-Mainnet} - ZAINO_VALIDATOR_SETTINGS__VALIDATOR_JSONRPC_LISTEN_ADDRESS: zebra:${Z3_ZEBRA_RPC_PORT:-18232} - ZAINO_VALIDATOR_SETTINGS__VALIDATOR_COOKIE_PATH: ${COOKIE_AUTH_FILE_DIR:-/var/run/auth}/.cookie - ZAINO_GRPC_SETTINGS__LISTEN_ADDRESS: 0.0.0.0:${ZAINO_GRPC_PORT:-8137} - ZAINO_JSON_SERVER_SETTINGS__JSON_RPC_LISTEN_ADDRESS: 0.0.0.0:${ZAINO_JSON_RPC_PORT:-8237} - ZAINO_GRPC_SETTINGS__TLS__CERT_PATH: ${ZAINO_GRPC_TLS_CERT_PATH:-/var/run/zaino/tls/zaino.crt} - ZAINO_GRPC_SETTINGS__TLS__KEY_PATH: ${ZAINO_GRPC_TLS_KEY_PATH:-/var/run/zaino/tls/zaino.key} + RUST_LOG: ${Z3_ZAINO_RUST_LOG:-${RUST_LOG:-info,reqwest=warn,hyper_util=warn}} + RUST_BACKTRACE: ${RUST_BACKTRACE:-full} + ZAINO_NETWORK: ${Z3_NETWORK:-Mainnet} + ZAINO_VALIDATOR_SETTINGS__VALIDATOR_JSONRPC_LISTEN_ADDRESS: zebra:${Z3_ZEBRA_RPC_PORT:-8232} + ZAINO_VALIDATOR_SETTINGS__VALIDATOR_COOKIE_PATH: /var/run/auth/.cookie + ZAINO_GRPC_SETTINGS__LISTEN_ADDRESS: 0.0.0.0:8137 + ZAINO_JSON_SERVER_SETTINGS__JSON_RPC_LISTEN_ADDRESS: 0.0.0.0:8237 volumes: - - ${Z3_ZAINO_DATA_PATH:-zaino_data}:/app/data - - ${Z3_COOKIE_PATH:-shared_cookie_volume}:${COOKIE_AUTH_FILE_DIR:-/var/run/auth}:ro - - ./config/zaino.toml:/etc/zaino/zindexer.toml:ro - configs: - - source: zaino_tls_cert - target: ${ZAINO_GRPC_TLS_CERT_PATH:-/var/run/zaino/tls/zaino.crt} - - source: zaino_tls_key - target: ${ZAINO_GRPC_TLS_KEY_PATH:-/var/run/zaino/tls/zaino.key} + - ${Z3_ZAINO_DATA_PATH:-zaino}:/app/data + - ${Z3_COOKIE_PATH:-cookie}:/var/run/auth:ro + - ${Z3_CONFIG_DIR:-./config/mainnet}/zaino.toml:/etc/zaino/zindexer.toml:ro ports: - - "${ZAINO_HOST_GRPC_PORT:-8137}:${ZAINO_GRPC_PORT:-8137}" - - "${ZAINO_HOST_JSONRPC_PORT:-8237}:${ZAINO_JSON_RPC_PORT:-8237}" - networks: - - z3_net + - "${Z3_ZAINO_HOST_GRPC_PORT:-8137}:8137" + - "${Z3_ZAINO_HOST_JSON_RPC_PORT:-8237}:8237" healthcheck: test: ["CMD-SHELL", "bash -c 'echo > /dev/tcp/127.0.0.1/8137' 2>/dev/null || exit 1"] interval: 30s @@ -116,7 +165,7 @@ services: start_interval: 5s zallet: - image: ${ZALLET_IMAGE:-electriccoinco/zallet:v0.1.0-alpha.3} + image: ${Z3_ZALLET_IMAGE:-electriccoinco/zallet:v0.1.0-alpha.3} platform: ${DOCKER_PLATFORM:-linux/amd64} build: context: ./zallet @@ -124,7 +173,6 @@ services: target: runtime tags: - z3_zallet:local - container_name: z3_zallet restart: unless-stopped stop_grace_period: 15s <<: *common @@ -133,78 +181,29 @@ services: depends_on: zebra: condition: service_healthy + cookie-permissions: + condition: service_healthy environment: - RUST_LOG: ${ZALLET_RUST_LOG:-info,hyper_util=warn,reqwest=warn} - volumes: - - ${Z3_ZALLET_DATA_PATH:-zallet_data}:/var/lib/zallet - - ${Z3_COOKIE_PATH:-shared_cookie_volume}:${COOKIE_AUTH_FILE_DIR:-/var/run/auth}:ro - - ./config/zallet.toml:/etc/zallet/zallet.toml:ro - - ./config/zallet_identity.txt:/etc/zallet/identity.txt:ro - ports: - - "${ZALLET_HOST_RPC_PORT:-28232}:${ZALLET_RPC_PORT:-28232}" - networks: - - z3_net - # No healthcheck: distroless image has no shell/curl - - zcashd: - # Optional zcashd node. It is intentionally isolated from the - # public P2P network unless the operator overrides the command. - profiles: [zcashd] - image: ${ZCASHD_IMAGE:-zodlinc/zcashd:v6.12.1} - # The default zcashd image currently publishes linux/amd64 only. - platform: ${ZCASHD_DOCKER_PLATFORM:-linux/amd64} - container_name: z3_zcashd - restart: unless-stopped - stop_grace_period: 30s - <<: *common - user: "999:999" - command: - - zcashd - - -conf=/dev/null - - -printtoconsole - - -i-am-aware-zcashd-will-be-replaced-by-zebrad-and-zallet-in-2025=1 - - -listen=0 - - -dnsseed=0 - - -dns=0 - - -connect=0 - - -server=1 - - -rpcuser=${ZCASHD_RPCUSER:-zebra} - - -rpcpassword=${ZCASHD_RPCPASSWORD:-zebra} - - -rpcbind=0.0.0.0 - - -rpcport=${ZCASHD_RPC_PORT:-38232} - - -rpcallowip=${ZCASHD_RPC_ALLOWIP:-0.0.0.0/0} - environment: - ZCASHD_RPCUSER: ${ZCASHD_RPCUSER:-zebra} - ZCASHD_RPCPASSWORD: ${ZCASHD_RPCPASSWORD:-zebra} - ZCASHD_RPCBIND: 0.0.0.0 - ZCASHD_RPCPORT: ${ZCASHD_RPC_PORT:-38232} - ZCASHD_ALLOWIP: ${ZCASHD_RPC_ALLOWIP:-0.0.0.0/0} + RUST_LOG: ${Z3_ZALLET_RUST_LOG:-${RUST_LOG:-info,hyper_util=warn,reqwest=warn}} volumes: - - ${Z3_ZCASHD_DATA_PATH:-zcashd_data}:/home/zcash/.zcash + - ${Z3_ZALLET_DATA_PATH:-zallet}:/var/lib/zallet + - ${Z3_COOKIE_PATH:-cookie}:/var/run/auth:ro + - ${Z3_CONFIG_DIR:-./config/mainnet}/zallet.toml:/etc/zallet/zallet.toml:ro + - ${Z3_CONFIG_DIR:-./config/mainnet}/zallet_identity.txt:/etc/zallet/identity.txt:ro ports: - - "${ZCASHD_HOST_RPC_BIND:-127.0.0.1}:${ZCASHD_HOST_RPC_PORT:-38232}:${ZCASHD_RPC_PORT:-38232}" - networks: - - z3_net - healthcheck: - test: ["CMD-SHELL", "zcash-cli -conf=/dev/null -rpcconnect=127.0.0.1 -rpcport=$${ZCASHD_RPCPORT} -rpcuser=$${ZCASHD_RPCUSER} -rpcpassword=$${ZCASHD_RPCPASSWORD} getblockchaininfo >/dev/null"] - interval: 10s - timeout: 5s - retries: 10 - start_period: 60s + - "${Z3_ZALLET_HOST_RPC_PORT:-28232}:28232" + # No healthcheck: the distroless image has no shell or curl. - # ============================================================================= - # Monitoring Stack (enabled with --profile monitoring) - # ============================================================================= - # Usage: docker compose --profile monitoring up -d - # Access: - # - Grafana: http://localhost:3000 (admin/admin) - # - Prometheus: http://localhost:9094 - # - Jaeger: http://localhost:16686 - # - AlertManager: http://localhost:9093 + # =========================================================================== + # Monitoring stack (--profile monitoring) + # =========================================================================== + # Mainnet defaults: Grafana 3000, Prometheus 9094, Jaeger UI 16686, + # Jaeger OTLP 4317/4318, Jaeger spanmetrics 8889, AlertManager 9093. + # Testnet and regtest env files offset every published monitoring port so + # multiple networks coexist on one host without binding collisions. jaeger: image: jaegertracing/jaeger:2.1.0 - container_name: z3_jaeger profiles: [monitoring] restart: unless-stopped <<: *common @@ -213,12 +212,10 @@ services: command: - --config=/etc/jaeger/config.yaml ports: - - "${JAEGER_UI_PORT:-16686}:16686" - - "${JAEGER_OTLP_GRPC_PORT:-4317}:4317" - - "${JAEGER_OTLP_HTTP_PORT:-4318}:4318" - - "${JAEGER_SPANMETRICS_PORT:-8889}:8889" - networks: - - z3_net + - "${Z3_JAEGER_UI_PORT:-16686}:16686" + - "${Z3_JAEGER_OTLP_GRPC_PORT:-4317}:4317" + - "${Z3_JAEGER_OTLP_HTTP_PORT:-4318}:4318" + - "${Z3_JAEGER_SPANMETRICS_PORT:-8889}:8889" healthcheck: test: ["CMD-SHELL", "wget -q --spider http://localhost:16686/ || exit 1"] interval: 5s @@ -227,7 +224,6 @@ services: prometheus: image: prom/prometheus:v3.2.0 - container_name: z3_prometheus profiles: [monitoring] restart: unless-stopped <<: *common @@ -243,9 +239,7 @@ services: - '--web.enable-lifecycle' - '--web.enable-admin-api' ports: - - "${PROMETHEUS_PORT:-9094}:9090" - networks: - - z3_net + - "${Z3_PROMETHEUS_PORT:-9094}:9090" depends_on: jaeger: condition: service_healthy @@ -257,7 +251,6 @@ services: grafana: image: grafana/grafana:11.5.1 - container_name: z3_grafana profiles: [monitoring] restart: unless-stopped <<: *common @@ -267,11 +260,9 @@ services: - grafana_data:/var/lib/grafana environment: GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH: /var/lib/grafana/dashboards/zebra_overview.json - GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:-admin} + GF_SECURITY_ADMIN_PASSWORD: ${GF_SECURITY_ADMIN_PASSWORD:-admin} ports: - - "${GRAFANA_PORT:-3000}:3000" - networks: - - z3_net + - "${Z3_GRAFANA_PORT:-3000}:3000" depends_on: prometheus: condition: service_healthy @@ -286,7 +277,6 @@ services: alertmanager: image: prom/alertmanager:v0.28.1 - container_name: z3_alertmanager profiles: [monitoring] restart: unless-stopped <<: *common @@ -297,33 +287,36 @@ services: - '--config.file=/etc/alertmanager/alertmanager.yml' - '--storage.path=/alertmanager' ports: - - "${ALERTMANAGER_PORT:-9093}:9093" - networks: - - z3_net + - "${Z3_ALERTMANAGER_PORT:-9093}:9093" healthcheck: test: ["CMD-SHELL", "wget -q --spider http://localhost:9093/-/healthy || exit 1"] interval: 10s timeout: 3s retries: 3 +# Named volumes are declared with explicit name: so the external Docker +# identifier is `${COMPOSE_PROJECT_NAME}-` rather than the default +# `${COMPOSE_PROJECT_NAME}_`. Downstream consumers reference these +# external names directly; see docs/contract.md for the public contract. volumes: - zebra_data: - zcashd_data: - zaino_data: - zallet_data: - shared_cookie_volume: + chain: + name: ${COMPOSE_PROJECT_NAME:-z3-mainnet}-chain + cookie: + name: ${COMPOSE_PROJECT_NAME:-z3-mainnet}-cookie + zaino: + name: ${COMPOSE_PROJECT_NAME:-z3-mainnet}-zaino + zallet: + name: ${COMPOSE_PROJECT_NAME:-z3-mainnet}-zallet prometheus_data: grafana_data: alertmanager_data: +# The default network is named explicitly so consumers attach via a stable +# identifier (not subject to Compose's project-prefix behavior). networks: - z3_net: - driver: bridge + default: + name: ${COMPOSE_PROJECT_NAME:-z3-mainnet} configs: - zaino_tls_cert: - file: ./config/tls/zaino.crt - zaino_tls_key: - file: ./config/tls/zaino.key prometheus_config: file: ./observability/prometheus/prometheus.yaml diff --git a/docs/contract.md b/docs/contract.md new file mode 100644 index 0000000..ccd4850 --- /dev/null +++ b/docs/contract.md @@ -0,0 +1,182 @@ +# Z3 Platform Contract + +> This is for **integrators and tooling** that attach to a running Z3 stack, and for agents that consume the API. If you only run Z3, you do not need any of this; go to the [README operator guide](../README.md#for-operators). + +This document is the human-facing version of [`z3-contract.yaml`](../z3-contract.yaml). The YAML is the source of truth for integration identifiers and is validated against [`z3-contract.schema.json`](../z3-contract.schema.json). This doc explains what each identifier means, what consumers can rely on, and what is explicitly out of scope. + +For copy-paste integration examples, see [`docs/integrations/`](integrations/). + +## Stability promise + +Identifiers in the contract are SemVer-stable. The shipped contract version is `1.0.0`: + +- **Patch** (1.0.x): documentation-only changes; no consumer impact. +- **Minor** (1.x.0): adds optional fields, new env vars, new optional services. Existing consumers continue working. +- **Major** (x.0.0): renames, removed identifiers, or port changes. Existing consumers need to migrate. + +Identifiers NOT in this contract (Compose service container names, internal config-rs env vars, monitoring volumes, scrape labels) are z3's implementation. They can change without a contract bump. + +### Contract version and image pins + +The contract version (`z3-contract.yaml` → `contract_version`) is the SemVer-stable API surface bumped per the policy above. Image pins are not part of the contract: they live as `${VAR:-tag}` defaults in `docker-compose.yml` and bump independently of the contract version (a Zebra patch release `4.4.1` → `4.4.2` does not require a contract bump). Operators override any pin with `Z3_ZEBRA_IMAGE`, `Z3_ZAINO_IMAGE`, or `Z3_ZALLET_IMAGE`. Platform constraints per image live in `z3-contract.yaml` under `image_platforms:`. + +## What z3 publishes + +### Networks + +Z3 runs as one of three Compose projects, one per Zcash network. The full set of identifiers (project name, external network name, `Z3_NETWORK` value) lives in `z3-contract.yaml` under `networks.`. + +`Z3_NETWORK` is PascalCase (it passes through to Zebra's `serde` deserializer). `COMPOSE_PROJECT_NAME` is lowercase (Docker Compose accepts only lowercase letters, digits, dashes, and underscores). Both are required; the `.env.` files set both. + +### Volumes + +Every per-network resource is a named Docker volume with an explicit `name:` declaration, so it is not subject to Compose's project-prefix behavior. Names follow the pattern `z3--` (e.g., `z3-mainnet-cookie`); the full set is in `z3-contract.yaml` under `networks..volumes`. + +Of these, `cookie` is part of the consumer-facing attachment surface only when the network's `rpc_auth.mode` is `cookie` (mainnet and testnet). Regtest carries the named volume as an internal Compose artifact, but its `rpc_auth.mode` is `username_password`; consumers should not mount `z3-regtest-cookie` expecting a readable RPC cookie. The other volumes are z3's storage and should not be mounted. + +### In-network DNS + +Inside the Docker network, services resolve at their bare service name (`zebra`, `zaino`, `zallet`). The YAML's `service_dns:` block carries the full list. + +No per-network suffix: the network itself is the discriminator. A consumer attached to `z3-testnet` uses `http://zebra:18232`; on `z3-mainnet` it uses `http://zebra:8232`. + +### Cookie file + +The path is `/var/run/auth/.cookie` on every container that uses cookie auth. Mainnet and testnet publish `rpc_auth.mode: cookie`; Z3's Zebra writes the cookie, and Z3's Zaino, Zallet, and any consumer attached to the cookie volume mount it read-only at this path. + +Regtest is the exception: it publishes `rpc_auth.mode: username_password`, sets `ZEBRA_RPC__ENABLE_COOKIE_AUTH=false`, and the regtest overlay removes the cookie mount from Zaino and Zallet. Zebra does not guarantee `/var/run/auth/.cookie` on regtest. Regtest consumers should use the documented username/password path, usually through the rpc-router defaults (`zebra` / `zebra`) or the per-service regtest config. + +### Port matrix + +Container ports follow Zebra's per-network defaults from upstream (`zebra-chain/src/parameters/network.rs:239`, `zebra-rpc/src/config/rpc.rs:17-47`). Host ports are explicit and globally unique across all published services and optional profiles, so all three networks coexist on one host. The full matrix (including the `monitoring` profile) is in `z3-contract.yaml` under `networks..ports`. + +For non-mainnet networks, host ports for services without an upstream per-network convention (Zaino, Zallet, monitoring) use a +10000 offset relative to mainnet, with two exceptions. Regtest Zebra RPC moves to host `29232` because `28232` is already mainnet Zallet's host port. Zebra's metrics port stays in-network only on every network (no host publication). + +Regtest also uses Zebra's testnet container-port defaults. + +### Healthchecks + +Per-service health surface. The YAML's `healthchecks:` block carries the per-service transport, port, and endpoint. + +Operational details worth knowing: + +- **Zaino's check is a TCP probe on the gRPC port.** It confirms a listener exists; it does not validate the gRPC handler. Do not use it for production routing decisions. +- **Zallet has no healthcheck.** Its distroless image has no shell or probe binary, so `depends_on: condition: service_healthy` against Zallet hangs. Gate on Zebra's `/ready` instead and assume Zallet follows. +- **`/ready` is sync-strict; `/healthy` is peer-only.** `/healthy` returns success when Zebra has at least `ZEBRA_HEALTH__MIN_CONNECTED_PEERS` peers; `/ready` additionally requires sync within `ZEBRA_HEALTH__READY_MAX_BLOCKS_BEHIND` of tip. Use `/ready` for production; the tracked `docker-compose.override.yml.example` flips to `/healthy` for development. + +Inside the Docker network, consumers wait on Zebra via `depends_on` with `condition: service_healthy`. Outside the network, consumers poll the published health port (per-network number in `z3-contract.yaml`). + +### Env var schema + +Three namespaces keep stack-level settings separate from service-native settings. + +**`Z3_*` (stack-level settings).** Used for host port mappings, image pins, volume path overrides, the per-service `RUST_LOG` split, multi-network selection (`Z3_NETWORK`, `Z3_CONFIG_DIR`), and scoping wrappers such as `Z3_REGTEST_RPC_ROUTER_USER`. Image-pin defaults are `${VAR:-tag}` fallbacks in `docker-compose.yml`. + +**Service-native names (passed through as-is).** Used when the underlying service has a documented convention. Operators map straight from the service's docs without learning a z3 wrapper. Examples: `GF_SECURITY_ADMIN_PASSWORD` (Grafana), `ZEBRA_*` (Zebra config-rs, including `ZEBRA_HEALTH__*`, `ZEBRA_RPC__ENABLE_COOKIE_AUTH`, `ZEBRA_NETWORK__EXTERNAL_ADDR`, `ZEBRA_TRACING__*`, `ZEBRA_MINING__MINER_ADDRESS`). + +**Ecosystem-standard names (inherited; not part of the z3 contract).** Documented for completeness because operators encounter them in our docs: `COMPOSE_FILE`, `RUST_LOG`, `RUST_BACKTRACE`. The YAML lists them under `ecosystem_vars:`. + +**Internal vars (not in the contract).** Z3 sets these inside the compose `environment:` block; operators set the public knobs above instead. Examples: `ZEBRA_RPC__LISTEN_ADDR`, `ZAINO_VALIDATOR_SETTINGS__*`. Also hardcoded in the compose (no operator override): Zaino and Zallet container ports, which do not differ per network. + +For the canonical machine-readable inventory (every variable, its namespace tag, and profile gating), see [`z3-contract.yaml`](../z3-contract.yaml) under `env_vars:` and `ecosystem_vars:`. + +### Profiles + +One Compose profile is available across every network: `monitoring` (Prometheus, Grafana, Jaeger, AlertManager). Enable with `docker compose --env-file .env. --profile monitoring up -d`. Profile-gated identifiers in the YAML carry an explicit `profile:` field. + +## What z3 does NOT publish + +These are explicitly OUT of the contract; they may change without a major version bump: + +- **Container names** (e.g., `z3-mainnet-zebra-1`). Compose autogenerates them; consumers should reference services by their in-network DNS name, not the container name. +- **Per-network config file contents** (the live `config//zallet.toml`, `config//zaino.toml`). These are local files (see "File ownership" below); their contents are not part of the consumer-facing contract. +- **Internal env vars** (`ZEBRA_RPC__LISTEN_ADDR`, `ZAINO_VALIDATOR_SETTINGS__*`, etc.). Z3 sets them inside the compose `environment:` block; operators set the public knobs documented above instead. +- **Monitoring identifiers** (Prometheus job labels, Grafana datasource UIDs, dashboard UIDs). Internal. +- **Compose configs** (`prometheus_config`). Internal. + +## File ownership + +Files in this repository fall into two categories. The separation lets operators iterate locally without merge conflicts and lets maintainers ship template updates without disturbing operator copies. + +### Maintainer-owned (tracked in git) + +These files are tracked defaults. Operators should not edit them; doing so creates merge conflicts on repository updates. + +| Path | Purpose | +|------|---------| +| `docker-compose.yml`, `docker-compose.regtest.yml` | Stack topology. Override via `docker-compose.override.yml` (see below). | +| `z3-contract.yaml`, `z3-contract.schema.json`, `docs/contract.md` | The contract, its schema, and this guide. | +| `.env.example` | Reference for every public env var. | +| `.env.mainnet`, `.env.testnet`, `.env.regtest` | Per-network defaults. Override via `.env`. | +| `config//zallet.toml.example` | Zallet config template. | +| `config//zaino.toml.example` | Zaino config template. | + +### Local (gitignored) + +These files are created locally by `scripts/setup-network.sh ` and edited freely. Repository updates never overwrite them. + +| Path | Created by | Purpose | +|------|------------|---------| +| `.env` | Local user | Per-host overrides; see "How `.env` is loaded" below. | +| `docker-compose.override.yml` | Local user (template in `.example`) | Per-host overrides for compose service definitions. Auto-loaded for mainnet. | +| `docker-compose..override.yml` | Local user (optional) | Per-host testnet/regtest overrides. **Not auto-loaded;** pass with `-f` or append to `COMPOSE_FILE` explicitly. | +| `config//zallet.toml` | `setup-network.sh` (cp from `.example`) | Active Zallet config. Edit to taste. | +| `config//zaino.toml` | `setup-network.sh` (cp from `.example`) | Active Zaino config. Edit to taste. | +| `config//zallet_identity.txt` | `setup-network.sh` (`rage-keygen`) | Per-operator wallet encryption key. Back this up. | + +After `git pull`, diff your live `.toml` against the refreshed `.example`; apply any desired changes by hand: + +```bash +diff config/mainnet/zallet.toml config/mainnet/zallet.toml.example +``` + +### How `.env` is loaded + +Docker Compose auto-loads `.env` from the working directory ONLY when no `--env-file` flag is given. The documented invocation pattern (`docker compose --env-file .env. up -d`) replaces that auto-load: variables in `.env.` are used for compose-level interpolation, and `.env` is not consulted. + +To make values in `.env` take effect under the `--env-file` pattern, use one of: + +```bash +# Option 1: Pass .env as a second --env-file (later wins for collisions) +docker compose --env-file .env.mainnet --env-file .env up -d + +# Option 2: Export in the shell before running compose +export Z3_JAEGER_OTLP_GRPC_PORT=14317 +docker compose --env-file .env.mainnet up -d + +# Option 3: Use a wrapper alias / script that always passes both files +``` + +Variables consumed through a service-level `env_file:` directive are still loaded from `.env` regardless of `--env-file`. Zebra uses this for optional config-rs settings. These variables reach the container's environment but do not influence compose's `${VAR}` interpolation. + +## Versioning + +`contract_version` in `z3-contract.yaml` is the SemVer for the API surface, decoupled from the z3 git tag. A z3 release may keep the same contract version or bump it for contract changes such as renamed identifiers, removed fields, or new ports. Image pins are not versioned: they're `${VAR:-tag}` defaults in `docker-compose.yml` that bump per upstream release. + +When the contract bumps, z3 publishes: + +- A `CHANGELOG.md` entry naming what changed. +- Release notes describing any required operator and consumer updates. +- A new SemVer tag on the z3 repository. + +Consumers should pin to a contract major version and only adopt minor or patch bumps automatically. + +## Validation + +The repository CI validates this contract on every PR. The job parses `z3-contract.yaml`, runs `docker compose --env-file .env. config` for each network, and asserts that every documented identifier (network name, volume name, port, env var) exists in the resolved compose output. A drift between this doc and the compose file is a CI failure. + +Operators can run the same validation locally: + +```bash +./scripts/validate-contract.py # port matrix and volume names per network +./scripts/validate-contract-parity.py # env var inventory across compose and .env.example +``` + +Consumers in any language can validate `z3-contract.yaml` against the shipped JSON Schema: + +```bash +# Example using `check-jsonschema` (any JSON Schema validator works). +pip install check-jsonschema pyyaml +python -c 'import yaml,json,sys; json.dump(yaml.safe_load(open("z3-contract.yaml")), sys.stdout)' \ + | check-jsonschema --schemafile z3-contract.schema.json /dev/stdin +``` diff --git a/docs/data/ecosystem_map.md b/docs/data/ecosystem_map.md deleted file mode 100644 index 3babd89..0000000 --- a/docs/data/ecosystem_map.md +++ /dev/null @@ -1,92 +0,0 @@ -# Zcash Ecosystem Map - -```mermaid -graph TD - %% Define Central Node - ZcashProtocol((Zcash Protocol\nzk-SNARKs, PoW Network)) - - %% Define Subgraphs for Categories - subgraph CoreDev [Core Protocol Development & Stewardship] - direction LR - ECC[Electric Coin Co.\n(zcashd, Zashi, R&D)] - ZF[Zcash Foundation\n(Zebra, Governance, ZCG Admin)] - end - - subgraph Funding [Grant Funding & Community Support] - direction LR - ZCG[Zcash Community Grants\n(Dev Fund Allocation)] - Ambassadors[Global Ambassadors] - end - - subgraph WalletsApps [Wallet & Application Development] - direction TB - ECCWallets(ECC Reference Wallets\n e.g., Zashi) - ThirdPartyWallets(Third-Party Wallets\n Nighthawk, YWallet, etc.) - SDKs(Zcash SDKs\n iOS, Android, Rust) - end - - subgraph Infra [Infrastructure & Services] - direction TB - Nodes(Node Operators\n zcashd/Zebra) - Pools(Mining Pools) - Explorers(Block Explorers) - Exchanges(Exchanges) - end - - subgraph Research [Research & Cryptography] - direction LR - ECCResearch(ECC Crypto Team) - ZFResearch(ZF Research Initiatives) - Academia(Academic Researchers) - end - - %% Define Relationships (Arrows indicate influence, data flow, funding, usage etc.) - - %% Core Development -> Protocol - ECC -->|Develops/Maintains 'zcashd'| ZcashProtocol - ZF -->|Develops 'Zebra', Stewards| ZcashProtocol - - %% Funding Relationships - ZF -- Administers & Supports --> ZCG - ZCG -- Funds Projects --> ThirdPartyWallets - ZCG -- Funds Projects --> SDKs - ZCG -- Funds Projects --> Infra - ZCG -- Funds Projects --> Research - ZCG -- Funds Projects --> Ambassadors - - %% Development -> Wallets/SDKs - ECC -- Develops --> ECCWallets - ECC -- Leads --> ECCResearch - ZF -- Supports --> ZFResearch - SDKs -- Enables Dev --> ThirdPartyWallets - SDKs -- Enables Dev --> ECCWallets - - %% Wallets/Apps -> Protocol & Infra - ECCWallets -- Uses --> ZcashProtocol - ThirdPartyWallets -- Uses --> ZcashProtocol - ECCWallets -- Interacts With --> Nodes - ThirdPartyWallets -- Interacts With --> Nodes - - %% Infrastructure -> Protocol & Users/Wallets - Nodes -- Maintain & Validate --> ZcashProtocol - Pools -- Secure (PoW) --> ZcashProtocol - Explorers -- Read Data From --> Nodes - Exchanges -- Interact With --> Nodes - Exchanges -- Provide Liquidity --> ZcashProtocol - Exchanges -- Serve Users --> WalletsApps - - %% Research -> Core Dev & Community - Research -- Informs --> CoreDev - ECCResearch -- Contributes To --> ECC - ZFResearch -- Contributes To --> ZF - Academia -- Publishes/Advises --> Research - - %% Community -> Protocol - Ambassadors -- Promote --> ZcashProtocol - - %% Style (Optional, simple styling) - style ZcashProtocol fill:#f9f,stroke:#333,stroke-width:2px - classDef category fill:#f3f3f3,stroke:#555,stroke-dasharray: 5 5 - class CoreDev,Funding,WalletsApps,Infra,Research category - ``` - \ No newline at end of file diff --git a/docs/data/rpc_mapping.md b/docs/data/rpc_mapping.md deleted file mode 100644 index 7d22c43..0000000 --- a/docs/data/rpc_mapping.md +++ /dev/null @@ -1,67 +0,0 @@ -# source: https://docs.google.com/spreadsheets/d/1UJxH1cowexGqadU32Uei5Qak6jGhXjb18-T_QBPmDAA/edit?gid=0#gid=0 - -| RPC Method | Where in new stack | Actions | -|---|---|---| -| `getaddressbalance` | Zebra (lightwalletd usage) / Zaino | Review implementation in Zebra | -| `getaddressdeltas` | Zaino | | -| `getaddressmempool` | - | Lower priority. Possibly deprecate | -| `getaddresstxids` | Zebra (lightwalletd usage) / Zaino | Review implementation in Zebra \| Implement in Zaino | -| `getaddressutxos` | Zebra | Review implementation in Zebra | -| `getbestblockhash` | Zebra / Zaino | | -| `getblock` | Zebra / Zaino | Being fully implemented in Zebra | -| `getblockchaininfo` | Zebra / Zaino | Review implementation in Zebra | -| `getblockcount` | Zebra / Zaino | | -| `getblockdeltas` | Zaino | Implement in Zaino | -| `getblockhash` | Zebra / Zaino | | -| `getblockhashes` | Zaino | Lower priority. Possibly deprecate | -| `getblockheader` | Zebra / Zaino | | -| `getchaintips` | Zaino | Implement in Zaino | -| `getdifficulty` | Zebra / Zaino | | -| `getmempoolinfo` | Zaino | Implement in Zaino | -| `getrawmempool` | Zebra / Zaino | | -| `getspentinfo` | Zaino | Implement in Zaino | -| `gettxout` | Zaino | Implement in Zaino | -| `gettxoutproof` | Zaino | Lower priority | -| `gettxoutsetinfo` | Zaino | | -| `verifychain` | - | Deprecate | -| `verifytxoutproof` | Zaino | Lower priority | -| `z_gettreestate` | Zebra / Zaino | Review implementation in Zebra | -| `getexperimentalfeatures` | - | Deprecate? Confirm what Zebra defines as experimental | -| `getinfo` | Zebra | Review implementation in Zebra | -| `getmemoryinfo` | Zebra | No plan to implement | -| `help` | Zebra | No plans to implement | -| `setlogfilter` | Zebra | No plans to implement | -| `stop` | Zebra | None | -| `z_getpaymentdisclosure` | - | Deprecate | -| `z_validatepaymentdisclosure` | - | Deprecate | -| `generate` | Zebra | None | -| `getgenerate` | Zebra | No plans to implement | -| `setgenerate` | Zebra | No plans to implement | -| `getblocksubsidy` | Zebra | None. All done in Zebra | -| `getblocktemplate` | Zebra | Review implementation in Zebra | -| `getlocalsolps` | Zebra | No plans to implement | -| `getmininginfo` | Zebra / Zaino | | -| `getnetworkhashps` | - | Deprecated | -| `getnetworksolps` | Zebra / Zaino | | -| `prioritisetransaction` | Zebra | No plans to implement | -| `submitblock` | Zebra | None | -| `addnode` | Zebra | Decide if we want to implement in Zebra | -| `clearbanned` | Zebra | Decide if we want to implement in Zebra | -| `disconnectnode` | Zebra | Decide if we want to implement in Zebra | -| `getaddednodeinfo` | Zebra | Decide if we want to implement in Zebra | -| `getnettotals` | Zebra | Decide if we want to implement in Zebra | -| `getnetworkinfo` | Zebra / Zaino | Implement in Zebra | -| `getpeerinfo` | Zebra / Zaino | Review implementation in Zebra | -| `listbanned` | Zebra | Decide if we want to implement in Zebra | -| `ping` | Zebra / Zaino | Implement in Zebra | -| `setban` | Zebra | Decide if we want to implement in Zebra | -| `createrawtransaction` | Zallet | | -| `decoderawtransaction` | Zallet | Lower priority. Might deprecate | -| `decodescript` | Zallet | Lower priority. Might deprecate | -| `fundrawtransaction` | Zallet | | -| `getrawtransaction` | Zebra / Zaino | Review implementation in Zebra | -| `sendrawtransaction` | Zebra / Zaino | Review implementation in Zebra | -| `signrawtransaction` | Zallet | | -| `validateaddress` | Zebra / Zaino | Implement in Zaino | -| `verifymessage` | Zallet | Implement in Zallet | -| `z_validateaddress` | Zebra | Implement in Zaino | \ No newline at end of file diff --git a/docs/data/zcashd_RPC_methods.md b/docs/data/zcashd_RPC_methods.md deleted file mode 100644 index a3d61e4..0000000 --- a/docs/data/zcashd_RPC_methods.md +++ /dev/null @@ -1,71 +0,0 @@ -# Zcashd RPC Methods Reference - -## Blockchain Information -- `getblockchaininfo` - Get current state of the blockchain -- `getbestblockhash` - Get hash of best (tip) block in the longest blockchain -- `getblockcount` - Get the current block count -- `getblock` - Get block data by hash -- `getblockhash` - Get hash of block at height -- `getblockheader` - Get block header by hash -- `getchaintips` - Get information about all known chain tips -- `z_gettreestate` - Get Sapling and Orchard tree state -- `z_getsubtreesbyindex` - Get note commitment subtrees -- `getdifficulty` - Get proof-of-work difficulty - -## Mempool Operations -- `getmempoolinfo` - Get mempool statistics -- `getrawmempool` - Get all transaction IDs in memory pool -- `gettxout` - Get details about an unspent transaction output -- `gettxoutsetinfo` - Get statistics about the unspent transaction output set - -## Chain Validation -- `verifychain` - Verify blockchain database -- `getblockdeltas` - Get block deltas by hash -- `getblockhashes` - Get block hashes in range -- `invalidateblock` - Permanently mark a block as invalid -- `reconsiderblock` - Remove invalid status from block and children - -## Mining -- `getlocalsolps` - Get local solutions per second -- `getnetworksolps` - Get network solutions per second -- `getnetworkhashps` - Get estimated network hashes per second -- `getmininginfo` - Get mining-related information -- `prioritisetransaction` - Change transaction priority -- `getblocktemplate` - Get block template for mining -- `submitblock` - Submit mined block -- `getblocksubsidy` - Get block subsidy reward -- `getgenerate` - Check if CPU mining is enabled -- `setgenerate` - Set generation on/off -- `generate` - Mine blocks immediately - -## Network and Node Info -- `getinfo` - Get general information -- `getmemoryinfo` - Get memory usage info -- `getconnectioncount` - Get connection count -- `getdeprecationinfo` - Get deprecation info -- `ping` - Ping other nodes -- `getpeerinfo` - Get peer connection info -- `addnode` - Add/remove/try a node -- `disconnectnode` - Disconnect from node -- `getaddednodeinfo` - Get info about added nodes -- `getnettotals` - Get network traffic info -- `getnetworkinfo` - Get network info -- `setban` - Ban a network address -- `listbanned` - List banned IPs/Subnets -- `clearbanned` - Clear banned addresses - -## Address and Validation -- `validateaddress` - Validate a transparent address -- `z_validateaddress` - Validate a shielded address -- `createmultisig` - Create multisig address -- `verifymessage` - Verify signed message -- `getexperimentalfeatures` - Get experimental feature state -- `getaddresstxids` - Get txids for address -- `getaddressbalance` - Get address balance -- `getaddressdeltas` - Get address deltas -- `getaddressutxos` - Get address utxos -- `getaddressmempool` - Get address mempool -- `getspentinfo` - Get spent info for output - -## Debug and Testing -- `setmocktime` - Set network time for testing \ No newline at end of file diff --git a/docs/data/zcashd_deprecation_team_rpc_method_support_9apr2025.md b/docs/data/zcashd_deprecation_team_rpc_method_support_9apr2025.md deleted file mode 100644 index f6a5342..0000000 --- a/docs/data/zcashd_deprecation_team_rpc_method_support_9apr2025.md +++ /dev/null @@ -1,129 +0,0 @@ -| Category | RPC call | Zebra support | Zcashd support | Zallet support | Zaino support | Where in new stack | Actions | Comments | Block Explorers | Insight Explorer | NH Explorer | Mining pool 1 | Mining pool 2 | Mining Pool 3 | Mining Pool 4 | Exchange 1 | Exchange 2 | Exchange 3 | Exchange 4 | Exchange 5 | Exchange 6 | -|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----|----| -| Addressindex | getaddressbalance | Partial | Yes | | | Zebra \| Zaino | Review implementation in Zebra | Zebra only supports lightwalletd usage of this RPC method | TRUE | TRUE | TRUE | | | | | | | | | | | -| Addressindex | getaddressdeltas | No | Yes | | | Zaino | | Related to prioritisetransaction | TRUE | TRUE | TRUE | | | | | | | | | | | -| Addressindex | getaddressmempool | No | Yes | | | \- | Lower priority. Possibly deprecate | Is anyone using this? Lower priority for now. Fee deltas, might want to make a more useful method in future | FALSE | FALSE | FALSE | | | | | | | | | | | -| Addressindex | getaddresstxids | Partial | Yes | | | Zebra \| Zaino | Review implementation in Zebra \| Implement in Zaino | Zebra only supports lightwalletd usage of this RPC method | FALSE | FALSE | FALSE | | | | | | | | | | | -| Addressindex | getaddressutxos | Partial | Yes | | | Zebra | Review implementation in Zebra | Zebra only supports lightwalletd usage of this RPC method | FALSE | FALSE | FALSE | | | | | | | | TRUE | | | -| Blockchain | getbestblockhash | Yes | Yes | | | Zebra \| Zaino | | | TRUE | TRUE | TRUE | | | | | | | TRUE | TRUE | | | -| Blockchain | getblock | Partial | Yes | | | Zebra \| Zaino | Being fully implemented in Zebra | Zebra only supports lightwalletd usage of this RPC method. anchor will not be supported. | TRUE | TRUE | TRUE | TRUE | TRUE | FALSE | FALSE | TRUE | TRUE | TRUE | TRUE | TRUE | | -| Blockchain | getblockchaininfo | Partial | Yes | | | Zebra \| Zaino | Review implementation in Zebra | Zebra only supports lightwalletd usage of this RPC method | TRUE | TRUE | TRUE | | | | | | TRUE | TRUE | | | TRUE | -| Blockchain | getblockcount | Yes | Yes | | | Zebra \| Zaino | | Requested by 2 mining pools and 1 exchange | TRUE | TRUE | TRUE | TRUE | TRUE | FALSE | FALSE | TRUE | TRUE | | TRUE | TRUE | | -| Blockchain | getblockdeltas | No | Explorer-only | | | Zaino | Implement in Zaino | Insight explorer | FALSE | TRUE | FALSE | | | | | | | | | | | -| Blockchain | getblockhash | Yes | Yes | | | Zebra \| Zaino | | Requested by 1 mining pool and 1 exchange | FALSE | FALSE | FALSE | TRUE | FALSE | FALSE | FALSE | TRUE | TRUE | TRUE | | TRUE | TRUE | -| Blockchain | getblockhashes | No | Explorer-only | | | Zaino | Lower priority. Possibly deprecate | Lower priority | TRUE | FALSE | TRUE | | | | | | | | | | | -| Blockchain | getblockheader | Yes | Yes | | | Zebra \| Zaino | | Requested by 1 mining pool. chainwork will not be supported (undocumented in zcashd) | TRUE | FALSE | TRUE | TRUE | FALSE | | | | | TRUE | | | | -| Blockchain | getchaintips | No | Yes | | | Zaino | Implement in Zaino | | FALSE | FALSE | FALSE | | | | | | | | | | | -| Blockchain | getdifficulty | Yes | Yes | | | Zebra \| Zaino | | Requested by 1 mining pool | TRUE | FALSE | TRUE | TRUE | FALSE | | | | | | | | | -| Blockchain | getmempoolinfo | No | Yes | | | Zaino | Implement in Zaino | | TRUE | FALSE | TRUE | | | | | | TRUE | | | | TRUE | -| Blockchain | getrawmempool | Yes | Yes | | | Zebra \| Zaino | | | TRUE | FALSE | TRUE | | | | | | | TRUE | TRUE | TRUE | | -| Blockchain | getspentinfo | No | Explorer-only | | | Zaino | Implement in Zaino | Insight explorer | FALSE | TRUE | FALSE | | | | | | | | | | | -| Blockchain | gettxout | No | Yes | | | Zaino | Implement in Zaino | Used by Exchange 2 | FALSE | FALSE | FALSE | | | | | | TRUE | TRUE | | | TRUE | -| Blockchain | gettxoutproof | No | Yes | | | Zaino | Lower priority | | FALSE | FALSE | FALSE | | | | | | | | | | | -| Blockchain | gettxoutsetinfo | No | Yes | | | Zaino | | | TRUE | FALSE | TRUE | | | | | | | | | | | -| Blockchain | verifychain | No | Yes | | | \- | Deprecate | zcashd debugging function | FALSE | FALSE | FALSE | | | | | | | | | | | -| Blockchain | verifytxoutproof | No | Yes | | | Zaino | Lower priority | Related to gettxoutproof. Standalone function, could be anywhere | FALSE | FALSE | FALSE | | | | | | | | | | | -| Blockchain | z_gettreestate | Partial | Yes | | | Zebra \| Zaino | Review implementation in Zebra | Zebra only supports lightwalletd usage of this RPC method | FALSE | FALSE | FALSE | | | | | | | | | | | -| Control | getexperimentalfeatures | No | Yes | | | \- | Deprecate? Confirm what Zebra defines as experimental | zcashd-specific | FALSE | FALSE | FALSE | | | | | | | | | | | -| Control | getinfo | Partial | Yes | | | Zebra | Review implementation in Zebra | Zebra only supports lightwalletd usage of this RPC method | TRUE | FALSE | TRUE | | | | | | | | | | | -| Control | getmemoryinfo | No | Yes | | | Zebra | No plan to implement | Lower priority | TRUE | FALSE | TRUE | | | | | | | | | | | -| Control | help | No | Yes | | | Zebra | No plans to implement | Lower priority | FALSE | FALSE | FALSE | | | | | | | | | | | -| Control | setlogfilter | No | Yes | | | Zebra | No plans to implement | Lower priority | FALSE | FALSE | FALSE | | | | | | | | | | | -| Control | stop | Yes | Yes | | | Zebra | None | | FALSE | FALSE | FALSE | | | | | | | | | | | -| Disclosure | z_getpaymentdisclosure | No | Disabled by default | | | \- | Deprecate | This is for Sprout, and is only enabled via the -paymentDisclosure feature flag. | FALSE | FALSE | FALSE | | | | | | | | | | | -| Disclosure | z_validatepaymentdisclosure | No | Disabled by default | | | \- | Deprecate | This is for Sprout, and is only enabled via the -paymentDisclosure feature flag. | TRUE | FALSE | TRUE | | | | | | | | | | | -| Generating | generate | Yes | Yes | | | Zebra | None | Regtest only | FALSE | FALSE | FALSE | | | | | | | | TRUE | | | -| Generating | getgenerate | No | Yes | | | Zebra | No plans to implement | Regtest only. Lower priority | FALSE | FALSE | FALSE | | | | | | | | TRUE | | | -| Generating | setgenerate | No | Yes | | | Zebra | No plans to implement | Regtest only. Lower priority | FALSE | FALSE | FALSE | | | | | | | | TRUE | | | -| Mining | getblocksubsidy | Yes | Yes | | | Zebra | None. All done in Zebra | Requested by 1 mining pool | FALSE | FALSE | TRUE | FALSE | TRUE | | | | | | | | | -| Mining | getblocktemplate | Partial | Yes | | | Zebra | Review implementation in Zebra | Server lists and work IDs are not supported in Zebra | FALSE | FALSE | FALSE | | | TRUE | TRUE | | | | | | | -| Mining | getlocalsolps | No | Yes | | | Zebra | No plans to implement | Testnet/Regtest only. Local CPU mining only; lower priority | FALSE | FALSE | FALSE | | | | | | | | | | | -| Mining | getmininginfo | Yes | Yes | | | Zebra \| Zaino | | | TRUE | FALSE | TRUE | | | | | | | | | | | -| Mining | getnetworkhashps | Yes | Disabled in 6.2.0 | | | \- | Deprecate | Deprecated | FALSE | FALSE | FALSE | | | | | | | | | | | -| Mining | getnetworksolps | Yes | Yes | | | Zebra \| Zaino | | Requested by 1 mining pool | TRUE | FALSE | TRUE | TRUE | FALSE | | | | | | | | | -| Mining | prioritisetransaction | No | Yes | | | Zebra | No plans to implement | Related to getaddressdeltas. Only implement if specifically requested by a mining pool | FALSE | FALSE | FALSE | | | | | | | | | | | -| Mining | submitblock | Yes | Yes | | | Zebra | None | Requested by 1 mining pool | FALSE | FALSE | FALSE | TRUE | FALSE | TRUE | TRUE | | | | | | | -| Network | addnode | No | Yes | | | Zebra | Decide if we want to implement in Zebra | Lower priority to implement in Zebra | FALSE | FALSE | FALSE | | | | | | | | | | | -| Network | clearbanned | No | Yes | | | Zebra | Decide if we want to implement in Zebra | Lower priority to implement in Zebra | FALSE | FALSE | FALSE | | | | | | | | | | | -| Network | disconnectnode | No | Yes | | | Zebra | Decide if we want to implement in Zebra | Lower priority to implement in Zebra | FALSE | FALSE | FALSE | | | | | | | | | | | -| Network | getaddednodeinfo | No | Yes | | | Zebra | Decide if we want to implement in Zebra | Lower priority to implement in Zebra | FALSE | FALSE | FALSE | | | | | | | | | | | -| Network | getconnectioncount | No | Yes | | | Zebra | Decide if we want to implement in Zebra | Lower priority to implement in Zebra | FALSE | FALSE | FALSE | | | | | | | | | | | -| Network | getdeprecationinfo | No | Yes | | | Zebra | Decide if we want to implement in Zebra | Lower priority to implement in Zebra | FALSE | FALSE | FALSE | | | | | | | | | | | -| Network | getnettotals | No | Yes | | | Zebra | Decide if we want to implement in Zebra | Lower priority to implement in Zebra | FALSE | FALSE | FALSE | | | | | | | | | | | -| Network | getnetworkinfo | No | Yes | | | Zebra \| Zaino | Implement in Zebra | Lower priority to implement in Zebra | TRUE | FALSE | TRUE | | | | | | | TRUE | | | TRUE | -| Network | getpeerinfo | Partial | Yes | | | Zebra \| Zaino | Review implementation in Zebra | Requested by 1 mining pool | TRUE | FALSE | TRUE | TRUE | FALSE | | | | | | | | | -| Network | listbanned | No | Yes | | | Zebra | Decide if we want to implement in Zebra | Lower priority to implement in Zebra | FALSE | FALSE | FALSE | | | | | | | | | | | -| Network | ping | No | Yes | | | Zebra \| Zaino | Implement in Zebra | Lower priority to implement in Zebra | FALSE | FALSE | FALSE | | | | | | | | | | | -| Network | setban | No | Yes | | | Zebra | Decide if we want to implement in Zebra | Lower priority to implement in Zebra | FALSE | FALSE | FALSE | | | | | | | | | | | -| Rawtransactions | createrawtransaction | No | Deprecated in 6.2.0 | Not planned | | Zallet | | Required by 2 exchanges. Zallet will expose PCZTs instead. Being implemented in Zebra by external contributor. There are other ways to do this with a newer API. | FALSE | FALSE | FALSE | | | FALSE | FALSE | TRUE | TRUE | | | | | -| Rawtransactions | decoderawtransaction | No | Yes | | | Zallet | Lower priority. Might deprecate | Just a utility method, implementable outside zebra \| requested by 1 mining pool | FALSE | FALSE | FALSE | TRUE | FALSE | FALSE | FALSE | TRUE | | | | | TRUE | -| Rawtransactions | decodescript | No | Yes | | | Zallet | Lower priority. Might deprecate | Just a utility method. | FALSE | FALSE | FALSE | | | | | | | | | | | -| Rawtransactions / wallet | fundrawtransaction | No | Deprecated in 6.2.0 | Not planned | | Zallet | | Zallet will expose PCZTs instead. | FALSE | FALSE | FALSE | | | | | | TRUE | | | | | -| Rawtransactions | getrawtransaction | Partial | Yes | | | Zebra \| Zaino | Review implementation in Zebra | Zebra only supports lightwalletd usage of this RPC method \| requested by 1 mining pool | TRUE | FALSE | TRUE | TRUE | FALSE | FALSE | FALSE | TRUE | | TRUE | TRUE | TRUE | TRUE | -| Rawtransactions | sendrawtransaction | Partial | Yes | | | Zebra \| Zaino | Review implementation in Zebra | Zebra only supports lightwalletd usage of this RPC method \| requested by 2 mining pools | FALSE | FALSE | FALSE | TRUE | FALSE | FALSE | FALSE | TRUE | TRUE | TRUE | TRUE | TRUE | | -| Rawtransactions / wallet | signrawtransaction | No | Deprecated in 6.2.0 | Not planned | | Zallet | | Zallet will expose PCZTs instead. | FALSE | FALSE | FALSE | | | | | | TRUE | TRUE | | | | -| Util | createmultisig | No | Yes | | | Zallet | | Blocked on P2SH support in zcash_client_sqlite | FALSE | FALSE | FALSE | | | | | | | | | | | -| Util | estimatefee | No | No (removed) | | | | Removed | Removed in https://github.com/zcash/zcash/issues/6557 | FALSE | FALSE | FALSE | | | | | | | | | | TRUE | -| Util | validateaddress | Yes | Yes | | | Zebra \| Zaino | Implement in Zaino | Requested by 1 mining pool | TRUE | FALSE | TRUE | TRUE | FALSE | FALSE | FALSE | TRUE | TRUE | | | | | -| Util | verifymessage | No | Yes | | | Zallet | Implement in Zallet | Requested by 1 mining pool | FALSE | FALSE | FALSE | TRUE | FALSE | | | | | | | | | -| Util | z_validateaddress | Yes | Yes | | | Zebra | Implement in Zaino | | TRUE | FALSE | TRUE | | | | | | | TRUE | | | | -| Wallet | addmultisigaddress | No | Yes | | | Zallet | Implement in Zallet | Blocked on P2SH support in zcash_client_sqlite: https://github.com/zcash/librustzcash/issues/1370 | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | backupwallet | No | Yes | | | Zallet | Implement in Zallet | Would be copying the SQLite database file; maybe better done as a CLI command? | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | dumpprivkey | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | TRUE | | | | -| Wallet | dumpwallet | No | No (removed) | | | | Removed | Removed in https://github.com/zcash/zcash/issues/5513 | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | encryptwallet | No | Experimental | Not planned | | Zallet | | Similar functionality would be implemented as CLI commands, not JSON-RPC methods. | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | getbalance | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | TRUE | TRUE | | | -| Wallet | getnewaddress | No | Disabled in 5.4.0 | Not planned | | Zallet | | Deprecated | FALSE | FALSE | FALSE | | | FALSE | FALSE | TRUE | TRUE | | | | | -| Wallet | getrawchangeaddress | No | Disabled in 5.4.0 | Not planned | | Zallet | | Deprecated | FALSE | FALSE | FALSE | | TRUE | FALSE | FALSE | TRUE | | | | | | -| Wallet | getreceivedbyaddress | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | gettransaction | No | Yes | | | Zallet | Implement in Zallet | Requested by 2 mining pools | FALSE | FALSE | FALSE | TRUE | TRUE | FALSE | FALSE | TRUE | TRUE | TRUE | TRUE | | | -| Wallet | getunconfirmedbalance | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | getwalletinfo | No | Yes | Stub | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | TRUE | | | TRUE | TRUE | | | | -| Wallet | importaddress | No | Yes | | | Zallet | Implement in Zallet | Less keen on supporting in JSON-RPC; if no one is using it then would leave out. | FALSE | FALSE | FALSE | | | | | | | | TRUE | | | -| Wallet | importprivkey | No | Yes | | | Zallet | Implement in Zallet | Less keen on supporting in JSON-RPC; if no one is using it then would leave out. | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | importpubkey | No | Yes | Not planned | | Zallet | | | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | importwallet | No | Yes | Not planned | | Zallet | | Would rather have specific commands for importing wallet material rather than a JSON-RPC | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | keypoolrefill | No | Deprecated in 6.2.0 | Not planned | | Zallet | | Only needed when interoperability with legacy Bitcoin infrastructure is required. | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | listaddresses | No | Yes | Yes | | Zallet | Enhance as support for other keys is added | | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | listaddressgroupings | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | listlockunspent | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | TRUE | | | | -| Wallet | listreceivedbyaddress | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | listsinceblock | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | TRUE | TRUE | | | | -| Wallet | listtransactions | No | Yes | | | Zallet | Implement in Zallet | Requested by 1 mining pool | FALSE | FALSE | FALSE | TRUE | FALSE | | | | | | | | | -| Wallet | listunspent | No | Yes | | | Zallet | Implement in Zallet | Requested by 2 mining pools | FALSE | FALSE | FALSE | TRUE | FALSE | TRUE | FALSE | TRUE | TRUE | | | | | -| Wallet | lockunspent | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | TRUE | TRUE | | | | -| Wallet | sendmany | No | Legacy | | | Zallet | Implement in Zallet | Legacy transaction creation API \| requested by 1 mining pool. May need to change semantics. | FALSE | FALSE | FALSE | FALSE | TRUE | TRUE | | | | | | | | -| Wallet | sendtoaddress | No | Legacy | | | Zallet | Implement in Zallet | Legacy transaction creation API. May need to change semantics. | FALSE | FALSE | FALSE | | | | | | TRUE | | | | | -| Wallet | settxfee | No | Deprecated in 6.2.0 | Not planned | | Zallet | Do not implement | Not ZIP 317-conformant. This is only used by legacy transaction creation APIs (sendtoaddress, sendmany, and fundrawtransaction) | FALSE | FALSE | FALSE | | | | | | TRUE | | | | | -| Wallet | signmessage | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | walletconfirmbackup | No | Internal | Not planned | | Zallet | | Internal method that is not intended to be called directly by users | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | z_exportkey | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | z_exportviewingkey | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | z_exportwallet | No | Yes | | | Zallet | Implement in Zallet | Might change output format to zeWIF, or we could add a format argument | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | z_getaddressforaccount | No | Yes | Yes | | Zallet | Implement in Zallet | Requested by 1 mining pool | FALSE | FALSE | FALSE | TRUE | FALSE | | | | | TRUE | | | | -| Wallet | z_getbalance | No | Disabled in 6.2.0 | Not planned | | Zallet | | Requested by 1 mining pool, should use z_getbalanceforaccount, z_getbalanceforviewingkey, or getbalance (for legacy transparent balance) instead. | FALSE | FALSE | FALSE | FALSE | TRUE | | | | | | | | | -| Wallet | z_getbalanceforaccount | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | TRUE | | | | -| Wallet | z_getbalanceforviewingkey | No | Yes | | | Zallet | Implement in Zallet | Zallet has UUID for all accounts including viewing keys, so not as necessary. | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | z_getmigrationstatus | No | Yes | Not planned | | Zallet | | Could implement dummy "not migrating" response | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | z_getnewaccount | No | Yes | WIP | | Zallet | Implement in Zallet | Requested by 1 mining pool | FALSE | FALSE | FALSE | TRUE | FALSE | | | | | TRUE | TRUE | | | -| Wallet | z_getnewaddress | No | Disabled in 5.4.0 | Not planned | | Zallet | | Deprecated | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | z_getnotescount | No | Yes | WIP | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | TRUE | | | | -| Wallet | z_getoperationresult | No | Yes | | | Zallet | Implement in Zallet | Requested by 2 mining pools | FALSE | FALSE | FALSE | TRUE | TRUE | | | | | TRUE | | | | -| Wallet | z_getoperationstatus | No | Yes | | | Zallet | Implement in Zallet | Requested by 2 mining pools | FALSE | FALSE | FALSE | TRUE | TRUE | | | | | | | | | -| Wallet | z_gettotalbalance | No | Deprecated in 5.0.0 | Not planned | | Zallet | | Deprecated \| requested by 1 mining pool, should use z_getbalanceforaccount or getbalance | FALSE | FALSE | FALSE | FALSE | TRUE | FALSE | FALSE | TRUE | | | | | | -| Wallet | z_importkey | No | Sprout or Sapling | | | Zallet | Implement in Zallet | Implement for Sapling. Orchard support not needed. | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | z_importviewingkey | No | Sprout or Sapling | | | Zallet | Implement in Zallet | Implement for Sapling. Unified Viewing Key support not needed. | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | z_importwallet | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | z_listaccounts | No | Yes | WIP | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | TRUE | TRUE | | | -| Wallet | z_listaddresses | No | Disabled in 5.4.0 | Not planned | | Zallet | | Deprecated, should use listaddresses | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | z_listoperationids | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | z_listreceivedbyaddress | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | z_listunifiedreceivers | Yes | Yes | Stub | | Zallet \| Zebra | Implement in Zallet | Requested by 1 mining pool | TRUE | FALSE | TRUE | TRUE | FALSE | | | | | TRUE | | | | -| Wallet | z_listunspent | No | Yes | WIP | | Zallet | | Requested by 1 mining pool | FALSE | FALSE | FALSE | TRUE | FALSE | | | | | TRUE | | | | -| Wallet | z_mergetoaddress | No | Yes | | | Zallet | Implement in Zallet | | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | z_sendmany | No | Yes | | | Zallet | Implement in Zallet | Requested by 2 mining pools | FALSE | FALSE | FALSE | TRUE | TRUE | | | | | TRUE | TRUE | | | -| Wallet | z_setmigration | No | Yes | | | Zallet | Do not implement | zcashd only supported Sprout-to-Sapling; Zallet won't support Sprout. | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | z_shieldcoinbase | No | Yes | | | Zallet | Implement in Zallet | Requested by 2 mining pools | FALSE | FALSE | FALSE | TRUE | TRUE | | | | | | TRUE | | | -| Wallet | z_viewtransaction | No | Yes | | | Zallet | Implement in Zallet | Maybe modify semantics to show entire transaction | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | zcbenchmark | No | Yes | Not planned | | Zallet | | | FALSE | FALSE | FALSE | | | | | | | | | | | -| Wallet | zcsamplejoinsplit | No | Yes | | | Zallet | Do not implement | Sprout-only | FALSE | FALSE | FALSE | | | | | | | | | | | diff --git a/docs/data/zebra_adoption_tracking/analyze_nodes.sh b/docs/data/zebra_adoption_tracking/analyze_nodes.sh deleted file mode 100755 index d2b4930..0000000 --- a/docs/data/zebra_adoption_tracking/analyze_nodes.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash -#set -x - -# Download nodes as HTML from https://mainnet.zcashexplorer.app/nodes with error handling -if ! wget -q -O memory-bank/data/zebra_adoption_tracking/nodes.html https://mainnet.zcashexplorer.app/nodes; then - echo "Error: Failed to download nodes HTML file." - exit 1 -fi -wget -q -O memory-bank/data/zebra_adoption_tracking/nodes.html https://mainnet.zcashexplorer.app/nodes - -# Read the local HTML file -html_content=$(cat memory-bank/data/zebra_adoption_tracking/nodes.html) - -# Extract lines containing version information and then the version string -versions=$(echo "$html_content" | grep '/Zebra:\|/MagicBean:' | sed 's/.*\/\(Zebra\|MagicBean\):[^/]*\/.*/\1/') - -# Count Zebra and MagicBean nodes -zebra_count=$(echo "$versions" | grep -c 'Zebra') -magicbean_count=$(echo "$versions" | grep -c 'MagicBean') - -# Calculate total nodes -total_nodes=$((zebra_count + magicbean_count)) - -# Calculate percentages -if [ "$total_nodes" -eq 0 ]; then - zebra_percentage=0 - magicbean_percentage=0 -else - zebra_percentage=$(awk "BEGIN { printf \"%.2f\", ($zebra_count * 100 / $total_nodes) }") - magicbean_percentage=$(awk "BEGIN { printf \"%.2f\", ($magicbean_count * 100 / $total_nodes) }") -fi - -# Output the results -echo "Total nodes: $total_nodes" -echo "Zebra nodes: $zebra_count ($zebra_percentage%)" -echo "MagicBean nodes: $magicbean_count ($magicbean_percentage%)" diff --git a/docs/docker-architecture.md b/docs/docker-architecture.md index 08d33de..703f0c8 100644 --- a/docs/docker-architecture.md +++ b/docs/docker-architecture.md @@ -1,143 +1,173 @@ # Docker Compose Architecture -This document explains the architectural decisions, patterns, and modern Docker Compose features used in the Z3 stack. It serves as a reference for contributors and operators who need to understand *why* things are structured the way they are. +This document explains the Docker Compose patterns and runtime behavior used in the z3 stack. It is a reference for contributors and operators who need to understand how the stack is structured. + +For the public contract (network names, volume names, port matrix), see [`contract.md`](contract.md). ## Overview ```text docker-compose.yml Base stack (Zebra + Zaino + Zallet + optional profiles) docker-compose.regtest.yml Regtest overlay (structural differences only) -.env.example Reference for all overridable variables -.env User overrides (gitignored, optional) -.env.regtest Regtest configuration (tracked) -config/ All service configs (mainnet defaults + regtest/ subdirectory) -scripts/ Operational scripts (regtest-init, check-zebra-readiness, etc.) +.env.mainnet Mainnet selection + canonical ports +.env.testnet Testnet selection + offset ports +.env.regtest Regtest selection + overlay loader +.env.example Reference for every public override +.env Operator-specific overrides (gitignored, optional) +config/mainnet/ Mainnet Zallet + Zaino configs + identity file +config/testnet/ Testnet equivalents +config/regtest/ Regtest equivalents +scripts/ Operational scripts (regtest-init, fix-permissions, etc.) ``` -The core principle: **`docker-compose.yml` is self-sufficient**. Every variable uses `${VAR:-default}` syntax, so `docker compose up` works on a fresh clone with zero configuration files. The `.env` file is purely optional; users create it only when they want to override a default. - -## Defaults-in-Compose Pattern +The core principle: **`docker-compose.yml` is self-sufficient for mainnet**. Every variable uses `${VAR:-default}` syntax, so `docker compose up` works on a fresh clone with zero configuration files. The per-network env files exist to switch networks; `.env` is purely optional operator overrides. -### How it works +## Defaults-in-compose pattern Every variable reference in `docker-compose.yml` includes a default value: ```yaml -image: ${ZEBRA_IMAGE:-zfnd/zebra:latest} +image: ${Z3_ZEBRA_IMAGE:-zfnd/zebra:5.0.0} environment: - ZEBRA_NETWORK__NETWORK: ${NETWORK_NAME:-Mainnet} + ZEBRA_NETWORK__NETWORK: ${Z3_NETWORK:-Mainnet} volumes: - - ${Z3_ZEBRA_DATA_PATH:-zebra_data}:/home/zebra/.cache/zebra + - ${Z3_CHAIN_DATA_PATH:-chain}:/home/zebra/.cache/zebra ports: - - "${Z3_ZEBRA_HOST_RPC_PORT:-18232}:${Z3_ZEBRA_RPC_PORT:-18232}" + - "${Z3_ZEBRA_HOST_RPC_PORT:-8232}:${Z3_ZEBRA_RPC_PORT:-8232}" ``` Docker Compose resolves `${VAR:-default}` as: use `VAR` if set and non-empty, otherwise use `default`. Values come from (highest precedence first): -1. Shell environment variables (`ZEBRA_IMAGE=custom docker compose up`) -2. `.env` file in the project root -3. The `:-default` fallback in the compose file +1. Shell environment variables (`Z3_ZEBRA_IMAGE=custom docker compose up`) +2. `--env-file ` arguments +3. `.env` file in the project root (auto-loaded) +4. The `:-default` fallback in the compose file -### Why not a tracked `.env` with all defaults? +The mainnet env file (`.env.mainnet`) sets only `COMPOSE_PROJECT_NAME`, `Z3_NETWORK`, and `Z3_CONFIG_DIR` because the compose defaults already match mainnet. Testnet and regtest env files set additional overrides (container ports, host ports, network selection) to switch the stack. -The previous approach kept a 183-line `.env` file tracked in git with all values populated. This had problems: +## Multi-environment support -- **Mandatory dependency**: `docker compose up` failed without the file -- **Duplication**: Three per-network copies (mainnet/testnet/regtest) with 95% identical content -- **Drift**: Changes to defaults required updating multiple files -- **Merge conflicts**: Users who modified `.env` locally hit conflicts on every pull +### Per-network Compose projects -With defaults in the compose file, none of these problems exist. A Testnet user creates a 1-line `.env`: +Z3 runs as one of three Compose projects: `z3-mainnet`, `z3-testnet`, `z3-regtest`. Each is a separate logical instance with its own resources. Project name comes from `COMPOSE_PROJECT_NAME` in the env file. -```env -NETWORK_NAME=Testnet -``` +| Network | Project | Selected via | +|---------|---------|--------------| +| Mainnet | `z3-mainnet` | `docker compose --env-file .env.mainnet up` | +| Testnet | `z3-testnet` | `docker compose --env-file .env.testnet up` | +| Regtest | `z3-regtest` | `docker compose --env-file .env.regtest up` | -Everything else inherits from the compose defaults. +Compose's native project boundary handles isolation: each project has its own containers, network, and volumes. Mainnet, testnet, and regtest can run concurrently on one host without collisions because their published host ports differ. Testnet keeps Zebra's canonical host ports; regtest uses explicit host ports where a simple offset would collide with another service. -## Multi-Environment Support +### Explicit `name:` declarations -### `COMPOSE_PROJECT_NAME` — Volume and Network Isolation +Compose's default behavior prefixes every volume and network with `${COMPOSE_PROJECT_NAME}_`. For internal services this is fine; for the public contract identifiers that consumers attach to, it is brittle: a project rename silently breaks every consumer. -Docker Compose automatically prefixes all resources (volumes, networks) with the project name. This gives each environment its own isolated storage without parameterizing volume names: +Z3 declares external-facing resources with explicit `name:`: -| Environment | Project name | Volume on disk | -|------------|-------------|----------------| -| Mainnet | `z3` (default, from directory) | `z3_zebra_data` | -| Regtest | `z3-regtest` (from `.env.regtest`) | `z3-regtest_zebra_data` | +```yaml +volumes: + chain: + name: ${COMPOSE_PROJECT_NAME:-z3-mainnet}-chain + cookie: + name: ${COMPOSE_PROJECT_NAME:-z3-mainnet}-cookie + +networks: + default: + name: ${COMPOSE_PROJECT_NAME:-z3-mainnet} +``` -The same `zebra_data` volume name in the compose file creates different actual volumes depending on the project name. No naming tricks needed. +The `name:` field is documented as "used as-is and not scoped with the project name" (Docker Compose reference). This makes `z3-testnet-cookie` and `z3-testnet` the stable external identifiers consumers reference via `external: true, name: ...`. Renaming the Compose project would not affect them. -### `COMPOSE_FILE` — Automatic Overlay Loading +Volumes and networks not part of the contract (Prometheus data, Compose configs) keep the default prefix; they are internal. -`.env.regtest` includes: +### `COMPOSE_FILE` for overlay loading + +`.env.regtest` includes `COMPOSE_FILE` so the regtest overlay is loaded with +the base compose: ```env COMPOSE_FILE=docker-compose.yml:docker-compose.regtest.yml ``` -When you run `docker compose --env-file .env.regtest up`, Compose automatically loads both files and merges them. The colon-separated list is processed left to right; later files override earlier ones. - -This means the regtest overlay only needs to contain *structural differences* from the base (different healthchecks, additional services, authentication changes). Everything else is inherited. +When run with `--env-file .env.regtest`, Compose loads both files and merges them. The colon-separated list is processed left to right; later files override earlier ones. The regtest overlay contains the peerless healthcheck, username/password auth on Zaino, and the optional rpc-router. Testnet needs no overlay: `.env.testnet` sets `COMPOSE_FILE=docker-compose.yml` and selects the network through `Z3_NETWORK=Testnet`. -### Compose File Merge Rules +### Compose file merge rules -When the regtest overlay defines the same service as the base, attributes merge as follows: +When an overlay defines the same service as the base, attributes merge as follows: | Attribute type | Merge behavior | |----------------|---------------| -| Scalars (`image`, `command`, `container_name`) | Override replaces | +| Scalars (`image`, `command`) | Override replaces | | `environment` | Merge by key name; override wins on conflict | | `volumes` (service-level) | Merge by mount target path; same target = override wins | | `ports` | Append | | `healthcheck` | Override replaces entirely | | `build.args` | Merge by key name; override wins on conflict | -Example: the base mounts `./config/zaino.toml` at `/etc/zaino/zindexer.toml`. The regtest overlay mounts `./config/regtest/zaino.toml` at the same target path. Because the target matches, the overlay's mount replaces the base's; no duplication, no conflict. +### `!override` YAML tag -### `!override` YAML Tag — Replacing Attributes in Overlays - -Standard YAML merge can add and overwrite keys but cannot *remove* them. Docker Compose v2.24+ added the `!override` tag to solve this: +Standard YAML merge can add and overwrite keys but cannot *remove* them. Docker Compose v2.24.4+ supports the `!override` tag used here: ```yaml -# In docker-compose.regtest.yml — replaces Zaino's entire environment block, -# removing the cookie auth path that the base compose sets services: zaino: environment: !override RUST_LOG: info ZAINO_NETWORK: Regtest - # ... only regtest-relevant vars, no cookie path + # only regtest-relevant vars; cookie path from base is dropped ``` `!override` fully replaces the attribute instead of merging. The regtest overlay uses this on Zaino's `environment` to switch from cookie-based authentication (base compose) to username/password authentication (credentials in `config/regtest/zaino.toml`). -A related tag, `!reset`, clears an attribute to its default value (e.g., `ports: !reset []` empties the port list instead of appending). +A related tag, `!reset`, clears an attribute to its default value. This stack requires Docker Compose v2.24.4 or later for these tags. + +## Per-network configuration + +### Zallet config files + +Zallet's `[indexer]` block hardcodes the validator address it connects to (Zebra's JSON-RPC). Because the JSON-RPC port differs per network (Mainnet 8232, Testnet 18232, Regtest 18232), Z3 ships one Zallet config per network: + +``` +config/mainnet/zallet.toml validator_address = "zebra:8232" +config/testnet/zallet.toml validator_address = "zebra:18232" +config/regtest/zallet.toml validator_address = "zebra:18232" (username/password auth) +``` + +The compose mount path is templated: -Both tags require Docker Compose v2.24.0 or later. +```yaml +volumes: + - ${Z3_CONFIG_DIR:-./config/mainnet}/zallet.toml:/etc/zallet/zallet.toml:ro +``` + +`Z3_CONFIG_DIR` is set per env file (`./config/mainnet`, `./config/testnet`, `./config/regtest`). To change Zallet behavior, edit the per-network file directly. + +### Zaino config files -## Extension Fields and YAML Anchors +Zaino's config is empty for mainnet and testnet (all settings come from env vars); regtest needs a non-empty file because it uses username/password auth that cannot be set via env vars (Zaino blocks env vars containing "password" for security). The compose mounts `${Z3_CONFIG_DIR}/zaino.toml` for symmetry with Zallet. -### `x-common` — Shared Service Configuration +### Zallet identity files + +Each network has its own age-encryption identity at `config//zallet_identity.txt`. The file is gitignored. Identities are generated by `scripts/setup-network.sh ` (which `scripts/regtest-init.sh` delegates to for regtest). See the README Quick start. + +## Extension fields and YAML anchors + +### `x-common`: shared service configuration ```yaml x-common: &common - logging: - driver: json-file - options: - max-size: "50m" - max-file: "5" cap_drop: [ALL] security_opt: [no-new-privileges:true] ``` -Services reference this with `<<: *common`, which merges all keys from the anchor into the service definition. This ensures consistent log rotation and security hardening across all services without repeating the configuration. +Services reference this with `<<: *common` for consistent security hardening without repeating the configuration. Logging is intentionally absent (see "Log rotation" below). -Top-level keys starting with `x-` are *extension fields*; Docker Compose ignores them during processing but they serve as anchor sources for YAML reuse. +Top-level keys starting with `x-` are *extension fields*; Compose ignores them during processing but they serve as anchor sources for YAML reuse. -### Zebra's `setpriv` Entrypoint +### Zebra's `setpriv` entrypoint -Zebra's Docker entrypoint starts as root, runs `mkdir` and `chown` to set up mounted volume directories, then uses `setpriv` (part of `util-linux`, included in Debian trixie) to drop to a non-root user. These pre-privilege-drop operations need capabilities that `cap_drop: [ALL]` removes, so Zebra adds back only the 5 it needs: +Zebra's Docker entrypoint starts as root, runs `mkdir` and `chown` to set up mounted volume directories, then uses `setpriv` (part of `util-linux`, included in Debian trixie) to drop to a non-root user. These pre-privilege-drop operations need capabilities that `cap_drop: [ALL]` removes, so Zebra adds back only the five it needs: ```yaml cap_add: [CHOWN, DAC_OVERRIDE, FOWNER, SETUID, SETGID] @@ -147,9 +177,9 @@ Zaino and Zallet run as non-root from the start and work with `cap_drop: [ALL]` ## Healthchecks -### `start_interval` — Two-Speed Healthchecks +### `start_interval`: two-speed healthchecks -Docker Engine 25.0+ supports `start_interval`, which checks more frequently during startup then backs off: +Docker Engine 25.0+ supports `start_interval`, which checks more frequently during startup and then backs off: ```yaml healthcheck: @@ -159,11 +189,11 @@ healthcheck: start_period: 90s # grace period before failures count ``` -During the `start_period`, the check runs every `start_interval` (5s). After the first success or after `start_period` expires, it switches to `interval` (30s). This means a service that becomes ready in 10 seconds is detected in ~15 seconds instead of waiting up to 120 seconds. +During the `start_period`, the check runs every `start_interval` (5s). After the first success or after `start_period` expires, it switches to `interval` (30s). A service that becomes ready in 10 seconds is detected in ~15 seconds instead of waiting up to 120 seconds. -### Zaino — Port Check Instead of Process Check +### Zaino: port check instead of process check -Zaino's image (`debian:bookworm-slim`) doesn't include `curl` or `netcat`. The healthcheck uses bash's built-in TCP socket capability: +Zaino's image (`debian:bookworm-slim`) does not include `curl` or `netcat`. The healthcheck uses bash's built-in TCP socket capability: ```yaml test: ["CMD-SHELL", "bash -c 'echo > /dev/tcp/127.0.0.1/8137' 2>/dev/null || exit 1"] @@ -171,15 +201,15 @@ test: ["CMD-SHELL", "bash -c 'echo > /dev/tcp/127.0.0.1/8137' 2>/dev/null || exi This verifies the gRPC port is actually accepting connections. The previous check (`zainod --version`) only confirmed the binary existed on disk; it would pass even if the service had crashed after startup. -### Regtest Zebra — RPC Check Instead of `/ready` +### Regtest Zebra: RPC check instead of `/ready` The base compose uses Zebra's `/ready` endpoint, which verifies the node is synced near the network tip. In regtest mode there are no peers and no network tip to sync to, so `/ready` would never succeed. The regtest overlay replaces this with a direct RPC call (`getblockchaininfo`) that confirms the RPC server is responding. -### Development Override +### Development override `docker-compose.override.yml.example` provides a ready-made override that switches Zebra's healthcheck from `/ready` to `/healthy`, allowing dependent services to start during sync. Copy it to `docker-compose.override.yml` (gitignored) for local development. -## Security Hardening +## Security hardening ### `cap_drop: [ALL]` @@ -189,57 +219,69 @@ Linux containers receive ~14 capabilities by default (including `CHOWN`, `DAC_OV Prevents processes inside the container from gaining additional privileges through setuid binaries or capability inheritance. Even if an attacker writes a setuid binary into a writable tmpfs, it won't escalate privileges. -### Log Rotation +### Log rotation -Without `max-size` and `max-file`, Docker's default `json-file` log driver grows logs unbounded. For a blockchain node running 24/7, this will eventually fill the disk. The `x-common` anchor configures 50MB per log file with 5 rotated files (250MB max per service). +z3 does not pin a `logging:` driver on any service. A Compose `logging:` block overrides the driver the operator configured on the Docker daemon (journald, local, a remote collector), so forcing `json-file` would silently undo that choice. Bounding log growth is the daemon's job: set a rotating default once in `/etc/docker/daemon.json`, which applies to every container on the host. -## Image Override Variables +```json +{ + "log-driver": "local", + "log-opts": { "max-size": "50m", "max-file": "5" } +} +``` -All service images are overridable via environment variables: +The `local` driver rotates by default and is more efficient than `json-file`. Operators who want per-service control add a `logging:` block in their override file instead. -```yaml -image: ${ZEBRA_IMAGE:-zfnd/zebra:latest} -image: ${ZAINO_IMAGE:-ghcr.io/zcashfoundation/zaino:sha-83e41d7} -image: ${ZALLET_IMAGE:-electriccoinco/zallet:v0.1.0-alpha.3} -image: ${ZCASHD_IMAGE:-zodlinc/zcashd:v6.12.1} -``` +## Image override variables + +All service images are overridable. Compose references each image as `${Z3__IMAGE:-}`; the default tags are inline in `docker-compose.yml` itself and bump per upstream release. This allows operators to: + +- Pin to a specific version or digest for reproducibility. +- Test a pre-release candidate without editing the compose file. +- Use a private registry mirror in air-gapped environments. +- Run CI with custom-built images via shell variables. + +Tags are pinned, never floating (`:latest`). On a consensus-critical node platform a silent major bump on the next `pull` or recreate could fork the operator off the network, so upgrades are deliberate: bump the inline default in a reviewed change, set `Z3__IMAGE` to move a single service, or let Renovate (`renovate.json`) raise an auditable bump PR. Dependabot stays scoped to GitHub Actions because it cannot parse the `${VAR:-tag}` default form. + +The `Z3_*_IMAGE` prefix marks these as part of the public contract; `z3-contract.yaml` lists the env-var schema in full. + +## Environment variable strategy + +### Explicit mapping via `environment:` -This allows operators to: +All services declare their environment variables explicitly in the `environment:` block. This prevents unintended variables from crossing service boundaries. Zallet does not support environment variable configuration at all, so it only receives `RUST_LOG`. -- Pin to a specific version or digest for reproducibility -- Test a pre-release candidate without editing the compose file -- Use a private registry mirror in air-gapped environments -- Run CI with custom-built images via shell variables +### Zebra's `env_file` exception -## Environment Variable Strategy +Zebra is the only service that also uses `env_file: [{path: ./.env, required: false}]`. Zebra uses config-rs, which auto-reads any `ZEBRA_*` environment variable. Optional config-rs variables like `ZEBRA_TRACING__OPENTELEMETRY_*` cannot be listed in the explicit `environment:` block with empty defaults, because config-rs treats empty strings as values and crashes when parsing `""` as a socket address. `ZEBRA_METRICS__ENDPOINT_ADDR` is the exception: z3 sets a non-empty default so the monitoring profile always has a Zebra scrape target. -### Explicit Mapping via `environment:` +The `env_file` passthrough allows these optional variables to reach Zebra only when the operator explicitly sets them in `.env`. When `.env` does not exist, Zebra receives only the explicit `environment:` variables and uses its built-in defaults. -All services declare their environment variables explicitly in the `environment:` block. This prevents unintended leakage of variables between services. Zallet does not support environment variable configuration at all, so it only receives `RUST_LOG`. +Non-`ZEBRA_*` variables from `env_file` are ignored because config-rs reads only variables that match its configured prefix. -### Zebra's `env_file` Exception +## Cookie-permissions sidecar -Zebra is the only service that also uses `env_file: [{path: ./.env, required: false}]`. This exists because Zebra uses config-rs, which auto-reads any `ZEBRA_*` environment variable. Optional config-rs variables like `ZEBRA_METRICS__ENDPOINT_ADDR` and `ZEBRA_TRACING__OPENTELEMETRY_*` cannot be listed in the explicit `environment:` block with empty defaults, because config-rs treats empty strings as values and crashes when parsing `""` as a socket address or integer. +Zebra writes the RPC cookie at `/var/run/auth/.cookie` with mode `0600` owned by uid 10001. Z3's consumer-attachment surface includes the cookie volume, so any service or downstream container that mounts it needs to read the cookie. -The `env_file` passthrough allows these optional variables to reach Zebra only when the user explicitly sets them in `.env`. When `.env` doesn't exist (the `required: false` case), Zebra receives only the explicit `environment:` variables and uses its built-in defaults for everything else. +The base compose includes a small `cookie-permissions` sidecar (`alpine:3` with `cap_add: [FOWNER]`) that polls every 5 seconds and chmods the cookie to `0644` once it appears. The cookie volume is already the consumer attachment surface, so loosening the file mode within that volume does not change the security boundary; anyone with access to mount the volume already has access to the cookie. -Non-ZEBRA variables (ZAINO_*, Z3_*, etc.) that leak through `env_file` are harmless because config-rs ignores variables that don't match its configured prefix. +Zaino and Zallet depend on the sidecar's healthcheck, so targeted starts such as `docker compose up -d zaino` also start the sidecar and wait until the cookie is readable. On mainnet and testnet the healthcheck waits for `/var/run/auth/.cookie`; on regtest it exits successfully because cookie auth is disabled and username/password auth is used instead. -## Regtest Overlay Constraints +## Regtest overlay constraints -### Zaino Authentication +### Zaino authentication -The base compose configures Zaino with cookie-based authentication (shared cookie volume with Zebra). Regtest disables cookie auth (`ENABLE_COOKIE_AUTH=false`), so the regtest overlay uses `environment: !override` on Zaino to replace the full environment block, removing the cookie path and all variables that are not needed in regtest. +The base compose configures Zaino with cookie-based authentication (shared cookie volume with Zebra). Regtest disables cookie auth (`ZEBRA_RPC__ENABLE_COOKIE_AUTH=false`), so the regtest overlay uses `environment: !override` on Zaino to replace the full environment block, removing the cookie path and other base vars. -Regtest instead uses username/password authentication configured in `config/regtest/zaino.toml`. These credentials cannot be set via environment variables because Zaino blocks sensitive keys (containing "password") in env vars for security reasons. +Regtest instead uses username/password authentication configured in `config/regtest/zaino.toml`. These credentials cannot be set via environment variables because Zaino blocks sensitive keys (containing "password") in env vars for security. -### Config File vs Environment Variable Conflicts +### Config file vs environment variable conflicts -Zaino uses config-rs, which merges values from both TOML config files and environment variables. If the same field is set in both places, config-rs panics with a "duplicate field" error. The regtest zaino config (`config/regtest/zaino.toml`) must only contain settings that are NOT set via environment variables. Currently it contains only `backend` and the auth credentials. +Zaino's config-rs merges values from both TOML config files and environment variables. If the same field is set in both places, config-rs panics with a "duplicate field" error. The regtest Zaino config must contain only settings that are not set via environment variables. Currently it contains only `backend` and the auth credentials. -### `docker compose run` and the `--config` Flag +### `docker compose run` and the `--config` flag -When using `docker compose run` to execute one-off commands (e.g., wallet initialization), the arguments replace the service's `command` from the compose file. This means the `--config /etc/zallet/zallet.toml` flag from the base service definition is not inherited. The init script must pass `--config` explicitly in every `compose run` invocation. +When using `docker compose run` to execute one-off commands (for example, wallet initialization), the arguments replace the service's `command` from the compose file. The `--config /etc/zallet/zallet.toml` flag from the base service definition is not inherited. The init script must pass `--config` explicitly in every `compose run` invocation. ## `stop_grace_period` diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 0000000..88123c6 --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,195 @@ +# Z3 FAQ + +Answers to behaviors that look like bugs but are working as designed, plus a few real footguns. This FAQ answers "why is the thing I'm running behaving this way?". For how the stack is built internally, see [docker-architecture.md](docker-architecture.md). + +Questions are grouped by what you are doing: + +- **[For operators](#for-operators)**: running the stack as infrastructure. +- **[For developers and testers](#for-developers-and-testers)**: building or testing against Z3, often across several networks at once. + +When an entry says "see README", the fix lives there; this FAQ only adds the diagnostic context. + +--- + +## For operators + +### Q: Why is my Zebra container marked `unhealthy` right after start? + +That's the `/ready` probe doing its job, not a fault. `/ready` requires the node to have at least `ZEBRA_HEALTH__MIN_CONNECTED_PEERS` (default `1`) and to be within `ZEBRA_HEALTH__READY_MAX_BLOCKS_BEHIND` (default `2`) of the network tip. A fresh start from a cold chain, or a restart on a cache that's a few minutes behind, will report `unhealthy` until both thresholds are met. + +For development, `/healthy` is a looser signal that only checks peer connectivity. The tracked `docker-compose.override.yml.example` flips the healthcheck to `/healthy` so Zaino and Zallet can start without waiting for full sync. Use it for dev, never for production where you want consumers to wait for a synced node. + +Don't run `docker compose up -d` (it starts Zaino and Zallet) until Zebra reports `/ready`. The poller `scripts/check-zebra-readiness.sh` waits for exactly that. README Quick start step 3 explains the two-phase boot. + +--- + +### Q: Where does z3 store chain state, and what do I actually back up? + +Chain state for each network is a Docker named volume, `z3--chain`, under Docker's data root (`/var/lib/docker/volumes/` on Linux). Mainnet is roughly 300 GB. Find the exact path with: + +```bash +docker volume inspect z3-mainnet-chain -f '{{.Mountpoint}}' +``` + +To keep it off the OS disk, set `Z3_CHAIN_DATA_PATH=/mnt/ssd/zebra-state` and run `./scripts/fix-permissions.sh zebra /mnt/ssd/zebra-state` before the first start. + +For backups, the only thing worth keeping is the wallet, and it takes two pieces **together**: the `z3--zallet` volume **and** the matching `config//zallet_identity.txt`. The wallet database in the volume is age-encrypted with that identity file, which lives outside the volume; restoring the volume without the identity leaves you with ciphertext you cannot open. Chain state is re-syncable and the RPC cookie is regenerated on boot, so neither belongs in a backup. + +`docker compose --env-file .env. down` keeps all volumes; `down -v` deletes them and forces a full re-sync. + +--- + +### Q: How do I set up the Zallet wallet on mainnet or testnet? + +`scripts/regtest-init.sh` initializes the regtest wallet automatically. On mainnet and testnet, the node and indexer run fine without a wallet; initialize Zallet's wallet encryption yourself, once per network, when you are ready to manage keys: + +```bash +docker compose --env-file .env. run --rm --no-deps zallet \ + --datadir /var/lib/zallet --config /etc/zallet/zallet.toml init-wallet-encryption +``` + +`--no-deps` skips starting Zebra (encryption setup does not need it). Each network keeps its Zallet data in a separate volume, so run this once per network you use. The wallet is encrypted with `config//zallet_identity.txt`, generated by `setup-network.sh`; back it up with the volume (see the data question above), because the wallet cannot be opened without it. Afterwards, use the Zallet RPC or `generate-mnemonic` to create or import keys. + +--- + +### Q: Why doesn't z3 set a logging driver, and how do I cap log size? + +By design. Pinning `logging.driver` in the compose file would override whatever logging driver you set on the Docker daemon (journald, local, awslogs, a remote collector). z3 leaves logging unset so your daemon default wins. + +To bound disk growth on a 24/7 node, set a rotating default once in `/etc/docker/daemon.json` and restart the daemon: + +```json +{ + "log-driver": "local", + "log-opts": { "max-size": "50m", "max-file": "5" } +} +``` + +The `local` driver rotates by default and is more efficient than `json-file`. This applies to every container on the host, not just z3. If you want per-service control, add a `logging:` block in your operator-local override file. + +--- + +### Q: Why is my container free to use all of my host's CPU and RAM? + +Because `docker-compose.yml` doesn't set `deploy.resources.limits` on any service. The choice is deliberate for a node platform: a constrained limit that makes sense on a 4-core laptop will silently throttle a 32-core production host, and the bound that's right for one operator is wrong for the next. + +If you want to bound a single noisy service (a bulk indexer, a one-off backfill) without hand-tuning everything else, add the limit in your operator-local override file rather than the tracked compose: + +```yaml +# docker-compose.override.yml (mainnet) or docker-compose.testnet.override.yml (testnet) +services: + zebra: + deploy: + resources: + limits: + cpus: "8" + memory: 8g +``` + +Keep the limit generous on the service you want to win contention (Zebra), tight on the service you want to lose it (the noisy consumer). Don't add limits to services you haven't actually seen misbehave, since under-sized limits cause more outages than they prevent. + +--- + +## For developers and testers + +### Q: How do per-network compose overrides work? + +Overrides are opt-in. A fresh clone boots with no override file, so nothing breaks before you run any setup. + +- **Mainnet:** Compose natively auto-loads `docker-compose.override.yml` when it is present and you pass no `-f` or `COMPOSE_FILE`. An absent file is not an error. +- **Testnet and regtest:** `.env.` sets `COMPOSE_FILE=docker-compose.yml:docker-compose..yml`, with no override entry, so the stack renders on a fresh clone. To add per-host customizations (pinning Zebra to `linux/arm64`, adding `deploy.resources.limits`), create `docker-compose..override.yml` and load it explicitly, either by passing `-f docker-compose.yml -f docker-compose..yml -f docker-compose..override.yml`, or by appending the override to `COMPOSE_FILE` in your operator-local `.env`. + +The compose merge order is left-to-right, so the override comes last and wins. The live override file is gitignored, so `git pull` never touches it. + +--- + +### Q: Why doesn't `DOCKER_PLATFORM` in my `.env` take effect? + +Because `docker compose --env-file .env.` *replaces* the auto-loaded `.env` for variable interpolation rather than layering on top of it. When you use `--env-file`, anything set only in `.env` is ignored for `${DOCKER_PLATFORM:-linux/amd64}` substitution. This is documented inside `.env` itself and in [docs/contract.md → How env is loaded](contract.md), but it bites everyone at least once. + +Two reliable workarounds: + +```bash +# (a) export before the call (shell env beats env-file in compose precedence) +export DOCKER_PLATFORM=linux/arm64 +docker compose --env-file .env.mainnet up -d + +# (b) pass .env as a second --env-file (loaded after the network one, so its values win) +docker compose --env-file .env.mainnet --env-file .env up -d +``` + +The mainnet stack also has a third path: `docker-compose.override.yml` (gitignored, auto-loaded for mainnet only) can hold an explicit `services.zebra.platform: linux/arm64`. That bypasses env-var interpolation entirely. For testnet and regtest the override file isn't auto-loaded; see the overrides question above. + +--- + +### Q: Why is my Zebra container running under emulation on Apple Silicon? + +The default `Z3_ZEBRA_IMAGE` is multi-arch and resolves to native arm64 on Apple Silicon. If `uname -m` inside the Zebra container reports `x86_64` instead of `aarch64`, you've either pinned `Z3_ZEBRA_IMAGE` to an amd64-only tag or exported `DOCKER_PLATFORM=linux/amd64`. Emulation pushes Halo2/Groth16 verification past its internal deadline and surfaces as `Transaction(InternalDowncastError("...Elapsed(())"))` followed by chain-tip sync stalls. + +Confirm what's actually running: + +```bash +docker exec z3-mainnet-zebra-1 uname -m # aarch64 = native, x86_64 = emulated +docker top z3-mainnet-zebra-1 -o cmd | head -3 # /usr/bin/qemu-x86_64 wrapper = emulated +``` + +Zaino and Zallet are pinned to amd64 by default (their upstream images publish amd64 only) and run under emulation; the workload is light enough that the CPU drain is barely noticeable next to Zebra's verifier. + +--- + +### Q: Can I run Zaino or Zallet natively on Apple Silicon? + +Not from the pinned tags. The default Zaino and Zallet images publish `linux/amd64` only (declared in [`z3-contract.yaml`](../z3-contract.yaml) under `image_platforms:`). Confirm with `docker buildx imagetools inspect `. The `unknown/unknown` entries in that output are OCI attestation manifests (SBOM/provenance), not real platform variants. + +Two ways forward if you need native arm64: + +1. **Build locally.** `docker-compose.yml` declares `build:` contexts pointing at the `./zaino` and `./zallet` submodules, so `DOCKER_PLATFORM=linux/arm64 docker compose build zaino zallet` produces local arm64 images. +2. **Wait for the upstream tag to gain a multi-arch publish, then bump the pin.** Existing tags never gain new platform variants after the fact; only new tags do. + +Leaving these two services under emulation is fine in practice; the workload is light compared to Zebra's verifier, which runs natively. + +Zaino's canonical upstream is [zingolabs/zaino](https://github.com/zingolabs/zaino), published to Docker Hub as `zingodevops/zainod` (matching the daemon binary name). `zingodevops/zaino` is an alias publishing identical digests. + +--- + +### Q: Why does `docker image inspect` report a different architecture than what's running? + +Because `docker image inspect ` returns metadata for whichever variant your local store currently has cached under that tag, not the variant the running container was launched from. On an arm64 host the local cache often holds the arm64 metadata for a multi-arch tag even when a `platform: linux/amd64`-pinned container is actively running the amd64 variant out of the same manifest list. The tag → arch mapping is not the container → arch mapping. + +For the actual running architecture, use runtime signals: + +```bash +docker exec uname -m # reports x86_64 or aarch64 +docker top -o cmd | head -3 # qemu-x86_64 wrapper = emulated +docker exec sh -c 'od -An -tx1 -N20 /path/to/binary' # ELF e_machine at offset 0x12 +``` + +The ELF `e_machine` field at offset `0x12` is `0x3e` for x86_64 and `0xb7` for aarch64. That's the definitive answer when uname or `/proc` aren't available. + +--- + +### Q: Why does my Zebra slow down when I add a heavy local RPC consumer? + +Because Zebra serves block fetches and indexer streaming from the same Tokio worker pool that drives consensus block verification. There is no built-in per-client RPC rate limit and no priority queue between request handlers and the verifier. A bulk indexer in catch-up mode (Zinder, lightwalletd, a fresh Zaino) with high `fetch_concurrency` can sustain enough RPC pressure to slow tip sync, and in extreme cases push transaction verification past its internal deadline. + +The CPU saturation is usually the consumer's choice, not Zebra's: turn the consumer's parallelism down before you reach for compose-level limits. For an indexer that's behind, a `fetch_concurrency` in the low single digits while it catches up is much friendlier than the defaults most clients ship with. + +If you can't control the consumer, the next lever is compose-level CPU limits (the resource-limits question under [For operators](#q-why-is-my-container-free-to-use-all-of-my-hosts-cpu-and-ram)) so the consumer can never starve the verifier. + +--- + +### Q: Why does regtest use username/password instead of cookie auth? + +Because regtest is meant to look like classic local dev, where username/password auth is the convention every existing tutorial and client library assumes. The regtest overlay (`docker-compose.regtest.yml`) disables Zebra's cookie auth and adds an `rpc-router` sidecar that authenticates with `zebra` / `zebra` against Zebra, so the same RPC client works without juggling cookie files. + +Cookie auth stays the default for mainnet and testnet, where Zaino and Zallet read the shared cookie volume directly. See [docs/regtest.md](regtest.md) for the full regtest workflow and the curl/grpcurl examples that use the regtest credentials. + +--- + +## Where to file something that isn't here + +If you hit a behavior that looks wrong and the FAQ doesn't cover it, the fastest path to triage is: + +1. **Check the container's actual runtime state** (the `uname -m` / `docker top` / ELF-header trio in the architecture-detection question above). Most "why is this slow?" reports trace back to a platform or resource-limit assumption that wasn't true. +2. **Look at the compose-resolved config**, not the source YAML: `docker compose --env-file .env. config ` shows you the variables after interpolation, which is what Docker actually receives. +3. **Open an issue** with the resolved config, the runtime signals, and the symptom. Without the resolved config, every triage round restarts from "is your env var actually set." diff --git a/docs/integrations/README.md b/docs/integrations/README.md new file mode 100644 index 0000000..6cef48a --- /dev/null +++ b/docs/integrations/README.md @@ -0,0 +1,35 @@ +# Z3 integration examples + +How downstream services attach to a running Z3 stack. Pick the example that matches how your service deploys. + +| Integration type | When to use it | Example | +|-----------|----------------|---------| +| Compose-peer | Your service runs as a Docker container in the same logical stack as Z3 | [compose-peer.md](compose-peer.md) | +| Host-side pointer | Your service runs outside Docker (a host process, a CLI tool, a developer-laptop dev server) | [host-side-pointer.md](host-side-pointer.md) | +| Lightwalletd-compatible client | Your service is a wallet or block explorer that speaks the `CompactTxStreamer` gRPC protocol | [lightwalletd-client.md](lightwalletd-client.md) | +| Ephemeral test fixture | Your test suite needs a controlled, deterministic chain | NOT a Z3 attachment; see [the ephemeral-fixture note](#ephemeral-test-fixtures) below | + +All examples assume a running Z3 stack on the relevant network. Bring one up first: + +```bash +cd +docker compose --env-file .env.mainnet up -d # or testnet/regtest +``` + +See [`contract.md`](../contract.md) for the public identifiers each example references. + +## Conventions + +Examples use `` as a placeholder for the network you're targeting. Cookie-auth examples apply to mainnet and testnet; regtest disables cookie auth and uses username/password through its regtest configs and rpc-router. + +Volume and network names follow the contract pattern `z3--` (and `z3-` for the external network). Verify the exact names with: + +```bash +./scripts/validate-contract.py +``` + +## Ephemeral test fixtures + +If your test suite needs a fresh chain on every run, a fixed tip, or full control over consensus parameters, **don't** attach to a shared Z3 stack. Spin up an ephemeral Zebra (the existing `zebra-test` harness or a per-test `zebrad` binary) and tear it down with the test. + +If your tests need an indexer or wallet behind that ephemeral Zebra, run them per-test too. Sharing chain state across parallel tests is what makes them flaky. diff --git a/docs/integrations/compose-peer.md b/docs/integrations/compose-peer.md new file mode 100644 index 0000000..de7467d --- /dev/null +++ b/docs/integrations/compose-peer.md @@ -0,0 +1,137 @@ +# Compose-peer integration + +Your service runs as a Docker container in the same logical stack as Z3. On mainnet and testnet, you attach to Z3's external network for DNS and to Z3's cookie volume for RPC auth. + +This is the most efficient integration type: services talk over the Docker network without going through the host, and the RPC cookie is mounted directly into your container. Regtest disables cookie auth; for regtest, omit the cookie volume and use the username/password path (see [Regtest auth](#regtest-auth) below). + +## Prerequisites + +- A running Z3 stack: `docker compose --env-file .env. up -d` in the Z3 repo. +- Your service's compose file lives in your repo (not in Z3). + +## Compose snippet + +The contract publishes the external network and cookie volume names per network as `z3-` and `z3--cookie` ([contract.md](../contract.md#volumes)). Reuse them by name from your own env file so the same compose works on every network: + +```yaml +# your-service/docker-compose.yml + +name: my-service-${Z3_NETWORK_SHORT:-mainnet} + +networks: + z3: + external: true + name: ${Z3_EXTERNAL_NETWORK:-z3-mainnet} + +volumes: + z3-cookie: + external: true + name: ${Z3_COOKIE_VOLUME:-z3-mainnet-cookie} + +services: + my-service: + image: my-service:latest + networks: [z3] + volumes: + - z3-cookie:/var/run/auth:ro + environment: + MY_SERVICE_ZEBRA_RPC_URL: http://zebra:${Z3_ZEBRA_CONTAINER_PORT:-8232} + MY_SERVICE_ZEBRA_COOKIE_PATH: /var/run/auth/.cookie + MY_SERVICE_NETWORK: ${Z3_NETWORK:-Mainnet} +``` + +The four `${Z3_*}` substitutions come from your service's per-network env file (below). The volume and network names match the contract's published identifiers exactly. + +## Per-network env files + +Mirror Z3's own pattern: every per-network env file sets `Z3_NETWORK` (PascalCase, what Zebra's serde wants) plus the lowercase / numeric values your compose snippet substitutes. `Z3_EXTERNAL_NETWORK` and `Z3_COOKIE_VOLUME` come straight from the [contract](../contract.md#volumes); `Z3_ZEBRA_CONTAINER_PORT` from the [port matrix](../contract.md#port-matrix). + +```bash +# my-service/.env.mainnet +Z3_NETWORK=Mainnet +Z3_NETWORK_SHORT=mainnet +Z3_EXTERNAL_NETWORK=z3-mainnet +Z3_COOKIE_VOLUME=z3-mainnet-cookie +Z3_ZEBRA_CONTAINER_PORT=8232 +``` + +```bash +# my-service/.env.testnet +Z3_NETWORK=Testnet +Z3_NETWORK_SHORT=testnet +Z3_EXTERNAL_NETWORK=z3-testnet +Z3_COOKIE_VOLUME=z3-testnet-cookie +Z3_ZEBRA_CONTAINER_PORT=18232 +``` + +```bash +# my-service/.env.regtest +Z3_NETWORK=Regtest +Z3_NETWORK_SHORT=regtest +Z3_EXTERNAL_NETWORK=z3-regtest +Z3_COOKIE_VOLUME=z3-regtest-cookie +Z3_ZEBRA_CONTAINER_PORT=18232 +``` + +## Per-network container ports + +Container ports are not host ports. Inside the network, services use Zebra's per-network defaults; Zaino and Zallet container ports do not vary by network. Regtest reuses Zebra's testnet container port, so testnet and regtest share the same in-network address (`zebra:18232`); only their published host ports differ (testnet `18232`, regtest `29232`). + +| Network | Zebra RPC | Zaino gRPC | Zallet RPC | +|---------|-----------|-------------|-------------| +| Mainnet | `zebra:8232` | `zaino:8137` | `zallet:28232` | +| Testnet | `zebra:18232` | `zaino:8137` | `zallet:28232` | +| Regtest | `zebra:18232` | `zaino:8137` | `zallet:28232` | + +The full per-network matrix lives in [`z3-contract.yaml`](../../z3-contract.yaml) under `networks..ports`. + +## Regtest auth + +Regtest disables cookie auth (the contract sets `rpc_auth.mode: username_password` for that network). The cookie volume `z3-regtest-cookie` exists but holds no readable cookie. Two ways to authenticate: + +- **Direct to Zebra:** point at `http://zebra:18232` with HTTP Basic, user/password = `zebra` / `zebra` by default. Override via `Z3_REGTEST_RPC_ROUTER_USER` / `Z3_REGTEST_RPC_ROUTER_PASSWORD` on the Z3 side; the same credentials gate the rpc-router and Zebra in regtest. +- **Through rpc-router:** point at `http://rpc-router:8181` for a unified JSON-RPC endpoint that proxies to Zebra or Zallet by method name. Same credentials. + +Drop the cookie volume from your service's compose on regtest, or guard the mount with a per-network override file. + +## Bring it up + +```bash +# Bring up Z3 first. +cd ../z3 && docker compose --env-file .env.mainnet up -d + +# Then bring up your service, pointing at the same network. +cd ../my-service +docker compose --env-file .env.mainnet up -d +``` + +## Verify the attachment + +`docker compose exec` references your service by its Compose service name (the key under `services:`), so you never need to know the container name Compose autogenerated: + +```bash +# Your service is on the z3 network +docker network inspect z3-mainnet --format '{{json .Containers}}' | jq + +# Your service can resolve zebra by DNS +docker compose --env-file .env.mainnet exec my-service nslookup zebra + +# Your service can read the cookie +docker compose --env-file .env.mainnet exec my-service head -c 20 /var/run/auth/.cookie +``` + +## Common pitfalls + +- **Attaching before Z3 is up:** Compose fails with `network z3- declared as external, but could not be found`. Run Z3 first. +- **Hardcoding `z3-mainnet` in your compose:** Template via `${Z3_EXTERNAL_NETWORK}` and `${Z3_COOKIE_VOLUME}` so the same compose file works against testnet and regtest by swapping the env file. +- **Mounting the regtest cookie volume:** Regtest does not publish a readable cookie file. Omit the mount or use the regtest auth path above. +- **Forgetting `:ro` on the cookie mount:** Mounting read-write means a bug in your service could corrupt Zebra's auth state. Always `:ro`. +- **Adding your own `depends_on: zebra`:** Only works if you and Z3 are in the same Compose project (rare). In separate projects, use a startup wait loop in your service's entrypoint that polls `http://zebra:` until it responds. +- **Worrying about cookie file UID:** Zebra writes the cookie as uid 10001 with mode 0600. Z3 runs a small `cookie-permissions` sidecar that chmods it to 0644 within the shared volume so your container can read it regardless of its own uid. No UID coordination required. + +## Tear down + +```bash +docker compose --env-file .env.mainnet down +# Your service's volumes go; Z3's volumes stay because they're declared external. +``` diff --git a/docs/integrations/host-side-pointer.md b/docs/integrations/host-side-pointer.md new file mode 100644 index 0000000..7437a00 --- /dev/null +++ b/docs/integrations/host-side-pointer.md @@ -0,0 +1,108 @@ +# Host-side pointer integration + +Your service runs outside Docker: a host process, a CLI tool, a developer-laptop dev server. On mainnet and testnet, you connect to Z3 via the published host ports and inject the cookie text because you can't mount a Docker volume into a non-container process. + +This integration type is less efficient than Compose-peer because every connection goes through the host network stack, but it fits development, scripting, and any service that lives outside Docker's lifecycle. + +## Prerequisites + +- A running Z3 stack: `docker compose --env-file .env. up -d` in the Z3 repo. + +## Connect via host ports + +Host ports per network (full matrix in [`z3-contract.yaml`](../../z3-contract.yaml) under `networks..ports`): + +| Service | Mainnet | Testnet | Regtest | +|---------|---------|---------|---------| +| Zebra RPC | `http://127.0.0.1:8232` | `http://127.0.0.1:18232` | `http://127.0.0.1:29232` | +| Zebra `/ready` | `http://127.0.0.1:8080/ready` | `http://127.0.0.1:18080/ready` | `http://127.0.0.1:28080/ready` | +| Zaino gRPC (plaintext h2c) | `127.0.0.1:8137` | `127.0.0.1:18137` | `127.0.0.1:28137` | +| Zaino JSON-RPC | `http://127.0.0.1:8237` | `http://127.0.0.1:18237` | `http://127.0.0.1:28237` | +| Zallet RPC | `http://127.0.0.1:28232` | `http://127.0.0.1:40232` | `http://127.0.0.1:50232` | +| rpc-router (regtest only) | n/a | n/a | `http://127.0.0.1:8181` | + +Testnet uses a `+10000` host-port offset from mainnet for services without an upstream per-network convention; regtest uses explicit host ports to avoid collisions with mainnet/testnet. + +## Read the RPC cookie + +A host process cannot mount a Docker volume. On mainnet and testnet, copy the cookie text out: + +```bash +# One-liner cookie reader +docker run --rm -v z3-mainnet-cookie:/auth:ro alpine \ + cat /auth/.cookie +``` + +Wrap it in a small helper your service runs at startup: + +```bash +#!/usr/bin/env bash +# get-z3-cookie.sh: print the Z3 cookie text for a given network. +# Usage: ./get-z3-cookie.sh mainnet +set -euo pipefail +NETWORK="${1:-mainnet}" +docker run --rm -v "z3-${NETWORK}-cookie:/auth:ro" alpine cat /auth/.cookie +``` + +Your service then reads it via env: + +```bash +export ZEBRA_RPC_URL=http://127.0.0.1:8232 +export ZEBRA_COOKIE=$(./get-z3-cookie.sh mainnet) +./my-service +``` + +## Use the cookie in an HTTP request + +Zebra's RPC auth expects HTTP Basic with the cookie as the password (Bitcoin Core / Zcash convention): + +```bash +COOKIE="$(./get-z3-cookie.sh mainnet)" +curl -sf -u "$COOKIE" -X POST -H 'Content-Type: application/json' \ + -d '{"jsonrpc":"2.0","method":"getblockchaininfo","params":[],"id":1}' \ + http://127.0.0.1:8232 | jq . +``` + +The cookie is in the format `__cookie__:`; the colon is the username/password separator that `curl -u` expects. Regtest disables cookie auth; see the next section. + +## Regtest auth + +Regtest's contract declares `rpc_auth.mode: username_password`. The cookie volume exists but holds no readable cookie. Authenticate with HTTP Basic using the rpc-router credentials (defaults: `zebra` / `zebra`, override via `Z3_REGTEST_RPC_ROUTER_USER` / `Z3_REGTEST_RPC_ROUTER_PASSWORD` on the Z3 side): + +```bash +# Direct to Zebra +curl -sf -u zebra:zebra -X POST -H 'Content-Type: application/json' \ + -d '{"jsonrpc":"2.0","method":"getblockchaininfo","params":[],"id":1}' \ + http://127.0.0.1:29232 | jq . + +# Through rpc-router (unified Zebra + Zallet JSON-RPC endpoint) +curl -sf -u zebra:zebra -X POST -H 'Content-Type: application/json' \ + -d '{"jsonrpc":"2.0","method":"getblockchaininfo","params":[],"id":1}' \ + http://127.0.0.1:8181 | jq . +``` + +## Health check before connecting + +Wait for Zebra to report ready before your service tries to talk to it: + +```bash +# Block until Zebra reports ready (or fail after 10 minutes) +for i in $(seq 1 200); do + if curl -sf http://127.0.0.1:8080/ready > /dev/null 2>&1; then + echo "Zebra ready"; break + fi + if [ "$i" -eq 200 ]; then echo "Zebra never became ready"; exit 1; fi + sleep 3 +done +``` + +## When to use this integration vs Compose-peer + +| Choose host-side pointer when | Choose Compose-peer when | +|--------------------------------|---------------------------| +| Your service is a dev server, REPL, or one-off script | Your service is a long-running daemon you ship in production | +| Your service is in a language without easy Docker integration | Your service deploys via Compose | +| You want to debug with native tools (gdb, dlv, language debuggers) | You want startup ordering via `depends_on` | +| You're iterating fast on the service itself | You're testing the consumer/platform integration | + +Both integration types can coexist for the same service. A common pattern: production uses Compose-peer; local development uses host-side pointer with `pnpm dev` / `cargo run` / etc. diff --git a/docs/integrations/lightwalletd-client.md b/docs/integrations/lightwalletd-client.md new file mode 100644 index 0000000..c21884d --- /dev/null +++ b/docs/integrations/lightwalletd-client.md @@ -0,0 +1,101 @@ +# Lightwalletd-compatible client integration + +Your service is a wallet, block explorer, or scanner that speaks the lightwalletd `CompactTxStreamer` gRPC protocol. Z3's Zaino exposes that protocol as plaintext HTTP/2 (h2c) on the documented port. Terminate TLS at a reverse proxy if you expose Zaino beyond the host (see [Edge TLS in production](#edge-tls-in-production)). + +## Prerequisites + +- A running Z3 stack: `docker compose --env-file .env. up -d` in the Z3 repo. +- A gRPC client for your language (Tonic for Rust, grpcio for Python, grpc-java, etc.) or the `grpcurl` CLI for ad-hoc calls. +- The lightwalletd / Zaino `.proto` files. Either vendor them or pull from the Zaino submodule (`zaino/zaino-proto/proto/service.proto`). + +## Endpoint per network + +Zaino's gRPC port is plaintext h2c and follows the contract's port matrix: + +| Network | Host endpoint | +|---------|----------------| +| Mainnet | `127.0.0.1:8137` | +| Testnet | `127.0.0.1:18137` | +| Regtest | `127.0.0.1:28137` | + +There is no TLS on this listener. The Z3 stack runs Zaino with its TLS guard compiled out (the `-no-tls` image), so intra-stack and host-side gRPC is unencrypted. Anything exposed to a network you do not control should sit behind a TLS-terminating reverse proxy. + +## Regtest auth + +Regtest's contract declares `rpc_auth.mode: username_password` for Zebra and Zallet; Zaino authenticates internally to Zebra using the same username/password (from `config/regtest/zaino.toml`). The Zaino gRPC surface itself does not require client auth on any network. The cookie volume `z3-regtest-cookie` exists but holds no readable cookie. If your test setup needs to talk to Zebra directly (mine blocks, inspect state), use the rpc-router host endpoint at `http://127.0.0.1:8181` with HTTP Basic, defaults `zebra` / `zebra` (override on the Z3 side via `Z3_REGTEST_RPC_ROUTER_USER` / `Z3_REGTEST_RPC_ROUTER_PASSWORD`). + +## Quick test with `grpcurl` + +The .proto files are already vendored in this repo as a submodule under +`zaino/zaino-proto/proto/`. Initialize it once and point grpcurl at that path. + +```bash +# One-time: fetch the vendored Zaino submodule +git submodule update --init zaino + +# Probe the endpoint (mainnet example). -plaintext skips TLS. +grpcurl -plaintext \ + -import-path zaino/zaino-proto/proto \ + -proto service.proto \ + 127.0.0.1:8137 \ + cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLightdInfo + +# Get the latest block +grpcurl -plaintext \ + -import-path zaino/zaino-proto/proto \ + -proto service.proto \ + -d '{}' \ + 127.0.0.1:8137 \ + cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLatestBlock +``` + +## Rust (Tonic) example + +```rust +use tonic::transport::{Channel, Endpoint}; +use compact_tx_streamer_client::CompactTxStreamerClient; + +// Plaintext h2c: use http:// and no TLS config. +let channel = Endpoint::from_static("http://127.0.0.1:8137") + .connect() + .await?; + +let mut client = CompactTxStreamerClient::new(channel); +let info = client.get_lightd_info(()).await?.into_inner(); +println!("Lightd info: {info:?}"); +``` + +## TypeScript (Connect-ES) example + +```typescript +import { createGrpcTransport } from "@connectrpc/connect-node"; +import { createClient } from "@connectrpc/connect"; +import { CompactTxStreamer } from "./gen/service_pb"; + +const transport = createGrpcTransport({ + httpVersion: "2", + baseUrl: "http://127.0.0.1:8137", // plaintext h2c; no TLS +}); + +const client = createClient(CompactTxStreamer, transport); +const info = await client.getLightdInfo({}); +``` + +## What's the difference between Zaino and Zallet? + +| Service | Audience | Protocol | +|---------|----------|----------| +| **Zaino** | External light-wallet clients (mobile wallets, scanners) | gRPC `CompactTxStreamer` over plaintext h2c | +| **Zallet** | Operator wallet (the operator's own keys, full RPC surface) | JSON-RPC over HTTP | + +If you're writing a mobile wallet or a public-facing scanner, use Zaino. If you're administering the operator's wallet (creating addresses, sending transactions, importing keys), use Zallet. + +## Edge TLS in production + +The gRPC listener is plaintext, which is fine inside the Docker network and for local development. Wallet clients on the public internet expect TLS, so for any deployment exposed beyond `127.0.0.1`: + +1. Put a reverse proxy (nginx, Caddy, Envoy, or your cloud load balancer) in front of Zaino and terminate TLS there with a certificate from a trusted CA. +2. Proxy decrypted h2c traffic to Zaino's gRPC port on the internal network. +3. Point clients at the proxy's `https://` endpoint; the proxy handles TLS and forwards plaintext gRPC to Zaino. + +This keeps certificate management at the edge where it belongs, rather than baking a self-signed cert into the node stack. diff --git a/docs/memory-bank/activeContext.md b/docs/memory-bank/activeContext.md deleted file mode 100644 index 686d1f6..0000000 --- a/docs/memory-bank/activeContext.md +++ /dev/null @@ -1,91 +0,0 @@ -# Active Context - -## Current Focus - -1. **Docker Orchestration:** - - Completed investigation of Zebra and Zaino Docker configurations. - - Documented Docker/orchestration findings in z3_docker.md. - - Next: Research Zallet's Docker requirements for full stack integration. - -2. **Z3 Stack Integration:** - - Planning unified Docker Compose for all components. - - Identifying volume management and networking requirements. - - Need to verify service communication patterns. - -3. **Production Readiness:** - - Developing monitoring and observability setup. - - Planning health checks and logging configuration. - - Exploring backup and recovery procedures. - -4. **Documentation:** - - Maintaining comprehensive Memory Bank updates. - - Documenting Docker deployment patterns. - - Creating configuration and troubleshooting guides. - -## Recent Changes - -1. **Docker Investigation:** - - Analyzed Zebra's multi-stage Dockerfile and compose configurations. - - Examined Zaino's Docker setup and runtime requirements. - - Created z3_docker.md to document findings and orchestration plan. - - Identified key configuration patterns and integration points. - -2. **Zebra Adoption Tracking Script:** - - Created and debugged `analyze_nodes.sh` to parse zcashexplorer.com HTML for Zebra and MagicBean node counts. - -3. **RPC Integration:** - - Mapped RPC methods to services using [RPC mapping](./data/rpc_mapping.md). - - Identified service communication paths for Docker networking. - - Examined Zebra and Zaino configuration for RPC endpoints. - -## Active Decisions - -1. **Docker Architecture:** - - Use multi-stage builds for all components - - Implement secure defaults (non-root users, TLS) - - Standardize volume management patterns - - Plan for production monitoring - -2. **Configuration Management:** - - Use TOML configs mounted into containers - - Implement consistent environment variable patterns - - Plan for secrets management - -## Important Patterns - -1. **Memory Bank Maintenance:** - - Keep documentation synchronized with implementation - - Document all configuration options and rationale - - Track deployment patterns and decisions - -2. **Docker Best Practices:** - - Multi-stage builds for efficient images - - Non-root users for security (UIDs 10001, 2003) - - Volume management for persistent data - - Service isolation and clear network boundaries - -3. **Configuration Patterns:** - - TOML files for service configuration - - Environment variables for runtime settings - - Consistent volume mount points - - Standard logging and metrics exposure - -## Learnings and Insights - -1. **Docker Architecture:** - - Zebra's Docker setup provides a robust template for production deployment - - Zaino's configuration allows flexible validator integration - - Volume management is critical for state persistence - - Service networking needs careful planning for security - -2. **Integration Patterns:** - - RPC routing determines network architecture - - Service discovery via Docker DNS - - Configuration must align across services - - Monitoring needs coordinated across stack - -3. **Project Architecture:** - - Modular components enable independent scaling - - Shared patterns improve maintainability - - Security considerations affect all layers - - Documentation crucial for successful deployment diff --git a/docs/memory-bank/productContext.md b/docs/memory-bank/productContext.md deleted file mode 100644 index 6ce2220..0000000 --- a/docs/memory-bank/productContext.md +++ /dev/null @@ -1,59 +0,0 @@ -# Product Context - -## Problem - -The Z3 project aims to replace the existing Zcashd software stack, which was forked from Bitcoin Core back in 2016. The c++ codebase has become difficult to maintain and extend. Zcashd is a monolithic application that includes full-node, indexing, and wallet functionalities. Replacing Zcashd with a modular and maintainable software stack addresses the following problems: - -* **Maintenance burden:** Zcashd's codebase is complex and challenging to maintain, leading to increased development costs and slower feature development. The Zcash codebase has also diverged significally from upstream (Bitcoin Core) which means that tracking upstream security fixes has become quite an onerous task. -* **Scalability limitations:** Zcashd's monolithic architecture limits its scalability and ability to handle increasing transaction volumes. -* **Lack of flexibility:** Zcashd's tight integration of components makes it difficult to adapt to new technologies and requirements. -* **Security risks:** Zcashd's complexity, and it's c++ codebase increases the risk of security vulnerabilities. The legacy architecture has key material in memory readable and writable by the same process that handles peer to peer networking (and everything else) which makes containing the blast radius of a memory corruption exploit difficult to impossible. - - -## Proposed Solution - -This project proposes replacing [Zcashd](https://github.com/zcash/zcash) with a modular software stack consisting of the following components: - -* **[Zebra](https://github.com/ZcashFoundation/zebra):** A full-node implementation of the Zcash protocol that provides consensus and network functionalities. Zebra has been under development by the Zcash Foundation since 2020 and is presently deployed by a number of decentralized node operators on mainnet. -* **[Zaino](https://github.com/zingolabs/zaino/):** An indexing service that provides light clients with blockchain data, replacing the legacy go light client server lightwalletd. Zaino is under active development by Zingo Labs, funded by a grant from [ZCG](https://zcashcommunitygrants.org/). -* **[Zallet](https://github.com/zcash/wallet):** A CLI wallet that provides wallet functionality that existed in Zcashd but is not planned for implementation in Zebra. Zallet is under active development by the Electric Coin Company. -* **Z3 Wrapper Service:** A new wrapper service that exposes the three previously mentioned services as a "single binary" to the users. Note that this capability could be achieved as a "single binary" that is deployed as a Debian package eg `sudo apt install zcash-z3`, or via Docker Compose or as a Helm chart. - -This modular architecture offers several advantages: - -* **Improved maintainability:** Each component can be developed and maintained independently, reducing the overall maintenance burden. -* **Enhanced scalability:** Each component can be scaled independently to meet specific demands. -* **Increased flexibility:** The modular design allows for easier adaptation to new technologies and requirements. -* **Reduced security risks:** Each component can be hardened and secured independently, reducing the overall attack surface. - -## User Experience Goals - -The project aims to provide a secure, reliable, and efficient experience for the following target users: - -* **Zcash protocol developers:** A well-documented and easy-to-use software stack that facilitates protocol development and experimentation. -* **Centralized cryptocurrency exchanges:** A secure, scalable and reliable modern full-node implementation that supports exchange operations (deposit, withdrawal). -* **Decentralized cryptocurrency exchanges:** A secure, scalable and reliable modern full-node implementation that supports decentralized exchange operations (deposit, withdrawal, swap). -* **Cryptocurrency wallet software platforms:** A secure and user-friendly full-node implementation that provides essential wallet functionalities. (send, receive, check balance). -* **Block explorers:** A scalable and reliable full-node implementation that provides fast access to archival blockchain data. - -The key user experience goals include: - -* **Seamless upgrade:** The user can upgrade from the legacy (Zcashd) daemon to the modern (Z3) stack without changing any of their integration code. Note that this goal is presently disputed with engineers from the ECC team stating that it's not feasible to make Z3 a "drop in replacement" for Zcashd without adding ~6mos of work to the schedule. They are presently tracking breaking changes to the wallet RPC's in [this Github issue](https://github.com/zcash/wallet/issues/41). -* **Security:** Protecting user funds and data from unauthorized access. Ensuring that the shielded supply remains free of counterfeiting bugs. -* **Reliability:** Ensuring the software stack operates consistently and without errors. -* **Observability:** Providing telemetry, logging and tracing to ensure proper operation in rugged production environments. -* **Efficiency:** Providing fast and responsive performance. -* **Usability:** Making the software stack easy to use and understand. - -## Key Metrics -* **% of mainnet nodes running Z3:** 0 as of April 2025, with a target of 100% by the time Zcashd is deprecated. Sources for determining this metric are TBD, some potential options include [Blockchair](https://blockchair.com/zcash/nodes), but the data at [zcashexplorer.app](https://mainnet.zcashexplorer.app/nodes) appears to be much better. TODO: figure out if they have an API. -* **% of centralized exchanges upgraded to Z3:** 0 as of April 2025, with a target of 100% by the time Zcashd is deprecated. Sources for determining this metric are TBD, and we are targeting 0 exchange de-listings due to upgrade friction. -* **% of decentralized exchanges upgraded to Z3:** 0 as of April 2025, with a target of 100% by the time Zcashd is deprecated. Sources for determining this metric are TBD, and we are targeting 0 exchange de-listings due to upgrade friction. -* **% of wallets upgraded to Z3:** 0 as of April 2025, with a target of 100% by the time Zcashd is deprecated. Sources for determining this metric are TBD. -* **% of block explorers upgraded to Z3:** 0 as of April 2025, with a target of 100% by the time Zcashd is deprecated. Sources for determining this metric are TBD. -* **% of PoW hashpower lost due to Z3 upgrade:** 0% as of April 2025, with a target of <=10% by the time Zcashd is deprecated. Sources for determining this metric are TBD. Specifically we do not want to introduce security risk to the network by making it hard for PoW miners and mining pools to update to the Z3 stack. - - -## Further Reading - -For technical details about the architecture and implementation, see the [Technical Context](techContext.md). diff --git a/docs/memory-bank/progress.md b/docs/memory-bank/progress.md deleted file mode 100644 index 1b343dd..0000000 --- a/docs/memory-bank/progress.md +++ /dev/null @@ -1,91 +0,0 @@ -# Progress - -## Working Functionality - -1. **Memory Bank:** - - Core files actively maintained and updated - - New z3_docker.md documents containerization findings - - RPC mapping documented and service assignments agreed upon - - Created and debugged `analyze_nodes.sh` script for tracking Zebra and MagicBean node counts - -2. **Docker Configuration:** - - Zebra's Docker setup analyzed and documented - - Multi-stage Dockerfile with test/runtime stages - - Multiple compose files for different scenarios - - Production-ready security features - - Zaino's Docker configuration examined - - Multi-stage build process - - TOML-based configuration - - gRPC service exposure - -3. **Integration Planning:** - - Unified Docker Compose draft completed - - Volume management strategy defined - - Network architecture planned - - Security considerations documented - -## Functionality Left To Build - -1. **Docker Integration:** - - Research and document Zallet's Docker requirements - - Create production Docker Compose with all components - - Implement monitoring and health checks - - Develop backup/restore procedures - - Create deployment documentation - -2. **Software Stack Development:** - - Complete [Zebra Ready for zcashd Deprecation milestone](https://github.com/orgs/ZcashFoundation/projects/9/views/11) - - Continue Zaino development as Lightwalletd replacement - - Progress Zallet development - - Build Z3 wrapper service for unified RPC routing - -3. **Production Readiness:** - - Implement security hardening - - Set up monitoring and alerting - - Create disaster recovery procedures - - Document operational procedures - -## Current Status - -1. **Documentation Phase:** - - Memory Bank established as knowledge repository - - Docker orchestration plan documented in z3_docker.md - - RPC routing assignments in progress - -2. **Development Progress:** - - Zebra: Docker configuration mature, ready for integration - - Zaino: Docker setup analyzed, integration points identified - - Zallet: Docker requirements to be researched - - Z3 Wrapper: Architecture planning stage - -3. **Next Steps:** - - Complete Zallet Docker investigation - - Implement unified Docker Compose - - Set up monitoring infrastructure - - Create deployment guides - -## Known Issues - -1. Some RPC method assignments still undecided between Zebra and Zaino -2. Monitoring and observability requirements need definition -3. Full integration testing plan needed for Docker deployment -4. Production backup procedures to be designed - -## Evolution of Decisions - -1. **RPC Service Mapping:** - - Initial mapping complete with Zcashd Deprecation Team - - Some methods still need final assignment - - RPC support requirements documented in techContext.md - -2. **Docker Architecture:** - - Decision to use multi-stage builds for all components - - Standardized security practices (non-root users, TLS) - - Consistent volume management approach - - Unified monitoring strategy planned - -3. **Integration Strategy:** - - Docker Compose as primary orchestration tool - - Service discovery via Docker networking - - TOML-based configuration management - - Secure communication between components diff --git a/docs/memory-bank/projectbrief.md b/docs/memory-bank/projectbrief.md deleted file mode 100644 index c522077..0000000 --- a/docs/memory-bank/projectbrief.md +++ /dev/null @@ -1,37 +0,0 @@ -# Project Brief - -## Overview -Z3: Building a modern, secure, reliable, scalable software stack that will replace [Zcashd](https://github.com/zcash/zcash). - -## Core Features -- A full-node implementation that implements the Zcash protocol (Zebra) -- A service that provides indexing and serves light clients blockchain data (Zaino) -- A cli-wallet that provides wallet functionality that existed in Zcashd but that is not planned for implementation in Zebra (Zallet) -- A wrapper service that exposes the three previously mentioned services as a "single binary" to the users - -## Target Users -- Developers of the Zcash protocol (including the Zcash Foundation, the Electric Coin Company, Shielded Labs, Zingo Labs and the broader community) -- Operators of centralized cryptocurrency exchanges (including Coinbase, Gemini, Kraken, Binance, etc.) -- Operators of decentralized cryptocurrency exchanges (including the Zcash DEX implemented with Near Intents) -- Operators of cryptocurrency wallet software platforms (including Zashi, Brave Wallet, etc.) -- Operators of ASIC based proof of work miners on the Zcash blockchain -- Operators of "finalizers" (known more commonly as validators) as Zcash prepares to transition to a hybrid Proof of Work / Proof of Stake consensus algorithm. -- Operators of block explorers on the Zcash network. We can leverage the [open source Nighthawk explorer](https://github.com/nighthawk-apps/zcash-explorer) (which is unfortunately no longer maintained) as a proof of concept application consuming the Z3 stack while it's under development. There is [work already underway](https://github.com/ZcashFoundation/zebra/issues/8435) to get this explorer working with Zebra. - -## Technical Preferences -- [Zcashd](https://github.com/zcash/zcash) is the legacy software that is being replaced -- [Zebra](https://github.com/ZcashFoundation/zebra) for the Zcash full node -- [Zaino](https://github.com/zingolabs/zaino/) for the indexing service that will interface with Zebrad via RPC and the ReadStateService, and also replace the legacy go light client server [lightwalletd](https://github.com/zcash/lightwalletd) -- [Zallet](https://github.com/zcash/wallet) for the cli-wallet that will replace the wallet functionality that originally existed in Zcashd -- The Z3 wrapper software that is the primary software deliverable of this project should be written in Rust, unless there is a *really* good reason to use another language or framework. -- Security is of paramount importance -- Clippy as the Rust linter of choice -- Git and Github for SCM -- Github Actions for CI -- Test driven development -- Docker for deployment -- [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#specification) as standard for SCM commit messaging - -## Further Reading - -For a detailed analysis of the problems we're solving and proposed solution, see the [Product Context](productContext.md). diff --git a/docs/memory-bank/systemPatterns.md b/docs/memory-bank/systemPatterns.md deleted file mode 100644 index deb6f80..0000000 --- a/docs/memory-bank/systemPatterns.md +++ /dev/null @@ -1,42 +0,0 @@ -# System Patterns - -## Architecture - -The system adopts a modular architecture, replacing the monolithic Zcashd with a set of specialized components: Zebra, Zaino, and Zallet. These components interact through well-defined interfaces, enabling independent development, deployment, and scaling. Z3 wrapper service provides a unified entry point for users, simplifying interaction with the underlying components. - -```mermaid -graph LR - U[User] --> W[Z3 wrapper service] - W --> Z[Zallet] - W --> ZA[Zaino] - W --> ZE[Zebra] - ZE[Zebra] --> ZA[Zaino] - Z[Zallet] --> ZE[Zebra] - Z[Zallet] --> ZA[Zaino] -``` - -## Key Decisions - -* **Rust Implementation:** The decision to implement most components in Rust was driven by a strong emphasis on security and performance. Rust's memory safety features and efficient execution make it well-suited for building critical blockchain infrastructure. -* **Component Selection:** The choice of Zebra, Zaino, and Zallet was based on their existing functionality, maturity, and alignment with the project's goals. These components provide a solid foundation for building a replacement for Zcashd. - -## Design Patterns - -* **Facade:** The Z3 wrapper service implements the Facade pattern, providing a simplified interface to the complex interactions between Zebra, Zaino, and Zallet. The Z3 wrapper service receives RPC requests from users and routes them to the appropriate backend service. - -## Component Relationships - -* **User and Z3 wrapper service:** The user interacts directly with the Z3 wrapper service, which acts as a single entry point to the system. The Z3 wrapper service handles user requests and routes them to the appropriate component (Zebra, Zaino, or Zallet). -* **Zebra and Zaino:** Zebra provides blockchain data to Zaino via RPC or the ReadStateService. Zaino indexes this data to provide efficient access for light clients and other applications. -* **Zallet and Zebra:** Zallet interacts with Zebra for transaction submission and balance retrieval. Zallet uses Zebra's API to construct and broadcast transactions to the Zcash network. -* **Zallet and Zaino:** Zallet may interact with Zaino to retrieve indexed blockchain data, such as transaction history or address balances. -* **Z3 wrapper service and Components:** The Z3 wrapper service orchestrates interactions between Zebra, Zaino, and Zallet, providing a unified interface for users. - -## Notes - -* It is not yet clear whether Zallet will communicate only with Zaino or also directly with Zebra. This will depend on the specific requirements of Zallet and the design of the interfaces between the components. -* There is ongoing discussion about Zaino being implemented as a library that will be integrated directly into wallet software. If this approach is adopted, users may not need to communicate with a separate Zaino service. - -## Further Reading - -For details about current implementation status and next steps, see the [Progress](progress.md) document. diff --git a/docs/memory-bank/techContext.md b/docs/memory-bank/techContext.md deleted file mode 100644 index b930a52..0000000 --- a/docs/memory-bank/techContext.md +++ /dev/null @@ -1,305 +0,0 @@ -# Tech Context - -## Technologies Used - -* **[Zcashd](https://github.com/zcash/zcash):** Is the legacy Zcash full-node client software being replaced by the Z3 stack -* **[Zebra](https://github.com/ZcashFoundation/zebra):** A full-node implementation of the Zcash protocol that provides consensus and network functionalities in Rust. Zebra has been under development by the Zcash Foundation since 2020 and is presently deployed by a number of decentralized node operators on mainnet. -* **[Zaino](https://github.com/zingolabs/zaino/):** An indexing service that provides light clients with blockchain data, replacing the legacy go light client server lightwalletd. Zaino is under active development by Zingo Labs, funded by a grant from [ZCG](https://zcashcommunitygrants.org/). -* **[Zallet](https://github.com/zcash/wallet):** A CLI wallet that provides wallet functionality that existed in Zcashd but is not planned for implementation in Zebra. Zallet is under active development by the Electric Coin Company. -* **Rust:** Primary language for Zebra, Zaino, and Zallet (version TBD). Justification: Security, performance, and memory safety. -* **gRPC:** In consideration for communication between Zebra and Zaino (version TBD). Justification: Efficient and standardized RPC framework. -* **Docker:** For deployment and containerization (version TBD). Justification: Consistent and reproducible environments. - -## Development Setup - -* **Rust toolchain:** Requires a recent version of the Rust toolchain, including `rustc`, `cargo`, and `clippy`. -* **Docker:** Requires Docker for building and running the services. -* **Git:** Requires Git for version control. -* **Editor:** VSCode with Rust Analyzer extension recommended. - -## Technical Constraints - -* **Performance:** The system must be able to handle high transaction volumes and provide low-latency access to blockchain data. -* **Security:** Security is paramount, and all components must be designed to resist attacks. -* **Compatibility:** The system must be compatible with the Zcash protocol and network. - -## RPC Methods used by Customers - -The Zcashd Deprecation team engaged with customers and found that they are using the following RPC methods in production. They discussed and came to agreement on which component in the new Z3 stack should service each of these methods. - -| RPC Method | Where in new stack | Actions | -| ----------------------------- | ---------------------------------- | ----------------------------------------------------- | -| `getaddressbalance` | Zebra (lightwalletd usage) / Zaino | Review implementation in Zebra | -| `getaddressdeltas` | Zaino | | -| `getaddressmempool` | - | Lower priority. Possibly deprecate | -| `getaddresstxids` | Zebra (lightwalletd usage) / Zaino | Review implementation in Zebra \| Implement in Zaino | -| `getaddressutxos` | Zebra | Review implementation in Zebra | -| `getbestblockhash` | Zebra / Zaino | | -| `getblock` | Zebra / Zaino | Being fully implemented in Zebra | -| `getblockchaininfo` | Zebra / Zaino | Review implementation in Zebra | -| `getblockcount` | Zebra / Zaino | | -| `getblockdeltas` | Zaino | Implement in Zaino | -| `getblockhash` | Zebra / Zaino | | -| `getblockhashes` | Zaino | Lower priority. Possibly deprecate | -| `getblockheader` | Zebra / Zaino | | -| `getchaintips` | Zaino | Implement in Zaino | -| `getdifficulty` | Zebra / Zaino | | -| `getmempoolinfo` | Zaino | Implement in Zaino | -| `getrawmempool` | Zebra / Zaino | | -| `getspentinfo` | Zaino | Implement in Zaino | -| `gettxout` | Zaino | Implement in Zaino | -| `gettxoutproof` | Zaino | Lower priority | -| `gettxoutsetinfo` | Zaino | | -| `verifychain` | - | Deprecate | -| `verifytxoutproof` | Zaino | Lower priority | -| `z_gettreestate` | Zebra / Zaino | Review implementation in Zebra | -| `getexperimentalfeatures` | - | Deprecate? Confirm what Zebra defines as experimental | -| `getinfo` | Zebra | Review implementation in Zebra | -| `getmemoryinfo` | Zebra | No plan to implement | -| `help` | Zebra | No plans to implement | -| `setlogfilter` | Zebra | No plans to implement | -| `stop` | Zebra | None | -| `z_getpaymentdisclosure` | - | Deprecate | -| `z_validatepaymentdisclosure` | - | Deprecate | -| `generate` | Zebra | None | -| `getgenerate` | Zebra | No plans to implement | -| `setgenerate` | Zebra | No plans to implement | -| `getblocksubsidy` | Zebra | None. All done in Zebra | -| `getblocktemplate` | Zebra | Review implementation in Zebra | -| `getlocalsolps` | Zebra | No plans to implement | -| `getmininginfo` | Zebra / Zaino | | -| `getnetworkhashps` | - | Deprecated | -| `getnetworksolps` | Zebra / Zaino | | -| `prioritisetransaction` | Zebra | No plans to implement | -| `submitblock` | Zebra | None | -| `addnode` | Zebra | Decide if we want to implement in Zebra | -| `clearbanned` | Zebra | Decide if we want to implement in Zebra | -| `disconnectnode` | Zebra | Decide if we want to implement in Zebra | -| `getaddednodeinfo` | Zebra | Decide if we want to implement in Zebra | -| `getnettotals` | Zebra | Decide if we want to implement in Zebra | -| `getnetworkinfo` | Zebra / Zaino | Implement in Zebra | -| `getpeerinfo` | Zebra / Zaino | Review implementation in Zebra | -| `listbanned` | Zebra | Decide if we want to implement in Zebra | -| `ping` | Zebra / Zaino | Implement in Zebra | -| `setban` | Zebra | Decide if we want to implement in Zebra | -| `createrawtransaction` | Zallet | | -| `decoderawtransaction` | Zallet | Lower priority. Might deprecate | -| `decodescript` | Zallet | Lower priority. Might deprecate | -| `fundrawtransaction` | Zallet | | -| `getrawtransaction` | Zebra / Zaino | Review implementation in Zebra | -| `sendrawtransaction` | Zebra / Zaino | Review implementation in Zebra | -| `signrawtransaction` | Zallet | | -| `validateaddress` | Zebra / Zaino | Implement in Zaino | -| `verifymessage` | Zallet | Implement in Zallet | -| `z_validateaddress` | Zebra | Implement in Zaino | - -It is very important that the project team focuses on the agreed upon RPC mapping and figures out exactly which RPC will be served by which component, as ultimately this information will be used by Z3 to route requests to the right backend service. Note that the RPC Method Support list seems to be imcomplete, with many methods indicating that they will be served by either Zebra or Zaino or both. We urgently need to drive these decisions to ground in order to move forward. - -## RPC Methods exposed by Zcashd - -The following [RPC methods](./data/zcashd_RPC_methods.md) are all of the methods historically exposed by the legacy Zcashd software. - -## Dependencies - -### Z3 Wrapper Dependencies - -TBD but generally speaking we want to use the same libraries and frameworks that the ZF Engineering team is familiar and comfortable with based on its work on Zebra. - -### Zebra Dependencies - -* Core crates: - * `zebra-chain`: Core data structures and crypto primitives for the Zcash protocol - * `zebra-consensus`: Consensus rules implementation and block validation - * `zebra-network`: P2P networking stack and peer management - * `zebra-state`: Chain state management and block storage - * `zebra-rpc`: JSON-RPC and gRPC server implementations - * `zebra-script`: Bitcoin script verification engine for transparent transactions - * `zebra-node-services`: Shared services used across multiple Zebra components - * `zebra-scan`: Block and transaction scanning utilities - * `zebra-grpc`: gRPC service definitions and implementations - * `zebra-utils`: Common utilities and helper functions - * `zebra-test`: Shared test infrastructure and helpers - * `tower-batch-control`: Custom Tower middleware for batch control - * `tower-fallback`: Custom Tower middleware for fallback handling - * `zebrad`: Main executable binary crate - -* Zcash Dependencies: - * `halo2`: Zero-knowledge proof system used for Orchard shielded transactions - * `orchard`: Implementation of the Orchard shielded pool protocol - * `zcash_primitives`: Core Zcash cryptographic primitives - * `zcash_proofs`: Zero-knowledge proof implementations - * `zcash_client_backend`: Client functionality shared between wallets - * `zcash_address`: Zcash address handling and encoding - * `zcash_encoding`: Binary serialization for Zcash types - * `zcash_history`: Block chain history handling - * `zip32`: Hierarchical deterministic wallets for Zcash - * `sapling-crypto`: Sapling zk-SNARK circuit implementations - * `incrementalmerkletree`: Incremental Merkle tree implementation - -* External dependencies: - * `tokio`: Asynchronous runtime and utilities - * `tower`: Service architecture and middleware - * `futures`: Async/await primitives and utilities - * `blake2b_simd`: High-performance hashing implementation - * `metrics`: Core metrics collection and reporting - * `metrics-exporter-prometheus`: Prometheus metrics exposition - * `tracing`: Structured logging and diagnostics - * `serde`: Data serialization framework - * `rocksdb`: Persistent key-value storage backend - * `hyper`: HTTP/HTTPS implementation - * `tonic`: gRPC implementation - * `jsonrpsee`: JSON-RPC framework - * `reqwest`: HTTP client - * `color-eyre`: Error reporting and handling - * `proptest`: Property-based testing framework - * `insta`: Snapshot testing support - * `criterion`: Benchmarking framework - -### Zaino Dependencies - -* Core crates: - * `zaino-serve`: Service implementation for light clients - * `zaino-state`: State management and storage - * `zaino-fetch`: Data fetching from Zebra nodes - * `zaino-proto`: gRPC protocol definitions - * `zaino-testutils`: Testing utilities - * `zainod`: Main executable binary - -* Zcash Dependencies: - * `zcash_client_backend`: Modified version of librustzcash client backend - * Custom fork: `zingolabs/librustzcash` - * Tag: `zcash_client_sqlite-0.12.1_plus_zingolabs_changes-test_2` - * Features: `lightwalletd-tonic` - * `zcash_protocol`: Protocol definitions from librustzcash - * Custom fork: `zingolabs/librustzcash` - * Tag: `zcash_client_sqlite-0.12.1_plus_zingolabs_changes-test_2` - -* Zebra Dependencies: - * `zebra-chain`: Core data structures (from `main` branch) - * `zebra-state`: State management (from `main` branch) - * `zebra-rpc`: RPC interfaces (from `main` branch) - -* Custom Dependencies: - * `zingolib`: Core functionality from Zingo Labs - * Tag: `zaino_dep_005` - * `zingo-infra-testutils`: Testing infrastructure - * `zingo-infra-services`: Service infrastructure - -* External Dependencies: - * `tokio`: Async runtime with full feature set - * `tokio-stream`: Stream utilities for async data processing - * `tonic`: gRPC implementation and server framework - * `tonic-build`: gRPC code generation - * `tower`: Service architecture with buffer and util features - * `tracing`: Logging infrastructure - * `reqwest`: HTTP client with rustls-tls support - * `lmdb`: Lightning Memory-Mapped Database - * `dashmap`: Thread-safe concurrent HashMap - * `indexmap`: Hash table with deterministic iteration - * `crossbeam-channel`: Multi-producer multi-consumer channels - -### Zallet Dependencies - -* Core crate: - * `zallet`: Main wallet implementation including CLI interface and core functionality - -* Zcash Dependencies: - * `zcash_client_backend`: Wallet functionality from librustzcash - * `zcash_client_sqlite`: SQLite storage implementation - * `zcash_primitives`: Core cryptographic primitives - * `zcash_keys`: Key management - * `zcash_protocol`: Protocol definitions - * `orchard`: Orchard shielded pool support - * `sapling`: Sapling shielded pool support - * `transparent`: Transparent address support - * `zip32`: HD wallet key derivation -Note that all of the Zcash dependencies in Zallet are presently pinned to a specific revision of Librustzcash. - -* External Dependencies: - * `tokio`: Async runtime and utilities - * `abscissa_core`: Application framework - * `deadpool-sqlite`: SQLite connection pooling - * `rusqlite`: SQLite database access - * `age`: File encryption - * `clap`: Command line argument parsing - * `jsonrpsee`: JSON-RPC client - * `tonic`: gRPC client (temporary for lightwalletd) - -### Common Dependencies - -* Runtime and async: - * `tokio`: Async runtime used by all components - * `tower`: Service architecture and middleware - -* Observability: - * `tracing`: Logging and diagnostics infrastructure used across all components - -* Serialization: - * `serde`: Data serialization framework - -* RPC: - * `tonic`: gRPC implementation (used by all, though Zallet's usage is temporary) - * `jsonrpsee`: JSON-RPC framework (Zebra and Zallet) - -* Error handling: - * `color-eyre`: Error reporting and handling - -* CLI: - * `clap`: Command-line argument parsing - -Note: While these dependencies are common, they might be used with different feature flags or versions across the components. The exact versions should be coordinated to ensure compatibility when integrating the components. - -## Tool Usage Patterns - -### Build Tools - -* `cargo`: - * Building: `cargo build --release` - * Testing: `cargo test` - * Dependency management: `cargo update` - * Documentation: `cargo doc --no-deps` - * Workspace management: `cargo workspace` - -### Code Quality - -* `clippy`: - * Standard linting: `cargo clippy` - * Strict checks: `cargo clippy -- -D warnings` - * Workspace checks: `cargo clippy --workspace` -* `fmt`: - * Code formatting: `cargo fmt` - * Format check: `cargo fmt -- --check` - -### Version Control - -* `git`: - * Branch management: Using feature branches - * Commit messages: Following Conventional Commits spec - * Code review: GitHub pull request workflow - * Tagging: Semantic versioning for releases - -### Containerization - -* `docker`: - * Local development: Docker Compose for service orchestration - * Testing: Isolated test environments - * CI/CD: GitHub Actions with Docker caching - * Production: Multi-stage builds for minimal images - -### Testing - -* Unit tests: Per-module tests using `#[cfg(test)]` -* Integration tests: Using `tests/` directory -* Property testing: Using `proptest` -* Snapshot testing: Using `insta` -* Benchmarking: Using `criterion` - -### Documentation - -* API docs: Using rustdoc comments -* Architecture docs: Using architecture decision records (ADRs) -* User guides: Using mdBook - -## Further Reading - -For details about design patterns and conventions used across the Z3 stack, see the [System Patterns](systemPatterns.md). diff --git a/docs/memory-bank/z3_component_interplay.md b/docs/memory-bank/z3_component_interplay.md deleted file mode 100644 index 616dded..0000000 --- a/docs/memory-bank/z3_component_interplay.md +++ /dev/null @@ -1,112 +0,0 @@ -# Z3 Stack: Zebra, Zaino, and Zallet Interplay - -## Introduction - -The Z3 project consists of three core components that work together to provide a modular replacement for the monolithic `zcashd`: Zebra (the consensus node), Zaino (the indexing service), and Zallet (the wallet application). Understanding their individual roles and how they currently interconnect is crucial for developers and users of the Z3 ecosystem. - -## Component Roles - -### 1. Zebra: The Consensus Full Node - -* **Core Responsibility:** Zebra is the Zcash full-node implementation. It maintains the integrity of the Zcash blockchain by enforcing consensus rules, managing peer-to-peer network connections, and validating all blocks and transactions. -* **Functionality:** - * Downloads and stores the complete Zcash blockchain. - * Validates new blocks and transactions against consensus rules. - * Participates in the Zcash P2P network, gossiping transactions and blocks. - * Provides RPC/gRPC interfaces for querying raw blockchain data (e.g., blocks, transactions, tree states) and for submitting transactions to the network. -* **Significance:** Zebra serves as the ultimate source of truth for blockchain data within the Z3 stack. - -### 2. Zaino: The Indexing Service - -* **Core Responsibility:** Zaino acts as a specialized indexing layer that sits between a full node (Zebra) and client applications (like light wallets or Zallet). Its primary goal is to provide a fast, efficient, and queryable source of blockchain data, replacing the functionality of `lightwalletd`. -* **Functionality:** - * Connects to Zebra to fetch new blocks and blockchain updates. - * Processes this data and builds optimized indexes (e.g., for compact blocks, transaction histories per address, note commitment tree states). - * Exposes gRPC and/or RPC interfaces for clients to retrieve this indexed data efficiently. -* **Significance:** Zaino enables performant data retrieval for applications that don't need the full raw blockchain, significantly improving sync times and query speeds for wallets and light clients. - -### 3. Zallet: The Wallet Application - -* **Core Responsibility:** Zallet is a command-line interface (CLI) wallet that manages user accounts, private keys, and facilitates Zcash transactions. It is designed to provide the wallet functionalities previously embedded within `zcashd`. -* **Functionality:** - * Securely stores and manages user private keys (encrypted using `age` encryption). - * Generates and manages Zcash addresses (Transparent, Sapling, Orchard, Unified Addresses). - * Constructs, signs, and broadcasts transactions. - * Scans the blockchain (via Zaino) to discover and track transactions relevant to its managed accounts. - * Maintains a local wallet database for storing account balances, transaction history, and notes. - * Exposes its own RPC interface for wallet-specific operations. -* **Significance:** Zallet provides the user-facing tools for managing Zcash funds and interacting with the Zcash network for shielded and transparent operations. - -## Interactions and Dependencies - -The Z3 components currently work together as follows: - -```mermaid -graph TD - User[User/Application] - Zallet[Zallet CLI Wallet] - Zaino[Zaino Indexing Service] - Zebra[Zebra Consensus Node] - Network[Zcash P2P Network] - - User --> Zallet - Zallet --> User - - User --> Zaino - Zaino --> User - - Zallet --> Zaino - Zallet -.-> Zebra - - Zaino --> Zebra - Zebra --> Zaino - - Zebra --> Network - Network --> Zebra - - classDef notes fill:#f9f,stroke:#333,stroke-dasharray:5 5 - - Note1[Zallet: JSON-RPC server, SQLite wallet.db,
uses zaino-fetch, zebra-chain libraries] - Note2[Zaino: Optimized indexes, serves compact blocks,
relays transactions to Zebra] - Note3[Zebra: Consensus validation,
RocksDB state storage, P2P connectivity] - - class Note1,Note2,Note3 notes - - Zallet --- Note1 - Zaino --- Note2 - Zebra --- Note3 -``` - -**Technical Connection Details:** - -* **Zallet to Zaino**: Primarily gRPC communication (via zaino-proto library) -* **Zallet to Zebra**: Occasional direct JSON-RPC/gRPC calls (via zebra-rpc library) -* **Zaino to Zebra**: JSON-RPC/gRPC to fetch blockchain data -* **User to Components**: JSON-RPC for Zallet wallet operations, gRPC for Zaino data requests - -**Key Interaction Flows:** - -1. **Blockchain Data Flow:** - * `Zebra` connects to the Zcash P2P network, downloads, and validates blocks. - * `Zaino` connects to `Zebra` (its "validator node") to fetch new blocks and tree states. Zaino then processes this data into its indexed database. - * `Zallet` connects to `Zaino` (specifically, its `FetchServiceSubscriber` interface) to download compact blocks and tree states relevant for its accounts. This allows Zallet to scan for incoming transactions and update its local wallet state. - -2. **Transaction Submission:** - * A user initiates a transaction via `Zallet`. - * `Zallet` constructs and signs the transaction using its locally stored keys. - * `Zallet` submits the raw transaction. The primary path for this appears to be via Zaino's `FetchService`, which would then relay it to `Zebra`. `Zallet`'s direct dependencies on `zebra-rpc` also suggest it can make direct calls to Zebra for certain operations. - * `Zebra` validates the transaction and broadcasts it to the Zcash P2P network. - -**Dependencies (as seen in `Cargo.toml` and code):** - -* **Zallet depends on:** - * Zaino libraries (`zaino-fetch`, `zaino-proto`, `zaino-state`) for accessing indexed chain data and possibly for submitting transactions. - * Zebra libraries (`zebra-chain`, `zebra-rpc`, `zebra-state`) for core Zcash data structures, RPC communication capabilities, and direct state interaction if needed. -* **Zaino depends on:** - * Zebra libraries (implicitly, as it needs to connect to a Zebra node as its data source). Its internal `FetchService` is configured to point to a validator (Zebra) RPC. -* **Zebra:** - * Has its own set of internal and Zcash-specific cryptographic dependencies but does not depend on Zaino or Zallet. - -## Conclusion - -The Z3 stack (Zebra, Zaino, Zallet) creates a robust, modular system for interacting with the Zcash blockchain. Zebra provides the secure consensus foundation, Zaino offers an efficient data indexing and retrieval layer, and Zallet delivers user-facing wallet management. This separation of concerns aims for improved maintainability, scalability, and security compared to the legacy `zcashd` monolith. diff --git a/docs/memory-bank/z3_docker.md b/docs/memory-bank/z3_docker.md deleted file mode 100644 index 05bcbda..0000000 --- a/docs/memory-bank/z3_docker.md +++ /dev/null @@ -1,135 +0,0 @@ -# Z3 Dockerization & Orchestration Summary - -## Current State - -### Zebra -- **Dockerfile:** Multi-stage build with test, runtime stages - - Build stage installs dependencies and builds binaries - - Runtime stage uses Debian bookworm-slim base - - Configurable non-root user (UID 10001 for security) - -- **Configuration:** - - Flexible via multiple methods: - - Environment variables - - `.env` files - - TOML config files - - Command-line arguments - - Default config at `$HOME/.config/zebrad.toml` - -- **Docker Compose Files:** - 1. `docker-compose.yml`: Base production setup - 2. `docker-compose.grafana.yml`: Monitoring with Prometheus/Grafana - 3. `docker-compose.lwd.yml`: Integration with lightwalletd - 4. `docker-compose.test.yml`: Testing environment - -- **Volume Management:** - - Primary state/cache at `/home/zebra/.cache/zebra` - - Cookie auth directory configurable - - Log files (optional) at customizable location - -- **Networking:** - - RPC port: 8232 (mainnet) / 18232 (testnet) - - Prometheus metrics: 9999 - - Tracing endpoint: 3000 - - P2P network ports configurable - -- **Security Features:** - - Non-root user - - Cookie authentication - - TLS support - - Safe privilege dropping via gosu - -### Zaino -- **Dockerfile:** Multi-stage Rust build - - Build stage with comprehensive Rust dependencies - - Runtime stage on Debian bookworm-slim - - Non-root user (UID 2003) - -- **Configuration:** - - TOML config file (`zindexer.toml`) - - CLI argument `--config` for config path - - Key settings: - - `grpc_listen_address` (default: localhost:8137) - - `validator_listen_address` (Zebra RPC endpoint) - - `db_path` (default: $HOME/.cache/zaino/) - - Network selection (Mainnet/Testnet) - -- **Volume Management:** - - Block cache DB at configurable path (default: $HOME/.cache/zaino/) - - Size configurable via `db_size` setting - -- **Networking:** - - gRPC service on port 8137 - - TLS support available - - Connects to Zebra's RPC endpoint - -## Unified Compose Plan - -A unified docker-compose should integrate Zebra and Zaino with the following considerations: - -1. **Service Dependencies:** -```yaml -services: - zebra: - # Base from zebra/docker/docker-compose.yml - volumes: - - zebra-cache:/home/zebra/.cache/zebra - ports: - - "18232:18232" # RPC (testnet) - - "9999:9999" # Metrics (optional) - - zaino: - depends_on: - - zebra - volumes: - - zaino-cache:/home/zaino/.cache/zaino - ports: - - "8137:8137" # gRPC -``` - -2. **Volume Management:** -```yaml -volumes: - zebra-cache: - driver: local - zaino-cache: - driver: local -``` - -3. **Networking:** -```yaml -networks: - z3net: - driver: bridge -``` - -4. **Configuration:** -- Mount custom config files for both services -- Ensure Zaino's validator_listen_address points to Zebra's RPC -- Consider adding Prometheus/Grafana from Zebra's monitoring setup - -## Next Steps - -1. **Zallet Integration:** - - Research Zallet's Docker requirements - - Determine how it connects to Zebra/Zaino - - Add to unified compose - -2. **Testing:** - - Develop integration tests between services - - Verify volume persistence - - Test network connectivity - -3. **Production Readiness:** - - Add health checks - - Configure logging - - Set up monitoring - - Document backup procedures - -4. **Documentation:** - - Document all config options - - Provide example compose files - - Add troubleshooting guide - -## Related work -Review the Helm charts created by https://github.com/zecrocks/zcash-stack to get a sense of how this project is running Zcash infrastructure in production. \ No newline at end of file diff --git a/docs/regtest.md b/docs/regtest.md index e5cbeac..b41d1eb 100644 --- a/docs/regtest.md +++ b/docs/regtest.md @@ -6,9 +6,8 @@ Uses the base `docker-compose.yml` with `docker-compose.regtest.yml` overlay and ## Prerequisites -- Docker with [Docker Compose](https://docs.docker.com/compose/install/) (v2.24.0+) +- Docker with [Docker Compose](https://docs.docker.com/compose/install/) (v2.24.4+) - [rage](https://github.com/str4d/rage/releases) for generating Zallet encryption keys -- TLS certificates generated (see Quick Start in the main [README](../README.md)) - For gRPC testing: [grpcurl](https://github.com/fullstorydev/grpcurl) and the zaino submodule initialized (`git submodule update --init zaino`) ## First-time setup @@ -21,16 +20,17 @@ From the repo root: This will: -1. Generate a Zallet encryption identity (if not already present) -2. Generate and inject the Zallet RPC password hash in `config/regtest/zallet.toml` -3. Start Zebra in regtest mode -4. Mine 1 block (activates Orchard at height 1) -5. Initialize the Zallet wallet (`init-wallet-encryption` + `generate-mnemonic`) +1. Copy the per-network config templates (`zebra.toml`, `zaino.toml`, `zallet.toml`) into live gitignored files +2. Generate a Zallet encryption identity (if not already present) +3. Generate and inject the Zallet RPC password hash in `config/regtest/zallet.toml` +4. Start Zebra in regtest mode with the activation heights Zaino expects (Canopy at 1, NU5/Orchard at 2) +5. Mine 2 blocks to activate Orchard +6. Initialize the Zallet wallet (`init-wallet-encryption` + `generate-mnemonic`) -Optionally override the RPC password (default is `zebra`): +Optionally override the rpc-router password (default is `zebra`). The `REGTEST_` infix marks the var as regtest-scoped: ```bash -RPC_PASSWORD='your-password' ./scripts/regtest-init.sh +Z3_REGTEST_RPC_ROUTER_PASSWORD='your-password' ./scripts/regtest-init.sh ``` ## Start the stack @@ -44,35 +44,14 @@ docker compose --env-file .env.regtest up -d Zebra, Zaino, and Zallet use pre-built images. The rpc-router builds from source on first run (takes a few minutes; subsequent runs use the Docker layer cache). > [!NOTE] -> Regtest uses the same host ports as mainnet/testnet. If other Z3 services are running, stop them first (`docker compose down`) or override port variables in `.env.regtest`. +> Regtest host ports are explicit and globally unique so all three networks (mainnet, testnet, regtest) can run concurrently on one host without binding collisions. | Service | Endpoint | Description | |---------|----------|-------------| | rpc-router | http://localhost:8181 | JSON-RPC router (Zebra + Zallet) | -| Zaino gRPC | https://localhost:8137 | lightwalletd-compatible gRPC (TLS) | -| Zebra RPC | http://localhost:18232 | Direct Zebra JSON-RPC | -| Zallet RPC | http://localhost:28232 | Direct Zallet JSON-RPC | -| zcashd RPC | http://localhost:38232 | Optional zcashd comparator (`--profile zcashd`) | - -## Optional zcashd comparator - -For local compatibility checks against zcashd, start the profiled zcashd service: - -```bash -docker compose --env-file .env.regtest --profile zcashd up -d zcashd -``` - -The regtest overlay starts zcashd with public P2P disabled (`-listen=0 -connect=0`) and, by default, the same NU activation heights used by Zallet. It uses a separate Docker volume (`zcashd_data`) and default RPC credentials `zebra` / `zebra`. See the [README platform section](../README.md#platform-configuration-arm64) for arm64 notes. - -For comparator runs that need a specific upgrade era, override the zcashd activation heights and use a separate data volume: - -```bash -Z3_ZCASHD_DATA_PATH=./.tmp/zcashd-canopy-data \ -ZCASHD_NU5_ACTIVATION_HEIGHT=100 \ -docker compose --env-file .env.regtest --profile zcashd up -d --force-recreate zcashd -``` - -This keeps the default Z3 regtest state separate from comparator state and allows V4/Canopy fixtures before NU5 activation. +| Zaino gRPC | localhost:28137 | lightwalletd-compatible gRPC (plaintext h2c) | +| Zebra RPC | http://localhost:29232 | Direct Zebra JSON-RPC | +| Zallet RPC | http://localhost:50232 | Direct Zallet JSON-RPC | ## Test routing @@ -97,7 +76,7 @@ curl -s -X POST -H "Content-Type: application/json" \ ## Test Zaino gRPC -Zaino exposes the [lightwalletd-compatible gRPC protocol](https://github.com/zcash/lightwalletd/blob/master/walletrpc/service.proto) on port 8137 with TLS. The `--insecure` flag tells grpcurl to accept the self-signed certificate. +Zaino exposes the [lightwalletd-compatible gRPC protocol](https://github.com/zcash/lightwalletd/blob/master/walletrpc/service.proto) as plaintext h2c (no TLS). In regtest the host port is `28137` (`Z3_ZAINO_HOST_GRPC_PORT`); the `-plaintext` flag tells grpcurl to skip TLS. Initialize the zaino submodule if you haven't already (needed for the proto files): @@ -108,21 +87,21 @@ git submodule update --init zaino Test with `GetLightdInfo` (from the repo root): ```bash -grpcurl -insecure \ +grpcurl -plaintext \ -import-path zaino/zaino-proto/proto \ -proto service.proto \ - 127.0.0.1:8137 \ + 127.0.0.1:28137 \ cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLightdInfo ``` Get the latest block height: ```bash -grpcurl -insecure \ +grpcurl -plaintext \ -import-path zaino/zaino-proto/proto \ -proto service.proto \ -d '{}' \ - 127.0.0.1:8137 \ + 127.0.0.1:28137 \ cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLatestBlock ``` @@ -160,25 +139,18 @@ docker compose --env-file .env.regtest down -v ## Monitoring in regtest -To enable monitoring, add the metrics endpoint to `.env.regtest`: - -```bash -ZEBRA_METRICS__ENDPOINT_ADDR=0.0.0.0:9999 -``` - -Then start with both flags: +Zebra's Prometheus endpoint is enabled by default on the internal `zebra:9999` scrape target. Start the monitoring profile: ```bash docker compose --env-file .env.regtest --profile monitoring up -d ``` -The port (9999) must match the Prometheus scrape target configured in `observability/prometheus/prometheus.yaml`. +Regtest monitoring UI ports are Grafana `23000`, Prometheus `29094`, Jaeger UI `36686`, and AlertManager `29093`. Jaeger also publishes OTLP gRPC `25317`, OTLP HTTP `25318`, and spanmetrics `28889`. ## Notes - Credentials: `zebra` / `zebra` (hardcoded for regtest only) -- Zallet uses regtest nuparams activating all upgrades at block 1 +- Regtest activates upgrades through Canopy at block 1 and NU5/Orchard at block 2 (Zebra, Zaino, and Zallet all agree) - Zaino uses username/password auth in regtest (not cookie auth) -- Zaino gRPC uses TLS with the same self-signed certificate as mainnet/testnet -- zcashd is optional and only starts when the `zcashd` profile is enabled +- Zaino gRPC is plaintext h2c on all networks; terminate edge TLS at a reverse proxy if exposed beyond the host - The rpc-router source is in `rpc-router/`; it is built automatically on first `docker compose up` diff --git a/observability/README.md b/observability/README.md index ed4c5d6..43f2e17 100644 --- a/observability/README.md +++ b/observability/README.md @@ -5,34 +5,42 @@ Metrics, alerting, and dashboards for the Z3 stack (Zebra, Zaino, Zallet). ## Quick Start ```bash -# 1. Enable Zebra metrics in .env (uncomment this line): -ZEBRA_METRICS__ENDPOINT_ADDR=0.0.0.0:9999 +# 1. Optional: enable Zebra trace export in .env +ZEBRA_TRACING__OPENTELEMETRY_ENDPOINT=http://jaeger:4318 +ZEBRA_TRACING__OPENTELEMETRY_SERVICE_NAME=zebra-mainnet +ZEBRA_TRACING__OPENTELEMETRY_SAMPLE_PERCENT=100 -# 2. Start the full stack with monitoring -docker compose --profile monitoring up -d +# 2. Start the full stack with monitoring. +# Passing .env as a second env file also applies local port and image overrides. +docker compose --env-file .env.mainnet --env-file .env --profile monitoring up -d # 3. View logs -docker compose logs -f zebra +docker compose --env-file .env.mainnet --env-file .env logs -f zebra ``` -> **Note**: OpenTelemetry tracing requires building Zebra with the `opentelemetry` feature. -> The pre-built Docker image does not include it. See the [Tracing section](#tracing-jaeger) for build instructions. +> **Note**: The monitoring profile starts Jaeger, but Zebra only exports spans +> after `ZEBRA_TRACING__OPENTELEMETRY_ENDPOINT` is set. The pinned Zebra image +> and the default local build use `default-release-binaries`, which includes +> OpenTelemetry. If you override `Z3_ZEBRA_BUILD_FEATURES`, keep +> `opentelemetry` in the feature list. ## Components -| Component | Port | URL | Purpose | -|-----------|------|-----|---------| -| **Zebra** | 9999 | - | Zcash node with metrics and tracing | -| **Prometheus** | 9094 | | Metrics collection and storage | -| **Grafana** | 3000 | | Dashboards and visualization | -| **Jaeger** | 16686 | | Distributed tracing UI | -| **AlertManager** | 9093 | | Alert routing | +| Component | Purpose | +|-----------|---------| +| **Zebra** | Zcash node with metrics and tracing (in-network scrape on `:9999`) | +| **Prometheus** | Metrics collection and storage | +| **Grafana** | Dashboards and visualization | +| **Jaeger** | Distributed tracing UI | +| **AlertManager** | Alert routing | + +Published host ports for these components are per-network and live in [`z3-contract.yaml`](../z3-contract.yaml) under `networks..ports` (the `monitoring` profile). Default Grafana credentials: `admin` / `admin` (you'll be prompted to change on first login) ## Architecture -``` +```text ┌─────────────────────────────────────────────────────────────────────┐ │ Zebra Node │ │ ┌─────────────────┐ ┌─────────────────────────────┐ │ @@ -82,21 +90,32 @@ See [grafana/README.md](grafana/README.md) for dashboard details. ### Tracing (Jaeger) -Distributed tracing via OpenTelemetry. Requires building Zebra with the `opentelemetry` feature (not included in the pre-built image): +Distributed tracing uses Zebra's OpenTelemetry exporter and the Jaeger collector +from the `monitoring` profile. Jaeger can run without Zebra traces; Zebra begins +exporting spans only after the OTLP endpoint is configured. + +Enable tracing in `.env`: ```bash -# Build Zebra with OpenTelemetry support -docker compose build --build-arg FEATURES="default-release-binaries opentelemetry" zebra +ZEBRA_TRACING__OPENTELEMETRY_ENDPOINT=http://jaeger:4318 +ZEBRA_TRACING__OPENTELEMETRY_SERVICE_NAME=zebra-mainnet +ZEBRA_TRACING__OPENTELEMETRY_SAMPLE_PERCENT=100 ``` -Then enable tracing in `.env`: +Then recreate Zebra with the local env file loaded: ```bash -ZEBRA_TRACING__OPENTELEMETRY_ENDPOINT=http://jaeger:4318 -ZEBRA_TRACING__OPENTELEMETRY_SERVICE_NAME=zebra -ZEBRA_TRACING__OPENTELEMETRY_SAMPLE_PERCENT=100 +docker compose --env-file .env.mainnet --env-file .env --profile monitoring up -d --force-recreate zebra ``` +`ZEBRA_TRACING__OPENTELEMETRY_*` values reach the Zebra container through its +service-level `env_file`. `Z3_*` values such as `Z3_ZEBRA_IMAGE` and +`Z3_JAEGER_OTLP_HTTP_PORT` are Compose interpolation inputs, so they need to be +exported in the shell or loaded with `--env-file .env`. + +If you build a custom Zebra image with `Z3_ZEBRA_BUILD_FEATURES`, include +`opentelemetry` or use `default-release-binaries`. + Jaeger provides: - **Distributed traces**: Follow a request through all components @@ -127,23 +146,29 @@ Add this to your `.env` file to enable Zebra metrics: ### Port Customization -Override default ports in `.env`: +Override default monitoring host ports via the `Z3_*` env vars in your env file or `.env`: ```bash -GRAFANA_PORT=3000 -PROMETHEUS_PORT=9094 -JAEGER_UI_PORT=16686 -ALERTMANAGER_PORT=9093 +Z3_GRAFANA_PORT=3000 +Z3_PROMETHEUS_PORT=9094 +Z3_JAEGER_UI_PORT=16686 +Z3_ALERTMANAGER_PORT=9093 ``` +See [`z3-contract.yaml`](../z3-contract.yaml) for the full env-var schema. + ## Common Tasks ### View Zebra's current metrics ```bash -curl -s http://localhost:9999/metrics | grep zcash +docker compose --env-file .env.mainnet exec zebra \ + curl -sf http://127.0.0.1:9999/metrics | grep zcash ``` +Zebra's metrics port is intentionally in-network only; Prometheus scrapes it on +the Compose network. + ### Query Prometheus directly ```bash @@ -156,10 +181,17 @@ curl -s 'http://localhost:9094/api/v1/query?query=zcash_state_tip_height' ### No metrics in Grafana 1. Verify `ZEBRA_METRICS__ENDPOINT_ADDR=0.0.0.0:9999` is set in `.env` -2. Restart Zebra: `docker compose restart zebra` -3. Check Zebra is exposing metrics: `docker compose exec zebra wget -qO- http://localhost:9999/metrics | head` +2. Restart Zebra: `docker compose --env-file .env.mainnet --env-file .env restart zebra` +3. Check Zebra is exposing metrics: `docker compose --env-file .env.mainnet exec zebra curl -sf http://127.0.0.1:9999/metrics | head` 4. Check Prometheus targets: +### No traces in Jaeger + +1. Verify Zebra has the OTLP env vars: `docker inspect z3-mainnet-zebra-1 --format '{{range .Config.Env}}{{println .}}{{end}}' | grep ZEBRA_TRACING__OPENTELEMETRY` +2. Check Zebra installed the tracing layer: `docker logs z3-mainnet-zebra-1 | grep 'installed OpenTelemetry tracing layer'` +3. Check Jaeger has the Zebra service: `curl -s http://127.0.0.1:16686/api/services` +4. Check Prometheus has span metrics: `curl -sG http://127.0.0.1:9094/api/v1/query --data-urlencode 'query=traces_span_metrics_calls_total{service_name="zebra-mainnet"}'` + ## Running Without Monitoring To run the Z3 stack without monitoring: diff --git a/observability/jaeger/README.md b/observability/jaeger/README.md index 18d7df2..c4c5907 100644 --- a/observability/jaeger/README.md +++ b/observability/jaeger/README.md @@ -320,16 +320,28 @@ These power the Monitor tab and can be scraped by Prometheus for Grafana dashboa ### No traces appearing -1. Ensure Zebra was built with OTel support: `docker compose build --build-arg FEATURES="default-release-binaries opentelemetry" zebra` -2. Check tracing env vars are set: `ZEBRA_TRACING__OPENTELEMETRY_ENDPOINT=http://jaeger:4318` -3. Verify Jaeger health: http://localhost:16686 -4. Check Jaeger logs: `docker compose logs jaeger` +1. Check tracing env vars are set in Zebra's container: + `docker inspect z3-mainnet-zebra-1 --format '{{range .Config.Env}}{{println .}}{{end}}' | grep ZEBRA_TRACING__OPENTELEMETRY` +2. Recreate Zebra after env changes: + `docker compose --env-file .env.mainnet --env-file .env --profile monitoring up -d --force-recreate zebra` +3. Check Zebra installed the exporter: + `docker logs z3-mainnet-zebra-1 | grep 'installed OpenTelemetry tracing layer'` +4. Verify Jaeger health: +5. Check Jaeger service discovery: + `curl -s http://127.0.0.1:16686/api/services` +6. Check Jaeger logs: `docker compose --profile monitoring logs jaeger` + +The pinned Zebra image and the default local build use `default-release-binaries`, +which includes OpenTelemetry. If you override `Z3_ZEBRA_BUILD_FEATURES`, keep +`opentelemetry` in the feature list. ### Monitor tab shows "No Data" 1. Ensure spanmetrics connector is configured 2. Wait for traces to accumulate (needs a few minutes) 3. Select the correct Span Kind filter +4. Confirm Prometheus has spanmetrics: + `curl -sG http://127.0.0.1:9094/api/v1/query --data-urlencode 'query=traces_span_metrics_calls_total{service_name="zebra-mainnet"}'` ### High memory usage diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..6f96e8f --- /dev/null +++ b/renovate.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:recommended"], + "github-actions": { + "enabled": false + }, + "packageRules": [ + { + "description": "Image pins are deliberate; raise PRs but never auto-merge a node-platform image bump.", + "matchManagers": ["docker-compose", "custom.regex"], + "automerge": false + } + ], + "customManagers": [ + { + "customType": "regex", + "description": "Bump the ${VAR:-repo:tag} image-pin defaults in the compose files.", + "managerFilePatterns": ["/(^|/)docker-compose[^/]*\\.ya?ml$/"], + "matchStrings": [ + "\\$\\{[A-Z0-9_]+:-(?[^:}\\s]+):(?[^}\\s]+)\\}" + ], + "datasourceTemplate": "docker" + } + ] +} diff --git a/scripts/check-zebra-readiness.sh b/scripts/check-zebra-readiness.sh index 2fe7c09..535fb94 100755 --- a/scripts/check-zebra-readiness.sh +++ b/scripts/check-zebra-readiness.sh @@ -1,20 +1,39 @@ -#!/bin/bash -# Polls Zebra's readiness endpoint until it returns "ok" -# Use this script during initial sync to know when Zebra is ready +#!/usr/bin/env bash +# Polls Zebra's readiness endpoint until it returns "ok". +# Run during initial sync to know when Zebra is ready for the rest of the stack. +# +# Usage: +# ./scripts/check-zebra-readiness.sh # mainnet (port 8080) +# ./scripts/check-zebra-readiness.sh 18080 # testnet +# ./scripts/check-zebra-readiness.sh 28080 # regtest -echo "Waiting for Zebra to sync..." -echo "This may take hours (mainnet: 24-72h, testnet: 2-12h)" -echo "You can safely Ctrl+C and check back later." -echo "" +set -euo pipefail + +PORT="${1:-8080}" +URL="http://127.0.0.1:${PORT}/ready" + +# Map the health port to the matching env file so the success message tells +# the operator the right docker compose invocation. +case "$PORT" in + 8080) ENV_FILE=".env.mainnet" ;; + 18080) ENV_FILE=".env.testnet" ;; + 28080) ENV_FILE=".env.regtest" ;; + *) ENV_FILE=".env." ;; +esac + +echo "Polling ${URL} every 30s." +echo "Initial sync takes hours: mainnet 24-72h, testnet 2-12h." +echo "Safe to Ctrl+C and re-run later; Zebra keeps syncing in the background." +echo while true; do - response=$(curl -s http://127.0.0.1:8080/ready) + response="$(curl -s "$URL" || true)" if [ "$response" = "ok" ]; then - echo "" - echo "Zebra is ready! You can now start the remaining services:" - echo " docker compose up -d" - break + echo + echo "Zebra is ready. Bring up the rest of the stack with:" + echo " docker compose --env-file ${ENV_FILE} up -d" + exit 0 fi - echo "$(date '+%H:%M:%S') - Not ready yet: $response" + echo "$(date '+%H:%M:%S') - not ready yet: ${response:-no response}" sleep 30 done diff --git a/scripts/fix-permissions.sh b/scripts/fix-permissions.sh index 77ea499..8fc0ca5 100755 --- a/scripts/fix-permissions.sh +++ b/scripts/fix-permissions.sh @@ -6,13 +6,12 @@ # Usage: # ./fix-permissions.sh # -# Services: zebra, zaino, zallet, zcashd, cookie +# Services: zebra, zaino, zallet, cookie # # Examples: # ./fix-permissions.sh zebra /mnt/ssd/zebra-state # ./fix-permissions.sh zaino /home/user/data/zaino # ./fix-permissions.sh zallet ~/Documents/zallet-data -# ./fix-permissions.sh zcashd /mnt/ssd/zcashd-data # ./fix-permissions.sh cookie /var/lib/z3/cookies # @@ -29,10 +28,8 @@ ZEBRA_UID=10001 ZEBRA_GID=10001 ZAINO_UID=1000 ZAINO_GID=1000 -ZALLET_UID=65532 -ZALLET_GID=65532 -ZCASHD_UID=999 -ZCASHD_GID=999 +ZALLET_UID=1000 +ZALLET_GID=1000 # Show usage usage() { @@ -41,15 +38,13 @@ usage() { echo "Services:" echo " zebra - Zebra blockchain state (UID:GID 10001:10001, perms 700)" echo " zaino - Zaino indexer data (UID:GID 1000:1000, perms 700)" - echo " zallet - Zallet wallet data (UID:GID 65532:65532, perms 700)" - echo " zcashd - Optional zcashd comparator data (UID:GID 999:999, perms 700)" + echo " zallet - Zallet wallet data (UID:GID 1000:1000, perms 700)" echo " cookie - Shared cookie directory (UID:GID 10001:10001, perms 750)" echo "" echo "Examples:" echo " $0 zebra /mnt/ssd/zebra-state" echo " $0 zaino /home/user/data/zaino" echo " $0 zallet ~/Documents/zallet-data" - echo " $0 zcashd /mnt/ssd/zcashd-data" exit 1 } @@ -78,21 +73,14 @@ case "$SERVICE" in OWNER_GID=$ZALLET_GID PERMS=700 ;; - zcashd) - OWNER_UID=$ZCASHD_UID - OWNER_GID=$ZCASHD_GID - PERMS=700 - ;; cookie) OWNER_UID=$ZEBRA_UID OWNER_GID=$ZEBRA_GID - PERMS=750 - echo -e "${YELLOW}WARNING: Cookie directory has special requirements.${NC}" - echo "Zaino (UID 1000) needs read access to Zebra's (UID 10001) cookie." - echo "After running this script, you may need to:" - echo " 1. Use ACLs: sudo setfacl -m u:1000:r ${DIR_PATH}" - echo " 2. Or create a shared group for both users" - echo " 3. Or keep cookie as Docker volume (recommended)" + PERMS=755 + echo -e "${YELLOW}NOTE: cookie permissions are handled by the cookie-permissions${NC}" + echo "sidecar in docker-compose.yml at runtime. It chmods the .cookie file to" + echo "0644 inside the volume so any consumer uid can read it. Bind-mounting" + echo "the cookie path is advanced; the default Docker named volume is preferred." echo "" ;; *) @@ -133,7 +121,7 @@ echo "" echo "To use this directory, update your .env file:" case "$SERVICE" in zebra) - echo " Z3_ZEBRA_DATA_PATH=${DIR_PATH}" + echo " Z3_CHAIN_DATA_PATH=${DIR_PATH}" ;; zaino) echo " Z3_ZAINO_DATA_PATH=${DIR_PATH}" @@ -141,9 +129,6 @@ case "$SERVICE" in zallet) echo " Z3_ZALLET_DATA_PATH=${DIR_PATH}" ;; - zcashd) - echo " Z3_ZCASHD_DATA_PATH=${DIR_PATH}" - ;; cookie) echo " Z3_COOKIE_PATH=${DIR_PATH}" ;; diff --git a/scripts/regtest-init.sh b/scripts/regtest-init.sh index f2f49a8..15d8d58 100755 --- a/scripts/regtest-init.sh +++ b/scripts/regtest-init.sh @@ -1,14 +1,16 @@ #!/usr/bin/env bash -# init.sh - Initialize the regtest wallet for the first time. +# regtest-init.sh: initialize the regtest wallet for the first time. # -# Run this once before starting the regtest stack. -# Safe to re-run: skips steps if already done. +# Delegates first-run file setup to scripts/setup-network.sh, then performs +# the regtest-specific steps: write the Zallet RPC password hash, mine the +# activation blocks, and generate the wallet mnemonic. +# +# Run this once before starting the regtest stack. Safe to re-run. # # Requirements: -# - Docker with Docker Compose v2.24.0+ -# - rage-keygen (for first-time identity generation) -# - openssl (for TLS certificate and RPC password hash generation) -# - No running z3_regtest_* containers +# - Docker with Docker Compose v2.24.4+ +# - rage-keygen, openssl (used by setup-network.sh) +# - No running z3-regtest-* containers set -euo pipefail @@ -17,57 +19,42 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" ENV_FILE="$REPO_ROOT/.env.regtest" COMPOSE="docker compose --env-file $ENV_FILE" -log() { - printf '%s\n' "$*" -} +# Source the env file so port + project vars are available in this shell. +set -a +# shellcheck disable=SC1090 +. "$ENV_FILE" +set +a -ensure_rage() { - if command -v rage-keygen > /dev/null 2>&1; then - log "rage already installed" - return - fi +PROJECT="${COMPOSE_PROJECT_NAME:-z3-regtest}" +ZEBRA_HOST_RPC="${Z3_ZEBRA_HOST_RPC_PORT:-29232}" +ZAINO_HOST_GRPC="${Z3_ZAINO_HOST_GRPC_PORT:-28137}" +CONFIG_DIR="${Z3_CONFIG_DIR:-./config/regtest}" - log "rage-keygen is required to generate a local zallet identity." - log "Install it, then re-run this script:" - log " macOS: brew install rage" - log " Debian/Ubuntu: install rage from https://github.com/str4d/rage/releases or provide rage-keygen in PATH" - exit 1 +log() { + printf '%s\n' "$*" } -ensure_local_identity() { - local identity_path="$REPO_ROOT/config/regtest/zallet_identity.txt" - - if [ -f "$identity_path" ]; then - log "==> Reusing existing local zallet identity..." - return +require_compose_v2() { + # This stack relies on the colon-separated COMPOSE_FILE merge and the + # !override tag, both Docker Compose v2.24.4+ features. The legacy v1 + # `docker-compose` binary cannot load them, so we require the v2 plugin + # and do not fall back to v1. + local ver need="2.24.4" + # A single `docker compose version --short` both proves the v2 plugin + # exists (non-zero exit otherwise) and yields the version to gate on. + if ! ver="$(docker compose version --short 2>/dev/null)"; then + log "Docker Compose v2 is required (the 'docker compose' plugin)." + log "The legacy 'docker-compose' v1 binary cannot load this stack's" + log "COMPOSE_FILE merge and !override tag. Install Compose v2.24.4+:" + log " https://docs.docker.com/compose/install/" + exit 1 fi - - ensure_rage - - log "==> Generating local zallet identity..." - mkdir -p "$REPO_ROOT/config/regtest" - rage-keygen -o "$identity_path" - chmod 600 "$identity_path" -} - -ensure_tls_certs() { - local cert_path="$REPO_ROOT/config/tls/zaino.crt" - local key_path="$REPO_ROOT/config/tls/zaino.key" - - if [ -f "$cert_path" ] && [ -f "$key_path" ]; then - log "==> Reusing existing TLS certificates..." - return + ver="${ver#v}" + if [ -n "$ver" ] && [ "$(printf '%s\n%s\n' "$need" "$ver" | sort -V | head -n1)" != "$need" ]; then + log "Docker Compose $ver found, but >= $need is required (the regtest" + log "overlay uses the !override tag). Upgrade Docker Compose." + exit 1 fi - - ensure_openssl - - log "==> Generating self-signed TLS certificate for Zaino..." - mkdir -p "$REPO_ROOT/config/tls" - openssl req -x509 -newkey rsa:4096 \ - -keyout "$key_path" -out "$cert_path" \ - -sha256 -days 365 -nodes -subj "/CN=localhost" \ - -addext "subjectAltName=DNS:localhost,DNS:zaino,IP:127.0.0.1" 2>/dev/null - log " TLS certificate written to config/tls/zaino.crt" } ensure_openssl() { @@ -75,23 +62,15 @@ ensure_openssl() { return fi - log "openssl is required to generate a local zallet RPC password hash." + log "openssl is required to generate the zallet RPC password hash." log "Install OpenSSL, then re-run this script." exit 1 } -seed_configs() { - for name in config/zallet.toml config/regtest/zallet.toml; do - if [ ! -f "$REPO_ROOT/$name" ] && [ -f "$REPO_ROOT/$name.default" ]; then - cp "$REPO_ROOT/$name.default" "$REPO_ROOT/$name" - log "==> Seeded $name from $name.default" - fi - done -} - update_zallet_rpc_pwhash() { - local config_path="$REPO_ROOT/config/regtest/zallet.toml" - local rpc_password="${RPC_PASSWORD:-zebra}" + local config_path="$REPO_ROOT/${CONFIG_DIR#./}/zallet.toml" + # Keep Zallet's RPC password hash aligned with the rpc-router credential. + local rpc_password="${Z3_REGTEST_RPC_ROUTER_PASSWORD:-zebra}" local placeholder="__GENERATED_BY_INIT_SH__" local salt local hash @@ -108,7 +87,6 @@ update_zallet_rpc_pwhash() { exit 1 fi - # Skip if already generated (not the placeholder) if ! grep -q "pwhash = \"${placeholder}\"" "$config_path"; then log "==> Zallet RPC pwhash already generated, skipping." return @@ -130,10 +108,11 @@ update_zallet_rpc_pwhash() { sed -E "s|^pwhash = \".*\"$|pwhash = \"${pwhash}\"|" "$config_path" > "$tmp" mv "$tmp" "$config_path" - log "==> Generated zallet RPC pwhash in config/regtest/zallet.toml" + log "==> Generated zallet RPC pwhash in ${CONFIG_DIR}/zallet.toml" } -# Use sudo for docker if needed +require_compose_v2 + DOCKER="docker" if ! docker info > /dev/null 2>&1; then DOCKER="sudo -E docker" @@ -142,12 +121,10 @@ fi cd "$REPO_ROOT" -seed_configs -ensure_local_identity -ensure_tls_certs +"$SCRIPT_DIR/setup-network.sh" regtest update_zallet_rpc_pwhash -# Clean up any leftover containers from previous runs +# Clean up any leftover containers from previous runs. $COMPOSE down --remove-orphans 2>/dev/null || true echo "==> Starting Zebra in regtest mode..." @@ -157,37 +134,37 @@ echo "==> Waiting for Zebra RPC to be ready..." until curl -sf -X POST \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"getblockchaininfo","params":[],"id":1}' \ - http://127.0.0.1:18232 > /dev/null 2>&1; do + "http://127.0.0.1:${ZEBRA_HOST_RPC}" > /dev/null 2>&1; do echo " Zebra not ready yet, retrying..." sleep 2 done echo " Zebra is ready." -echo "==> Mining 1 block (required for Orchard activation at height 1)..." +echo "==> Mining 2 blocks (NU5/Orchard activates at height 2 to match Zaino's regtest defaults)..." curl -s -u zebra:zebra \ -X POST -H "Content-Type: application/json" \ - -d '{"jsonrpc":"2.0","method":"generate","params":[1],"id":1}' \ - http://127.0.0.1:18232 | grep -q '"result"' -echo " Block mined." + -d '{"jsonrpc":"2.0","method":"generate","params":[2],"id":1}' \ + "http://127.0.0.1:${ZEBRA_HOST_RPC}" | grep -q '"result"' +echo " Blocks mined." -# Volume name includes the project prefix from COMPOSE_PROJECT_NAME -VOLUME_PREFIX="z3-regtest" +# Compose names the Zallet data volume as ${COMPOSE_PROJECT_NAME}-zallet. +ZALLET_VOLUME="${PROJECT}-zallet" echo "==> Running init-wallet-encryption..." # Remove stale lock file and wallet database if present (left by a previous # interrupted run). wallet.db will be recreated with the correct schema by # init-wallet-encryption; leaving a stale one causes a schema mismatch error. -$DOCKER run --rm -v "${VOLUME_PREFIX}_zallet_data:/data" busybox \ +$DOCKER run --rm -v "${ZALLET_VOLUME}:/data" busybox \ sh -c 'rm -f /data/.lock /data/wallet.db' -# Check if wallet already initialized: generate-mnemonic stores an age-encrypted -# file; if one exists the full init sequence has already completed successfully. -ALREADY_INIT=$($DOCKER run --rm -v "${VOLUME_PREFIX}_zallet_data:/data" busybox \ + +# generate-mnemonic stores an age-encrypted file; if one exists the full init +# sequence has already completed successfully. +ALREADY_INIT=$($DOCKER run --rm -v "${ZALLET_VOLUME}:/data" busybox \ sh -c 'ls /data/*.age 2>/dev/null | wc -l') if [ "${ALREADY_INIT:-0}" -gt 0 ]; then echo " Wallet already initialized, skipping." else $COMPOSE run --rm zallet --datadir /var/lib/zallet --config /etc/zallet/zallet.toml init-wallet-encryption - echo "==> Running generate-mnemonic..." $COMPOSE run --rm zallet --datadir /var/lib/zallet --config /etc/zallet/zallet.toml generate-mnemonic fi @@ -198,5 +175,5 @@ $COMPOSE down echo "" echo "Wallet initialized. Now run:" echo " docker compose --env-file .env.regtest up -d" -echo " # Router will be available at http://127.0.0.1:8181" -echo " # Zaino gRPC will be available at 127.0.0.1:8137" +echo " Router available at http://127.0.0.1:8181" +echo " Zaino gRPC available at 127.0.0.1:${ZAINO_HOST_GRPC}" diff --git a/scripts/setup-network.sh b/scripts/setup-network.sh new file mode 100755 index 0000000..9e8b9d5 --- /dev/null +++ b/scripts/setup-network.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# setup-network.sh: idempotent first-run setup for a z3 network. +# +# Copies per-network .example TOML templates into the live gitignored paths +# that docker-compose.yml mounts; generates a Zallet identity and shared TLS +# cert if missing. +# +# Usage: +# ./scripts/setup-network.sh +# +# Safe to re-run: every step skips if its output already exists. The live +# TOMLs and identity file are local and gitignored. +# +# For regtest, this script only handles file setup; the operational steps +# (mining the activation block, generating the wallet mnemonic) live in +# scripts/regtest-init.sh, which delegates here first. + +set -euo pipefail + +NETWORK="${1:-}" +case "$NETWORK" in + mainnet|testnet|regtest) ;; + *) + echo "Usage: $0 " >&2 + exit 1 + ;; +esac + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +CONFIG_DIR="$REPO_ROOT/config/$NETWORK" + +log() { printf '%s\n' "$*"; } + +copy_template() { + local file="$1" + local example="$CONFIG_DIR/$file.example" + local active="$CONFIG_DIR/$file" + + if [ -f "$active" ]; then + log "==> $NETWORK/$file: present, leaving operator copy untouched." + return + fi + + if [ ! -f "$example" ]; then + log "FAIL: missing template $example" >&2 + exit 1 + fi + + cp "$example" "$active" + log "==> $NETWORK/$file: created from .example template." +} + +ensure_identity() { + local identity="$CONFIG_DIR/zallet_identity.txt" + + if [ -f "$identity" ]; then + log "==> $NETWORK/zallet_identity.txt: present." + return + fi + + if ! command -v rage-keygen >/dev/null 2>&1; then + log "FAIL: rage-keygen not found." >&2 + log " Install rage from https://github.com/str4d/rage/releases" >&2 + exit 1 + fi + + rage-keygen -o "$identity" + chmod 600 "$identity" + log "==> $NETWORK/zallet_identity.txt: generated." +} + +mkdir -p "$CONFIG_DIR" + +copy_template zaino.toml +copy_template zallet.toml +# Regtest needs a Zebra TOML to activate NU5/NU6 at heights Zaino expects. +# Mainnet and testnet use Zebra's built-in network defaults. +if [ "$NETWORK" = "regtest" ]; then + copy_template zebra.toml +fi +ensure_identity + +log +log "Setup complete for $NETWORK." +log "Next: docker compose --env-file .env.$NETWORK up -d zebra" diff --git a/scripts/validate-contract-parity.py b/scripts/validate-contract-parity.py new file mode 100755 index 0000000..78aee14 --- /dev/null +++ b/scripts/validate-contract-parity.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +"""validate-contract-parity.py: env-var inventory parity test. + +Asserts that the set of operator-facing environment variables in +z3-contract.yaml matches reality: + + compose substitutions -> env_vars (every ${VAR} must be contracted) + env_vars <-> .env.example (mutual: no orphans either way) + +Complements validate-contract.py, which asserts the per-network port +matrix against resolved compose output. + +Exit codes: + 0 all parity checks pass + 1 one or more parity checks failed + 2 prerequisite missing (PyYAML, files) +""" + +from __future__ import annotations + +import pathlib +import re +import sys + +try: + import yaml +except ImportError: + print("FAIL: PyYAML is required. Install with: pip install pyyaml", file=sys.stderr) + sys.exit(2) + +ROOT = pathlib.Path(__file__).resolve().parent.parent + +COMPOSE_FILES = [ + ROOT / "docker-compose.yml", + ROOT / "docker-compose.regtest.yml", +] +CONTRACT_FILE = ROOT / "z3-contract.yaml" +ENV_EXAMPLE = ROOT / ".env.example" + +VAR_NAME = r"[A-Z][A-Z0-9_]+" + +# $${VAR} is Compose's literal-escape (resolved by the container shell, not +# Compose), so the negative lookbehind skips it. ${VAR} and ${VAR:-default} +# both match. +COMPOSE_SUB_RE = re.compile(rf"(? str: + return path.read_text() if path.exists() else "" + + +def collect_compose_vars() -> set[str]: + found: set[str] = set() + for f in COMPOSE_FILES: + found |= {m.group(1) for m in COMPOSE_SUB_RE.finditer(read_text(f))} + return found + + +def collect_contract_vars(contract: dict) -> set[str]: + env_vars: set[str] = set() + for _, entries in contract.get("env_vars", {}).items(): + env_vars |= {e["name"] for e in entries} + return env_vars + + +def collect_ecosystem_vars(contract: dict) -> set[str]: + return {e["name"] for e in contract.get("ecosystem_vars", [])} + + +def collect_env_example_vars() -> set[str]: + return {m.group(1) for m in ENV_LINE_RE.finditer(read_text(ENV_EXAMPLE))} + + +def report_diff(subset_label: str, subset: set[str], + superset_label: str, superset: set[str]) -> int: + missing = subset - superset + if not missing: + print(f" OK every var in {subset_label} is present in {superset_label}") + return 0 + print(f" FAIL {len(missing)} var(s) in {subset_label} missing from {superset_label}:") + for v in sorted(missing): + print(f" - {v}") + return 1 + + +def main() -> int: + for f in [*COMPOSE_FILES, CONTRACT_FILE, ENV_EXAMPLE]: + if not f.exists(): + print(f"FAIL: missing {f.relative_to(ROOT)}") + return 1 + + contract = yaml.safe_load(CONTRACT_FILE.read_text()) + compose = collect_compose_vars() + env_vars = collect_contract_vars(contract) + ecosystem_vars = collect_ecosystem_vars(contract) + env_example = collect_env_example_vars() + + documented = env_vars | ecosystem_vars + + failures = 0 + + # Compose substitutions may include ecosystem-standard names (RUST_LOG, + # RUST_BACKTRACE) used as fallback chains; allow them. + print("== Compose substitutions vs contract (env_vars u ecosystem_vars) ==") + failures += report_diff("compose substitutions", compose, + "z3-contract.yaml (env + ecosystem)", documented) + + # .env.example documents both z3 surface and ecosystem fallbacks the + # operator may want to set. + print() + print("== Contract (env_vars u ecosystem_vars) vs .env.example ==") + failures += report_diff("z3-contract.yaml env_vars", env_vars, ".env.example", env_example) + failures += report_diff(".env.example", env_example, + "z3-contract.yaml (env + ecosystem)", documented) + + print() + if failures == 0: + print("PASS: contract inventory is in sync with compose and .env.example.") + return 0 + print(f"FAIL: {failures} parity check(s) failed.") + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/validate-contract.py b/scripts/validate-contract.py new file mode 100755 index 0000000..819a077 --- /dev/null +++ b/scripts/validate-contract.py @@ -0,0 +1,317 @@ +#!/usr/bin/env python3 +"""validate-contract.py: assert resolved Compose output matches z3-contract.yaml. + +Parses the platform contract's port matrix, volume names, RPC auth mode, and +service DNS directly from z3-contract.yaml, runs `docker compose --env-file +.env. --profile monitoring config` for each declared +network, and asserts that the resolved values match. + +Volumes and ports can be plain values or {name, profile} / {container, host, +profile} dicts. Profile-gated entries are checked the same way: the renderer +runs with all profiles enabled, so every contracted identifier is expected +in the output. + +Requires PyYAML. Pre-installed on GitHub Actions ubuntu-latest. For local +runs: pip install pyyaml. + +Exit codes: + 0 every assertion passes + 1 one or more assertions failed + 2 prerequisite missing (docker, PyYAML, env files, contract) +""" + +from __future__ import annotations + +import pathlib +import re +import subprocess +import sys + +try: + import yaml +except ImportError: + print("FAIL: PyYAML is required. Install with: pip install pyyaml", file=sys.stderr) + sys.exit(2) + +ROOT = pathlib.Path(__file__).resolve().parent.parent +CONTRACT_FILE = ROOT / "z3-contract.yaml" +PROMETHEUS_CONFIG = ROOT / "observability" / "prometheus" / "prometheus.yaml" + + +def volume_name(entry: str | dict) -> str: + """volumes.zaino can be a string or {name, profile?} dict.""" + return entry if isinstance(entry, str) else entry["name"] + + +class Asserter: + def __init__(self) -> None: + self.failures = 0 + + def present(self, label: str, pattern: str, haystack: str) -> None: + if re.search(pattern, haystack, re.MULTILINE): + print(f" OK {label}") + else: + print(f" FAIL {label} (expected pattern: {pattern})") + self.failures += 1 + + def absent(self, label: str, pattern: str, haystack: str) -> None: + if re.search(pattern, haystack, re.MULTILINE): + print(f" FAIL {label} (unexpected pattern: {pattern})") + self.failures += 1 + else: + print(f" OK {label}") + + def fail(self, label: str) -> None: + print(f" FAIL {label}") + self.failures += 1 + + +def load_contract() -> dict: + if not CONTRACT_FILE.exists(): + print(f"FAIL: missing {CONTRACT_FILE.relative_to(ROOT)}", file=sys.stderr) + sys.exit(2) + return yaml.safe_load(CONTRACT_FILE.read_text()) + + +def compose_files_for_network(network_name: str) -> list[str]: + files = ["docker-compose.yml"] + overlay = ROOT / f"docker-compose.{network_name}.yml" + if overlay.exists(): + files.append(overlay.name) + return files + + +def render_compose(network_name: str, env_file: pathlib.Path) -> str: + # Pass -f explicitly so local override files do not affect contract checks. + compose_args: list[str] = [] + for compose_file in compose_files_for_network(network_name): + compose_args.extend(["-f", compose_file]) + + try: + proc = subprocess.run( + [ + "docker", "compose", + *compose_args, + "--env-file", str(env_file), + "--profile", "monitoring", + "config", + ], + cwd=ROOT, capture_output=True, text=True, check=True, + ) + return proc.stdout + except FileNotFoundError: + print("FAIL: docker not found in PATH. Install Docker Engine with the", + file=sys.stderr) + print(" Compose v2 plugin: https://docs.docker.com/compose/install/", + file=sys.stderr) + sys.exit(2) + except subprocess.CalledProcessError as e: + stderr = e.stderr or "" + if "is not a docker command" in stderr or "'compose'" in stderr: + print("FAIL: the 'docker compose' v2 plugin is not available.", + file=sys.stderr) + print(" This stack requires Compose v2.24.4+; the legacy v1", + file=sys.stderr) + print(" 'docker-compose' binary is not supported. Install v2:", + file=sys.stderr) + print(" https://docs.docker.com/compose/install/", file=sys.stderr) + sys.exit(2) + print(f"FAIL: docker compose config failed for {env_file.name}:") + print(stderr) + sys.exit(1) + + +def validate_no_undeclared_host_ports(asserter: Asserter, ports: dict, config: str) -> None: + """Assert compose publishes no host port the contract omits (compose -> contract).""" + contract_hosts = {p["host"] for p in ports.values() if p.get("host") is not None} + rendered_hosts = {int(p) for p in re.findall(r'published: "(\d+)"', config)} + undeclared = sorted(rendered_hosts - contract_hosts) + for extra in undeclared: + asserter.fail(f"published host port {extra} is not declared in the contract") + if not undeclared: + print(" OK no undeclared published host ports") + + +def validate_network(asserter: Asserter, network_name: str, spec: dict) -> None: + env_file = ROOT / f".env.{network_name}" + if not env_file.exists(): + print(f" FAIL: missing {env_file.relative_to(ROOT)}") + asserter.failures += 1 + return + + print(f"== Network: {network_name} ({env_file.name}) ==") + config = render_compose(network_name, env_file) + + project = spec["compose_project"] + asserter.present(f"project name = {project}", + rf"^name: {re.escape(project)}$", config) + asserter.present(f"external network = {spec['external_network']}", + rf"^ name: {re.escape(spec['external_network'])}$", config) + + for _, vol_entry in spec["volumes"].items(): + vol = volume_name(vol_entry) + asserter.present(f"volume = {vol}", + rf"name: {re.escape(vol)}$", config) + + ports = spec["ports"] + + # Zebra container ports appear in the service environment block. + asserter.present( + f"Zebra RPC listen = {ports['zebra_rpc']['container']}", + rf"ZEBRA_RPC__LISTEN_ADDR: 0\.0\.0\.0:{ports['zebra_rpc']['container']}$", + config, + ) + asserter.present( + f"Zebra metrics listen = {ports['zebra_metrics']['container']}", + rf"ZEBRA_METRICS__ENDPOINT_ADDR: 0\.0\.0\.0:{ports['zebra_metrics']['container']}$", + config, + ) + + auth_mode = spec.get("rpc_auth", {}).get("mode") + if auth_mode == "cookie": + asserter.present( + "Zebra cookie auth enabled", + r"ZEBRA_RPC__ENABLE_COOKIE_AUTH: ['\"]?true['\"]?$", + config, + ) + asserter.present( + "Zaino cookie path configured", + r"ZAINO_VALIDATOR_SETTINGS__VALIDATOR_COOKIE_PATH: /var/run/auth/\.cookie$", + config, + ) + elif auth_mode == "username_password": + asserter.present( + "Zebra cookie auth disabled", + r"ZEBRA_RPC__ENABLE_COOKIE_AUTH: ['\"]?false['\"]?$", + config, + ) + asserter.absent( + "Zaino cookie path omitted", + r"ZAINO_VALIDATOR_SETTINGS__VALIDATOR_COOKIE_PATH:", + config, + ) + + # Host ports: every contracted port that has a host mapping must be + # published. Entries without a host key (zebra_metrics on every network, + # zebra_p2p on regtest) are container-only, so the assertion is skipped. + for key, port_spec in ports.items(): + host = port_spec.get("host") + if host is None: + continue + asserter.present(f"{key} host = {host}", + rf'published: "{host}"', config) + + # Bidirectional guard: compose must not publish a host port the contract + # omits (the loop above already covers contract -> compose). + validate_no_undeclared_host_ports(asserter, ports, config) + + # Zaino must point at Zebra's per-network RPC container port. + asserter.present( + f"Zaino -> Zebra RPC = {ports['zebra_rpc']['container']}", + rf"ZAINO_VALIDATOR_SETTINGS__VALIDATOR_JSONRPC_LISTEN_ADDRESS: " + rf"zebra:{ports['zebra_rpc']['container']}", + config, + ) + + +def validate_healthchecks(asserter: Asserter, network_name: str, + config: str, healthchecks: dict) -> None: + """Spot-check rendered healthcheck.test shape against contract.healthchecks. + + Catches drift like flipping Zebra's healthcheck from /ready to /healthy, + removing Zaino's TCP probe, or accidentally adding a healthcheck to a + service the contract declares as transport: none. + + Regtest's Zebra healthcheck diverges (peerless network uses an RPC probe + instead of /ready); the contract documents this so it is skipped here. + """ + for service, spec in healthchecks.items(): + transport = spec.get("transport") + port = spec.get("port") + + if service == "zebra" and network_name == "regtest": + asserter.present( + "Regtest Zebra healthcheck = getblockchaininfo RPC", + r"getblockchaininfo", config, + ) + continue + + if transport == "http" and spec.get("readiness"): + pattern = rf"http://127\.0\.0\.1:{port}{re.escape(spec['readiness'])}" + asserter.present( + f"{service} healthcheck = http {port}{spec['readiness']}", + pattern, config, + ) + elif transport == "tcp": + pattern = rf"/dev/tcp/127\.0\.0\.1/{port}" + asserter.present( + f"{service} healthcheck = tcp {port}", + pattern, config, + ) + # transport: none is not asserted positively (Zallet has no probe binary). + + +def validate_unique_host_ports(asserter: Asserter, contract: dict) -> None: + seen: dict[int, str] = {} + for network_name, spec in contract["networks"].items(): + for key, port_spec in spec["ports"].items(): + host = port_spec.get("host") + if host is None: + continue + label = f"{network_name}.{key}" + existing = seen.get(host) + if existing is not None: + print(f" FAIL host port {host} used by both {existing} and {label}") + asserter.failures += 1 + else: + seen[host] = label + + +def validate_prometheus_scrape_target(asserter: Asserter, contract: dict) -> None: + """Assert the Prometheus zebra job targets the contract's zebra_metrics container port.""" + if not PROMETHEUS_CONFIG.exists(): + print(" SKIP observability/prometheus/prometheus.yaml not found") + return + + expected_port = contract["networks"]["mainnet"]["ports"]["zebra_metrics"]["container"] + config = PROMETHEUS_CONFIG.read_text() + pattern = rf'job_name:\s*["\']?zebra["\']?[\s\S]*?targets:\s*\[\s*["\']zebra:{expected_port}["\']' + if re.search(pattern, config): + print(f" OK Prometheus zebra scrape target = zebra:{expected_port}") + else: + print(f" FAIL Prometheus zebra scrape target does not match contract") + print(f" (expected zebra:{expected_port} in {PROMETHEUS_CONFIG.relative_to(ROOT)})") + asserter.failures += 1 + + +def main() -> int: + contract = load_contract() + asserter = Asserter() + + healthchecks = contract.get("healthchecks", {}) + for net_name, net_spec in contract["networks"].items(): + validate_network(asserter, net_name, net_spec) + if healthchecks: + env_file = ROOT / f".env.{net_name}" + if env_file.exists(): + rendered = render_compose(net_name, env_file) + print(f"-- Healthchecks ({net_name}) --") + validate_healthchecks(asserter, net_name, rendered, healthchecks) + + print("== Cross-network host ports ==") + validate_unique_host_ports(asserter, contract) + + print() + print("== Prometheus scrape target ==") + validate_prometheus_scrape_target(asserter, contract) + + print() + if asserter.failures == 0: + print("PASS: all contract assertions hold.") + return 0 + print(f"FAIL: {asserter.failures} assertion(s) did not hold.") + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/z3-contract.schema.json b/z3-contract.schema.json new file mode 100644 index 0000000..10bc3e6 --- /dev/null +++ b/z3-contract.schema.json @@ -0,0 +1,204 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/ZcashFoundation/z3/blob/main/z3-contract.schema.json", + "title": "Z3 Platform Contract", + "description": "Schema for z3-contract.yaml. Consumers validate the parsed YAML against this schema before relying on its identifiers.", + "type": "object", + "required": [ + "contract_version", + "versioning", + "service_dns", + "cookie_path", + "networks", + "healthchecks", + "env_vars", + "profiles" + ], + "additionalProperties": false, + "properties": { + "contract_version": { + "type": "string", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "description": "SemVer string. Bumped per the policy under versioning:." + }, + "versioning": { + "type": "object", + "required": ["policy", "major", "minor", "patch"], + "additionalProperties": false, + "properties": { + "policy": {"const": "semver"}, + "major": {"type": "string"}, + "minor": {"type": "string"}, + "patch": {"type": "string"} + } + }, + "service_dns": { + "type": "object", + "description": "Map of service name to its in-network DNS identifier. Entries with a profile: key only resolve when that Compose profile is active.", + "additionalProperties": {"$ref": "#/$defs/dnsEntry"} + }, + "cookie_path": { + "type": "string", + "pattern": "^/", + "description": "Absolute path of the RPC cookie file inside every container that needs auth." + }, + "networks": { + "type": "object", + "required": ["mainnet", "testnet", "regtest"], + "additionalProperties": false, + "properties": { + "mainnet": {"$ref": "#/$defs/network"}, + "testnet": {"$ref": "#/$defs/network"}, + "regtest": {"$ref": "#/$defs/network"} + } + }, + "healthchecks": { + "type": "object", + "additionalProperties": {"$ref": "#/$defs/healthcheck"} + }, + "env_vars": { + "type": "object", + "description": "Public env-var schema, grouped by function.", + "additionalProperties": { + "type": "array", + "items": {"$ref": "#/$defs/envVar"} + } + }, + "ecosystem_vars": { + "type": "array", + "description": "Ecosystem-standard env vars documented for completeness but not part of the z3 contract.", + "items": {"$ref": "#/$defs/envVar"} + }, + "profiles": { + "type": "object", + "description": "Compose profiles available across all networks.", + "additionalProperties": {"type": "string"} + }, + "image_platforms": { + "type": "object", + "description": "Platform constraints per image. Each value is the list of platforms the upstream image publishes; entries with only linux/amd64 run under emulation on arm64 hosts.", + "additionalProperties": { + "type": "array", + "items": {"type": "string", "pattern": "^linux/(amd64|arm64)$"}, + "minItems": 1 + } + } + }, + "$defs": { + "profileName": { + "type": "string", + "enum": ["monitoring"] + }, + "networkName": { + "type": "string", + "enum": ["mainnet", "testnet", "regtest"] + }, + "namespaceName": { + "type": "string", + "enum": ["z3", "zebra", "zaino", "grafana", "compose", "docker", "rust"] + }, + "dnsEntry": { + "type": "object", + "required": ["dns"], + "additionalProperties": false, + "properties": { + "dns": {"type": "string"}, + "profile": {"$ref": "#/$defs/profileName"} + } + }, + "volumeEntry": { + "oneOf": [ + {"type": "string"}, + { + "type": "object", + "required": ["name"], + "additionalProperties": false, + "properties": { + "name": {"type": "string"}, + "profile": {"$ref": "#/$defs/profileName"} + } + } + ] + }, + "portEntry": { + "type": "object", + "required": ["container"], + "additionalProperties": false, + "properties": { + "container": {"type": "integer", "minimum": 1, "maximum": 65535}, + "host": {"type": "integer", "minimum": 1, "maximum": 65535}, + "profile": {"$ref": "#/$defs/profileName"} + } + }, + "network": { + "type": "object", + "required": ["z3_network", "compose_project", "external_network", "rpc_auth", "volumes", "ports"], + "additionalProperties": false, + "properties": { + "z3_network": {"type": "string", "enum": ["Mainnet", "Testnet", "Regtest"]}, + "compose_project": {"type": "string", "pattern": "^z3-(mainnet|testnet|regtest)$"}, + "external_network": {"type": "string", "pattern": "^z3-(mainnet|testnet|regtest)$"}, + "rpc_auth": {"$ref": "#/$defs/rpcAuth"}, + "volumes": { + "type": "object", + "additionalProperties": {"$ref": "#/$defs/volumeEntry"} + }, + "ports": { + "type": "object", + "additionalProperties": {"$ref": "#/$defs/portEntry"} + } + } + }, + "rpcAuth": { + "type": "object", + "required": ["mode"], + "additionalProperties": false, + "properties": { + "mode": {"type": "string", "enum": ["cookie", "username_password"]}, + "credential_env_vars": { + "type": "object", + "required": ["user", "password"], + "additionalProperties": false, + "properties": { + "user": {"type": "string", "pattern": "^[A-Z][A-Z0-9_]*$"}, + "password": {"type": "string", "pattern": "^[A-Z][A-Z0-9_]*$"} + } + }, + "note": {"type": "string"} + }, + "allOf": [ + { + "if": { "properties": { "mode": { "const": "username_password" } }, "required": ["mode"] }, + "then": { "required": ["credential_env_vars"] } + } + ] + }, + "healthcheck": { + "type": "object", + "required": ["transport"], + "additionalProperties": false, + "properties": { + "transport": {"type": "string", "enum": ["http", "tcp", "cli", "none"]}, + "port": {"type": "integer", "minimum": 1, "maximum": 65535}, + "liveness": {"type": ["string", "null"]}, + "readiness": {"type": ["string", "null"]}, + "profile": {"$ref": "#/$defs/profileName"}, + "note": {"type": "string"} + } + }, + "envVar": { + "type": "object", + "required": ["name", "namespace"], + "additionalProperties": false, + "properties": { + "name": {"type": "string", "pattern": "^[A-Z][A-Z0-9_]*$"}, + "namespace": {"$ref": "#/$defs/namespaceName"}, + "description": {"type": "string"}, + "scope": {"type": "string", "enum": ["host", "container"]}, + "values": {"type": "array", "items": {"type": "string"}}, + "profile": {"$ref": "#/$defs/profileName"}, + "network": {"$ref": "#/$defs/networkName"} + } + } + } +} diff --git a/z3-contract.yaml b/z3-contract.yaml new file mode 100644 index 0000000..ece36c2 --- /dev/null +++ b/z3-contract.yaml @@ -0,0 +1,254 @@ +# Z3 platform contract. +# +# This file is the canonical machine-readable inventory of stable identifiers: +# networks, volumes, ports, env vars, service DNS, healthchecks, and image +# platforms. Consumers can validate it against z3-contract.schema.json. The +# repository validators check it against the resolved Compose output: +# scripts/validate-contract.py per-network port matrix + volumes +# scripts/validate-contract-parity.py env-var inventory across compose / .env.example +# +# Companion artifacts: +# z3-contract.schema.json JSON Schema for this file +# docs/contract.md concept guide (stability promise, file ownership, .env loading) +# docs/integrations/ integration examples + +contract_version: "1.0.0" + +# Version-bump policy. Independent from the z3 git tag. +versioning: + policy: semver + major: "Breaking: renamed/removed identifier, port change, removed service" + minor: "Additive: new optional field, new env var, new optional service" + patch: "Documentation-only or clarification" + +# In-network DNS names. Same on every Z3 network; the network itself +# is the discriminator at the Compose-project level. Entries with a +# `profile:` key only resolve when that Compose profile is active. +service_dns: + zebra: {dns: zebra} + zaino: {dns: zaino} + zallet: {dns: zallet} + +# Cookie file path inside containers on networks whose rpc_auth.mode is cookie. +cookie_path: "/var/run/auth/.cookie" + +# Per-network identifiers and port matrix. +# +# Container ports follow Zebra's per-network defaults from upstream +# (zebra-chain/src/parameters/network.rs and zebra-rpc/src/config/rpc.rs). +# Host ports are explicit so multiple networks coexist on one host. +# Port entries with a `profile:` key only appear when that profile is active. +networks: + mainnet: + z3_network: "Mainnet" + compose_project: "z3-mainnet" + external_network: "z3-mainnet" + rpc_auth: + mode: cookie + volumes: + chain: "z3-mainnet-chain" + cookie: "z3-mainnet-cookie" + zaino: "z3-mainnet-zaino" + zallet: "z3-mainnet-zallet" + ports: + zebra_rpc: {container: 8232, host: 8232} + zebra_p2p: {container: 8233, host: 8233} # published for inbound peers + zebra_health: {container: 8080, host: 8080} + zebra_metrics: {container: 9999} # in-network Prometheus scrape endpoint + zaino_grpc: {container: 8137, host: 8137} + zaino_json_rpc: {container: 8237, host: 8237} + zallet_rpc: {container: 28232, host: 28232} + prometheus: {container: 9090, host: 9094, profile: monitoring} + grafana: {container: 3000, host: 3000, profile: monitoring} + jaeger_ui: {container: 16686, host: 16686, profile: monitoring} + jaeger_otlp_grpc: {container: 4317, host: 4317, profile: monitoring} + jaeger_otlp_http: {container: 4318, host: 4318, profile: monitoring} + jaeger_spanmetrics: {container: 8889, host: 8889, profile: monitoring} + alertmanager: {container: 9093, host: 9093, profile: monitoring} + + testnet: + z3_network: "Testnet" + compose_project: "z3-testnet" + external_network: "z3-testnet" + rpc_auth: + mode: cookie + volumes: + chain: "z3-testnet-chain" + cookie: "z3-testnet-cookie" + zaino: "z3-testnet-zaino" + zallet: "z3-testnet-zallet" + ports: + # Health, Zaino, Zallet host ports use a +10000 offset relative to + # mainnet so the two networks can run concurrently on the same host. + zebra_rpc: {container: 18232, host: 18232} + zebra_p2p: {container: 18233, host: 18233} + zebra_health: {container: 8080, host: 18080} + zebra_metrics: {container: 9999} + zaino_grpc: {container: 8137, host: 18137} + zaino_json_rpc: {container: 8237, host: 18237} + zallet_rpc: {container: 28232, host: 40232} + prometheus: {container: 9090, host: 19094, profile: monitoring} + grafana: {container: 3000, host: 13000, profile: monitoring} + jaeger_ui: {container: 16686, host: 26686, profile: monitoring} + jaeger_otlp_grpc: {container: 4317, host: 15317, profile: monitoring} + jaeger_otlp_http: {container: 4318, host: 15318, profile: monitoring} + jaeger_spanmetrics: {container: 8889, host: 18889, profile: monitoring} + alertmanager: {container: 9093, host: 19093, profile: monitoring} + + regtest: + z3_network: "Regtest" + compose_project: "z3-regtest" + external_network: "z3-regtest" + rpc_auth: + mode: username_password + credential_env_vars: + user: Z3_REGTEST_RPC_ROUTER_USER + password: Z3_REGTEST_RPC_ROUTER_PASSWORD + note: "Cookie auth is disabled; rpc-router and service configs use username/password. The cookie volume may exist, but it does not carry a readable RPC cookie." + volumes: + chain: "z3-regtest-chain" + cookie: "z3-regtest-cookie" + zaino: "z3-regtest-zaino" + zallet: "z3-regtest-zallet" + ports: + # Regtest inherits Zebra's testnet container-port defaults. + # Host ports are explicit to avoid cross-service collisions. + zebra_rpc: {container: 18232, host: 29232} + zebra_p2p: {container: 18233} + zebra_health: {container: 8080, host: 28080} + zebra_metrics: {container: 9999} + zaino_grpc: {container: 8137, host: 28137} + zaino_json_rpc: {container: 8237, host: 28237} + zallet_rpc: {container: 28232, host: 50232} + rpc_router: {container: 8181, host: 8181} + prometheus: {container: 9090, host: 29094, profile: monitoring} + grafana: {container: 3000, host: 23000, profile: monitoring} + jaeger_ui: {container: 16686, host: 36686, profile: monitoring} + jaeger_otlp_grpc: {container: 4317, host: 25317, profile: monitoring} + jaeger_otlp_http: {container: 4318, host: 25318, profile: monitoring} + jaeger_spanmetrics: {container: 8889, host: 28889, profile: monitoring} + alertmanager: {container: 9093, host: 29093, profile: monitoring} + +# Per-service health endpoints. Consumers attached to the external network +# can wait via depends_on with condition: service_healthy. +healthchecks: + zebra: + transport: http + port: 8080 + liveness: "/healthy" # peer connectivity (>= ZEBRA_HEALTH__MIN_CONNECTED_PEERS) + readiness: "/ready" # synced within ZEBRA_HEALTH__READY_MAX_BLOCKS_BEHIND of tip + zaino: + transport: tcp + port: 8137 + note: "TCP probe on the gRPC port. Confirms a listener exists; does not validate the gRPC handler. Consumers should not rely on this for production routing decisions." + zallet: + transport: none + note: "No healthcheck: distroless image has no shell or probe binary. A consumer using depends_on: condition: service_healthy against zallet will hang. Wait for Zebra's /ready instead and assume Zallet is up shortly after." + +# Public env-var schema. +# +# Grouped by function (what the var configures). Each entry carries a +# `namespace:` tag identifying the convention the name follows: +# +# z3 Stack-level setting +# zebra Zebra config-rs (double-underscore is config-rs nesting separator) +# zaino Zaino config-rs +# grafana Grafana GF_* convention +# compose Docker Compose's own knobs +# docker Docker / Compose ecosystem convention +# +# Native-namespace entries (zebra, zaino, grafana) are passed through +# unchanged. +env_vars: + network: + - {name: COMPOSE_PROJECT_NAME, namespace: compose, description: "Compose project name; selects the per-network instance."} + - {name: Z3_NETWORK, namespace: z3, values: [Mainnet, Testnet, Regtest], description: "Network selector. PascalCase per Zebra's serde."} + - {name: Z3_CONFIG_DIR, namespace: z3, description: "Per-network config dir (zallet.toml, zaino.toml, zallet_identity.txt)."} + + images: + # Image-pin defaults live in docker-compose.yml as ${VAR:-tag} fallbacks. + # The env vars below override those defaults at runtime; see image_platforms + # at the end of this file for per-image platform constraints. + - {name: Z3_ZEBRA_IMAGE, namespace: z3} + - {name: Z3_ZAINO_IMAGE, namespace: z3} + - {name: Z3_ZALLET_IMAGE, namespace: z3} + - {name: Z3_ZEBRA_BUILD_FEATURES, namespace: z3, description: "Build arg when building Zebra from source."} + - {name: DOCKER_PLATFORM, namespace: docker, description: "Optional platform pin. Zebra 5.x is multi-arch (no pin needed); Zaino and Zallet publish amd64 only and are pinned in compose. Override to build Zaino/Zallet native arm64 from the submodules."} + + ports: + # Host ports (z3 controls the per-network port matrix; testnet/regtest offset). + - {name: Z3_ZEBRA_HOST_RPC_PORT, namespace: z3, scope: host} + - {name: Z3_ZEBRA_HOST_P2P_PORT, namespace: z3, scope: host, description: "Published p2p port for inbound peers (mainnet/testnet; regtest is peerless and omits it)."} + - {name: Z3_ZEBRA_HOST_HEALTH_PORT, namespace: z3, scope: host} + - {name: Z3_ZAINO_HOST_GRPC_PORT, namespace: z3, scope: host} + - {name: Z3_ZAINO_HOST_JSON_RPC_PORT, namespace: z3, scope: host} + - {name: Z3_ZALLET_HOST_RPC_PORT, namespace: z3, scope: host} + - {name: Z3_REGTEST_RPC_ROUTER_HOST_PORT, namespace: z3, scope: host, network: regtest, description: "rpc-router host port (regtest only)."} + # Container ports: only Zebra's vary per network (upstream Zebra defaults + # 8232 mainnet / 18232 testnet). Zaino and Zallet container ports + # are hardcoded in the compose because they do not differ per network. + - {name: Z3_ZEBRA_RPC_PORT, namespace: z3, scope: container} + - {name: Z3_ZEBRA_P2P_PORT, namespace: z3, scope: container, description: "Zebra p2p port; network default 8233 mainnet / 18233 testnet."} + - {name: ZEBRA_NETWORK__EXTERNAL_ADDR, namespace: zebra, description: "Address advertised to peers for inbound p2p when behind NAT or a firewall."} + + paths: + - {name: Z3_CHAIN_DATA_PATH, namespace: z3, description: "Override chain volume with a bind-mount path."} + - {name: Z3_ZAINO_DATA_PATH, namespace: z3} + - {name: Z3_ZALLET_DATA_PATH, namespace: z3} + - {name: Z3_COOKIE_PATH, namespace: z3, description: "Override cookie volume (advanced; breaks the shared-volume pattern)."} + + auth: + - {name: ZEBRA_RPC__ENABLE_COOKIE_AUTH, namespace: zebra, description: "Toggles Zebra RPC cookie auth. Disabled in regtest."} + # rpc-router runs only under the regtest overlay; the REGTEST infix marks scope. + - {name: Z3_REGTEST_RPC_ROUTER_USER, namespace: z3, network: regtest, description: "rpc-router credential (regtest only)."} + - {name: Z3_REGTEST_RPC_ROUTER_PASSWORD, namespace: z3, network: regtest, description: "rpc-router credential (regtest only)."} + + health: + - {name: ZEBRA_HEALTH__MIN_CONNECTED_PEERS, namespace: zebra} + - {name: ZEBRA_HEALTH__READY_MAX_BLOCKS_BEHIND, namespace: zebra} + - {name: ZEBRA_HEALTH__ENFORCE_ON_TEST_NETWORKS, namespace: zebra} + + logging: + # Per-service RUST_LOG split. Each compose entry chains + # Z3__RUST_LOG -> RUST_LOG -> default, so a global RUST_LOG=debug + # flips the whole stack and per-service overrides still win. + - {name: Z3_ZEBRA_RUST_LOG, namespace: z3} + - {name: Z3_ZAINO_RUST_LOG, namespace: z3} + - {name: Z3_ZALLET_RUST_LOG, namespace: z3} + - {name: ZEBRA_TRACING__FILTER, namespace: zebra, description: "Advanced: Zebra's own tracing-subscriber filter (independent of RUST_LOG)."} + + mining: + - {name: ZEBRA_MINING__MINER_ADDRESS, namespace: zebra, description: "Required for regtest mining."} + + observability: + - {name: Z3_PROMETHEUS_PORT, namespace: z3, profile: monitoring} + - {name: Z3_GRAFANA_PORT, namespace: z3, profile: monitoring} + - {name: GF_SECURITY_ADMIN_PASSWORD, namespace: grafana, profile: monitoring, description: "Grafana admin password. Native GF_* name (no z3 wrapper)."} + - {name: Z3_JAEGER_UI_PORT, namespace: z3, profile: monitoring} + - {name: Z3_ALERTMANAGER_PORT, namespace: z3, profile: monitoring} + - {name: Z3_JAEGER_OTLP_GRPC_PORT, namespace: z3, profile: monitoring} + - {name: Z3_JAEGER_OTLP_HTTP_PORT, namespace: z3, profile: monitoring} + - {name: Z3_JAEGER_SPANMETRICS_PORT, namespace: z3, profile: monitoring, description: "Jaeger spanmetrics Prometheus scrape port (Jaeger SPM)."} + - {name: ZEBRA_METRICS__ENDPOINT_ADDR, namespace: zebra, description: "Zebra Prometheus scrape endpoint. Default: 0.0.0.0:9999."} + - {name: ZEBRA_TRACING__OPENTELEMETRY_ENDPOINT, namespace: zebra} + - {name: ZEBRA_TRACING__OPENTELEMETRY_SERVICE_NAME, namespace: zebra} + - {name: ZEBRA_TRACING__OPENTELEMETRY_SAMPLE_PERCENT, namespace: zebra} + +# Ecosystem-standard names z3 inherits but does not own. Documented because +# operators may set them; parity checkers skip these. +ecosystem_vars: + - {name: COMPOSE_FILE, namespace: compose, description: "Compose's overlay loader. .env.regtest sets this to load the regtest overlay."} + - {name: RUST_LOG, namespace: rust, description: "Global Rust log level."} + - {name: RUST_BACKTRACE, namespace: rust, description: "Global Rust backtrace toggle. Zaino reads it directly (default: full); set in shell or .env to enable for Zebra/Zallet too."} + +# Compose profiles available across all networks. +profiles: + monitoring: "Prometheus + Grafana + Jaeger + AlertManager" + +# Platform constraints per image. Entries listed as amd64-only run under +# emulation on arm64 hosts even when DOCKER_PLATFORM=linux/arm64 is set. +# For native arm64 of Zaino and Zallet, build locally from the submodules. +image_platforms: + zebra: [linux/amd64, linux/arm64] + zaino: [linux/amd64] + zallet: [linux/amd64] diff --git a/zcashd b/zcashd deleted file mode 160000 index cfcfcd9..0000000 --- a/zcashd +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cfcfcd93b06d2ee897f1d24eb62692c9e9e0e66d From dfb9d0eae6d3f67a3c184e0d8fcb1166e7740724 Mon Sep 17 00:00:00 2001 From: Gustavo Valverde Date: Wed, 10 Jun 2026 13:46:45 -0700 Subject: [PATCH 56/57] refactor: drop git submodules and the orphaned image-mirror CI (#45) A plain `git clone` now boots the full stack: the default compose pulls pinned images and needs no source checkout. The zebra, zaino, and zallet submodules are removed, and the from-source build path moves to an opt-in overlay (docker-compose.build.yml) whose contexts point at a gitignored vendor/ dir. scripts/vendor.sh clones each upstream repo there at the tag matching its image pin. Also deletes the build-z3-images workflow and its sub-build helper. It only triggered when its own file changed and pushed registry images that nothing in compose consumed. Docs drop every git submodule instruction in favor of vendor.sh plus the build overlay, and the gRPC proto examples point at vendor/zaino/zaino-proto. Adds a TCP-tuning production note referencing #36. --- .env.example | 2 +- .github/workflows/build-z3-images.yaml | 79 -------- .github/workflows/sub-build-docker-image.yaml | 182 ------------------ .gitignore | 3 + .gitmodules | 9 - README.md | 19 +- docker-compose.build.yml | 40 ++++ docker-compose.yml | 24 +-- docs/contract.md | 2 +- docs/docker-architecture.md | 1 + docs/faq.md | 2 +- docs/integrations/lightwalletd-client.md | 13 +- docs/regtest.md | 10 +- scripts/vendor.sh | 50 +++++ z3-contract.yaml | 4 +- zaino | 1 - zallet | 1 - zebra | 1 - 18 files changed, 124 insertions(+), 319 deletions(-) delete mode 100644 .github/workflows/build-z3-images.yaml delete mode 100644 .github/workflows/sub-build-docker-image.yaml delete mode 100644 .gitmodules create mode 100644 docker-compose.build.yml create mode 100755 scripts/vendor.sh delete mode 160000 zaino delete mode 160000 zallet delete mode 160000 zebra diff --git a/.env.example b/.env.example index da4673f..f2aed88 100644 --- a/.env.example +++ b/.env.example @@ -28,7 +28,7 @@ # Platform pin. Zebra is multi-arch and selects the host arch automatically. # Zaino and Zallet are pinned to amd64 in compose because their upstream # images publish amd64 only; setting DOCKER_PLATFORM=linux/arm64 with the -# ./zaino and ./zallet submodules initialized builds them native arm64. +# opt-in build overlay (docker-compose.build.yml) builds them native arm64. # DOCKER_PLATFORM=linux/arm64 # Host ports (mainnet defaults; .env.testnet and .env.regtest override them) diff --git a/.github/workflows/build-z3-images.yaml b/.github/workflows/build-z3-images.yaml deleted file mode 100644 index 0f5ff4e..0000000 --- a/.github/workflows/build-z3-images.yaml +++ /dev/null @@ -1,79 +0,0 @@ -name: Build Z3 images - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -on: - workflow_dispatch: - inputs: - no_cache: - description: "Disable Docker cache for this build" - required: false - type: boolean - default: false - push: - branches: - - main - paths: - - '.github/workflows/build-z3-images.yaml' - - pull_request: - paths: - - '.github/workflows/build-z3-images.yaml' - -jobs: - build-zebra: - name: Build zebra Docker - permissions: - contents: 'read' - id-token: 'write' - packages: 'write' - uses: ./.github/workflows/sub-build-docker-image.yaml - with: - repository: ZcashFoundation/zebra - ref: 'main' - dockerfile_path: ./docker/Dockerfile - dockerfile_target: runtime - image_name: zebra - no_cache: ${{ inputs.no_cache || false }} - rust_backtrace: full - rust_lib_backtrace: full - rust_log: info - - build-zaino: - name: Build zaino Docker - permissions: - contents: 'read' - id-token: 'write' - packages: 'write' - uses: ./.github/workflows/sub-build-docker-image.yaml - with: - repository: zingolabs/zaino - ref: 'dev' - dockerfile_path: ./Dockerfile - dockerfile_target: runtime - image_name: zaino - no_cache: ${{ inputs.no_cache || false }} - rust_backtrace: full - rust_lib_backtrace: full - rust_log: info - read_rust_version: true - - build-zallet: - name: Build zallet Docker - permissions: - contents: 'read' - id-token: 'write' - packages: 'write' - uses: ./.github/workflows/sub-build-docker-image.yaml - with: - repository: zcash/wallet - ref: 'main' - dockerfile_path: ./Dockerfile - dockerfile_target: runtime - image_name: zallet - no_cache: ${{ inputs.no_cache || false }} - rust_backtrace: full - rust_lib_backtrace: full - rust_log: info diff --git a/.github/workflows/sub-build-docker-image.yaml b/.github/workflows/sub-build-docker-image.yaml deleted file mode 100644 index 4ddcbad..0000000 --- a/.github/workflows/sub-build-docker-image.yaml +++ /dev/null @@ -1,182 +0,0 @@ -# This workflow automates the building and pushing of Docker images based on user-defined inputs. It includes: -# - Accepting various inputs like image name, Dockerfile path, target, and additional Rust-related parameters. -# - Authenticates with GitHub Container Registry. -# - Uses Docker Buildx for improved build performance and caching. -# - Builds the Docker image and pushes it to GitHub Container Registry. -# TODO: Manages caching strategies to optimize build times across different branches. -name: Build docker image - -on: - workflow_call: - inputs: - repository: - required: true - type: string - ref: - required: true - type: string - image_name: - required: true - type: string - dockerfile_path: - required: true - type: string - dockerfile_target: - required: true - type: string - short_sha: - required: false - type: string - rust_backtrace: - required: false - type: string - rust_lib_backtrace: - required: false - type: string - # defaults to: vars.RUST_LOG - rust_log: - required: false - type: string - features: - required: false - type: string - no_cache: - description: "Disable the Docker cache for this build" - required: false - type: boolean - default: false - read_rust_version: - description: "Read RUST_VERSION from rust-toolchain.toml in the checked-out upstream repo and pass it as a build-arg" - required: false - type: boolean - default: false - - outputs: - image_digest: - description: "The image digest to be used on a caller workflow" - value: ${{ jobs.build.outputs.image_digest }} - -env: - FEATURES: ${{ inputs.features }} - RUST_LOG: ${{ inputs.rust_log || vars.RUST_LOG }} - CARGO_INCREMENTAL: ${{ vars.CARGO_INCREMENTAL }} - -jobs: - build: - name: Build images - timeout-minutes: 30 - runs-on: ubuntu-latest - environment: ${{ github.event_name == 'release' && 'prod' || 'dev' }} - outputs: - image_digest: ${{ steps.docker_build.outputs.digest }} - image_name: ${{ fromJSON(steps.docker_build.outputs.metadata)['image.name'] }} - permissions: - contents: "read" - id-token: "write" - packages: "write" - env: - DOCKER_BUILD_SUMMARY: ${{ vars.DOCKER_BUILD_SUMMARY }} - steps: - - - name: Checkout ${{ inputs.repository }} - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - repository: ${{ inputs.repository }} - ref: ${{ inputs.ref }} - persist-credentials: false - - - name: Read RUST_VERSION from rust-toolchain.toml - if: ${{ inputs.read_rust_version }} - id: rust_version - env: - UPSTREAM_REPO: ${{ inputs.repository }} - UPSTREAM_REF: ${{ inputs.ref }} - run: | - set -euo pipefail - if [[ ! -f rust-toolchain.toml ]]; then - echo "rust-toolchain.toml not found in ${UPSTREAM_REPO}@${UPSTREAM_REF}" >&2 - exit 1 - fi - channel=$(grep -E '^[[:space:]]*channel[[:space:]]*=' rust-toolchain.toml \ - | head -n1 \ - | sed -E 's/^[[:space:]]*channel[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/') - if [[ -z "$channel" ]]; then - echo "no [toolchain].channel found in rust-toolchain.toml" >&2 - exit 1 - fi - if [[ ! "$channel" =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then - echo "channel '$channel' is not a concrete numeric version (x.y or x.y.z)" >&2 - exit 1 - fi - echo "rust_version=$channel" >> "$GITHUB_OUTPUT" - - - name: Checkout Z3 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - path: z3 - persist-credentials: false - - - name: Inject slug/short variables - uses: rlespinasse/github-slug-action@9e7def61550737ba68c62d34a32dd31792e3f429 # v5.5.0 - with: - short-length: 7 - - # Automatic tag management and OCI Image Format Specification for labels - - name: Docker meta - id: meta - uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 - with: - # list of Docker images to use as base name for tags - # We only publish images to DockerHub if a release is not a pre-release - # Ref: https://github.com/orgs/community/discussions/26281#discussioncomment-3251177 - images: | - ghcr.io/${{ env.GITHUB_REPOSITORY_OWNER_PART }}/${{ inputs.image_name }} - # generate Docker tags based on the following events/attributes - tags: | - # - `pr-xxx`: Tags images with the pull request number. - # - `branch-name`: Tags images with the branch name (e.g., `main`, `dev`). - # - `edge`: Tags the latest build on the default branch (e.g., `main`) - # - `schedule`: Tags images built during scheduled workflows (e.g., nightly or periodic builds) - type=ref,event=pr - type=ref,event=branch - type=edge,enable={{is_default_branch}} - type=schedule - # - `sha-xxxxxx`: Uses the commit SHA (shortened) to tag images for precise identification. - type=sha,event=pr - type=sha,event=branch - - - name: Login to GitHub Container Registry - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - # Setup Docker Buildx to use Docker Build Cloud - - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - - # Build and push image to GitHub Container Registry - - name: Build & push - id: docker_build - uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 - with: - target: ${{ inputs.dockerfile_target }} - context: . - file: ${{ inputs.dockerfile_path }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - build-args: | - SHORT_SHA=${{ env.GITHUB_SHA_SHORT }} - ${{ inputs.read_rust_version && format('RUST_VERSION={0}', steps.rust_version.outputs.rust_version) || '' }} - push: true - # It's recommended to build images with max-level provenance attestations - # https://docs.docker.com/build/ci/github-actions/attestations/ - provenance: mode=max - sbom: true - # Don't read from the cache if the caller disabled it. - # https://docs.docker.com/engine/reference/commandline/buildx_build/#options - no-cache: ${{ inputs.no_cache }} - cache-from: type=gha,scope=z3-${{ inputs.image_name }} - cache-to: type=gha,mode=max,scope=z3-${{ inputs.image_name }} diff --git a/.gitignore b/.gitignore index 951fd23..e83caca 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,6 @@ docker-compose.regtest.override.yml # Python bytecode caches (from scripts/validate-contract*.py) __pycache__/ *.pyc + +# Opt-in source-build checkouts (scripts/vendor.sh) +vendor/ diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 3d7a3e1..0000000 --- a/.gitmodules +++ /dev/null @@ -1,9 +0,0 @@ -[submodule "zebra"] - path = zebra - url = https://github.com/ZcashFoundation/zebra -[submodule "zaino"] - path = zaino - url = https://github.com/zingolabs/zaino -[submodule "zallet"] - path = zallet - url = https://github.com/zcash/wallet diff --git a/README.md b/README.md index 033a535..c26bd4e 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ docker compose --env-file .env.mainnet up -d zebra docker compose --env-file .env.mainnet up -d ``` -Images are pulled automatically; no build step or submodule init is needed. +Images are pulled automatically; no build step or source checkout is needed. > [!IMPORTANT] > Running step 3 before Zebra reaches `/ready` makes Zaino and Zallet restart-loop until the sync catches up. The poller in step 2 exits only when Zebra is synced. @@ -92,6 +92,7 @@ Z3 ships production-shaped defaults, but a few choices are yours to make before - **Plan the wallet backup.** Keep the `z3--zallet` volume and `config//zallet_identity.txt` together; nothing else needs backup. - **Set a log rotation policy.** Z3 does not pin a logging driver, so containers use your Docker daemon default. Add size limits in `/etc/docker/daemon.json` (see the [FAQ](docs/faq.md)); otherwise logs grow unbounded on a 24/7 node. - **Decide p2p exposure.** Mainnet and testnet publish Zebra's p2p port for inbound peers. Behind NAT or a firewall, set `ZEBRA_NETWORK__EXTERNAL_ADDR` to the address peers should dial. Regtest is peerless and publishes no p2p. +- **Tune the host network (Linux).** On a busy mainnet node, default kernel TCP buffer and connection-backlog limits can cap Zebra's peer throughput. See [Zebra's TCP tuning notes](https://github.com/ZcashFoundation/zebra/pull/10513) for the `sysctl` values worth raising. - **Bound resources on a shared host.** No CPU or memory limits are set by default: right for a dedicated node, easy to get wrong on a shared box. Add `deploy.resources.limits` in an override file if you need them. Z3 ships safe defaults: pinned image versions (no surprise upgrades), non-root containers with Linux capabilities dropped, health checks that hold the wallet back until the node is synced, and automatic restart. Upgrades stay deliberate: bump the version pin in a reviewed change, or set `Z3__IMAGE`. @@ -229,15 +230,17 @@ Based on [Zebra's official requirements](https://zebra.zfnd.org/user/requirement
Setup details -### Submodules +### Building from source -Pre-built images are used by default. To build from source: +Pre-built images are pulled by default. To build Zebra, Zaino, and Zallet from upstream source instead, fetch the sources and add the opt-in build overlay: ```bash -git submodule update --init --recursive -docker compose --env-file .env.mainnet build +scripts/vendor.sh +docker compose -f docker-compose.yml -f docker-compose.build.yml build ``` +`scripts/vendor.sh` clones each upstream repo into a gitignored `vendor/` directory at the tag matching its image pin. + ### First-run setup (`setup-network.sh`) `./scripts/setup-network.sh ` is idempotent and does everything needed before the first `docker compose up`: @@ -262,11 +265,11 @@ diff config/mainnet/zallet.toml config/mainnet/zallet.toml.example Zebra is multi-arch; Docker picks the host's native arch automatically, no override needed. Zaino and Zallet are pinned to `linux/amd64` because their upstream images publish amd64 only. On Apple Silicon those two run under emulation by default; the workload is light enough that this rarely matters. -To run Zaino and Zallet natively on arm64, build them locally from the submodules: +To run Zaino and Zallet natively on arm64, build them from source: ```bash -git submodule update --init --recursive -DOCKER_PLATFORM=linux/arm64 docker compose --env-file .env.mainnet build zaino zallet +scripts/vendor.sh zaino zallet +DOCKER_PLATFORM=linux/arm64 docker compose -f docker-compose.yml -f docker-compose.build.yml build zaino zallet docker compose --env-file .env.mainnet up -d ``` diff --git a/docker-compose.build.yml b/docker-compose.build.yml new file mode 100644 index 0000000..6c759a2 --- /dev/null +++ b/docker-compose.build.yml @@ -0,0 +1,40 @@ +# Opt-in source-build overlay. +# +# The default stack pulls pinned images and needs no source checkout. This +# overlay re-adds build: contexts so contributors can build Zebra, Zaino, and +# Zallet from upstream source (for native arm64, or to test a local patch). +# +# Usage: +# scripts/vendor.sh # clone upstream into vendor/ at the pinned tags +# docker compose -f docker-compose.yml -f docker-compose.build.yml build +# +# or append it to COMPOSE_FILE for the session: +# COMPOSE_FILE=docker-compose.yml:docker-compose.build.yml docker compose build zaino +# +# The build args here mirror the published pins (Zebra's FEATURES, Zaino's +# NO_TLS guard) so a local build matches the image it replaces. Bump the tags +# in scripts/vendor.sh together with the image pins in docker-compose.yml. + +services: + zebra: + build: + context: ./vendor/zebra + dockerfile: docker/Dockerfile + target: runtime + args: + FEATURES: ${Z3_ZEBRA_BUILD_FEATURES:-default-release-binaries} + + zaino: + build: + context: ./vendor/zaino + dockerfile: Dockerfile + args: + NO_TLS: "true" + + zallet: + build: + context: ./vendor/zallet + dockerfile: Dockerfile + target: runtime + tags: + - z3_zallet:local diff --git a/docker-compose.yml b/docker-compose.yml index d00ec2d..1d842ea 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,12 +34,6 @@ services: # Zebra is multi-arch; Docker selects the host's native variant. # DOCKER_PLATFORM forces a specific arch for cross-architecture testing. platform: ${DOCKER_PLATFORM:-} - build: - context: ./zebra - dockerfile: docker/Dockerfile - target: runtime - args: - FEATURES: ${Z3_ZEBRA_BUILD_FEATURES:-default-release-binaries} restart: unless-stopped stop_grace_period: 30s <<: *common @@ -120,17 +114,11 @@ services: zaino: # The -no-tls tag compiles out Zaino's "TLS required on a non-private bind" # guard, so intra-container gRPC on 0.0.0.0:8137 runs as plaintext h2c. - # NO_TLS=true below makes a local arm64 build match that pin. Terminate - # edge TLS at a reverse proxy if Zaino is exposed beyond the host. + # Terminate edge TLS at a reverse proxy if Zaino is exposed beyond the host. image: ${Z3_ZAINO_IMAGE:-zingodevops/zainod:0.4.0-rc.2-no-tls} - # Zaino publishes linux/amd64 only; arm64 hosts run under emulation - # unless the operator builds from the ./zaino submodule. + # Zaino publishes linux/amd64 only; arm64 hosts run under emulation unless + # the operator builds from source (see docker-compose.build.yml). platform: ${DOCKER_PLATFORM:-linux/amd64} - build: - context: ./zaino - dockerfile: Dockerfile - args: - NO_TLS: "true" restart: unless-stopped stop_grace_period: 15s <<: *common @@ -167,12 +155,6 @@ services: zallet: image: ${Z3_ZALLET_IMAGE:-electriccoinco/zallet:v0.1.0-alpha.3} platform: ${DOCKER_PLATFORM:-linux/amd64} - build: - context: ./zallet - dockerfile: Dockerfile - target: runtime - tags: - - z3_zallet:local restart: unless-stopped stop_grace_period: 15s <<: *common diff --git a/docs/contract.md b/docs/contract.md index ccd4850..913003a 100644 --- a/docs/contract.md +++ b/docs/contract.md @@ -104,7 +104,7 @@ These files are tracked defaults. Operators should not edit them; doing so creat | Path | Purpose | |------|---------| -| `docker-compose.yml`, `docker-compose.regtest.yml` | Stack topology. Override via `docker-compose.override.yml` (see below). | +| `docker-compose.yml`, `docker-compose.regtest.yml`, `docker-compose.build.yml` | Stack topology and the opt-in source-build overlay. Override via `docker-compose.override.yml` (see below). | | `z3-contract.yaml`, `z3-contract.schema.json`, `docs/contract.md` | The contract, its schema, and this guide. | | `.env.example` | Reference for every public env var. | | `.env.mainnet`, `.env.testnet`, `.env.regtest` | Per-network defaults. Override via `.env`. | diff --git a/docs/docker-architecture.md b/docs/docker-architecture.md index 703f0c8..eb0c982 100644 --- a/docs/docker-architecture.md +++ b/docs/docker-architecture.md @@ -9,6 +9,7 @@ For the public contract (network names, volume names, port matrix), see [`contra ```text docker-compose.yml Base stack (Zebra + Zaino + Zallet + optional profiles) docker-compose.regtest.yml Regtest overlay (structural differences only) +docker-compose.build.yml Opt-in source-build overlay (scripts/vendor.sh) .env.mainnet Mainnet selection + canonical ports .env.testnet Testnet selection + offset ports .env.regtest Regtest selection + overlay loader diff --git a/docs/faq.md b/docs/faq.md index 88123c6..5986cbc 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -143,7 +143,7 @@ Not from the pinned tags. The default Zaino and Zallet images publish `linux/amd Two ways forward if you need native arm64: -1. **Build locally.** `docker-compose.yml` declares `build:` contexts pointing at the `./zaino` and `./zallet` submodules, so `DOCKER_PLATFORM=linux/arm64 docker compose build zaino zallet` produces local arm64 images. +1. **Build locally.** Fetch the upstream sources with `scripts/vendor.sh zaino zallet`, then build with the opt-in overlay: `DOCKER_PLATFORM=linux/arm64 docker compose -f docker-compose.yml -f docker-compose.build.yml build zaino zallet`. 2. **Wait for the upstream tag to gain a multi-arch publish, then bump the pin.** Existing tags never gain new platform variants after the fact; only new tags do. Leaving these two services under emulation is fine in practice; the workload is light compared to Zebra's verifier, which runs natively. diff --git a/docs/integrations/lightwalletd-client.md b/docs/integrations/lightwalletd-client.md index c21884d..d673e7c 100644 --- a/docs/integrations/lightwalletd-client.md +++ b/docs/integrations/lightwalletd-client.md @@ -6,7 +6,7 @@ Your service is a wallet, block explorer, or scanner that speaks the lightwallet - A running Z3 stack: `docker compose --env-file .env. up -d` in the Z3 repo. - A gRPC client for your language (Tonic for Rust, grpcio for Python, grpc-java, etc.) or the `grpcurl` CLI for ad-hoc calls. -- The lightwalletd / Zaino `.proto` files. Either vendor them or pull from the Zaino submodule (`zaino/zaino-proto/proto/service.proto`). +- The lightwalletd / Zaino `.proto` files. Fetch them into the Z3 repo with `scripts/vendor.sh zaino` (`vendor/zaino/zaino-proto/proto/service.proto`). ## Endpoint per network @@ -26,23 +26,22 @@ Regtest's contract declares `rpc_auth.mode: username_password` for Zebra and Zal ## Quick test with `grpcurl` -The .proto files are already vendored in this repo as a submodule under -`zaino/zaino-proto/proto/`. Initialize it once and point grpcurl at that path. +Fetch the upstream Zaino source (proto files included) and point grpcurl at that path: ```bash -# One-time: fetch the vendored Zaino submodule -git submodule update --init zaino +# One-time: fetch the Zaino proto files into vendor/ +scripts/vendor.sh zaino # Probe the endpoint (mainnet example). -plaintext skips TLS. grpcurl -plaintext \ - -import-path zaino/zaino-proto/proto \ + -import-path vendor/zaino/zaino-proto/proto \ -proto service.proto \ 127.0.0.1:8137 \ cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLightdInfo # Get the latest block grpcurl -plaintext \ - -import-path zaino/zaino-proto/proto \ + -import-path vendor/zaino/zaino-proto/proto \ -proto service.proto \ -d '{}' \ 127.0.0.1:8137 \ diff --git a/docs/regtest.md b/docs/regtest.md index b41d1eb..607a20a 100644 --- a/docs/regtest.md +++ b/docs/regtest.md @@ -8,7 +8,7 @@ Uses the base `docker-compose.yml` with `docker-compose.regtest.yml` overlay and - Docker with [Docker Compose](https://docs.docker.com/compose/install/) (v2.24.4+) - [rage](https://github.com/str4d/rage/releases) for generating Zallet encryption keys -- For gRPC testing: [grpcurl](https://github.com/fullstorydev/grpcurl) and the zaino submodule initialized (`git submodule update --init zaino`) +- For gRPC testing: [grpcurl](https://github.com/fullstorydev/grpcurl) and the Zaino proto files (`scripts/vendor.sh zaino`) ## First-time setup @@ -78,17 +78,17 @@ curl -s -X POST -H "Content-Type: application/json" \ Zaino exposes the [lightwalletd-compatible gRPC protocol](https://github.com/zcash/lightwalletd/blob/master/walletrpc/service.proto) as plaintext h2c (no TLS). In regtest the host port is `28137` (`Z3_ZAINO_HOST_GRPC_PORT`); the `-plaintext` flag tells grpcurl to skip TLS. -Initialize the zaino submodule if you haven't already (needed for the proto files): +Fetch the Zaino proto files if you haven't already: ```bash -git submodule update --init zaino +scripts/vendor.sh zaino ``` Test with `GetLightdInfo` (from the repo root): ```bash grpcurl -plaintext \ - -import-path zaino/zaino-proto/proto \ + -import-path vendor/zaino/zaino-proto/proto \ -proto service.proto \ 127.0.0.1:28137 \ cash.z.wallet.sdk.rpc.CompactTxStreamer/GetLightdInfo @@ -98,7 +98,7 @@ Get the latest block height: ```bash grpcurl -plaintext \ - -import-path zaino/zaino-proto/proto \ + -import-path vendor/zaino/zaino-proto/proto \ -proto service.proto \ -d '{}' \ 127.0.0.1:28137 \ diff --git a/scripts/vendor.sh b/scripts/vendor.sh new file mode 100755 index 0000000..477ca28 --- /dev/null +++ b/scripts/vendor.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# Clone the upstream node sources into vendor/ at the tags matching the pinned +# images in docker-compose.yml, for the opt-in source-build overlay +# (docker-compose.build.yml). vendor/ is gitignored. +# +# Usage: +# scripts/vendor.sh # fetch all three (zebra, zaino, zallet) +# scripts/vendor.sh zaino # fetch one +# Re-run after bumping an image pin to update the matching checkout. +# +# Tags track the image pins in docker-compose.yml; bump them together. +set -euo pipefail +cd "$(dirname "$0")/.." + +declare -a ALL=(zebra zaino zallet) +declare -A URL=( + [zebra]="https://github.com/ZcashFoundation/zebra" + [zaino]="https://github.com/zingolabs/zaino" + [zallet]="https://github.com/zcash/wallet" +) +declare -A REF=( + [zebra]="v5.0.0" + [zaino]="0.4.0-rc.2" + [zallet]="v0.1.0-alpha.3" +) + +vendor_one() { + local name="$1" dir="vendor/$1" url="${URL[$1]}" ref="${REF[$1]}" + if [ -d "$dir/.git" ]; then + echo "==> $name: fetching $ref" + git -C "$dir" fetch --depth 1 origin "$ref" + git -C "$dir" checkout --quiet --recurse-submodules FETCH_HEAD + git -C "$dir" submodule update --init --recursive --depth 1 + else + echo "==> $name: cloning $ref" + git clone --depth 1 --branch "$ref" --recurse-submodules --shallow-submodules \ + "$url" "$dir" + fi +} + +targets=("$@") +[ ${#targets[@]} -eq 0 ] && targets=("${ALL[@]}") +for t in "${targets[@]}"; do + [ -n "${URL[$t]:-}" ] || { echo "unknown component: $t (choose: ${ALL[*]})" >&2; exit 1; } + vendor_one "$t" +done + +echo +echo "Done. Build with:" +echo " docker compose -f docker-compose.yml -f docker-compose.build.yml build ${targets[*]}" diff --git a/z3-contract.yaml b/z3-contract.yaml index ece36c2..f55349e 100644 --- a/z3-contract.yaml +++ b/z3-contract.yaml @@ -173,7 +173,7 @@ env_vars: - {name: Z3_ZAINO_IMAGE, namespace: z3} - {name: Z3_ZALLET_IMAGE, namespace: z3} - {name: Z3_ZEBRA_BUILD_FEATURES, namespace: z3, description: "Build arg when building Zebra from source."} - - {name: DOCKER_PLATFORM, namespace: docker, description: "Optional platform pin. Zebra 5.x is multi-arch (no pin needed); Zaino and Zallet publish amd64 only and are pinned in compose. Override to build Zaino/Zallet native arm64 from the submodules."} + - {name: DOCKER_PLATFORM, namespace: docker, description: "Optional platform pin. Zebra 5.x is multi-arch (no pin needed); Zaino and Zallet publish amd64 only and are pinned in compose. Override to build Zaino/Zallet native arm64 from source (docker-compose.build.yml)."} ports: # Host ports (z3 controls the per-network port matrix; testnet/regtest offset). @@ -247,7 +247,7 @@ profiles: # Platform constraints per image. Entries listed as amd64-only run under # emulation on arm64 hosts even when DOCKER_PLATFORM=linux/arm64 is set. -# For native arm64 of Zaino and Zallet, build locally from the submodules. +# For native arm64 of Zaino and Zallet, build locally from source (docker-compose.build.yml). image_platforms: zebra: [linux/amd64, linux/arm64] zaino: [linux/amd64] diff --git a/zaino b/zaino deleted file mode 160000 index ba87fb0..0000000 --- a/zaino +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ba87fb02250593ab27afbeb35866c4baa64e7d80 diff --git a/zallet b/zallet deleted file mode 160000 index b7b080f..0000000 --- a/zallet +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b7b080fe95f9dc72a5faac07773dae03fa1c1f15 diff --git a/zebra b/zebra deleted file mode 160000 index 1e6519e..0000000 --- a/zebra +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1e6519ea91e2d3035c20aadd4d9a40dcac2eed3a From 1e7d989eac4fc2b774ddb706cf9486c3b46f7611 Mon Sep 17 00:00:00 2001 From: DC Date: Wed, 17 Jun 2026 11:57:10 -0600 Subject: [PATCH 57/57] fix: make Zallet config readable by the container uid (1000) Zallet runs on a distroless image pinned to user "1000:1000" (no shell, entrypoint, or capabilities to chown/override at runtime). It reads two bind-mounted host files, zallet.toml and zallet_identity.txt, whose host ownership/mode pass straight through. Operators whose host uid \!= 1000 hit: zallet fatal error: config error: path error: /etc/zallet/zallet.toml: Permission denied (os error 13) because both files end up mode 0600 owned by the host uid: setup-network.sh chmod 600s the age key, and regtest-init.sh rewrites zallet.toml via mktemp+mv, leaking mktemp's 0600 onto the config. Decouple host uid from container uid the same way the cookie-permissions sidecar already does for Zebra's RPC cookie, rather than parametrizing the pinned uid (which would force operators to also solve data-volume ownership that distroless Zallet cannot self-heal): - setup-network.sh: chmod 644 the copied non-secret TOMLs; grant the age key read to uid 1000 only via `setfacl -m u:1000:r` (stays 0600 otherwise), with a warning + chmod 644 fallback when setfacl/acl is unavailable. - regtest-init.sh: chmod 644 zallet.toml after the pwhash rewrite. - docs + README: document that uid \!= 1000 operators need no uid coordination for Zallet config; note the acl/setfacl soft prerequisite. - ci.yaml: reproduce the scripts' permission end-state in regtest staging and assert a --user 1000:1000 container can read both config files. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci.yaml | 15 +++++++++++++++ README.md | 2 ++ docs/docker-architecture.md | 6 ++++++ scripts/regtest-init.sh | 3 +++ scripts/setup-network.sh | 13 +++++++++++++ 5 files changed, 39 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5c85560..68e234c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -85,6 +85,12 @@ jobs: SALT=$(openssl rand -hex 16) HASH=$(printf 'zebra' | openssl dgst -sha256 -mac HMAC -macopt "key:$SALT" | awk '{print $NF}') sed -i "s|__GENERATED_BY_INIT_SH__|${SALT}\$${HASH}|" config/regtest/zallet.toml + # Mirror the permission end-state of setup-network.sh / regtest-init.sh + # so the readability assertion below exercises the real contract: + # non-secret TOMLs world-readable, the age key 0600 + ACL for uid 1000. + chmod 644 config/regtest/zallet.toml config/regtest/zaino.toml config/regtest/zebra.toml + chmod 600 config/regtest/zallet_identity.txt + setfacl -m u:1000:r config/regtest/zallet_identity.txt - name: Start Zebra (regtest) run: | @@ -136,6 +142,15 @@ jobs: - name: Verify Zallet accepts compose command run: docker compose --env-file .env.regtest run --rm --no-deps zallet --help + - name: Assert Zallet config is readable by the container uid (1000) + # Zallet runs as uid 1000 (distroless image, no runtime chown). Guards + # the config-readability contract: zallet.toml is world-readable and the + # age key is reachable by uid 1000 via ACL. Catches a regression of the + # mktemp-0600 mode leaking back onto the rewritten config. + run: | + docker run --rm --user 1000:1000 -v "$PWD/config/regtest":/c:ro alpine \ + sh -c 'cat /c/zallet.toml >/dev/null && cat /c/zallet_identity.txt >/dev/null && echo "uid 1000 read OK"' + - name: Verify the cookie volume is attachable # Regtest disables cookie auth (ZEBRA_RPC__ENABLE_COOKIE_AUTH=false) so # Zebra does not write /auth/.cookie. This check only verifies that the diff --git a/README.md b/README.md index c26bd4e..e10ae0a 100644 --- a/README.md +++ b/README.md @@ -358,6 +358,8 @@ Fix permissions before starting: Zebra, Zaino, and Zallet each run as a specific non-root user. Directories must have correct ownership (set by the script) and `700` permissions. Never use `755` or `777`. +Operators whose host uid is not `1000` do **not** need to coordinate uids for Zallet's bind-mounted config. Zallet runs as uid 1000 (distroless image), and `setup-network.sh` makes `zallet.toml` readable by that uid (`0644`) and grants the age key `zallet_identity.txt` read access for uid 1000 via a POSIX ACL (`setfacl -m u:1000:r`) while keeping it `0600` for everyone else. Install the `acl` package on Linux for this; if `setfacl` is unavailable the script warns and you can fall back to `chmod 644` on the identity file. +
diff --git a/docs/docker-architecture.md b/docs/docker-architecture.md index eb0c982..d99073f 100644 --- a/docs/docker-architecture.md +++ b/docs/docker-architecture.md @@ -268,6 +268,12 @@ The base compose includes a small `cookie-permissions` sidecar (`alpine:3` with Zaino and Zallet depend on the sidecar's healthcheck, so targeted starts such as `docker compose up -d zaino` also start the sidecar and wait until the cookie is readable. On mainnet and testnet the healthcheck waits for `/var/run/auth/.cookie`; on regtest it exits successfully because cookie auth is disabled and username/password auth is used instead. +## Zallet config readability and operator uid + +Zallet uses a distroless image with no shell, entrypoint script, or Linux capabilities, so the base compose pins it with `user: "1000:1000"` and it runs as that uid from PID 1 — unlike Zebra (root entrypoint that drops to uid 10001) and Zaino (root entrypoint with `cap_add: [DAC_OVERRIDE, ...]`, which bypasses file-permission checks before dropping privileges). Zallet therefore can only read its two bind-mounted host config files — `config//zallet.toml` → `/etc/zallet/zallet.toml` and `config//zallet_identity.txt` → `/etc/zallet/identity.txt` — if they are readable by uid 1000. + +The same "no UID coordination required" property that the cookie sidecar provides applies here: the setup scripts make this config readable by uid 1000 regardless of the operator's host uid. `scripts/setup-network.sh` writes `zallet.toml` (and the other non-secret TOMLs) mode `0644`; `scripts/regtest-init.sh` restores `0644` after its `mktemp`+`mv` pwhash rewrite (which would otherwise leave the file `0600`). The age key `zallet_identity.txt` is a long-lived wallet secret, so instead of widening it to all local users it stays mode `0600` with a POSIX ACL granting read to uid 1000 only (`setfacl -m u:1000:r`). `setfacl` (the `acl` package on Linux, with an ACL-capable filesystem such as ext4/xfs) is a soft prerequisite; if it is unavailable the script warns and the operator can fall back to `chmod 644` on the identity file. + ## Regtest overlay constraints ### Zaino authentication diff --git a/scripts/regtest-init.sh b/scripts/regtest-init.sh index 15d8d58..0df7f8d 100755 --- a/scripts/regtest-init.sh +++ b/scripts/regtest-init.sh @@ -107,6 +107,9 @@ update_zallet_rpc_pwhash() { sed -E "s|^pwhash = \".*\"$|pwhash = \"${pwhash}\"|" "$config_path" > "$tmp" mv "$tmp" "$config_path" + # mktemp creates the temp file mode 0600 and mv carries that mode onto the + # config; restore world-read so the zallet container (uid 1000) can read it. + chmod 644 "$config_path" log "==> Generated zallet RPC pwhash in ${CONFIG_DIR}/zallet.toml" } diff --git a/scripts/setup-network.sh b/scripts/setup-network.sh index 9e8b9d5..81aa45f 100755 --- a/scripts/setup-network.sh +++ b/scripts/setup-network.sh @@ -48,6 +48,10 @@ copy_template() { fi cp "$example" "$active" + # Make the config world-readable so the pinned zallet container uid (1000) + # can read it regardless of the operator's host uid/umask. These TOMLs are + # not secret; the age key is handled separately in ensure_identity. + chmod 644 "$active" log "==> $NETWORK/$file: created from .example template." } @@ -67,6 +71,15 @@ ensure_identity() { rage-keygen -o "$identity" chmod 600 "$identity" + # Zallet runs as uid 1000 (distroless image, no runtime chown). Grant that + # uid read access to the age key without widening it to other host users. + if command -v setfacl >/dev/null 2>&1; then + setfacl -m u:1000:r "$identity" \ + || log "WARN: setfacl failed on $identity; zallet (uid 1000) may not be able to read it." + else + log "WARN: setfacl not found. Grant uid 1000 read on $identity before starting zallet" + log " (install the 'acl' package, or 'chmod 644 $identity' to allow all local users)." + fi log "==> $NETWORK/zallet_identity.txt: generated." }