فهرست منبع

explorer: Delete old code.

This will be rewritten with better architecture.
x 6 ماه پیش
والد
کامیت
d0b55e3c06
85فایلهای تغییر یافته به همراه0 افزوده شده و 10128 حذف شده
  1. 0 46
      bin/explorer/Makefile
  2. 0 334
      bin/explorer/README.md
  3. BIN
      bin/explorer/docs/images/explorer-block-details.png
  4. BIN
      bin/explorer/docs/images/explorer-gas-metrics-total-consumption.png
  5. BIN
      bin/explorer/docs/images/explorer-gas-metrics-tx-consumption.png
  6. BIN
      bin/explorer/docs/images/explorer-home.png
  7. BIN
      bin/explorer/docs/images/explorer-native-contract-list.png
  8. BIN
      bin/explorer/docs/images/explorer-native-contract-source-code.png
  9. BIN
      bin/explorer/docs/images/explorer-source-code-list.png
  10. BIN
      bin/explorer/docs/images/explorer-tx-details.png
  11. 0 1
      bin/explorer/explorerd/.gitignore
  12. 0 46
      bin/explorer/explorerd/Cargo.toml
  13. 0 216
      bin/explorer/explorerd/Makefile
  14. 0 220
      bin/explorer/explorerd/README.md
  15. 0 65
      bin/explorer/explorerd/explorerd_config.toml
  16. 0 316
      bin/explorer/explorerd/src/config.rs
  17. 0 83
      bin/explorer/explorerd/src/error.rs
  18. 0 302
      bin/explorer/explorerd/src/main.rs
  19. 0 310
      bin/explorer/explorerd/src/rpc/blocks.rs
  20. 0 198
      bin/explorer/explorerd/src/rpc/contracts.rs
  21. 0 395
      bin/explorer/explorerd/src/rpc/mod.rs
  22. 0 132
      bin/explorer/explorerd/src/rpc/statistics.rs
  23. 0 127
      bin/explorer/explorerd/src/rpc/transactions.rs
  24. 0 398
      bin/explorer/explorerd/src/service/blocks.rs
  25. 0 595
      bin/explorer/explorerd/src/service/contracts.rs
  26. 0 107
      bin/explorer/explorerd/src/service/mod.rs
  27. 0 140
      bin/explorer/explorerd/src/service/statistics.rs
  28. 0 418
      bin/explorer/explorerd/src/service/sync.rs
  29. 0 497
      bin/explorer/explorerd/src/service/transactions.rs
  30. 0 456
      bin/explorer/explorerd/src/store/contract_metadata.rs
  31. 0 1378
      bin/explorer/explorerd/src/store/metrics.rs
  32. 0 82
      bin/explorer/explorerd/src/store/mod.rs
  33. 0 205
      bin/explorer/explorerd/src/test_utils/mod.rs
  34. 0 2
      bin/explorer/site/.gitignore
  35. 0 76
      bin/explorer/site/Makefile
  36. 0 186
      bin/explorer/site/README.md
  37. 0 136
      bin/explorer/site/app.py
  38. 0 36
      bin/explorer/site/blueprints/__init__.py
  39. 0 48
      bin/explorer/site/blueprints/block.py
  40. 0 94
      bin/explorer/site/blueprints/contract.py
  41. 0 94
      bin/explorer/site/blueprints/explore.py
  42. 0 45
      bin/explorer/site/blueprints/transaction.py
  43. 0 31
      bin/explorer/site/gunicorn_config.py
  44. 0 183
      bin/explorer/site/log.py
  45. 0 4
      bin/explorer/site/requirements.txt
  46. 0 145
      bin/explorer/site/rpc.py
  47. 0 34
      bin/explorer/site/site_config.toml
  48. 0 5
      bin/explorer/site/static/bootstrap.min.css
  49. BIN
      bin/explorer/site/static/font-files/Chomsky.otf
  50. BIN
      bin/explorer/site/static/font-files/Chomsky.woff
  51. BIN
      bin/explorer/site/static/font-files/Chomsky.woff2
  52. BIN
      bin/explorer/site/static/font-files/IBMPlexMonoExtraLight.otf
  53. BIN
      bin/explorer/site/static/font-files/IBMPlexMonoExtraLight.woff
  54. BIN
      bin/explorer/site/static/font-files/IBMPlexMonoExtraLight.woff2
  55. BIN
      bin/explorer/site/static/font-files/IBMPlexSansArabic-Regular.woff
  56. BIN
      bin/explorer/site/static/font-files/IBMPlexSansArabic-Regular.woff2
  57. BIN
      bin/explorer/site/static/font-files/Inter-Bold.woff
  58. BIN
      bin/explorer/site/static/font-files/Inter-BoldItalic.woff
  59. BIN
      bin/explorer/site/static/font-files/Inter-Italic.woff
  60. BIN
      bin/explorer/site/static/font-files/Inter-Regular.woff
  61. BIN
      bin/explorer/site/static/font-files/Inter-SemiBold.woff
  62. BIN
      bin/explorer/site/static/font-files/Inter-SemiBoldItalic.woff
  63. BIN
      bin/explorer/site/static/font-files/InterDisplay-Black.woff
  64. BIN
      bin/explorer/site/static/font-files/InterDisplay-BlackItalic.woff
  65. BIN
      bin/explorer/site/static/font-files/InterDisplay-ExtraLight.woff
  66. BIN
      bin/explorer/site/static/font-files/InterDisplay-ExtraLight.woff2
  67. BIN
      bin/explorer/site/static/font-files/ReemKufi-Bold.woff
  68. BIN
      bin/explorer/site/static/font-files/ReemKufi-Bold.woff2
  69. BIN
      bin/explorer/site/static/font-files/weissrundgotisch.regular.woff
  70. BIN
      bin/explorer/site/static/font-files/weissrundgotisch.regular.woff2
  71. 0 208
      bin/explorer/site/static/img/darkfi.svg
  72. BIN
      bin/explorer/site/static/img/darkfi05.jpg
  73. BIN
      bin/explorer/site/static/img/darkfi25.jpg
  74. BIN
      bin/explorer/site/static/img/icon.png
  75. BIN
      bin/explorer/site/static/img/sunset2.png
  76. 0 1079
      bin/explorer/site/static/style.css
  77. 0 35
      bin/explorer/site/templates/404.html
  78. 0 35
      bin/explorer/site/templates/500.html
  79. 0 194
      bin/explorer/site/templates/base.html
  80. 0 60
      bin/explorer/site/templates/block.html
  81. 0 46
      bin/explorer/site/templates/contract_source.html
  82. 0 55
      bin/explorer/site/templates/contract_source_list.html
  83. 0 147
      bin/explorer/site/templates/index.html
  84. 0 55
      bin/explorer/site/templates/transaction.html
  85. 0 28
      bin/explorer/site/wsgi.py

+ 0 - 46
bin/explorer/Makefile

@@ -1,46 +0,0 @@
-.POSIX:
-
-# Suppress all directory-related messages for cleaner output
-MAKEFLAGS += --no-print-directory
-
-help:
-	@echo "Explorer Makefile Commands:"
-	@echo ""
-	@echo "Network management:"
-	@echo "  make start-localnet - Start explorer environment on Darkfi localnet"
-	@echo "  make start-testnet  - Start explorer environment on Darkfi testnet"
-	@echo "  make start-mainnet  - Start explorer environment on Darkfi mainnet"
-	@echo "  make stop           - Stop running explorer environment"
-
-# Start a full explorer environment on Darkfi networks (localnet, testnet, mainnet)
-start-%:
-	@echo "Starting $* environment..."
-	@sh -c ' \
-		trap "echo Interrupt detected; make stop; exit 0" INT; \
-		$(MAKE) -C ./explorerd start-$*; \
-		$(MAKE) -C ./explorerd await-startup-$*; \
-		$(MAKE) -C ./site start-$*; \
-		echo "Started $* environment. Press Ctrl-C to stop."; \
-		while true; do sleep 1; done; \
-	'
-
-# Start no-sync explorer (localnet/testnet/mainnet) without connecting to or syncing with the Darkfi blockchain
-start-no-sync-%:
-	@echo "Starting $* environment (no sync)..."
-	@sh -c ' \
-		trap "echo Interrupt detected; make stop; exit 0" INT; \
-		$(MAKE) -C ./explorerd start-no-sync-$*; \
-		$(MAKE) -C ./explorerd await-startup-no-sync-$*; \
-		$(MAKE) -C ./site start-$*; \
-		echo "Started $* environment in no sync mode. Press Ctrl-C to stop."; \
-		while true; do sleep 1; done; \
-	'
-
-# Stop the running network
-stop:
-	@$(MAKE) -C ./explorerd stop || true; \
-	$(MAKE) -C ./site stop || true; \
-
-.PHONY: start-% check-darkfid check-explorerd check-site stop
-
-

+ 0 - 334
bin/explorer/README.md

@@ -1,334 +0,0 @@
-# DarkFi Blockchain Explorer
-
-The DarkFi Blockchain Explorer facilitates the exploration of chain data and interaction with the DarkFi blockchain. It provides detailed insights into block and transaction activity, gas usage metrics, and native contract source code. 
-
-## Table of Contents
-1. [Key Features](#key-features)
-2. [Network Status](#network-status)
-3. [Quick Start Guide](#quick-start-guide)
-
-4. [Explorer Components](#explorer-components)
-    - [Explorer Nodes](#explorer-nodes)
-    - [Explorer Site](#explorer-site)
-
-5. [Configuration](#configuration)
-    - [Configuration Files](#configuration-files)
-    - [Supported Networks](#supported-networks)
-
-6. [Feature Details](#feature-details)
-    - [Home Page Overview](#home-page-overview)
-    - [Block and Transaction Exploration](#block-and-transaction-exploration)
-    - [Gas Analytics](#gas-analytics)
-        - [Network-Wide Gas Consumption Metrics](#network-wide-gas-consumption-metrics)
-        - [Transaction-Level Gas Consumption Metrics](#transaction-level-gas-consumption-metrics)
-    - [Native Contract Source Code Navigation](#native-contract-source-code-navigation)
-
-## Key Features
-- **DarkFi Blockchain Synchronization**: Ensures real-time synchronization of blockchain data, providing an accurate and unified view of DarkFi activity across supported networks.
-- **Blockchain Data Exploration**: Access detailed information about blocks and transactions.
-- **Gas Analytics**: Analyze gas usage metrics at both the network level and on a per-transaction basis.
-- **Native Contract Source Code Access**: Review the implementation details of native contract source code for improved transparency.
-- **Search Blocks and Transactions**: Locate specific blocks or transactions using search.
-
-## Network Status
-
-The testnet and mainnet configurations serve as placeholders in preparation for their respective launch. When starting environment with these environment configurations , the site will connect to an Explorer Node that point to their respective darkfid networks, but currently only display the network's genesis block.
-
-In addition, testnet and mainnet configurations are currently using development servers for the site and work is ongoing for production-like setups.
-
-## Quick Start Guide
-
-The DarkFi Explorer provides a visual interface to explore blockchains on DarkFi networks. Follow the steps below to launch explorer environments for your desired Darkfi blockchain network. 
-
-### Prerequisites
-Before you begin, ensure you have the following installed and configured:
-- **Rust 1.86 or later**: Required for building components written in Rust (like `darkfid`, `explorerd`)
-- **Python 3.12**: Required for building and running the Explorer Site (`site`).
-- **Darkfi Project Dependencies**: System dependencies required to compile Darkfi code. For details, see [Darkfi Build Dependencies](../../README.md#build).
-- **Darkfid**: Runs DarkFi blockchain nodes on respective networks.
-
-> **Note** When using the `make` commands in the [Quick Start Guide](#quick-start-guide), Darkfid is automatically built when needed.
-
-### Start Localnet Explorer Environment
-
-```sh
-# Launch an explorer environment using the localnet configuration  
-make start-localnet
-```
-
-### Start Testnet Explorer Environment
-
-```sh
-# Launch an explorer environment using the latest testnet configuration 
-make start-testnet
-```
-
-### Start Mainnet Explorer Environment
-
-```sh
-# Launch an explorer environment using the latest mainnet configuration 
-make start-mainnet
-```
-
-### Start Explorer Environment in No-Sync Mode
-An explorer environment can be started in a no-sync mode that does not connect a darkfi blockchain network or sync new blocks, allowing you to work with an existing local explorer database. This feature is particularly useful for UI development, testing, development tasks that don't require syncing, or exploring functionality without the overhead of running a blockchain network.
-
-Below is an example of how to launch a no-sync environment configured for localnet. You can also start testnet or mainnet similarly (using `start-no-sync-testnet` or `start-no-sync-mainnet`):
-
-```sh
-# Launch localnet explorer environment without connecting or syncing with a Darkfi blockchain network
-make start-no-sync-localnet
-```
-
-> **Note** Once started, navigate to http://127.0.0.1:5000 in your browser to access the explorer interface.
-
-### Confirming Successful Start
-
-Upon successful initialization of the Explorer environment, confirmation messages will appear in the startup logs. If these logs are not visible, the Explorer environment is still initializing. Allow sufficient time for the initialization process to complete before accessing the block explorer.
-
-```
-[INFO] ========================================================================================
-[INFO]                    Started DarkFi Explorer Node
-[INFO] ========================================================================================
-[INFO]   - Network: localnet
-[INFO]   - JSON-RPC Endpoint: tcp://127.0.0.1:14567
-[INFO]   - Database: ~/.local/share/darkfi/explorerd/localnet
-[INFO]   - Configuration: ./explorerd_config.toml
-[INFO]   - Reset Blocks: No
-[INFO] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-[INFO]   - Synced Blocks: 9
-[INFO]   - Synced Transactions: 9
-[INFO]   - Connected Darkfi Node: tcp://127.0.0.1:28345
-[INFO] ========================================================================================
-[INFO] All is good. Waiting for block notifications...
-Started explorer site on localnet network (PID=31911)
-Started localnet environment. Press Ctrl-C to stop.
-```
-
-> **Note** Localnet explorer site logs appear directly in the console, while testnet and mainnet logs are written to `~/.local/share/darkfi/explorer_site/{network}/app.log`. For non-localnet environments, the console displays: `See site logfile /home/user/.local/share/darkfi/explorer_site/{network}/app.log for server startup details`.
-> **Note** In no-sync mode, you will see “Started DarkFi Explorer Node (No-Sync Mode)” in the banner header, and the “Connected Darkfi Node:” line will show “Not connected”.
-
-The explorer site log contains the following statup details.
-```
-============================================================
-Started Explorer Site
-============================================================
-Network: localnet
-Explorer Node Endpoint: 127.0.0.1:14567
-Log Path: ~/.local/share/darkfi/explorer_site/localnet
-============================================================
-```
-
-### Stopping the Explorer Environment
-
-#### Ctrl-C
-To stop the environment, press the keyboard combination Ctrl-C and wait for all components to stop.
-
-#### Make Command
-You can also stop the environment using Make:
-```sh
-# Stop the running explorer environment (nodes and site)
-make stop
-```
-
-### Getting Help
-```shell
-make help
-```
-
-## Explorer Components
-
-### Explorer Nodes
-
-DarkFi explorers rely on explorer nodes who are responsible for syncing with DarkFi blockchain networks. Explorer nodes function as background daemons, running continuously to ensure the Explorer database stays up-to-date. They synchronize with the latest blocks during startup and actively listen for new blocks using DarkFi's subscription interface. This ensures that the Explorer provides an accurate and consistent view of blockchain activity. Explorer nodes are implemented in Rust, designed to reliably handle real-world blockchain changes, including reorganizations (reorgs) and block updates, while maintaining data integrity.
-
-For more details on explorer nodes and their functionality, see [explorerd/README.md](explorerd/README.md).
-
-### Explorer Site
-
-The Explorer Site is a web application that serves as the user interface for viewing DarkFi blockchain data. It provides an interactive way for users to explore blockchain information, analyze gas usage, and navigate contract source code. The site connects to explorer nodes to fetch real-time data, ensuring accurate and up-to-date information is provided.
-
-For more details on the Explorer Site, see [site/README.md](site/README.md).
-
-## Configuration
-
-### Configuration Files
-
-The Explorer uses configuration files to define `darkfid` endpoints for block synchronization, `explorerd` endpoints for retrieving blockchain data, and other settings such as log file locations. These configurations can be reviewed and updated as needed.
-
-| Configuration File | Description                                                                                                              |
-| --- |--------------------------------------------------------------------------------------------------------------------------|
-| **[Darkfid Config](../darkfid/darkfid_config.toml)** | Contains darkfid network configurations for `localnet`, `testnet`, and `mainnet`.                                        |
-| **[Explorerd Config](./explorerd/explorerd_config.toml)** | Specifies the `darkfid` endpoint for block synchronization and the `explorerd` endpoint for retrieving synchronized data. |
-| **[ExplorerSite Config](./site/site_config.toml)** | Defines the `explorerd` endpoints for fetching synchronized data, as well as other settings for the Explorer site.|
-
-### Supported Networks
-The Explorer is configured to support the following pre-configured DarkFi blockchain networks.
-
-| **Network**    | **Description**                                              |
-| -------------- | ------------------------------------------------------------ |
-| **`localnet`** | Ideal for development and testing in a local environment using `darkfid`'s localnet configuration. |
-| **`testnet`**  | Used for testing the explorer with `darkfid`'s testnet network (pending availability). |
-| **`mainnet`**  | The production environment for running the explorer on DarkFi's canonical chain (pending availability). |
-
-> **Note** Once the testnet and mainnet are fully available, the explorer will sync more than just the genesis blocks for these networks.
-
-## Feature Details
-
-### Home Page Overview
-
-The explorer’s home page serves as the starting point for navigating blockchain data and features. It provides a high-level overview of available resources, including:
-- **Search Box**: Search for blocks and transactions based on hash.
-- **At-a-Glance Summary**: View summaries of network activity, like recent blocks, native contracts, and total gas consumption.
-- **Navigation Links**: Access block details and native contract source code, as well as navigate DarkFi social media links.
-
-<details>
-<summary>Click to view the Home Page</summary>
-<img src="docs/images/explorer-home.png" width="60%" alt="DarkFi Blockchain Explorer Home Page">
-</details>
-
-___
-
-### Block and Transaction Exploration
-The Explorer provides insights into blockchain activity by giving users the ability to view blocks, obtain transaction details, and analyze the relationships between blocks and transactions.
-
-#### Key Features
-- **Block Details Overview**: Provides key information about each block.
-- **Transaction Details**: Displays details for individual transactions.
-- **Search Functionality**: Enables users to locate specific blocks or transactions using the Explorer's search tools.
-- **Detail Navigation**: Allows users to navigate into block-level details from the Block Explorer home page or view transaction details from within blocks.
-
-#### Block Details
-The Explorer presents detailed information about each block, offering insights into the transactions it contains and other metadata. Below, you’ll find an example of the block details view.
-
-<img src="docs/images/explorer-block-details.png" width="60%" alt="DarkFi Block Details Example">
-
-The detailed block view displays the following details.
-
-| **Field** | **Description** |
-| --- | --- |
-| **Version** | Indicates the block's version. |
-| **Previous Block** | A navigable link to the previous block's hash. |
-| **Block Hash** | The unique identifier of the block. |
-| **Block Height** | The block's position within the blockchain. |
-| **Timestamp** | The UTC time at which the block was created. |
-| **Nonce** | The proof-of-work or equivalent nonce value for the block. |
-| **Root** | The Merkle root summarizing the hashes of all transactions. |
-| **Signature** | The digital signature of the block. |
-| **Transactions** | List of transaction hashes, each navigable for detailed views. |
-
-#### Transaction Details
-
-The Explorer provides an in-depth view of individual transactions, whether accessed from a block's detailed view or using the search functionality. Below is an example of the transaction details view.
-
-<img src="docs/images/explorer-tx-details.png" width="60%" alt="Transaction Details Example">
-
-For each transaction, the Explorer displays the following details.
-
-| **Field** | **Description** |
-| --- | --- |
-| **Transaction Hash** | A unique identifier for the transaction. |
-| **Block** | The hash of the containing block, linking back to the block’s details. |
-| **Payload** | Call data associated with the transaction. |
-| **Gas Consumption** | The amount of gas used for execution (see [Transaction Gas Consumption](#transaction-level-gas-consumption-metrics) for more details). |
-
-#### Key Benefits
-This feature enables users to:
-1. View key details of blockchain activity, including blocks and transactions.
-2. Analyze relationships between blocks and transactions.
-3. Search for specific blocks or transactions of interest.
-4. Gain insights into gas usage across the blockchain and individual transactions.
-
-## Gas Analytics
-
-The Explorer provides detailed analytics on gas usage, covering both network-wide metrics and transaction-level consumption. This feature empowers users to understand execution costs, identify optimization opportunities, and manage gas expenses.
-
-### Key Features
-
-| Feature                        | Description                                                  |
-| ------------------------------ | ------------------------------------------------------------ |
-| **Network-Wide Gas Metrics**   | Displays gas consumption across all operations on the blockchain, categorized into multiple categories such as WASM, ZK circuits, signatures, and deployments. |
-| **Transaction-Level Analysis** | Provides per-transaction gas usage details, allowing users to evaluate the efficiency and execution costs of individual transactions. |
-
-### Network-Wide Gas Consumption Metrics
-
-The Explorer tracks gas consumption across the blockchain broken down by category. Below is an example of the Explorer’s network-wide gas tracking view.
-
-<img src="docs/images/explorer-gas-metrics-total-consumption.png" width="60%" alt="Total Gas Consumption">
-
-Each category is outlined in the table below.
-
-| Category                 | Description                                                  |
-| ------------------------ | ------------------------------------------------------------ |
-| **Total Gas Usage**      | The cumulative amount of gas consumed across all blockchain transactions. |
-| **WASM Gas Usage**       | Tracks gas used by WASM-based smart contract execution.      |
-| **ZK Circuit Gas Usage** | Monitors gas usage related to Zero-Knowledge Proof execution. |
-| **Signature Gas Usage**  | Represents gas consumed for transaction signatures.          |
-| **Deployment Gas Usage** | Gas required for deploying contracts on the blockchain.      |
-
-For each of these categories, the Explorer provides the following metrics.
-
-| Metric                | Description                                                  |
-| --------------------- | ------------------------------------------------------------ |
-| **Average Gas Usage** | The average gas spent per transaction across relevant operations. |
-| **Minimum Gas Usage** | The smallest amount of gas recorded in any transaction.      |
-| **Maximum Gas Usage** | The highest gas value observed across transactions.          |
-
-### Transaction-Level Gas Consumption Metrics
-
-The Explorer provides detailed data on gas consumption for individual transactions, helping users evaluate the cost and efficiency of specific operations. Below is an example of the gas consumption view for a specific transaction.
-
-<img src="docs/images/explorer-gas-metrics-tx-consumption.png" width="60%" alt="Transaction Gas Consumption">
-
-### Transaction Metrics
-
-| Metric              | Description                                                  |
-| ------------------- | ------------------------------------------------------------ |
-| **Total Gas Usage** | The total gas consumed by all operations within the transaction. |
-| **WASM Gas**        | The gas used during WASM contract execution.                 |
-| **ZK Circuit Gas**  | Gas consumed for Zero-Knowledge Proof components in the transaction. |
-| **Signature Gas**   | Gas required for transaction signature verification.         |
-| **Deployment Gas**  | Gas spent specifically on deploying smart contracts.         |
-
-### Key Benefits
-This feature empowers users to:
-1. Track blockchain-wide gas consumption across various operational categories
-2. Analyze the gas efficiency and cost of transactions, identifying resource-intensive operations and opportunities for optimization
-3. Improve cost management by understanding precise gas usage for specific actions
-___
-
-## Native Contract Source Code Navigation
-
-The Explorer allows users to inspect native contracts deployed on the DarkFi blockchain by presenting their metadata and source code. This feature enhances transparency and facilitates a better understanding of native DarkFi contract behavior.
-
-### Key Features
-- **Metadata Exploration**: Displays contract metadata such as its name and description.
-- **Native Contract List**: Provides access to all DarkFi native contracts directly from the Explorer home page.
-- **Source Code List for a Contract**: Displays all source files associated with a native contract.
-- **Detailed Source File View**: Allows users to view individual source files with syntax highlighting for better readability.
-
-### List of Native Contracts
-The Explorer presents users with an organized list of native contracts on the Explorer home page.
-
-<img src="docs/images/explorer-native-contract-list.png" width="60%" alt="DarkFi Explorer Native Contracts List">
-
-### List of Source Code for a Contract
-
-From the list of native contracts, users can navigate to view the individual source files associated with a specific contract.
-
-<img src="docs/images/explorer-source-code-list.png" width="60%" alt="DarkFi Explorer Native Contract Source Code List">
-
-### Source Code View
-
-Users can select source files from the list to view source code content.
-
-<img src="docs/images/explorer-native-contract-source-code.png" width="60%" alt="DarkFi Explorer Native Source Code">
-
-### Key Benefits
-
-This feature allows users to:
-1. Transparently review the purpose and behavior of DarkFi native contracts
-2. View contract source code associated with the native smart contracts
-3. Explore Zero-Knowledge Proof implementations within the contract code
-4. Examine interactions between associated files to understand the overall contract structure
-

BIN
bin/explorer/docs/images/explorer-block-details.png


BIN
bin/explorer/docs/images/explorer-gas-metrics-total-consumption.png


BIN
bin/explorer/docs/images/explorer-gas-metrics-tx-consumption.png


BIN
bin/explorer/docs/images/explorer-home.png


BIN
bin/explorer/docs/images/explorer-native-contract-list.png


BIN
bin/explorer/docs/images/explorer-native-contract-source-code.png


BIN
bin/explorer/docs/images/explorer-source-code-list.png


BIN
bin/explorer/docs/images/explorer-tx-details.png


+ 0 - 1
bin/explorer/explorerd/.gitignore

@@ -1 +0,0 @@
-/native_contracts_src

+ 0 - 46
bin/explorer/explorerd/Cargo.toml

@@ -1,46 +0,0 @@
-[package]
-name = "explorerd"
-version = "0.5.0"
-description = "Daemon to listen for new blocks from darkfid and store them in an easily accessible format for further usage."
-authors = ["Dyne.org foundation <foundation@dyne.org>"]
-repository = "https://codeberg.org/darkrenaissance/darkfi"
-license = "AGPL-3.0-only"
-edition = "2021"
-
-[dependencies]
-# Darkfi
-darkfi = {path = "../../../", features = ["async-daemonize", "validator", "rpc"]}
-darkfi-sdk = {path = "../../../src/sdk"}
-darkfi-serial = {path = "../../../src/serial"}
-
-# JSON-RPC
-async-trait = "0.1.89"
-tinyjson = "2.5.1"
-url = "2.5.7"
-
-# Daemon
-easy-parallel = "3.3.1"
-signal-hook-async-std = "0.4.0"
-signal-hook = "0.4.1"
-tracing-subscriber = { version = "0.3.22", default-features = false, features = ["fmt"] }
-tracing-appender = "0.2.4"
-smol = "2.0.2"
-
-# Argument parsing
-serde = {version = "1.0.228", features = ["derive"]}
-structopt = "0.3.26"
-structopt-toml = "0.5.1"
-
-# Database
-sled-overlay = "0.1.15"
-
-# Misc
-blake3 = "1.8.2"
-tracing = "0.1.44"
-lazy_static = "1.5.0"
-tar = "0.4.44"
-toml = "0.9.8"
-thiserror = "2.0.17"
-
-# Testing
-tempdir = "0.3.7"

+ 0 - 216
bin/explorer/explorerd/Makefile

@@ -1,216 +0,0 @@
-.POSIX:
-
-# Suppress all directory-related messages for cleaner output
-MAKEFLAGS += --no-print-directory
-
-# Install prefix
-PREFIX = $(HOME)/.cargo
-
-# Cargo binary
-CARGO = cargo
-
-# Compile target
-RUST_TARGET = $(shell rustc -Vv | grep '^host: ' | cut -d' ' -f2)
-# Uncomment when doing musl static builds
-#RUSTFLAGS = -C target-feature=+crt-static -C link-self-contained=yes
-
-# Root directory of the project
-PROJECT_ROOT = ../../..
-
-# Directory where logs are stored
-LOG_HOME := $(shell echo ~/.local/share/darkfi/logs)
-
-# This is the main binary built by the Makefile.
-# Its corresponding configuration file is used to start the node.
-EXPLORERD_BIN    := $(shell grep '^name = ' Cargo.toml | sed 1q | cut -d' ' -f3 | tr -d '"')
-EXPLORERD_CONFIG := $(EXPLORERD_BIN)_config.toml
-
-# If these binaries are missing when launching the explorer node environment,
-# the Makefile (via a sub-make call) will build them if needed. Their configurations
-# are used to start the nodes.
-DARKFID_BIN      := $(PROJECT_ROOT)/darkfid
-DARKFID_CONFIG   := $(PROJECT_ROOT)/bin/darkfid/darkfid_config.toml
-
-# Source to build
-SRC = \
-	Cargo.toml \
-	$(PROJECT_ROOT)/Cargo.toml \
-	$(shell find src -type f -name '*.rs') \
-	$(shell find $(PROJECT_ROOT)/src -type f -name '*.rs') \
-
-all: $(EXPLORERD_BIN)
-
-help:
-	@echo "Explorerd Makefile Commands:"
-	@echo ""
-	@echo "Build targets:"
-	@echo "  make               - Build the $(EXPLORERD_BIN) binary"
-	@echo "  make clean         - Remove build artifacts"
-	@echo "  make install       - Install $(EXPLORERD_BIN) to $(PREFIX)/bin"
-	@echo "  make uninstall     - Remove $(EXPLORERD_BIN) from $(PREFIX)/bin"
-	@echo ""
-	@echo "Network management:"
-	@echo "  make start-localnet - Start the explorer node environment on localnet"
-	@echo "  make start-testnet  - Start the explorer node environment on testnet"
-	@echo "  make start-mainnet  - Start the explorer node environment on mainnet"
-	@echo "  ** Use VERBOSE=-vv or -vvv for debugging (applies to start-% commands only)"
-	@echo ""
-	@echo "  make stop           - Stop all nodes running within the explorer node environment"
-	@echo ""
-	@echo "Utility targets:"
-	@echo "  make bundle_contracts_src - Bundle contract sources and ZK proofs into tar archives in native_contracts_src directory"
-	@echo "  make await-startup-{network} - Wait until nodes are ready (used in scripting, replace {network} with localnet/testnet/mainnet)"
-	@echo ""
-	@echo "Log files are stored in: $(LOG_HOME)/{localnet|testnet|mainnet}/"
-
-$(EXPLORERD_BIN): $(SRC) bundle_contracts_src
-	RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) build --target=$(RUST_TARGET) --release --package $@
-	cp -f $(PROJECT_ROOT)/target/$(RUST_TARGET)/release/$@ $@
-	cp -f $(PROJECT_ROOT)/target/$(RUST_TARGET)/release/$@ $(PROJECT_ROOT)/$@
-
-clean:
-	RUSTFLAGS="$(RUSTFLAGS)" $(CARGO) clean --target=$(RUST_TARGET) --release --package $(EXPLORERD_BIN)
-	rm -f $(EXPLORERD_BIN) $(PROJECT_ROOT)/$(EXPLORERD_BIN)
-	rm -rf native_contracts_src
-
-install: all
-	mkdir -p $(DESTDIR)$(PREFIX)/bin
-	cp -f $(EXPLORERD_BIN) $(DESTDIR)$(PREFIX)/bin
-	chmod 755 $(DESTDIR)$(PREFIX)/bin/$(EXPLORERD_BIN)
-
-uninstall:
-	rm -f $(DESTDIR)$(PREFIX)/bin/$(EXPLORERD_BIN)
-
-# Bundles contract sources and ZK proofs into tar archives, supporting both GNU and BSD tar
-bundle_contracts_src:
-	@TMP_DIR="native_contracts_src/tmp"; \
-	CONTRACT_SRC_DIR="$(PROJECT_ROOT)/src/contract"; \
-	CONTRACTS="deployooor money dao"; \
-	set -e; \
-	# Create a temporary directory and clean up on exit \
-	mkdir -p "$$TMP_DIR"; \
-	trap "rm -rf $$TMP_DIR" 0 1 2 15; \
-	# Bundle each native contract \
-	for contract in $$CONTRACTS; do \
-		PROOF_DIR="$$TMP_DIR/$$contract/proof"; \
-		mkdir -p "$$TMP_DIR/$$contract"; \
-		cp -R "$$CONTRACT_SRC_DIR/$$contract/src/"* "$$TMP_DIR/$$contract"; \
-		# Include zk proofs if they exist \
-		if [ -d "$$CONTRACT_SRC_DIR/$$contract/proof" ]; then \
-			mkdir -p "$$PROOF_DIR"; \
-			find "$$CONTRACT_SRC_DIR/$$contract/proof" -type f ! -name '*.bin' -exec cp {} "$$PROOF_DIR/" \; ; \
-			# Move witness files to their own directory if present  \
-			if find "$$PROOF_DIR" -name '*.json' | grep -q .; then \
-				mkdir -p "$$PROOF_DIR/witness"; \
-				mv "$$PROOF_DIR"/*.json "$$PROOF_DIR/witness"; \
-			fi; \
-		fi; \
-		(cd "$$TMP_DIR/$$contract" && tar --format=pax -cf ../../$${contract}_contract_src.tar *); \
-	done;
-
-# Starts an explorer node environment for `localnet`, `testnet`, or `mainnet` networks.
-# It validates the input network, stops any currently running nodes, initializes the log directory,
-# and starts the remaining nodes while waiting for them to properly initialize.
-start-%: check-darkfid check-explorerd
-	@if [ "$*" != "localnet" ] && [ "$*" != "testnet" ] && [ "$*" != "mainnet" ]; then \
-		echo "Error: Unsupported network '$*'. Use 'localnet', 'testnet', or 'mainnet'."; \
-		exit 1; \
-	fi
-	@$(MAKE) stop suppress_not_running=1
-	@echo "Starting explorer node environment $*..."
-	@sh -c ' \
-		LOG_DIR=$(LOG_HOME)/$*; \
-		mkdir -p "$$LOG_DIR"; \
-		$(DARKFID_BIN) $(VERBOSE) --log "$$LOG_DIR/darkfid.log" -c $(DARKFID_CONFIG) --network $* & echo $$! >> PIDs.txt; sleep 2; \
-		$(call wait_for_darkfid_startup, $$LOG_DIR) \
-		./$(EXPLORERD_BIN) $(VERBOSE) --log "$$LOG_DIR/explorerd.log" -c $(EXPLORERD_CONFIG) --network $* & echo $$! >> PIDs.txt; \
-		$(call wait_for_explorerd_startup, $$LOG_DIR) \
-	'
-
-# Starts an explorer node in no-sync mode for `localnet`, `testnet`, or `mainnet` networks.
-# In no-sync mode, no connections are made with the Darkfi blockchain network and the explorer
-# relies solely on a local database without attempting synchronization. As with the `start-%`
-# target, inputs are verified, any running nodes are stopped, the log directory is initialized,
-# and the node is started.
-start-no-sync-%: check-contracts check-explorerd
-	@if [ "$*" != "localnet" ] && [ "$*" != "testnet" ] && [ "$*" != "mainnet" ]; then \
-		echo "Error: Unsupported network '$*'. Use 'localnet', 'testnet', or 'mainnet'."; \
-		exit 1; \
-	fi
-	@$(MAKE) stop suppress_not_running=1
-	@echo "Starting explorer node environment (no sync) $*..."
-	@sh -c ' \
-		LOG_DIR=$(LOG_HOME)/$*; \
-		mkdir -p "$$LOG_DIR"; \
-		./$(EXPLORERD_BIN) --log "$$LOG_DIR/explorerd.log" -c $(EXPLORERD_CONFIG) --network $* --no-sync & echo $$! >> PIDs.txt; \
-		$(call wait_for_explorerd_startup, $$LOG_DIR) \
-	'
-
-# Check and build darkfid if it does not exist
-check-darkfid:
-	@if [ ! -f "$(DARKFID_BIN)" ]; then \
-		echo "Building darkfid..."; \
-		$(MAKE) -C "$(PROJECT_ROOT)" darkfid; \
-	fi
-
-# Check and build explorerd if it does not exist
-check-explorerd:
-	@if [ ! -f "$(EXPLORERD_BIN)" ]; then \
-		echo "Building explorerd..."; \
-		$(MAKE) -C .; \
-	fi
-
-# Check and build contracts if they do not exist
-check-contracts:
-	@if [ ! -f "$(PROJECT_ROOT)/src/contract/money/darkfi_money_contract.wasm" ] \
-	  || [ ! -f "$(PROJECT_ROOT)/contract/dao/darkfi_dao_contract.wasm" ] \
-	  || [ ! -f "$(PROJECT_ROOT)/contract/deployooor/darkfi_deployooor_contract.wasm" ]; then \
-		echo "Building contracts..."; \
-		$(MAKE) -C "$(PROJECT_ROOT)" contracts; \
-	fi
-
-# Stop the running network
-# Usage: make stop [suppress_not_running=1]
-stop:
-	@if [ -f PIDs.txt ]; then \
-		while read PID; do \
-			if ps -p $$PID > /dev/null 2>&1; then \
-				kill -15 $$PID 2>/dev/null; \
-				sleep 5; \
-				ps -p $$PID > /dev/null 2>&1 && kill -9 $$PID 2>/dev/null; \
-			fi; \
-		done < PIDs.txt; \
-		rm -f PIDs.txt; \
-		echo "Stopped explorer node environment"; \
-	else \
-		if [ "$(suppress_not_running)" != "1" ]; then \
-			echo "Explorer node environment not running, nothing to stop."; \
-		fi; \
-	fi
-
-# Wait for the network to start
-await-startup-%:
-	@$(call wait_for_darkfid_startup,$(LOG_HOME)/$*)
-	@$(call wait_for_explorerd_startup,$(LOG_HOME)/$*)
-
-# Wait for the explorer node environment to start in no-sync mode (skip waiting for darkfid startup)
-await-startup-no-sync-%:
-	@$(call wait_for_explorerd_startup,$(LOG_HOME)/$*)
-
-# Waits for Darkfid to start
-define wait_for_darkfid_startup
-  log_dir=$(strip $(1)); \
-  while ! grep -q "Blockchain synced!" "$$log_dir/darkfid.log" 2>/dev/null; do \
-    sleep 1; \
-  done;
-endef
-
-# Waits for Explorerd to start
-define wait_for_explorerd_startup
-  log_dir=$(strip $(1)); \
-  while ! grep -q "Started DarkFi Explorer Node" "$$log_dir/explorerd.log" 2>/dev/null; do \
-    sleep 1; \
-  done;
-endef
-
-.PHONY: help all clean install uninstall bundle_contracts_src check-darkfid check-explorerd stop start-%

+ 0 - 220
bin/explorer/explorerd/README.md

@@ -1,220 +0,0 @@
-# Explorerd
-
-The `explorerd` is a Rust-based daemon responsible for running a DarkFi Explorer node to synchronize blockchain data with DarkFi nodes across multiple networks. During startup, it syncs missing or outdated blocks and then listens for live updates to ensure the Explorer provides accurate and up-to-date information about blockchain activity, including blocks, transactions, and key metrics such as gas consumption.
-
-The `explorerd` is designed to handle real-world blockchain events, such as reorganizations ("reorgs"), missing blocks, and divergences between networks. Its primary purpose is to ensure a consistent view of DarkFi blockchain data across different networks. For its storage layer, it leverages **sled** to take advantage of its performance, scalability, and reliability.
-
----
-
-## Key Features
-
-- **Full Blockchain Synchronization**: Ensures the Explorer's database reflects the current state of the DarkFi blockchain by syncing missing or outdated block data.
-- **Reorg Mitigation**: Detects and resolves chain reorganizations to maintain alignment with the respective networks.
-- **Real-Time Updates**: Leverages DarkFi's subscription interface to receive live block and transaction data.
-- **On-The-Fly Metric Calculations**: Computes analytics and blockchain metrics for use in the Explorer's UI. This includes maintaining data such as running totals, min/max values, and transaction counts when processing blocks, allowing for efficient gas metric calculations without iterating through previous transactions.
-
-## Network Status
-
-The testnet and mainnet configurations serve as placeholders in preparation for their respective launch. When starting daemons with these configurations, each node will connect to their respective darkfid network, but currently only sync the network's genesis block. Once the DarkFi blockchain testnet and mainnet are fully available, the explorer daemon will sync blocks beyond genesis for these networks.
-
-## Prerequisites
-Before you begin, ensure you have the following installed and configured:
-- **Rust 1.86 or later**: Required for building components written in Rust (like `darkfid`, `explorerd`).
-- **Darkfi Project Dependencies**: System dependencies required to compile DarkFi code (see [Darkfi Build Dependencies](../../../README.md#build)).
-- **Darkfid**: Runs DarkFi blockchain nodes on respective networks.
-
-> **Note** When using the `make` commands in the [Quick Start Guide](#quick-start-guide), Darkfid is automatically built when needed.
-
-## Quick-Start Guide
-
-Run the following commands to run a node on respective network.
-
-### Start a Localnet Node
-
-```sh
-# Run a node using localnet configuration 
-make start-localnet
-```
-
-### Start a Testnet Node
-
-```sh
-# Run a node using testnet configuration 
-make start-testnet
-```
-
-### Start a Mainnet Node
-
-```sh
-# Run a node using mainnet configuration 
-make start-mainnet
-```
-
-### Start in No-Sync Mode
-You can run explorerd in a no-sync mode that does not connect to any nodes or sync blocks. This approach is useful if you only need to work with an existing local explorer database, perform tests, or work on the site UI without running blockchain nodes or miners (which can be resource-intensive).
-
-Below is an example of how to launch a no-sync environment for localnet. You can also start testnet or mainnet in the same manner (for example, using `start-no-sync-testnet` or `start-no-sync-mainnet`).
-
-```sh
-# Launch an explorer without connecting to or syncing with any blockchain nodes
-make start-no-sync-localnet
-```
-
-### Stopping the Node
-
-```sh
-# Stop the running explorer node
-make stop
-```
-
-### Confirming Successful Start 
-
-When a DarkFi Explorer Node successfully starts, users should a startup banner displaying the node's configuration details and current sync status. Here is a successful localnet node startup example:
-
-```
-[INFO] ========================================================================================
-[INFO]                    Started DarkFi Explorer Node
-[INFO] ========================================================================================
-[INFO]   - Network: localnet
-[INFO]   - JSON-RPC Endpoint: tcp://127.0.0.1:14567
-[INFO]   - Database: ~/.local/share/darkfi/explorerd/localnet
-[INFO]   - Configuration: ./explorerd_config.toml
-[INFO]   - Reset Blocks: No
-[INFO] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-[INFO]   - Synced Blocks: 8
-[INFO]   - Synced Transactions: 8
-[INFO]   - Connected Darkfi Node: tcp://127.0.0.1:28345
-[INFO] ========================================================================================
-```
-> **Note** In no-sync mode, you will see “Started DarkFi Explorer Node (No-Sync Mode)” in the banner header, and the “Connected Darkfi Node:” line will show “Not connected”.
-
-### Getting Help
-```shell
-make help
-```
-
-## Detailed Guide
-
-### Configuration
-
-The `explorerd` uses a TOML configuration file to manage different network settings, located at [Explorerd Config](explorerd_config.toml). These settings are configured to automatically connect to DarkFi blockchain nodes running on localnet, testnet, and mainnet. When running an explorer daemon for the first time without a configuration file, the default configuration file is automatically copied to `~/.config/darkfi/explorerd_config.toml`. Once this file exists, running explorerd again will automatically start a node based on this configuration.
-
-#### Example Configuration
-
-Below is an example of a localnet configuration for `explorerd` (`~/.config/darkfi/explorerd_config.toml`):
-
-```toml
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-# Localnet Configuration
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-[network_config."localnet"]
-
-# Path to daemon database
-database = "~/.local/share/darkfi/explorerd/localnet"
-
-# darkfid JSON-RPC endpoint
-endpoint = "tcp://127.0.0.1:28345"
-
-## Localnet JSON-RPC settings
-[network_config."localnet".rpc]
-# JSON-RPC listen URL
-rpc_listen = "tcp://127.0.0.1:14567"
-
-# Disabled RPC methods
-#rpc_disabled_methods = []
-```
-
-**Note**: When updating the configuration, ensure the `endpoint` field matches the `rpc_listen` endpoint in the respective `darkfid` configuration, and that the `database` and `rpc_listen` paths are consistent with your environment.
-
-#### Supported DarkFi Blockchain Networks
-
-The explorer daemon comes pre-configured to automatically connect to the following DarkFi's blockchain environments:
-- **`localnet`**: For development and testing in a local environment
-- **`testnet`**: For testing on DarkFi's test network (pending availability)
-- **`mainnet`**: For production use on DarkFi's main network (pending availability)
-
-For more details about these darkfid networks, see the [Darkfid Configuration](../../darkfid/darkfid_config.toml).
-
-> **Note**: Once the testnet and mainnet are fully available, the explorer will sync more than just the genesis blocks for these networks.
-
-#### Custom Configuration
-
-To create a custom explorerd configuration, use [Explorerd Configuration](../explorerd_config.toml) as a template and modify it as needed. Ensure that the `endpoint` in the `explorerd` configuration matches the `rpc_listen` value in the corresponding `darkfid_config.toml` file, enabling proper connection to the corresponding DarkFi blockchain node.
-
-**Example Alignment**:
-
-- `darkfid_config.toml`:
-
-  ```toml
-  [network_config."localnet".rpc]
-  rpc_listen = "tcp://127.0.0.1:28345"
-  ```
-
-- `explorerd_config.toml`:
-
-  ```toml
-  [network_config."localnet"]
-  endpoint = "tcp://127.0.0.1:28345"
-  ```
-
-### Installation
-
-The `explorerd` binary can be installed to your system path using the `make install` command, which places it in `~/.cargo/bin/explorerd`. This directory is typically included in your PATH, making the command accessible system-wide.
-
-From the `explorerd` directory:
-
-```bash
-make install
-```
-
-> Use `make` without `install` to build the `explorerd` binary in `bin/explorerd` without installing it.
-
-### Running an Explorer Node
-
-#### 1. Start Supporting Nodes
-
-##### Using darkfid's networks
-
-Start the `darkfid` daemon using their configurations.
-
-```bash
-# Start darkfid (example uses localnet) 
-darkfid --network localnet
-```
-
-> Replace `localnet` with `testnet` or `mainnet` to connect to those networks instead.
-
-##### Using the single-node development environment
-
-Run the DarkFi single-node development environment.
-
-```bash
-# Change directory and start the single-node environment
-cd contrib/localnet/darkfid-single-node
-./tmux_sessions.sh
-```
-> Ensure you update your explorerd configuration file to point to the single node network
-
-#### 2. Start the `explorerd` Daemon
-
-##### Connecting to darkfid networks
-
-Run the `explorerd` daemon to sync with the darkfid instance.
-
-```bash
-# Start the explorer daemon (example uses localnet)
-explorerd --network localnet
-```
-
-> Replace `localnet` with `testnet` or `mainnet` to connect to those networks instead.
-
-##### Connecting to custom networks
-
-To connect to a custom blockchain network, provide a configuration file with your specific network settings:
-
-```bash
-# Run explorerd with a custom configuration
-explorerd -c explorerd-config.toml --network localnet
-```
-
-> Update the `endpoint` in your `explorerd-config.toml` to point to your custom `darkfid` instance. The `--network` parameter can be set to `localnet`, `testnet`, or `mainnet` to match your target network configuration.

+ 0 - 65
bin/explorer/explorerd/explorerd_config.toml

@@ -1,65 +0,0 @@
-## explorerd configuration file
-##
-## Please make sure you go through all the settings so you can configure
-## your daemon properly.
-##
-## The default values are left commented. They can be overridden either by
-## uncommenting, or by using the command-line.
-
-# Blockchain network to use
-network = "testnet"
-
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-# Localnet Configuration
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-[network_config."localnet"]
-
-# Path to daemon database
-database = "~/.local/share/darkfi/explorerd/localnet"
-
-# darkfid JSON-RPC endpoint
-endpoint = "tcp://127.0.0.1:28345"
-
-## Localnet JSON-RPC settings
-[network_config."localnet".rpc]
-# JSON-RPC listen URL
-rpc_listen = "tcp://127.0.0.1:14567"
-
-# Disabled RPC methods
-#rpc_disabled_methods = []
-
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-# Testnet Configuration
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-[network_config."testnet"]
-
-# Path to daemon database
-database = "~/.local/share/darkfi/explorerd/testnet"
-
-# darkfid JSON-RPC endpoint
-endpoint = "tcp://127.0.0.1:18345"
-
-## Localnet JSON-RPC settings
-[network_config."testnet".rpc]
-rpc_listen = "tcp://127.0.0.1:14667"
-
-# Disabled RPC methods
-#rpc_disabled_methods = []
-
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-# Mainnet Configuration
-#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-[network_config."mainnet"]
-
-# Path to daemon database
-database = "~/.local/share/darkfi/explorerd/mainnet"
-
-# darkfid JSON-RPC endpoint
-endpoint = "tcp://127.0.0.1:8345"
-
-## Localnet JSON-RPC settings
-[network_config."mainnet".rpc]
-rpc_listen = "tcp://127.0.0.1:14767"
-
-# Disabled RPC methods
-#rpc_disabled_methods = []

+ 0 - 316
bin/explorer/explorerd/src/config.rs

@@ -1,316 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2024 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use std::{
-    fmt,
-    path::{Path, PathBuf},
-    str::FromStr,
-};
-
-use serde::Deserialize;
-use structopt::StructOpt;
-use tracing::{debug, error};
-use url::Url;
-
-use darkfi::{rpc::settings::RpcSettingsOpt, util::file::load_file, Error, Result};
-
-/// Represents an explorer configuration
-#[derive(Clone, Debug, Deserialize, StructOpt)]
-pub struct ExplorerConfig {
-    /// Current active network
-    #[allow(dead_code)] // Part of the config file
-    pub network: String,
-    /// Supported network configurations
-    pub network_config: NetworkConfigs,
-    /// Path to the configuration if read from a file
-    pub path: Option<String>,
-}
-
-impl ExplorerConfig {
-    /// Creates a new configuration from a given file path.
-    /// If the file cannot be loaded or parsed, an error is returned.
-    pub fn new(config_path: String) -> Result<Self> {
-        // Load the configuration file from the specified path
-        let config_content = load_file(Path::new(&config_path)).map_err(|err| {
-            Error::ConfigError(format!(
-                "Failed to read the configuration file {config_path}: {err:?}"
-            ))
-        })?;
-
-        // Parse the loaded content into a configuration instance
-        let mut config = toml::from_str::<Self>(&config_content).map_err(|e| {
-            error!(target: "explorerd::config", "Failed parsing TOML config: {e}");
-            Error::ConfigError(format!("Failed to parse the configuration file {config_path}"))
-        })?;
-
-        // Set the configuration path
-        config.path = Some(config_path);
-
-        debug!(target: "explorerd::config", "Successfully loaded configuration: {config:?}");
-
-        Ok(config)
-    }
-
-    /// Returns the currently active network configuration.
-    #[allow(dead_code)] // Test case currently using
-    pub fn active_network_config(&self) -> Option<ExplorerNetworkConfig> {
-        self.get_network_config(self.network.as_str())
-    }
-
-    /// Returns the network configuration for specified network.
-    pub fn get_network_config(&self, network: &str) -> Option<ExplorerNetworkConfig> {
-        match network {
-            "localnet" => self.network_config.localnet.clone(),
-            "testnet" => self.network_config.testnet.clone(),
-            "mainnet" => self.network_config.mainnet.clone(),
-            _ => None,
-        }
-    }
-}
-
-/// Provides a default `ExplorerConfig` configuration using the `testnet` network.
-impl Default for ExplorerConfig {
-    fn default() -> Self {
-        Self {
-            network: String::from("testnet"),
-            network_config: NetworkConfigs::default(),
-            path: None,
-        }
-    }
-}
-
-/// Attempts to convert a [`PathBuf`] to an [`ExplorerConfig`] by
-/// loading and parsing from specified file path.
-impl TryFrom<&PathBuf> for ExplorerConfig {
-    type Error = Error;
-    fn try_from(path: &PathBuf) -> Result<Self> {
-        let path_str = path.to_str().ok_or_else(|| {
-            Error::ConfigError("Unable to convert PathBuf to a valid UTF-8 path string".to_string())
-        })?;
-
-        // Create configuration and return
-        ExplorerConfig::new(path_str.to_string())
-    }
-}
-
-/// Deserializes a `&str` containing explorer content in TOML format into an [`ExplorerConfig`] instance.
-impl FromStr for ExplorerConfig {
-    type Err = String;
-    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
-        let config: ExplorerConfig =
-            toml::from_str(s).map_err(|e| format!("Failed to parse ExplorerdConfig: {e}"))?;
-        Ok(config)
-    }
-}
-
-/// Represents network configurations for localnet, testnet, and mainnet.
-#[derive(Debug, Clone, Deserialize, StructOpt)]
-pub struct NetworkConfigs {
-    /// Local network configuration
-    pub localnet: Option<ExplorerNetworkConfig>,
-    /// Testnet network configuration
-    pub testnet: Option<ExplorerNetworkConfig>,
-    /// Mainnet network configuration
-    pub mainnet: Option<ExplorerNetworkConfig>,
-}
-
-/// Provides a default `NetworkConfigs` configuration using the `testnet` network.
-impl Default for NetworkConfigs {
-    fn default() -> Self {
-        NetworkConfigs {
-            localnet: None,
-            testnet: Some(ExplorerNetworkConfig::default()),
-            mainnet: None,
-        }
-    }
-}
-
-/// Deserializes a `&str` containing network configs content in TOML format into an [`NetworkConfigs`] instance.
-impl FromStr for NetworkConfigs {
-    type Err = String;
-    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
-        let config: NetworkConfigs =
-            toml::from_str(s).map_err(|e| format!("Failed to parse NetworkConfigs: {e}"))?;
-        Ok(config)
-    }
-}
-
-/// Struct representing the configuration for an explorer network.
-#[derive(Clone, Deserialize, StructOpt)]
-#[structopt()]
-#[serde(default)]
-pub struct ExplorerNetworkConfig {
-    #[structopt(flatten)]
-    /// JSON-RPC settings used to set up a server that the explorer listens on for incoming RPC requests.
-    pub rpc: RpcSettingsOpt,
-
-    #[structopt(long, default_value = "~/.local/share/darkfi/explorerd/testnet")]
-    /// Path to the explorer's database.
-    pub database: String,
-
-    #[structopt(short, long, default_value = "tcp://127.0.0.1:28345")]
-    /// Endpoint of the DarkFi node JSON-RPC server to sync with.
-    pub endpoint: Url,
-}
-
-/// Attempts to convert a tuple `(PathBuf, &str)` representing a configuration file path
-/// and network name into an `ExplorerNetworkConfig`.
-impl TryFrom<(&PathBuf, &String)> for ExplorerNetworkConfig {
-    type Error = Error;
-    fn try_from(path_and_network: (&PathBuf, &String)) -> Result<Self> {
-        // Load the ExplorerConfig from the given file path
-        let config: ExplorerConfig = path_and_network.0.try_into()?;
-        // Retrieve the network configuration for the specified network
-        match config.get_network_config(path_and_network.1) {
-            Some(config) => Ok(config),
-            None => Err(Error::ConfigError(format!(
-                "Failed to retrieve network configuration for network: {}",
-                path_and_network.1
-            ))),
-        }
-    }
-}
-
-/// Provides a default `ExplorerNetworkConfig` instance using `structopt` default values defined
-/// in the `ExplorerNetworkConfig` struct.
-impl Default for ExplorerNetworkConfig {
-    fn default() -> Self {
-        Self::from_iter(&[""])
-    }
-}
-
-/// Provides a user-friendly debug view of the `ExplorerdNetworkConfig` configuration.
-impl fmt::Debug for ExplorerNetworkConfig {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        let mut debug_struct = f.debug_struct("ExplorerdConfig");
-        debug_struct
-            .field("rpc_listen", &self.rpc.rpc_listen.to_string().trim_end_matches('/'))
-            .field("db_path", &self.database)
-            .field("endpoint", &self.endpoint.to_string().trim_end_matches('/'));
-        debug_struct.finish()
-    }
-}
-
-/// Deserializes a `&str` containing network config content in TOML format into an [`ExplorerNetworkConfig`] instance.
-impl FromStr for ExplorerNetworkConfig {
-    type Err = String;
-    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
-        let config: ExplorerNetworkConfig = toml::from_str(s)
-            .map_err(|e| format!("Failed to parse ExplorerdNetworkConfig: {e}"))?;
-        Ok(config)
-    }
-}
-
-#[cfg(test)]
-/// Contains test cases for validating the functionality and correctness of the `ExplorerConfig`
-/// and related components using a configuration loaded from a TOML file.
-mod tests {
-    use std::path::Path;
-
-    use darkfi::util::logger::{setup_test_logger, Level};
-    use tracing::warn;
-
-    use super::*;
-
-    /// Validates the functionality of initializing and interacting with `ExplorerConfig`
-    /// loaded from a TOML file, ensuring correctness of the network-specific configurations.
-    #[test]
-    fn test_explorerd_config_from_file() {
-        // Constants for expected configurations
-        const CONFIG_PATH: &str = "explorerd_config.toml";
-        const ACTIVE_NETWORK: &str = "testnet";
-
-        const NETWORK_CONFIGS: &[(&str, &str, &str, &str)] = &[
-            (
-                "localnet",
-                "~/.local/share/darkfi/explorerd/localnet",
-                "tcp://127.0.0.1:28345/",
-                "tcp://127.0.0.1:14567/",
-            ),
-            (
-                "testnet",
-                "~/.local/share/darkfi/explorerd/testnet",
-                "tcp://127.0.0.1:18345/",
-                "tcp://127.0.0.1:14667/",
-            ),
-            (
-                "mainnet",
-                "~/.local/share/darkfi/explorerd/mainnet",
-                "tcp://127.0.0.1:8345/",
-                "tcp://127.0.0.1:14767/",
-            ),
-        ];
-
-        if setup_test_logger(
-            &["sled", "runtime", "net"],
-            false,
-            Level::Info,
-            //Level::Verbose,
-            //Level::Debug,
-            //Level::Trace,
-        )
-        .is_err()
-        {
-            warn!("Logger already initialized");
-        }
-
-        // Ensure the configuration file exists
-        assert!(Path::new(CONFIG_PATH).exists());
-
-        // Load the configuration
-        let config = ExplorerConfig::new(CONFIG_PATH.to_string())
-            .expect("Failed to load configuration from file");
-
-        // Validate the expected network
-        assert_eq!(config.network, ACTIVE_NETWORK);
-
-        // Validate the path is correctly set
-        assert_eq!(config.path.as_deref(), Some(CONFIG_PATH));
-
-        // Validate that `active_network_config` correctly retrieves the testnet configuration
-        let active_config = config.active_network_config();
-        assert!(active_config.is_some(), "Active network configuration should not be None.");
-        let active_config = active_config.unwrap();
-        assert_eq!(active_config.database, NETWORK_CONFIGS[1].1); // Testnet database
-        assert_eq!(active_config.endpoint.to_string(), NETWORK_CONFIGS[1].2);
-        assert_eq!(&active_config.rpc.rpc_listen.to_string(), NETWORK_CONFIGS[1].3);
-
-        // Validate all network configurations values (localnet, testnet, mainnet)
-        for &(network, expected_db, expected_endpoint, expected_rpc) in NETWORK_CONFIGS {
-            let network_config = config.get_network_config(network);
-
-            if let Some(config) = network_config {
-                assert_eq!(config.database, expected_db);
-                assert_eq!(config.endpoint.to_string(), expected_endpoint);
-                assert_eq!(config.rpc.rpc_listen.to_string(), expected_rpc);
-            } else {
-                assert!(network_config.is_none(), "{network} configuration is missing");
-            }
-        }
-
-        // Validate (path, network).try_into()
-        let config_path_buf = &PathBuf::from(CONFIG_PATH);
-        let mainnet_string = &String::from("mainnet");
-        let mainnet_config: ExplorerNetworkConfig = (config_path_buf, mainnet_string)
-            .try_into()
-            .expect("Failed to load explorer network config");
-        assert_eq!(mainnet_config.database, NETWORK_CONFIGS[2].1); // Mainnet database
-        assert_eq!(mainnet_config.endpoint.to_string(), NETWORK_CONFIGS[2].2);
-        assert_eq!(&mainnet_config.rpc.rpc_listen.to_string(), NETWORK_CONFIGS[2].3);
-    }
-}

+ 0 - 83
bin/explorer/explorerd/src/error.rs

@@ -1,83 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use std::sync::Arc;
-
-use darkfi::{
-    error::RpcError,
-    rpc::jsonrpc::{ErrorCode, JsonError},
-    Error,
-};
-
-// Constant for the error code
-pub const ERROR_CODE_PING_DARKFID_FAILED: i32 = -32300;
-
-/// Custom RPC errors available for blockchain explorer.
-/// These represent specific RPC-related failures.
-#[derive(Debug, thiserror::Error)]
-pub enum ExplorerdError {
-    #[error("Ping darkfid failed: {0}")]
-    PingDarkfidFailed(String),
-
-    #[error("Invalid contract ID: {0}")]
-    InvalidContractId(String),
-
-    #[error("Invalid header hash: {0}")]
-    InvalidHeaderHash(String),
-
-    #[error("Invalid tx hash: {0}")]
-    InvalidTxHash(String),
-}
-
-/// Provides a conversion from [`ExplorerdError`] to darkfi [`Error`] type.
-impl From<ExplorerdError> for Error {
-    fn from(err: ExplorerdError) -> Self {
-        let error: RpcError = err.into();
-        error.into()
-    }
-}
-
-/// Conversion from [`ExplorerdError`] to [`RpcError`]
-impl From<ExplorerdError> for RpcError {
-    fn from(err: ExplorerdError) -> Self {
-        RpcError::ServerError(Arc::new(err))
-    }
-}
-
-/// Helper function to convert `ExplorerdError` into error code with
-/// corresponding error message.
-pub fn to_error_code_message(e: &ExplorerdError) -> (i32, String) {
-    match e {
-        ExplorerdError::PingDarkfidFailed(_) => (ERROR_CODE_PING_DARKFID_FAILED, e.to_string()),
-        ExplorerdError::InvalidContractId(_) |
-        ExplorerdError::InvalidHeaderHash(_) |
-        ExplorerdError::InvalidTxHash(_) => (ErrorCode::InvalidParams.code(), e.to_string()),
-    }
-}
-
-/// Constructs a [`JsonError`] representing a server error using the provided
-/// [`ExplorerdError`] , request ID, and optional custom message, returning a [`JsonError`]
-/// with a corresponding server error code and message.
-pub fn server_error(e: &ExplorerdError, id: u16, msg: Option<&str>) -> JsonError {
-    let (code, default_msg) = to_error_code_message(e);
-
-    // Use the provided custom message if available; otherwise, use the default.
-    let message = msg.unwrap_or(&default_msg).to_string();
-
-    JsonError::new(ErrorCode::ServerError(code), Some(message), id)
-}

+ 0 - 302
bin/explorer/explorerd/src/main.rs

@@ -1,302 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use std::{collections::HashSet, path::Path, sync::Arc};
-
-use smol::{lock::Mutex, stream::StreamExt};
-use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
-use tracing::{error, info};
-use url::Url;
-
-use darkfi::{
-    async_daemonize, cli_desc,
-    rpc::server::{listen_and_serve, RequestHandler},
-    system::{StoppableTask, StoppableTaskPtr},
-    util::path::get_config_path,
-    Error, Result,
-};
-
-use crate::{
-    config::ExplorerNetworkConfig,
-    rpc::DarkfidRpcClient,
-    service::{sync::subscribe_sync_blocks, ExplorerService},
-};
-
-/// Configuration management across multiple networks (localnet, testnet, mainnet)
-mod config;
-
-/// Manages JSON-RPC interactions for the explorer
-mod rpc;
-
-/// Core logic for block synchronization, chain data access, metadata storage/retrieval,
-/// and statistics computation
-mod service;
-
-/// Manages persistent storage for blockchain, contracts, metrics, and metadata
-mod store;
-
-/// Crate errors
-mod error;
-
-/// Test utilities used for unit and integration testing
-#[cfg(test)]
-mod test_utils;
-
-const CONFIG_FILE: &str = "explorerd_config.toml";
-const CONFIG_FILE_CONTENTS: &str = include_str!("../explorerd_config.toml");
-
-#[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)]
-#[serde(default)]
-#[structopt(name = "explorerd", about = cli_desc!())]
-struct Args {
-    #[structopt(short, long)]
-    /// Configuration file to use
-    config: Option<String>,
-
-    #[structopt(short, long, default_value = "testnet")]
-    /// Explorer network (localnet, testnet, mainnet)
-    network: String,
-
-    #[structopt(long)]
-    /// Reset the database and start syncing from first block
-    reset: bool,
-
-    #[structopt(short, long)]
-    /// Set log file to output to
-    log: Option<String>,
-
-    #[structopt(short, parse(from_occurrences))]
-    /// Increase verbosity (-vvv supported)
-    verbose: u8,
-
-    #[structopt(short, long)]
-    /// Disable synchronization and connections to `darkfid`, operating solely
-    /// on the local explorer database without attempting to connect or sync.
-    /// If not specified, the application will attempt to connect and sync by default.
-    no_sync: bool,
-}
-
-/// Defines a daemon structure responsible for handling incoming JSON-RPC requests and delegating them
-/// to the backend layer for processing. It provides a JSON-RPC interface for managing operations related to
-/// blocks, transactions, contracts, and metrics.
-///
-/// Upon startup, the daemon initializes a background task to handle incoming JSON-RPC requests.
-/// This includes processing operations related to blocks, transactions, contracts, and metrics by
-/// delegating them to the backend and returning appropriate RPC responses. Additionally, the daemon
-/// synchronizes blocks from the `darkfid` daemon into the explorer database and subscribes
-/// to new blocks, ensuring that the local database remains updated in real-time.
-pub struct Explorerd {
-    /// Explorer service instance
-    pub service: ExplorerService,
-    /// JSON-RPC connection tracker
-    pub rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
-    /// JSON-RPC client to execute requests to darkfid daemon
-    pub darkfid_client: Arc<DarkfidRpcClient>,
-    /// Darkfi blockchain node endpoint to sync with when not in no-sync mode
-    darkfid_endpoint: Url,
-    /// A asynchronous executor used to create an RPC client when not in no-sync mode
-    executor: Arc<smol::Executor<'static>>,
-}
-
-impl Explorerd {
-    /// Creates a new `BlockchainExplorer` instance.
-    async fn new(
-        db_path: String,
-        darkfid_endpoint: Url,
-        ex: Arc<smol::Executor<'static>>,
-    ) -> Result<Self> {
-        // Initialize darkfid rpc client
-        let darkfid_client = Arc::new(DarkfidRpcClient::new());
-
-        // Create explorer service
-        let service = ExplorerService::new(db_path, darkfid_client.clone())?;
-
-        // Initialize the explorer service
-        service.init().await?;
-
-        Ok(Self {
-            service,
-            rpc_connections: Mutex::new(HashSet::new()),
-            darkfid_client,
-            darkfid_endpoint,
-            executor: ex,
-        })
-    }
-
-    /// Establishes a connection to the configured darkfid endpoint, returning a successful
-    /// result if the connection is successful, or an error otherwise.
-    async fn connect(&self) -> Result<()> {
-        self.darkfid_client.connect(self.darkfid_endpoint.clone(), self.executor.clone()).await
-    }
-}
-
-async_daemonize!(realmain);
-async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
-    info!(target: "explorerd", "Initializing DarkFi blockchain explorer node...");
-
-    // Resolve the configuration path
-    let config_path = get_config_path(args.config.clone(), CONFIG_FILE)?;
-
-    // Get explorer network configuration
-    let config: ExplorerNetworkConfig = (&config_path, &args.network).try_into()?;
-
-    // Initialize the explorer daemon instance
-    let explorer =
-        Explorerd::new(config.database.clone(), config.endpoint.clone(), ex.clone()).await?;
-    let explorer = Arc::new(explorer);
-    info!(target: "explorerd", "Node initialized successfully!");
-
-    // JSON-RPC server
-    // Here we create a task variable so we can manually close the task later.
-    let rpc_task = StoppableTask::new();
-    let explorer_ = explorer.clone();
-    rpc_task.clone().start(
-        listen_and_serve(config.rpc.clone().into(), explorer.clone(), None, ex.clone()),
-        |res| async move {
-            match res {
-                Ok(()) | Err(Error::RpcServerStopped) => explorer_.stop_connections().await,
-                Err(e) => {
-                    error!(target: "explorerd", "Failed starting sync JSON-RPC server: {e}")
-                }
-            }
-        },
-        Error::RpcServerStopped,
-        ex.clone(),
-    );
-    info!(target: "explorerd", "Started JSON-RPC server: {}", config.rpc.rpc_listen.to_string().trim_end_matches("/"));
-
-    // Declare task variables optional in case we are in no-sync mode
-    let mut subscriber_task = None;
-    let mut listener_task = None;
-
-    // Do not sync when in no-sync mode
-    if !args.no_sync {
-        explorer.connect().await?;
-
-        // Sync blocks
-        sync_blocks(explorer.clone(), args.reset).await?;
-
-        // Subscribe blocks
-        (subscriber_task, listener_task) =
-            subscribe_blocks(explorer.clone(), config.endpoint.clone(), ex.clone(), args.reset)
-                .await?;
-    }
-
-    log_started_banner(explorer.clone(), &config, &args, &config_path, args.no_sync);
-    info!(target: "explorerd::", "All is good. Waiting for block notifications...");
-
-    // Signal handling for graceful termination.
-    let (signals_handler, signals_task) = SignalHandler::new(ex)?;
-    signals_handler.wait_termination(signals_task).await?;
-    info!(target: "explorerd", "Caught termination signal, cleaning up and exiting...");
-
-    info!(target: "explorerd", "Stopping JSON-RPC server...");
-    rpc_task.stop().await;
-
-    // Stop darkfid listener task if it exists
-    if let Some(task) = listener_task {
-        info!(target: "explorerd", "Stopping darkfid listener...");
-        task.stop().await;
-    }
-
-    // Stop darkfid subscribe task if it exists
-    if let Some(task) = subscriber_task {
-        info!(target: "explorerd", "Stopping darkfid subscriber...");
-        task.stop().await;
-    }
-
-    info!(target: "explorerd", "Stopping JSON-RPC client...");
-    let _ = explorer.darkfid_client.stop().await;
-
-    Ok(())
-}
-
-/// Synchronizes blocks from the `darkfid` daemon.
-async fn sync_blocks(explorer: Arc<Explorerd>, reset: bool) -> Result<()> {
-    info!(target: "explorerd", "Syncing blocks from darkfid...");
-    if let Err(e) = explorer.service.sync_blocks(reset).await {
-        let error_message = format!("Error syncing blocks: {e:?}");
-        error!(target: "explorerd", "{error_message}");
-        return Err(Error::DatabaseError(error_message));
-    }
-    Ok(())
-}
-
-/// Subscribes to new blocks from the `darkfid` daemon, returning optional
-/// stoppable tasks for the subscriber and listener.
-async fn subscribe_blocks(
-    explorer: Arc<Explorerd>,
-    endpoint: Url,
-    executor: Arc<smol::Executor<'static>>,
-    reset: bool,
-) -> Result<(Option<StoppableTaskPtr>, Option<StoppableTaskPtr>)> {
-    info!(target: "explorerd", "Subscribing to new blocks...");
-
-    let result = match subscribe_sync_blocks(explorer.clone(), endpoint.clone(), executor.clone())
-        .await
-    {
-        Ok((subscriber_task, listener_task)) => Ok((subscriber_task, listener_task)),
-        Err(e) => {
-            // If out of sync, sync blocks and retry subscription
-            if e.to_string().contains("Blockchain not fully synced") {
-                sync_blocks(explorer.clone(), reset).await?;
-                subscribe_sync_blocks(explorer.clone(), endpoint.clone(), executor.clone()).await
-            } else {
-                let error_message = format!("Error setting up blocks subscriber: {e:?}");
-                error!(target: "explorerd", "{error_message}");
-                return Err(Error::DatabaseError(error_message));
-            }
-        }
-    };
-
-    let (subscriber_task, listener_task) = result?;
-    info!(target: "explorerd", "Successfully subscribed to new blocks!");
-    Ok((Some(subscriber_task), Some(listener_task)))
-}
-
-/// Logs a banner displaying the startup details of the DarkFi Explorer Node.
-fn log_started_banner(
-    explorer: Arc<Explorerd>,
-    config: &ExplorerNetworkConfig,
-    args: &Args,
-    config_path: &Path,
-    no_sync: bool,
-) {
-    // Generate the `connected_node` string based on sync mode
-    let connected_node = if no_sync {
-        "Not connected".to_string()
-    } else {
-        config.endpoint.to_string().trim_end_matches('/').to_string()
-    };
-
-    // Log the banner
-    info!(target: "explorerd", "========================================================================================");
-    info!(target: "explorerd", "                   Started DarkFi Explorer Node{}                                        ",
-        if no_sync { " (No-Sync Mode)" } else { "" });
-    info!(target: "explorerd", "========================================================================================");
-    info!(target: "explorerd", "  - Network: {}", args.network);
-    info!(target: "explorerd", "  - JSON-RPC Endpoint: {}", config.rpc.rpc_listen.to_string().trim_end_matches('/'));
-    info!(target: "explorerd", "  - Database: {}", config.database);
-    info!(target: "explorerd", "  - Configuration: {}", config_path.to_str().unwrap_or("Error: configuration path not found!"));
-    info!(target: "explorerd", "  - Reset Blocks: {}", if args.reset { "Yes" } else { "No" });
-    info!(target: "explorerd", "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
-    info!(target: "explorerd", "  - Synced Blocks: {}", explorer.service.db.blockchain.len());
-    info!(target: "explorerd", "  - Synced Transactions: {}", explorer.service.db.blockchain.len());
-    info!(target: "explorerd", "  - Connected Darkfi Node: {connected_node}");
-    info!(target: "explorerd", "========================================================================================");
-}

+ 0 - 310
bin/explorer/explorerd/src/rpc/blocks.rs

@@ -1,310 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use tinyjson::JsonValue;
-
-use darkfi::{
-    blockchain::BlockInfo,
-    error::RpcError,
-    rpc::jsonrpc::{parse_json_array_number, parse_json_array_string},
-    util::encoding::base64,
-    Result,
-};
-use darkfi_serial::deserialize_async;
-
-use crate::{rpc::DarkfidRpcClient, Explorerd};
-
-impl DarkfidRpcClient {
-    /// Retrieves a block from at a given height returning the corresponding [`BlockInfo`].
-    pub async fn get_block_by_height(&self, height: u32) -> Result<BlockInfo> {
-        let params = self
-            .request(
-                "blockchain.get_block",
-                &JsonValue::Array(vec![JsonValue::Number(height as f64)]),
-            )
-            .await?;
-        let param = params.get::<String>().unwrap();
-        let bytes = base64::decode(param).unwrap();
-        let block = deserialize_async(&bytes).await?;
-        Ok(block)
-    }
-
-    /// Retrieves the last confirmed block returning the block height and its header hash.
-    pub async fn get_last_confirmed_block(&self) -> Result<(u32, String)> {
-        let rep =
-            self.request("blockchain.last_confirmed_block", &JsonValue::Array(vec![])).await?;
-        let params = rep.get::<Vec<JsonValue>>().unwrap();
-        let height = *params[0].get::<f64>().unwrap() as u32;
-        let hash = params[1].get::<String>().unwrap().clone();
-
-        Ok((height, hash))
-    }
-}
-
-impl Explorerd {
-    // RPCAPI:
-    // Queries the database to retrieve last N blocks.
-    // Returns an array of readable blocks upon success.
-    //
-    // **Params:**
-    // * `array[0]`: `u16` Number of blocks to retrieve (as string)
-    //
-    // **Returns:**
-    // * Array of `BlockRecord` encoded into a JSON.
-    //
-    // **Example API Usage:**
-    // --> {"jsonrpc": "2.0", "method": "blocks.get_last_n_blocks", "params": [10], "id": 1}
-    // <-- {"jsonrpc": "2.0", "result": {...}, "id": 1}
-    pub async fn blocks_get_last_n_blocks(&self, params: &JsonValue) -> Result<JsonValue> {
-        // Extract the number of last blocks to fetch
-        let num_last_blocks = parse_json_array_number("num_last_blocks", 0, params)? as usize;
-
-        // Fetch the blocks
-        let blocks_result = self.service.get_last_n(num_last_blocks)?;
-
-        // Transform blocks to `JsonValue`
-        if blocks_result.is_empty() {
-            Ok(JsonValue::Array(vec![]))
-        } else {
-            let json_blocks: Vec<JsonValue> =
-                blocks_result.into_iter().map(|block| block.to_json_array()).collect();
-            Ok(JsonValue::Array(json_blocks))
-        }
-    }
-
-    // RPCAPI:
-    // Queries the database to retrieve blocks in provided heights range.
-    // Returns an array of readable blocks upon success.
-    //
-    // **Params:**
-    // * `array[0]`: `u32` Starting height (as string)
-    // * `array[1]`: `u32` Ending height range (as string)
-    //
-    // **Returns:**
-    // * Array of `BlockRecord` encoded into a JSON.
-    //
-    // **Example API Usage:**
-    // --> {"jsonrpc": "2.0", "method": "blocks.get_blocks_in_heights_range", "params": [10, 15], "id": 1}
-    // <-- {"jsonrpc": "2.0", "result": {...}, "id": 1}
-    pub async fn blocks_get_blocks_in_heights_range(
-        &self,
-        params: &JsonValue,
-    ) -> Result<JsonValue> {
-        // Extract the start range
-        let start = parse_json_array_number("start", 0, params)? as u32;
-
-        // Extract the end range
-        let end = parse_json_array_number("end", 1, params)? as u32;
-
-        // Validate for valid range
-        if start > end {
-            return Err(RpcError::InvalidJson(format!(
-                "Invalid range: start ({start}) cannot be greater than end ({end})"
-            ))
-            .into());
-        }
-
-        // Fetch the blocks
-        let blocks_result = self.service.get_by_range(start, end)?;
-
-        // Transform blocks to `JsonValue` and return result
-        if blocks_result.is_empty() {
-            Ok(JsonValue::Array(vec![]))
-        } else {
-            let json_blocks: Vec<JsonValue> =
-                blocks_result.into_iter().map(|block| block.to_json_array()).collect();
-            Ok(JsonValue::Array(json_blocks))
-        }
-    }
-
-    // RPCAPI:
-    // Queries the database to retrieve the block corresponding to the provided hash.
-    // Returns the readable block upon success.
-    //
-    // **Params:**
-    // * `array[0]`: `String` Block header hash
-    //
-    // **Returns:**
-    // * `BlockRecord` encoded into a JSON.
-    //
-    // **Example API Usage:**
-    // --> {"jsonrpc": "2.0", "method": "blocks.get_block_by_hash", "params": ["5cc...2f9"], "id": 1}
-    // <-- {"jsonrpc": "2.0", "result": {...}, "id": 1}
-    pub async fn blocks_get_block_by_hash(&self, params: &JsonValue) -> Result<JsonValue> {
-        // Extract header hash
-        let header_hash = parse_json_array_string("header_hash", 0, params)?;
-
-        // Fetch and transform block to `JsonValue`
-        match self.service.get_block_by_hash(&header_hash)? {
-            Some(block) => Ok(block.to_json_array()),
-            None => Ok(JsonValue::Array(vec![])),
-        }
-    }
-}
-
-#[cfg(test)]
-/// Test module for validating the functionality of RPC methods related to explorer blocks.
-/// Focuses on ensuring proper error handling for invalid parameters across several use cases,
-/// including cases with missing values, unsupported types, invalid ranges, and unparsable inputs.
-mod tests {
-
-    use tinyjson::JsonValue;
-
-    use darkfi::rpc::{
-        jsonrpc::{ErrorCode, JsonRequest, JsonResult},
-        server::RequestHandler,
-    };
-
-    use crate::test_utils::{
-        setup, validate_invalid_rpc_header_hash, validate_invalid_rpc_parameter,
-    };
-
-    #[test]
-    /// Tests the handling of invalid parameters for the `blocks.get_last_n_blocks` JSON-RPC method.
-    /// Verifies that missing and an invalid `num_last_blocks` value results in an appropriate error.
-    fn test_blocks_get_last_n_blocks_invalid_params() {
-        smol::block_on(async {
-            // Define rpc_method and parameter names
-            let rpc_method = "blocks.get_last_n_blocks";
-            let parameter_name = "num_last_blocks";
-
-            // Set up the Explorerd instance
-            let explorerd = setup();
-
-            // Test for missing `start` parameter
-            validate_invalid_rpc_parameter(
-                &explorerd,
-                rpc_method,
-                &[],
-                ErrorCode::InvalidParams.code(),
-                &format!("Parameter '{parameter_name}' at index 0 is missing"),
-            )
-            .await;
-
-            // Test for invalid num_last_blocks parameter
-            validate_invalid_rpc_parameter(
-                &explorerd,
-                rpc_method,
-                &[JsonValue::String("invalid_number".to_string())],
-                ErrorCode::InvalidParams.code(),
-                &format!("Parameter '{parameter_name}' is not a supported number type"),
-            )
-            .await;
-        });
-    }
-
-    #[test]
-    /// Tests the handling of invalid parameters for the `blocks.get_blocks_in_heights_range`
-    /// JSON-RPC method. Verifies that invalid/missing `start` or `end` parameter values, or an
-    /// invalid range where `start` is greater than `end`, result in appropriate errors.
-    fn test_blocks_get_blocks_in_heights_range_invalid_params() {
-        smol::block_on(async {
-            // Define rpc_method and parameter names
-            let rpc_method = "blocks.get_blocks_in_heights_range";
-            let start_parameter_name = "start";
-            let end_parameter_name = "end";
-
-            // Set up the Explorerd instance
-            let explorerd = setup();
-
-            // Test for missing `start` parameter
-            validate_invalid_rpc_parameter(
-                &explorerd,
-                rpc_method,
-                &[],
-                ErrorCode::InvalidParams.code(),
-                &format!("Parameter '{start_parameter_name}' at index 0 is missing"),
-            )
-            .await;
-
-            // Test for invalid `start` parameter
-            validate_invalid_rpc_parameter(
-                &explorerd,
-                rpc_method,
-                &[JsonValue::String("invalid_number".to_string()), JsonValue::Number(10.0)],
-                ErrorCode::InvalidParams.code(),
-                &format!("Parameter '{start_parameter_name}' is not a supported number type"),
-            )
-            .await;
-
-            // Test for invalid `end` parameter
-            validate_invalid_rpc_parameter(
-                &explorerd,
-                rpc_method,
-                &[JsonValue::Number(10.0)],
-                ErrorCode::InvalidParams.code(),
-                &format!("Parameter '{end_parameter_name}' at index 1 is missing"),
-            )
-            .await;
-
-            // Test for invalid `end` parameter
-            validate_invalid_rpc_parameter(
-                &explorerd,
-                rpc_method,
-                &[JsonValue::Number(10.0), JsonValue::String("invalid_number".to_string())],
-                ErrorCode::InvalidParams.code(),
-                &format!("Parameter '{end_parameter_name}' is not a supported number type"),
-            )
-            .await;
-
-            // Test invalid range where `start` > `end`
-            let request = JsonRequest {
-                id: 1,
-                jsonrpc: "2.0",
-                method: rpc_method.to_string(),
-                params: JsonValue::Array(vec![JsonValue::Number(20.0), JsonValue::Number(10.0)]),
-            };
-
-            let response = explorerd.handle_request(request).await;
-
-            // Verify that `start > end` error is raised
-            match response {
-                JsonResult::Error(actual_error) => {
-                    let expected_error_code = ErrorCode::InvalidParams.code();
-                    assert_eq!(
-                        actual_error.error.code,
-                        expected_error_code
-                    );
-                    assert_eq!(
-                        actual_error.error.message,
-                        "Invalid range: start (20) cannot be greater than end (10)"
-                    );
-                }
-                _ => panic!(
-                    "Expected a JSON error response for method: {rpc_method}, but got something else",
-                ),
-            }
-        });
-    }
-    #[test]
-    /// Tests the handling of invalid parameters for the `blocks.get_block_by_hash` JSON-RPC method.
-    /// Verifies that an invalid `header_hash` value, either a numeric type or invalid hash string,
-    /// results in appropriate error.
-    fn test_blocks_get_block_by_hash_invalid_params() {
-        smol::block_on(async {
-            // Define the RPC method name
-            let rpc_method = "blocks.get_block_by_hash";
-
-            // Set up the explorerd
-            let explorerd = setup();
-
-            // Validate when provided with an invalid tx hash
-            validate_invalid_rpc_header_hash(&explorerd, rpc_method);
-        });
-    }
-}

+ 0 - 198
bin/explorer/explorerd/src/rpc/contracts.rs

@@ -1,198 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use std::str::FromStr;
-
-use tinyjson::JsonValue;
-
-use darkfi::{
-    rpc::jsonrpc::{parse_json_array_string, validate_empty_params},
-    Result,
-};
-use darkfi_sdk::crypto::ContractId;
-
-use crate::{error::ExplorerdError, Explorerd};
-
-impl Explorerd {
-    // RPCAPI:
-    // Retrieves the native contracts deployed in the DarkFi network.
-    // Returns a JSON array containing Contract IDs along with their associated metadata upon success.
-    //
-    // **Params:**
-    // * `None`
-    //
-    // **Returns:**
-    // * Array of `ContractRecord`s encoded into a JSON.
-    //
-    // **Example API Usage:**
-    // --> {"jsonrpc": "2.0", "method": "contracts.get_native_contracts", "params": ["5cc...2f9"], "id": 1}
-    // <-- {"jsonrpc": "2.0", "result": ["BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o", "Money Contract", "The money contract..."], "id": 1}
-    pub async fn contracts_get_native_contracts(&self, params: &JsonValue) -> Result<JsonValue> {
-        // Validate that no parameters are provided
-        validate_empty_params(params)?;
-
-        // Retrieve native contracts
-        let contract_records = self.service.get_native_contracts()?;
-
-        // Transform contract records into a JSON array and return result
-        if contract_records.is_empty() {
-            Ok(JsonValue::Array(vec![]))
-        } else {
-            let json_blocks: Vec<JsonValue> = contract_records
-                .into_iter()
-                .map(|contract_record| contract_record.to_json_array())
-                .collect();
-            Ok(JsonValue::Array(json_blocks))
-        }
-    }
-
-    // RPCAPI:
-    // Retrieves the source code paths for the contract associated with the specified Contract ID.
-    // Returns a JSON array containing the source code paths upon success.
-    //
-    // **Params:**
-    // * `array[0]`: `String` Contract ID
-    //
-    // **Returns:**
-    // * `JsonArray` containing source code paths for the specified Contract ID.
-    //
-    // **Example API Usage:**
-    // --> {"jsonrpc": "2.0", "method": "contracts.get_contract_source_code_paths", "params": ["BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o"], "id": 1}
-    // <-- {"jsonrpc": "2.0", "result": ["path/to/source1.rs", "path/to/source2.rs"], "id": 1}
-    pub async fn contracts_get_contract_source_code_paths(
-        &self,
-        params: &JsonValue,
-    ) -> Result<JsonValue> {
-        // Extract contract ID
-        let contact_id_str = parse_json_array_string("contract_id", 0, params)?;
-
-        // Convert the contract string to a `ContractId` instance
-        let contract_id = ContractId::from_str(&contact_id_str)
-            .map_err(|_| ExplorerdError::InvalidContractId(contact_id_str))?;
-
-        // Retrieve source code paths for the contract
-        let paths = self.service.get_contract_source_paths(&contract_id)?;
-
-        // Tranform found paths into `JsonValues`
-        let json_value_paths = paths.iter().map(|path| JsonValue::String(path.clone())).collect();
-
-        Ok(JsonValue::Array(json_value_paths))
-    }
-
-    // RPCAPI:
-    // Retrieves contract source code content using the provided Contract ID and source path.
-    // Returns the source code content as a JSON string upon success.
-    //
-    // **Params:**
-    // * `array[0]`: `String` Contract ID
-    // * `array[1]`: `String` Source path
-    //
-    // **Returns:**
-    // * `String` containing the content of the contract source file.
-    //
-    // **Example API Usage:**
-    // --> {"jsonrpc": "2.0", "method": "contracts.get_contract_source", "params": ["BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o", "client/lib.rs"], "id": 1}
-    // <-- {"jsonrpc": "2.0", "result": "/* This file is ...", "id": 1}
-    pub async fn contracts_get_contract_source(&self, params: &JsonValue) -> Result<JsonValue> {
-        // Extract the contract ID
-        let contact_id_str = parse_json_array_string("contract_id", 0, params)?;
-
-        // Convert the contract string to a `ContractId` instance
-        let contract_id = ContractId::from_str(&contact_id_str)
-            .map_err(|_| ExplorerdError::InvalidContractId(contact_id_str))?;
-
-        // Extract the source path
-        let source_path = parse_json_array_string("source_path", 1, params)?;
-
-        // Retrieve the contract source code, transform it into a `JsonValue`, and return the result
-        match self.service.get_contract_source_content(&contract_id, &source_path)? {
-            Some(source_file) => Ok(JsonValue::String(source_file)),
-            None => Ok(JsonValue::from(std::collections::HashMap::<String, JsonValue>::new())),
-        }
-    }
-}
-
-#[cfg(test)]
-/// Test module for validating the functionality of RPC methods related to explorer contracts.
-/// Focuses on ensuring proper error handling for invalid parameters across several use cases,
-/// including cases with missing values, unsupported types, and unparsable inputs.
-mod tests {
-
-    use tinyjson::JsonValue;
-
-    use darkfi::rpc::jsonrpc::ErrorCode;
-
-    use crate::test_utils::{
-        setup, validate_empty_rpc_parameters, validate_invalid_rpc_contract_id,
-        validate_invalid_rpc_parameter,
-    };
-
-    #[test]
-    /// Tests the `contracts.get_native_contracts` method to ensure it correctly handles cases where
-    /// empty parameters are supplied, returning an expected result or error response.
-    fn test_contracts_get_native_contracts_empty_params() {
-        smol::block_on(async {
-            validate_empty_rpc_parameters(&setup(), "contracts.get_native_contracts").await;
-        });
-    }
-
-    #[test]
-    /// Tests the `contracts.get_contract_source_code_paths` method to ensure it correctly handles cases
-    /// with invalid or missing `contract_id` parameters, returning appropriate error responses.
-    fn test_contracts_get_contract_source_code_paths_invalid_params() {
-        validate_invalid_rpc_contract_id(&setup(), "contracts.get_contract_source_code_paths");
-    }
-
-    #[test]
-    /// Tests the `contracts.get_contract_source` method to ensure it correctly handles cases
-    /// with invalid or missing parameters, returning appropriate error responses.
-    fn test_contracts_get_contract_source_invalid_params() {
-        let test_method = "contracts.get_contract_source";
-        let parameter_name = "source_path";
-
-        smol::block_on(async {
-            // Set up the explorerd instance
-            let explorerd = setup();
-
-            validate_invalid_rpc_contract_id(&explorerd, test_method);
-
-            // Test for missing `source_path` parameter
-            validate_invalid_rpc_parameter(
-                &explorerd,
-                test_method,
-                &[JsonValue::String("BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o".to_string())],
-                ErrorCode::InvalidParams.code(),
-                &format!("Parameter '{parameter_name}' at index 1 is missing"),
-            )
-            .await;
-
-            // Test for invalid `source_path` parameter
-            validate_invalid_rpc_parameter(
-                &explorerd,
-                test_method,
-                &[
-                    JsonValue::String("BZHKGQ26bzmBithTQYTJtjo2QdCqpkR9tjSBopT4yf4o".to_string()),
-                    JsonValue::Number(123.0), // Invalid `source_path` type
-                ],
-                ErrorCode::InvalidParams.code(),
-                &format!("Parameter '{parameter_name}' is not a valid string"),
-            )
-            .await;
-        });
-    }
-}

+ 0 - 395
bin/explorer/explorerd/src/rpc/mod.rs

@@ -1,395 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use std::{collections::HashSet, sync::Arc, time::Instant};
-
-use async_trait::async_trait;
-use smol::lock::{MutexGuard, RwLock};
-use tinyjson::JsonValue;
-use tracing::{debug, error, trace, warn};
-use url::Url;
-
-use darkfi::{
-    error::RpcError,
-    rpc::{
-        client::RpcClient,
-        jsonrpc::{
-            validate_empty_params, ErrorCode, JsonError, JsonRequest, JsonResponse, JsonResult,
-        },
-        server::RequestHandler,
-    },
-    system::StoppableTaskPtr,
-    Error, Result,
-};
-
-use crate::{
-    error::{server_error, ExplorerdError},
-    Explorerd,
-};
-
-/// RPC block related requests
-pub mod blocks;
-
-/// RPC handlers for contract-related perations
-pub mod contracts;
-
-/// RPC handlers for blockchain statistics and metrics
-pub mod statistics;
-
-/// RPC handlers for transaction data, lookups, and processing
-pub mod transactions;
-
-#[async_trait]
-impl RequestHandler<()> for Explorerd {
-    /// Handles an incoming JSON-RPC request by executing the appropriate individual request handler
-    /// implementation based on the request's `method` field and using the provided parameters.
-    /// Supports methods across various categories, including block-related queries, contract interactions,
-    /// transaction lookups, statistical queries, and miscellaneous operations. If an invalid
-    /// method is requested, an appropriate error is returned.
-    ///
-    /// The function performs the error handling, allowing individual RPC method handlers to propagate
-    /// errors via the `?` operator. It ensures uniform translation of errors into JSON-RPC error responses.
-    /// Additionally, it handles the creation of `JsonResponse` or `JsonError` objects, enabling method
-    /// handlers to focus solely on core logic. Individual RPC handlers return a `JsonValue`, which this
-    /// function translates into the corresponding `JsonResult`.
-    ///
-    /// Unified logging is incorporated, so individual handlers only propagate the error
-    /// for it to be logged. Logs include detailed error information, such as method names, parameters,
-    /// and JSON-RPC errors, providing consistent and informative error trails for debugging.
-    ///
-    /// ## Example Log Message
-    /// ```
-    /// 05:11:02 [ERROR] RPC Request Failure: method: transactions.get_transactions_by_header_hash,
-    /// params: ["0x0222"], error: {"error":{"code":-32602,"message":"Invalid header hash: 0x0222"},
-    /// "id":1,"jsonrpc":"2.0"}
-    /// ```
-    async fn handle_request(&self, req: JsonRequest) -> JsonResult {
-        debug!(target: "explorerd::rpc", "--> {}", req.stringify().unwrap());
-
-        // Store method and params for later use
-        let method = req.method.as_str();
-        let params = &req.params;
-
-        // Handle ping case, as it returns a JsonResponse
-        if method == "ping" {
-            return self.pong(req.id, params.clone()).await
-        }
-
-        // Match all other methods
-        let result = match req.method.as_str() {
-            // =====================
-            // Blocks methods
-            // =====================
-            "blocks.get_last_n_blocks" => self.blocks_get_last_n_blocks(params).await,
-            "blocks.get_blocks_in_heights_range" => {
-                self.blocks_get_blocks_in_heights_range(params).await
-            }
-            "blocks.get_block_by_hash" => self.blocks_get_block_by_hash(params).await,
-
-            // =====================
-            // Transactions methods
-            // =====================
-            "transactions.get_transactions_by_header_hash" => {
-                self.transactions_get_transactions_by_header_hash(params).await
-            }
-            "transactions.get_transaction_by_hash" => {
-                self.transactions_get_transaction_by_hash(params).await
-            }
-
-            // =====================
-            // Statistics methods
-            // =====================
-            "statistics.get_basic_statistics" => self.statistics_get_basic_statistics(params).await,
-            "statistics.get_metric_statistics" => {
-                self.statistics_get_metric_statistics(params).await
-            }
-            "statistics.get_latest_metric_statistics" => {
-                self.statistics_get_latest_metric_statistics(params).await
-            }
-
-            // =====================
-            // Contract methods
-            // =====================
-            "contracts.get_native_contracts" => self.contracts_get_native_contracts(params).await,
-            "contracts.get_contract_source_code_paths" => {
-                self.contracts_get_contract_source_code_paths(params).await
-            }
-            "contracts.get_contract_source" => self.contracts_get_contract_source(params).await,
-
-            // =====================
-            // Miscellaneous methods
-            // =====================
-            "ping_darkfid" => self.ping_darkfid(params).await,
-
-            // TODO: add any other useful methods
-
-            // ==============
-            // Invalid method
-            // ==============
-            _ => Err(RpcError::MethodNotFound(method.to_string()).into()),
-        };
-
-        // Process the result of the individual request handler, handling success or errors and translating
-        // them into an appropriate `JsonResult`.
-        match result {
-            // Successfully completed the request
-            Ok(value) => JsonResponse::new(value, req.id).into(),
-
-            // Handle errors when processing parameters
-            Err(Error::RpcServerError(RpcError::InvalidJson(e))) => {
-                let json_error =
-                    JsonError::new(ErrorCode::InvalidParams, Some(e.to_string()), req.id);
-
-                // Log the parameter error
-                log_request_failure(&req.method, params, &json_error);
-
-                // Convert error to JsonResult
-                json_error.into()
-            }
-
-            // Handle server errors
-            Err(Error::RpcServerError(RpcError::ServerError(e))) => {
-                // Remove the extra '&' and reference directly from e
-                let json_error = match e.downcast_ref::<ExplorerdError>() {
-                    Some(e_expl) => {
-                        // Successfully downcast to ExplorerdRpcError; call the typed function
-                        server_error(e_expl, req.id, None)
-                    }
-                    None => {
-                        // Return InternalError with the logged details
-                        JsonError::new(ErrorCode::InternalError, Some(e.to_string()), req.id)
-                    }
-                };
-
-                // Log the server error
-                log_request_failure(&req.method, params, &json_error);
-
-                // Convert error to JsonResult
-                json_error.into()
-            }
-
-            // Catch-all for any other unexpected errors
-            Err(e) => {
-                // Return InternalError with the logged details
-                let json_error =
-                    JsonError::new(ErrorCode::InternalError, Some(e.to_string()), req.id);
-
-                // Log the unexpected error
-                log_request_failure(&req.method, params, &json_error);
-
-                // Convert error to JsonResult
-                json_error.into()
-            }
-        }
-    }
-
-    async fn connections_mut(&self) -> MutexGuard<'_, HashSet<StoppableTaskPtr>> {
-        self.rpc_connections.lock().await
-    }
-}
-
-/// A RPC client for interacting with a Darkfid JSON-RPC endpoint, enabling communication with Darkfid blockchain nodes.
-/// Supports connection management, request handling, and graceful shutdowns.
-/// Implemented for shared access across ownership boundaries using `Arc`, with connection state managed via an `RwLock`.
-pub struct DarkfidRpcClient {
-    /// JSON-RPC client used to communicate with the Darkfid daemon. A value of `None` indicates no active connection.
-    /// The `RwLock` allows the client to be shared across ownership boundaries while managing the connection state.
-    rpc_client: RwLock<Option<RpcClient>>,
-}
-
-impl DarkfidRpcClient {
-    /// Creates a new client with an inactive connection.
-    pub fn new() -> Self {
-        Self { rpc_client: RwLock::new(None) }
-    }
-
-    /// Checks if there is an active connection to Darkfid.
-    pub async fn connected(&self) -> Result<bool> {
-        Ok(self.rpc_client.read().await.is_some())
-    }
-
-    /// Establishes a connection to the Darkfid node, storing the resulting client if successful.
-    /// If already connected, logs a message and returns without connecting again.
-    pub async fn connect(&self, endpoint: Url, ex: Arc<smol::Executor<'static>>) -> Result<()> {
-        let mut rpc_client_guard = self.rpc_client.write().await;
-
-        if rpc_client_guard.is_some() {
-            warn!(target: "explorerd::rpc::connect", "Already connected to darkfid");
-            return Ok(());
-        }
-
-        *rpc_client_guard = Some(RpcClient::new(endpoint, ex).await?);
-        Ok(())
-    }
-
-    /// Closes the connection with the connected darkfid, returning if there is no active connection.
-    /// If the connection is stopped, sets `rpc_client` to `None`.
-    pub async fn stop(&self) -> Result<()> {
-        let mut rpc_client_guard = self.rpc_client.write().await;
-
-        // If there's an active connection, stop it and clear the reference
-        if let Some(ref rpc_client) = *rpc_client_guard {
-            rpc_client.stop().await;
-            *rpc_client_guard = None;
-            return Ok(());
-        }
-
-        // If there's no connection, log the message and do nothing
-        warn!(target: "explorerd::rpc::stop", "Not connected to darkfid, nothing to stop.");
-        Ok(())
-    }
-
-    /// Sends a request to the client's Darkfid JSON-RPC endpoint using the given method and parameters.
-    /// Returns the received response or an error if no active connection to Darkfid exists.
-    pub async fn request(&self, method: &str, params: &JsonValue) -> Result<JsonValue> {
-        let rpc_client_guard = self.rpc_client.read().await;
-
-        if let Some(ref rpc_client) = *rpc_client_guard {
-            debug!(target: "explorerd::rpc::request", "Executing request {method} with params: {params:?}");
-            let latency = Instant::now();
-            let req = JsonRequest::new(method, params.clone());
-            let rep = rpc_client.request(req).await?;
-            let latency = latency.elapsed();
-            trace!(target: "explorerd::rpc::request", "Got reply: {rep:?}");
-            debug!(target: "explorerd::rpc::request", "Latency: {latency:?}");
-            return Ok(rep);
-        };
-
-        Err(Error::Custom("Not connected, is the explorer running in no-sync mode?".to_string()))
-    }
-
-    /// Sends a ping request to the client's darkfid endpoint to verify connectivity,
-    /// returning `true` if the ping is successful or an error if the request fails.
-    async fn ping(&self) -> Result<bool> {
-        self.request("ping", &JsonValue::Array(vec![])).await?;
-        Ok(true)
-    }
-}
-
-impl Default for DarkfidRpcClient {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
-impl Explorerd {
-    // RPCAPI:
-    // Pings configured darkfid daemon for liveness.
-    // Returns `true` on success.
-    //
-    // **Example API Usage:**
-    // --> {"jsonrpc": "2.0", "method": "ping_darkfid", "params": [], "id": 1}
-    // <-- {"jsonrpc": "2.0", "result": true, "id": 1}
-    async fn ping_darkfid(&self, params: &JsonValue) -> Result<JsonValue> {
-        // Log the start of the operation
-        debug!(target: "explorerd::rpc::ping_darkfid", "Pinging darkfid daemon...");
-
-        // Validate that the parameters are empty
-        validate_empty_params(params)?;
-
-        // Attempt to ping the darkfid daemon
-        self.darkfid_client
-            .ping()
-            .await
-            .map_err(|e| ExplorerdError::PingDarkfidFailed(e.to_string()))?;
-
-        // Ping succeeded, return a successful Boolean(true) value
-        Ok(JsonValue::Boolean(true))
-    }
-}
-
-/// Auxiliary function that logs RPC request failures by generating a structured log message
-/// containing the provided `req_method`, `params`, and `error` details.
-/// Formats the error message by stringifying the JSON parameters and error,
-/// and performs the log operation without returning a value.
-fn log_request_failure(req_method: &str, params: &JsonValue, error: &JsonError) {
-    // Stringify the params
-    let params_stringified = match params.stringify() {
-        Ok(params) => params,
-        Err(e) => format!("Failed to stringify params: {e:?}"),
-    };
-
-    // Stringfy the error
-    let error_stringified = match error.stringify() {
-        Ok(err_str) => err_str,
-        Err(e) => format!("Failed to stringify error: {e:?}"),
-    };
-
-    // Format the error message for the log
-    let error_message = format!("RPC Request Failure: method: {req_method}, params: {params_stringified}, error: {error_stringified}");
-
-    // Log the error
-    error!(target: "explorerd::rpc::handle_request", "{error_message}");
-}
-
-/// Test module for validating API functions within this `mod.rs` file. It ensures that the core API
-/// functions behave as expected and that they handle invalid parameters properly.
-#[cfg(test)]
-mod tests {
-    use tinyjson::JsonValue;
-
-    use darkfi::rpc::jsonrpc::JsonRequest;
-
-    use super::*;
-    use crate::{
-        error::ERROR_CODE_PING_DARKFID_FAILED,
-        test_utils::{setup, validate_empty_rpc_parameters},
-    };
-
-    #[test]
-    /// Validates the failure scenario of the `ping_darkfid` JSON-RPC method by sending a request
-    /// to a disconnected darkfid endpoint, ensuring the response is an error with the expected
-    /// code and message.
-    fn test_ping_darkfid_failure() {
-        smol::block_on(async {
-            // Set up the Explorerd instance
-            let explorerd = setup();
-
-            // Prepare a JSON-RPC request for `ping_darkfid`
-            let request = JsonRequest {
-                id: 1,
-                jsonrpc: "2.0",
-                method: "ping_darkfid".to_string(),
-                params: JsonValue::Array(vec![]),
-            };
-
-            // Call `handle_request` on the Explorerd instance
-            let response = explorerd.handle_request(request).await;
-
-            // Verify the response is a `JsonError` with the `PingFailed` error code
-            match response {
-                JsonResult::Error(actual_error) => {
-                    let expected_error_code = ERROR_CODE_PING_DARKFID_FAILED;
-                    let expected_error_msg = "Ping darkfid failed: Not connected, is the explorer running in no-sync mode?";
-                    assert_eq!(actual_error.error.code, expected_error_code);
-                    assert_eq!(actual_error.error.message, expected_error_msg);
-                }
-                _ => panic!("Expected a JSON object for the response, but got something else"),
-            }
-        });
-    }
-
-    /// Tests the `ping_darkfid` method to ensure it correctly handles cases where non-empty parameters
-    /// are supplied, returning an expected error response.
-    #[test]
-    fn test_ping_darkfid_empty_params() {
-        smol::block_on(async {
-            validate_empty_rpc_parameters(&setup(), "ping_darkfid").await;
-        });
-    }
-}

+ 0 - 132
bin/explorer/explorerd/src/rpc/statistics.rs

@@ -1,132 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use std::vec::Vec;
-
-use tinyjson::JsonValue;
-
-use darkfi::{rpc::jsonrpc::validate_empty_params, Result};
-
-use crate::Explorerd;
-
-impl Explorerd {
-    // RPCAPI:
-    // Queries the database to retrieve current basic statistics.
-    // Returns the readable transaction upon success.
-    //
-    // **Params:**
-    // * `None`
-    //
-    // **Returns:**
-    // * `BaseStatistics` encoded into a JSON.
-    //
-    // **Example API Usage:**
-    // --> {"jsonrpc": "2.0", "method": "statistics.get_basic_statistics", "params": [], "id": 1}
-    // <-- {"jsonrpc": "2.0", "result": {...}, "id": 1}
-    pub async fn statistics_get_basic_statistics(&self, params: &JsonValue) -> Result<JsonValue> {
-        // Validate that no parameters are provided
-        validate_empty_params(params)?;
-
-        // Attempt to retrieve base statistics; if found, convert to a JSON array,
-        // otherwise return an empty JSON array
-        match self.service.get_base_statistics()? {
-            Some(statistics) => Ok(statistics.to_json_array()),
-            None => Ok(JsonValue::Array(vec![])),
-        }
-    }
-
-    // RPCAPI:
-    // Queries the database to retrieve all metrics statistics.
-    // Returns a collection of metric statistics upon success.
-    //
-    // **Params:**
-    // * `None`
-    //
-    // **Returns:**
-    // * `MetricsStatistics` array encoded into a JSON.
-    //
-    // **Example API Usage:**
-    // --> {"jsonrpc": "2.0", "method": "statistics.get_metric_statistics", "params": [], "id": 1}
-    // <-- {"jsonrpc": "2.0", "result": {...}, "id": 1}
-    pub async fn statistics_get_metric_statistics(&self, params: &JsonValue) -> Result<JsonValue> {
-        // Validate that no parameters are provided
-        validate_empty_params(params)?;
-
-        // Retrieve metric statistics
-        let statistics = self.service.get_metrics_statistics().await?;
-
-        // Convert each metric statistic into a JSON array, returning the collected array
-        let statistics_json: Vec<JsonValue> =
-            statistics.iter().map(|m| m.to_json_array()).collect();
-        Ok(JsonValue::Array(statistics_json))
-    }
-
-    // RPCAPI:
-    // Queries the database to retrieve latest metric statistics.
-    // Returns the readable metric statistics upon success.
-    //
-    // **Params:**
-    // * `None`
-    //
-    // **Returns:**
-    // * `MetricsStatistics` encoded into a JSON.
-    //
-    // **Example API Usage:**
-    // --> {"jsonrpc": "2.0", "method": "statistics.get_latest_metric_statistics", "params": [], "id": 1}
-    // <-- {"jsonrpc": "2.0", "result": {...}, "id": 1}
-    pub async fn statistics_get_latest_metric_statistics(
-        &self,
-        params: &JsonValue,
-    ) -> Result<JsonValue> {
-        // Validate that no parameters are provided
-        validate_empty_params(params)?;
-
-        // Retrieve the latest statistics
-        let statistics = self.service.get_latest_metrics_statistics().await?;
-
-        // Convert the retrieved metrics into a JSON array and return it
-        Ok(statistics.to_json_array())
-    }
-}
-
-#[cfg(test)]
-/// Test module for validating the functionality of RPC methods related to explorer statistics.
-/// Focuses on ensuring proper error handling for invalid parameters across several use cases.
-mod tests {
-
-    use crate::test_utils::{setup, validate_empty_rpc_parameters};
-
-    /// Tests all RPC-related statistics calls when provided with empty parameters, ensuring they
-    /// handle the input correctly and return appropriate validation responses.
-    #[test]
-    fn test_statistics_rpc_calls_for_empty_parameters() {
-        smol::block_on(async {
-            let explorerd = setup();
-
-            let rpc_methods = [
-                "statistics.get_latest_metric_statistics",
-                "statistics.get_metric_statistics",
-                "statistics.get_basic_statistics",
-            ];
-
-            for rpc_method in rpc_methods.iter() {
-                validate_empty_rpc_parameters(&explorerd, rpc_method).await;
-            }
-        });
-    }
-}

+ 0 - 127
bin/explorer/explorerd/src/rpc/transactions.rs

@@ -1,127 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use tinyjson::JsonValue;
-
-use darkfi::{rpc::jsonrpc::parse_json_array_string, Result};
-use darkfi_sdk::tx::TransactionHash;
-
-use crate::{error::ExplorerdError, Explorerd};
-
-impl Explorerd {
-    // RPCAPI:
-    // Queries the database to retrieve the transactions corresponding to the provided block header hash.
-    // Returns the readable transactions upon success.
-    //
-    // **Params:**
-    // * `array[0]`: `String` Block header hash
-    //
-    // **Returns:**
-    // * Array of `TransactionRecord` encoded into a JSON.
-    //
-    // **Example API Usage:**
-    // --> {"jsonrpc": "2.0", "method": "transactions.get_transactions_by_header_hash", "params": ["5cc...2f9"], "id": 1}
-    // <-- {"jsonrpc": "2.0", "result": {...}, "id": 1}
-    pub async fn transactions_get_transactions_by_header_hash(
-        &self,
-        params: &JsonValue,
-    ) -> Result<JsonValue> {
-        // Extract header hash
-        let header_hash = parse_json_array_string("header_hash", 0, params)?;
-
-        // Retrieve transactions by header hash
-        let transactions = self.service.get_transactions_by_header_hash(&header_hash)?;
-
-        // Convert transactions into a JSON array, return result
-        Ok(JsonValue::Array(transactions.iter().map(|tx| tx.to_json_array()).collect()))
-    }
-
-    // RPCAPI:
-    // Queries the database to retrieve the transaction corresponding to the provided hash.
-    // Returns the readable transaction upon success.
-    //
-    // **Params:**
-    // * `array[0]`: `String` Transaction hash
-    //
-    // **Returns:**
-    // * `TransactionRecord` encoded into a JSON.
-    //
-    // **Example API Usage:**
-    // --> {"jsonrpc": "2.0", "method": "transactions.get_transaction_by_hash", "params": ["7e7...b4d"], "id": 1}
-    // <-- {"jsonrpc": "2.0", "result": {...}, "id": 1}
-    pub async fn transactions_get_transaction_by_hash(
-        &self,
-        params: &JsonValue,
-    ) -> Result<JsonValue> {
-        // Extract transaction hash
-        let tx_hash_str = parse_json_array_string("tx_hash", 0, params)?;
-
-        // Convert the provided hash into a `TransactionHash` instance
-        let tx_hash = tx_hash_str
-            .parse::<TransactionHash>()
-            .map_err(|_| ExplorerdError::InvalidTxHash(tx_hash_str.to_string()))?;
-
-        // Retrieve the transaction by its hash, returning the result as a JsonValue array
-        match self.service.get_transaction_by_hash(&tx_hash)? {
-            Some(transaction) => Ok(transaction.to_json_array()),
-            None => Ok(JsonValue::Array(vec![])),
-        }
-    }
-}
-
-#[cfg(test)]
-/// Test module for validating the functionality of RPC methods related to explorer transactions.
-/// Focuses on ensuring proper error handling for invalid parameters across several use cases,
-/// including cases with missing values, unsupported types, and unparsable inputs.
-mod tests {
-
-    use crate::test_utils::{
-        setup, validate_invalid_rpc_header_hash, validate_invalid_rpc_tx_hash,
-    };
-
-    #[test]
-    /// Tests the handling of invalid parameters for the `transactions.get_transactions_by_header_hash` JSON-RPC method.
-    /// Verifies that missing and an invalid `header_hash` value results in an appropriate error.
-    fn test_transactions_get_transactions_by_header_hash() {
-        smol::block_on(async {
-            // Define the RPC method name
-            let rpc_method = "transactions.get_transactions_by_header_hash";
-
-            // Set up the explorerd
-            let explorerd = setup();
-
-            // Validate when provided with an invalid header hash
-            validate_invalid_rpc_header_hash(&explorerd, rpc_method);
-        });
-    }
-    #[test]
-    /// Tests the handling of invalid parameters for the `transactions.get_transaction_by_hash` JSON-RPC method.
-    /// Verifies that missing and an invalid `tx_hash` value results in an appropriate error.
-    fn test_transactions_get_transaction_by_hash() {
-        smol::block_on(async {
-            // Define the RPC method name
-            let rpc_method = "transactions.get_transaction_by_hash";
-
-            // Set up the explorerd
-            let explorerd = setup();
-
-            // Validate when provided with an invalid tx hash
-            validate_invalid_rpc_tx_hash(&explorerd, rpc_method);
-        });
-    }
-}

+ 0 - 398
bin/explorer/explorerd/src/service/blocks.rs

@@ -1,398 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use sled_overlay::sled::{transaction::ConflictableTransactionError, Transactional};
-use tinyjson::JsonValue;
-use tracing::{debug, warn};
-
-use darkfi::{
-    blockchain::{
-        block_store::append_tx_to_merkle_tree, BlockInfo, BlockchainOverlay, HeaderHash,
-        SLED_BLOCK_DIFFICULTY_TREE, SLED_BLOCK_ORDER_TREE, SLED_BLOCK_TREE,
-    },
-    runtime::vm_runtime::Runtime,
-    util::time::Timestamp,
-    Error, Result,
-};
-use darkfi_sdk::{
-    crypto::{schnorr::Signature, MerkleTree},
-    tx::TransactionHash,
-    ContractError,
-};
-use darkfi_serial::AsyncEncodable;
-
-use crate::{error::ExplorerdError, ExplorerService};
-
-#[derive(Debug, Clone)]
-/// Structure representing a block record.
-pub struct BlockRecord {
-    /// Header hash identifier of the block
-    pub header_hash: String,
-    /// Block version
-    pub version: u8,
-    /// Previous block hash
-    pub previous: String,
-    /// Block height
-    pub height: u32,
-    /// Block creation timestamp
-    pub timestamp: Timestamp,
-    /// The block's nonce. This value changes arbitrarily with mining.
-    pub nonce: u32,
-    /// Merkle tree root of the transactions hashes contained in this block
-    pub transactions_root: String,
-    /// Contracts states Monotree(SMT) root this block commits to
-    pub state_root: String,
-    /// Block Proof of Work type
-    pub pow_data: String,
-    /// Block producer signature
-    pub signature: Signature,
-}
-
-impl BlockRecord {
-    /// Auxiliary function to convert a `BlockRecord` into a `JsonValue` array.
-    pub fn to_json_array(&self) -> JsonValue {
-        JsonValue::Array(vec![
-            JsonValue::String(self.header_hash.clone()),
-            JsonValue::Number(self.version as f64),
-            JsonValue::String(self.previous.clone()),
-            JsonValue::Number(self.height as f64),
-            JsonValue::String(self.timestamp.to_string()),
-            JsonValue::Number(self.nonce as f64),
-            JsonValue::String(self.transactions_root.clone()),
-            JsonValue::String(self.state_root.clone()),
-            JsonValue::String(self.pow_data.clone()),
-            JsonValue::String(format!("{:?}", self.signature)),
-        ])
-    }
-}
-
-impl From<&BlockInfo> for BlockRecord {
-    fn from(block: &BlockInfo) -> Self {
-        Self {
-            header_hash: block.hash().to_string(),
-            version: block.header.version,
-            previous: block.header.previous.to_string(),
-            height: block.header.height,
-            timestamp: block.header.timestamp,
-            nonce: block.header.nonce,
-            transactions_root: block.header.transactions_root.to_string(),
-            state_root: blake3::Hash::from_bytes(block.header.state_root).to_string(),
-            pow_data: format!("{:?}", block.header.pow_data),
-            signature: block.signature,
-        }
-    }
-}
-
-impl ExplorerService {
-    /// Resets blocks in the database by clearing all block related trees, returning an Ok result on success.
-    pub fn reset_blocks(&self) -> Result<()> {
-        let db = &self.db.blockchain.sled_db;
-        // Initialize block related trees to reset
-        let trees_to_reset = [SLED_BLOCK_TREE, SLED_BLOCK_ORDER_TREE, SLED_BLOCK_DIFFICULTY_TREE];
-
-        // Iterate over each tree and remove its entries
-        for tree_name in &trees_to_reset {
-            let tree = db.open_tree(tree_name)?;
-            tree.clear()?;
-            let tree_name_str = std::str::from_utf8(tree_name)?;
-            debug!(target: "explorerd::blocks", "Successfully reset block tree: {tree_name_str}");
-        }
-
-        Ok(())
-    }
-
-    /// Adds the provided [`BlockInfo`] to the block explorer database.
-    ///
-    /// This function processes each transaction in the block, calculating
-    /// and updating the latest [`crate::store::metrics::GasMetrics`] for
-    /// non-genesis blocks and for transactions that are not PoW rewards.
-    /// PoW reward transactions update the contract runtime state as required.
-    /// After processing all transactions, the block is permanently persisted to
-    /// the explorer database.
-    pub async fn put_block(&self, block: &BlockInfo) -> Result<()> {
-        let blockchain_overlay = BlockchainOverlay::new(&self.db.blockchain)?;
-        let mut tree = MerkleTree::new(1);
-
-        // Initialize collections that store gas related data
-        let mut tx_gas_data = Vec::with_capacity(block.txs.len());
-        let mut txs_hashes_with_gas_data = Vec::with_capacity(block.txs.len());
-
-        // Apply transactions to WASM runtime for PoW rewards or calculate fees
-        for (idx, tx) in block.txs.iter().enumerate() {
-            // Apply PoW reward transactions to update contract runtime state
-            if tx.is_pow_reward() {
-                let mut payload = vec![];
-                tx.calls.encode_async(&mut payload).await?;
-
-                let call = &tx.calls[0];
-                let wasm =
-                    blockchain_overlay.lock().unwrap().contracts.get(call.data.contract_id)?;
-                let block_target = self.db.blockchain.blocks.get_last()?.0 + 1;
-
-                let mut runtime = Runtime::new(
-                    &wasm,
-                    blockchain_overlay.clone(),
-                    call.data.contract_id,
-                    block.header.height,
-                    block_target,
-                    tx.hash(),
-                    0,
-                )?;
-
-                // Execute and apply state changes
-                let mut state_update = vec![call.data.data[0]];
-                let exec_result = runtime.exec(&payload);
-
-                // Handle duplicate coin error (thrown as Custom(7)) after a reorg.
-                // Ensures blocks with PoW reward coin already applied to contract state syncs.
-                if let Err(Error::ContractError(ContractError::Custom(7))) = exec_result {
-                    warn!(target: "explorerd::blocks::put_block",
-                        "PoW reward coin already applied to the contract state for contract ID {} at height {} for tx: {}. Skipping re-application.",
-                        call.data.contract_id,
-                        block.header.height,
-                        tx.hash()
-                    );
-                    continue;
-                }
-
-                state_update.append(&mut exec_result?);
-                runtime.apply(&state_update)?;
-
-                // Append transaction hash to the Merkle tree
-                append_tx_to_merkle_tree(&mut tree, tx);
-
-                // No gas calculations needed for PoW rewards, proceed to next transaction
-                continue;
-            }
-
-            // Calculate gas data for non-PoW reward transactions and non-genesis blocks
-            if block.header.height != 0 {
-                tx_gas_data.insert(idx, self.calculate_tx_gas_data(tx, true).await?);
-                txs_hashes_with_gas_data.insert(idx, tx.hash());
-            }
-        }
-
-        // Insert gas metrics into the store
-        if !tx_gas_data.is_empty() {
-            self.db.metrics_store.insert_gas_metrics(
-                block.header.height,
-                &block.header.timestamp,
-                &txs_hashes_with_gas_data,
-                &tx_gas_data,
-            )?;
-        }
-
-        // Add the block and commit the changes to persist it
-        let _ = blockchain_overlay.lock().unwrap().add_block(block)?;
-        blockchain_overlay.lock().unwrap().overlay.lock().unwrap().apply()?;
-        debug!(target: "explorerd::blocks::put_block", "Added block {block:?}");
-
-        Ok(())
-    }
-
-    /// Provides the total block count.
-    pub fn get_block_count(&self) -> usize {
-        self.db.blockchain.len()
-    }
-
-    /// Fetch all known blocks from the database.
-    pub fn get_blocks(&self) -> Result<Vec<BlockRecord>> {
-        // Fetch blocks and handle any errors encountered
-        let blocks = &self.db.blockchain.get_all().map_err(|e| {
-            Error::DatabaseError(format!("[get_blocks] Block retrieval failed: {e:?}"))
-        })?;
-
-        // Transform the found blocks into a vector of block records
-        let block_records: Vec<BlockRecord> = blocks.iter().map(BlockRecord::from).collect();
-
-        Ok(block_records)
-    }
-
-    /// Fetch a block given its header hash from the database.
-    pub fn get_block_by_hash(&self, header_hash: &str) -> Result<Option<BlockRecord>> {
-        // Parse header hash, returning an error if parsing fails
-        let header_hash = header_hash
-            .parse::<HeaderHash>()
-            .map_err(|_| ExplorerdError::InvalidHeaderHash(header_hash.to_string()))?;
-
-        // Fetch block by hash and handle encountered errors
-        match self.db.blockchain.get_blocks_by_hash(&[header_hash]) {
-            Ok(blocks) => Ok(blocks.first().map(BlockRecord::from)),
-            Err(Error::BlockNotFound(_)) => Ok(None),
-            Err(e) => Err(Error::DatabaseError(format!(
-                "[get_block_by_hash] Block retrieval failed: {e:?}"
-            ))),
-        }
-    }
-
-    /// Fetch a block given its height from the database.
-    pub fn get_block_by_height(&self, height: u32) -> Result<Option<BlockRecord>> {
-        // Fetch block by height and handle encountered errors
-        match self.db.blockchain.get_blocks_by_heights(&[height]) {
-            Ok(blocks) => Ok(blocks.first().map(BlockRecord::from)),
-            Err(Error::BlockNotFound(_)) => Ok(None),
-            Err(e) => Err(Error::DatabaseError(format!(
-                "[get_block_by_height] Block retrieval failed: {e:?}"
-            ))),
-        }
-    }
-
-    /// Fetch the last block from the database.
-    pub fn last_block(&self) -> Result<Option<(u32, String)>> {
-        let block_store = &self.db.blockchain.blocks;
-
-        // Return None result when no blocks exist
-        if block_store.is_empty() {
-            return Ok(None);
-        }
-
-        // Blocks exist, retrieve last block
-        let (height, header_hash) = block_store.get_last().map_err(|e| {
-            Error::DatabaseError(format!("[last_block] Block retrieval failed: {e:?}"))
-        })?;
-
-        // Convert header hash to a string and return result
-        Ok(Some((height, header_hash.to_string())))
-    }
-
-    /// Fetch the last N blocks from the database.
-    pub fn get_last_n(&self, n: usize) -> Result<Vec<BlockRecord>> {
-        // Fetch the last n blocks and handle any errors encountered
-        let blocks_result = &self.db.blockchain.get_last_n(n).map_err(|e| {
-            Error::DatabaseError(format!("[get_last_n] Block retrieval failed: {e:?}"))
-        })?;
-
-        // Transform the found blocks into a vector of block records
-        let block_records: Vec<BlockRecord> = blocks_result.iter().map(BlockRecord::from).collect();
-
-        Ok(block_records)
-    }
-
-    /// Fetch blocks within a specified range from the database.
-    pub fn get_by_range(&self, start: u32, end: u32) -> Result<Vec<BlockRecord>> {
-        // Fetch blocks in the specified range and handle any errors encountered
-        let blocks_result = &self.db.blockchain.get_by_range(start, end).map_err(|e| {
-            Error::DatabaseError(format!("[get_by_range]: Block retrieval failed: {e:?}"))
-        })?;
-
-        // Transform the found blocks into a vector of block records
-        let block_records: Vec<BlockRecord> = blocks_result.iter().map(BlockRecord::from).collect();
-
-        Ok(block_records)
-    }
-
-    /// Resets the `blocks` and `transactions` trees to a specified
-    /// height by removing entries above the `reset_height`,
-    /// returning a result that indicates success or failure.
-    ///
-    /// The function retrieves the last explorer block and iteratively
-    /// rolls back entries in the `main`, `order`, and `difficulty`
-    /// trees to the specified `reset_height`. It also resets the `main`
-    /// and `location` trees to reflect the transaction state at
-    /// the given height.
-    ///
-    /// This operation is performed atomically using a sled transaction
-    /// applied across the affected sled trees, ensuring consistency and
-    /// avoiding partial updates.
-    pub fn reset_to_height(&self, reset_height: u32) -> Result<()> {
-        let block_store = &self.db.blockchain.blocks;
-        let tx_store = &self.db.blockchain.transactions;
-
-        // Get the last block height
-        let (last_block_height, _) = block_store.get_last().map_err(|e| {
-            Error::DatabaseError(format!(
-                "[reset_to_height]: Failed to get the last block height: {e:?}"
-            ))
-        })?;
-
-        debug!(target: "explorerd::blocks::reset_to_height", 
-            "Resetting to height {reset_height} from last block height {last_block_height}: block_count={}, txs_count={}", 
-            block_store.len(), tx_store.len());
-
-        // Skip resetting blocks if `reset_height` is greater than or equal to `last_block_height`
-        if reset_height >= last_block_height {
-            warn!(target: "explorerd::blocks::reset_to_height",
-                    "Nothing to reset because reset_height is greater than or equal to last_block_height: {reset_height} >= {last_block_height}");
-            return Ok(());
-        }
-
-        // Get the associated block infos in order to obtain transactions to reset
-        let block_infos_to_reset =
-            &self.db.blockchain.get_by_range(reset_height + 1, last_block_height).map_err(|e| {
-                Error::DatabaseError(format!(
-                    "[reset_to_height]: Failed to get the transaction hashes to reset: {e:?}"
-                ))
-            })?;
-
-        // Collect the transaction hashes from the blocks that need resetting
-        let txs_hashes_to_reset: Vec<(u32, TransactionHash)> = block_infos_to_reset
-            .iter()
-            .flat_map(|block_info| {
-                let block_height = block_info.header.height;
-                block_info.txs.iter().map(move |tx| {
-                    debug!(target: "explorerd::blocks::reset_to_height", "Adding trx to delete for height {block_height}: {}", tx.hash());
-                    (block_height, tx.hash())
-                })
-            })
-            .collect();
-
-        // Perform the reset operation atomically using a sled transaction
-        let tx_result = (&block_store.main, &block_store.order, &block_store.difficulty, &tx_store.main, &tx_store.location)
-            .transaction(|(block_main, block_order, block_difficulty, tx_main, tx_location)| {
-                // Traverse the block heights in reverse, removing each block up to (but not including) reset_height
-                for height in (reset_height + 1..=last_block_height).rev() {
-                    let height_key = height.to_be_bytes();
-
-                    // Fetch block from `order` tree to obtain the block hash needed to remove blocks from `main` tree
-                    let order_header_hash = block_order.get(height_key).map_err(ConflictableTransactionError::Abort)?;
-
-                    if let Some(header_hash) = order_header_hash {
-
-                        // Remove block from the `main` tree
-                        block_main.remove(&header_hash).map_err(ConflictableTransactionError::Abort)?;
-
-                        // Remove block from the `difficulty` tree
-                        block_difficulty.remove(&height_key).map_err(ConflictableTransactionError::Abort)?;
-
-                        // Remove block from the `order` tree
-                        block_order.remove(&height_key).map_err(ConflictableTransactionError::Abort)?;
-                    }
-
-                    debug!(target: "explorerd::blocks::reset_to_height", "Removed block at height: {height}");
-                }
-
-                // Iterate through the transaction hashes, removing the related transactions
-                for (height, tx_hash) in txs_hashes_to_reset.iter() {
-                    // Remove transaction from the `main` tree
-                    tx_main.remove(tx_hash.inner()).map_err(ConflictableTransactionError::Abort)?;
-                    // Remove transaction from the `location` tree
-                    tx_location.remove(tx_hash.inner()).map_err(ConflictableTransactionError::Abort)?;
-                    debug!(target: "explorerd::blocks::reset_to_height", "Removed transaction at height {height}: {tx_hash}");
-                }
-
-                Ok(())
-            })
-            .map_err(|e| {
-                Error::DatabaseError(format!("[reset_to_height]: Resetting height failed: {e:?}"))
-            });
-
-        debug!(target: "explorerd::blocks::reset_to_height", "Successfully reset to height {reset_height}: block_count={}, txs_count={}", block_store.len(), tx_store.len());
-
-        tx_result
-    }
-}

+ 0 - 595
bin/explorer/explorerd/src/service/contracts.rs

@@ -1,595 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use std::{
-    io::{Cursor, Read},
-    str::FromStr,
-};
-
-use tar::Archive;
-use tinyjson::JsonValue;
-use tracing::info;
-
-use darkfi::{
-    blockchain::BlockchainOverlay, validator::utils::deploy_native_contracts, Error, Result,
-};
-use darkfi_sdk::crypto::{ContractId, DAO_CONTRACT_ID, DEPLOYOOOR_CONTRACT_ID, MONEY_CONTRACT_ID};
-use darkfi_serial::deserialize;
-
-use crate::{
-    store::{
-        contract_metadata::{ContractMetaData, ContractSourceFile},
-        NATIVE_CONTRACT_SOURCE_ARCHIVES,
-    },
-    ExplorerService,
-};
-
-/// Represents a contract record embellished with details that are not stored on-chain.
-#[derive(Debug, Clone)]
-pub struct ContractRecord {
-    /// The Contract ID as a string
-    pub id: String,
-
-    /// The optional name of the contract
-    pub name: Option<String>,
-
-    /// The optional description of the contract
-    pub description: Option<String>,
-}
-
-impl ContractRecord {
-    /// Auxiliary function to convert a `ContractRecord` into a `JsonValue` array.
-    pub fn to_json_array(&self) -> JsonValue {
-        JsonValue::Array(vec![
-            JsonValue::String(self.id.clone()),
-            JsonValue::String(self.name.clone().unwrap_or_default()),
-            JsonValue::String(self.description.clone().unwrap_or_default()),
-        ])
-    }
-}
-
-impl ExplorerService {
-    /// Fetches the total contract count of all deployed contracts in the explorer database.
-    pub fn get_contract_count(&self) -> usize {
-        self.db.blockchain.contracts.wasm.len()
-    }
-
-    /// Retrieves all contracts from the store excluding native contracts (DAO, Deployooor, and Money),
-    /// transforming them into `Vec` of [`ContractRecord`]s, and returns the result.
-    pub fn get_contracts(&self) -> Result<Vec<ContractRecord>> {
-        let native_contracts = [*DAO_CONTRACT_ID, *DEPLOYOOOR_CONTRACT_ID, *MONEY_CONTRACT_ID];
-        self.get_filtered_contracts(|contract_id| !native_contracts.contains(contract_id))
-    }
-
-    /// Retrieves all native contracts (DAO, Deployooor, and Money) from the store, transforming them
-    /// into `Vec` of [`ContractRecord`]s and returns the result.
-    pub fn get_native_contracts(&self) -> Result<Vec<ContractRecord>> {
-        let native_contracts = [*DAO_CONTRACT_ID, *DEPLOYOOOR_CONTRACT_ID, *MONEY_CONTRACT_ID];
-        self.get_filtered_contracts(|contract_id| native_contracts.contains(contract_id))
-    }
-
-    /// Fetches a list of source code file paths for a given [ContractId], returning an empty vector
-    /// if no contracts are found.
-    pub fn get_contract_source_paths(&self, contract_id: &ContractId) -> Result<Vec<String>> {
-        self.db.contract_meta_store.get_source_paths(contract_id).map_err(|e| {
-            Error::DatabaseError(format!(
-                "[get_contract_source_paths] Retrieval of contract source code paths failed: {e:?}"
-            ))
-        })
-    }
-
-    /// Fetches [`ContractMetaData`] for a given [`ContractId`], returning `None` if no metadata is found.
-    pub fn get_contract_metadata(
-        &self,
-        contract_id: &ContractId,
-    ) -> Result<Option<ContractMetaData>> {
-        self.db.contract_meta_store.get(contract_id).map_err(|e| {
-            Error::DatabaseError(format!(
-                "[get_contract_metadata] Retrieval of contract metadata paths failed: {e:?}"
-            ))
-        })
-    }
-
-    /// Fetches the source code content for a specified [`ContractId`] and `path`, returning `None` if
-    /// no source content is found.
-    pub fn get_contract_source_content(
-        &self,
-        contract_id: &ContractId,
-        path: &str,
-    ) -> Result<Option<String>> {
-        self.db.contract_meta_store.get_source_content(contract_id, path).map_err(|e| {
-            Error::DatabaseError(format!(
-                "[get_contract_source_content] Retrieval of contract source file failed: {e:?}"
-            ))
-        })
-    }
-
-    /// Adds source code for a specified [`ContractId`] from a provided tar file (in bytes).
-    ///
-    /// This function extracts the tar archive from `tar_bytes`, then loads each source file
-    /// into the store. Each file is keyed by its path prefixed with the Contract ID.
-    /// Returns a successful result or an error.
-    pub fn add_contract_source(&self, contract_id: &ContractId, tar_bytes: &[u8]) -> Result<()> {
-        // Untar the source code
-        let source = untar_source(tar_bytes)?;
-
-        // Insert contract source code
-        self.db.contract_meta_store.insert_source(contract_id, &source).map_err(|e| {
-            Error::DatabaseError(format!(
-                "[add_contract_source] Adding of contract source code failed: {e:?}"
-            ))
-        })
-    }
-
-    /// Adds provided [`ContractId`] with corresponding [`ContractMetaData`] pairs into the contract
-    /// metadata store, returning a successful result upon success.
-    pub fn add_contract_metadata(
-        &self,
-        contract_ids: &[ContractId],
-        metadata: &[ContractMetaData],
-    ) -> Result<()> {
-        self.db.contract_meta_store.insert_metadata(contract_ids, metadata).map_err(|e| {
-            Error::DatabaseError(format!(
-                "[add_contract_metadata] Upload of contract source code failed: {e:?}"
-            ))
-        })
-    }
-
-    /// Deploys native contracts required for gas calculation and retrieval.
-    pub async fn deploy_native_contracts(&self) -> Result<()> {
-        let overlay = BlockchainOverlay::new(&self.db.blockchain)?;
-        deploy_native_contracts(&overlay, 10).await?;
-        overlay.lock().unwrap().overlay.lock().unwrap().apply()?;
-        Ok(())
-    }
-
-    /// Loads native contract source code into the explorer database by extracting it from tar archives
-    /// created during the explorer build process. The extracted source code is associated with
-    /// the corresponding [`ContractId`] for each loaded contract and stored.
-    pub fn load_native_contract_sources(&self) -> Result<()> {
-        // Iterate each native contract source archive
-        for (contract_id_str, archive_bytes) in NATIVE_CONTRACT_SOURCE_ARCHIVES.iter() {
-            // Untar the native contract source code
-            let source_code = untar_source(archive_bytes)?;
-
-            // Parse contract id into a contract id instance
-            let contract_id = &ContractId::from_str(contract_id_str)?;
-
-            // Add source code into the `ContractMetaStore`
-            self.db.contract_meta_store.insert_source(contract_id, &source_code)?;
-            info!(target: "explorerd: load_native_contract_sources", "Loaded native contract source {contract_id_str}");
-        }
-        Ok(())
-    }
-
-    /// Loads [`ContractMetaData`] for deployed native contracts into the explorer database by adding descriptive
-    /// information (e.g., name and description) used to display contract details.
-    pub fn load_native_contract_metadata(&self) -> Result<()> {
-        let contract_ids = [*MONEY_CONTRACT_ID, *DAO_CONTRACT_ID, *DEPLOYOOOR_CONTRACT_ID];
-
-        // Create pre-defined native contract metadata
-        let metadatas = [
-            ContractMetaData::new(
-                "Money".to_string(),
-                "Facilitates money transfers, atomic swaps, minting, freezing, and staking of consensus tokens".to_string(),
-            ),
-            ContractMetaData::new(
-                "DAO".to_string(),
-                "Provides functionality for Anonymous DAOs".to_string(),
-            ),
-            ContractMetaData::new(
-                "Deployoor".to_string(),
-                "Handles non-native smart contract deployments".to_string(),
-            ),
-        ];
-
-        // Load contract metadata into the `ContractMetaStore`
-        self.db.contract_meta_store.insert_metadata(&contract_ids, &metadatas)?;
-        info!(target: "explorerd: load_native_contract_metadata", "Loaded metadata for native contracts");
-
-        Ok(())
-    }
-
-    /// Converts a [`ContractId`] into a [`ContractRecord`].
-    ///
-    /// This function retrieves the [`ContractMetaData`] associated with the provided Contract ID
-    /// and uses any found metadata to construct a contract record. Upon success, the function
-    /// returns a [`ContractRecord`] containing relevant details about the contract.
-    fn to_contract_record(&self, contract_id: &ContractId) -> Result<ContractRecord> {
-        let metadata = self.db.contract_meta_store.get(contract_id)?;
-        let name: Option<String>;
-        let description: Option<String>;
-
-        // Set name and description based on the presence of metadata
-        if let Some(metadata) = metadata {
-            name = Some(metadata.name);
-            description = Some(metadata.description);
-        } else {
-            name = None;
-            description = None;
-        }
-
-        // Return transformed contract record
-        Ok(ContractRecord { id: contract_id.to_string(), name, description })
-    }
-
-    /// Auxiliary function that retrieves [`ContractRecord`]s filtered by a provided `filter_fn` closure.
-    ///
-    /// This function accepts a filter function `Fn(&ContractId) -> bool` that determines
-    /// which contracts are included based on their [`ContractId`]. It iterates over
-    /// Contract IDs stored in the blockchain's contract tree, applying the filter function to decide inclusion.
-    /// Converts the filtered Contract IDs into [`ContractRecord`] instances, returning them as a `Vec`,
-    /// or an empty `Vec` if no contracts are found.
-    fn get_filtered_contracts<F>(&self, filter_fn: F) -> Result<Vec<ContractRecord>>
-    where
-        F: Fn(&ContractId) -> bool,
-    {
-        let contract_keys = self.db.blockchain.contracts.wasm.iter().keys();
-
-        // Iterate through stored Contract IDs, filtering out the contracts based filter
-        contract_keys
-            .filter_map(|serialized_contract_id| {
-                // Deserialize the serialized Contract ID
-                let contract_id: ContractId = match serialized_contract_id
-                    .map_err(Error::from)
-                    .and_then(|id_bytes| deserialize(&id_bytes).map_err(Error::from))
-                {
-                    Ok(id) => id,
-                    Err(e) => {
-                        return Some(Err(Error::DatabaseError(format!(
-                            "[get_filtered_contracts] Contract ID retrieval or deserialization failed: {e:?}"
-                        ))));
-                    }
-                };
-
-                // Apply the filter
-                if filter_fn(&contract_id) {
-                    // Convert the matching Contract ID into a `ContractRecord`, return result
-                    return match self.to_contract_record(&contract_id).map_err(|e| {
-                        Error::DatabaseError(format!("[get_filtered_contracts] Failed to convert contract: {e:?}"))
-                    }) {
-                        Ok(record) => Some(Ok(record)),
-                        Err(e) => Some(Err(e)),
-                    };
-                }
-
-                // Skip contracts that do not match the filter
-                None
-            })
-            .collect::<Result<Vec<ContractRecord>>>()
-    }
-}
-
-/// Auxiliary function that extracts source code files from a TAR archive provided as a byte slice [`&[u8]`],
-/// returning a `Vec` of [`ContractSourceFile`]s representing the extracted file paths and their contents.
-pub fn untar_source(tar_bytes: &[u8]) -> Result<Vec<ContractSourceFile>> {
-    // Use a Cursor and archive to read the tar file
-    let cursor = Cursor::new(tar_bytes);
-    let mut archive = Archive::new(cursor);
-
-    // Vectors to hold the source paths and source contents
-    let mut source: Vec<ContractSourceFile> = Vec::new();
-
-    // Iterate through the entries in the tar archive
-    for tar_entry in archive.entries()? {
-        let mut tar_entry = tar_entry?;
-        let path = tar_entry.path()?.to_path_buf();
-
-        // Check if the entry is a file
-        if tar_entry.header().entry_type().is_file() {
-            let mut content = Vec::new();
-            tar_entry.read_to_end(&mut content)?;
-
-            // Convert the contents into a string
-            let source_content = String::from_utf8(content)
-                .map_err(|_| Error::ParseFailed("Failed converting source code to a string"))?;
-
-            // Collect source paths and contents
-            let path_str = path.to_string_lossy().into_owned();
-            source.push(ContractSourceFile::new(path_str, source_content));
-        }
-    }
-
-    Ok(source)
-}
-
-/// This test module ensures the correctness of the [`ExplorerService`] functionality with
-/// respect to smart contracts.
-///
-/// The tests in this module cover adding, loading, storing, retrieving, and validating contract
-/// metadata and source code. The primary goal is to validate the accuracy and reliability of
-/// the `ExplorerService` when handling contract-related operations.
-#[cfg(test)]
-mod tests {
-    use std::{fs::File, io::Read, path::Path, slice, sync::Arc};
-
-    use darkfi::{
-        util::logger::{setup_test_logger, Level},
-        Error::Custom,
-    };
-    use darkfi_sdk::crypto::MONEY_CONTRACT_ID;
-    use tar::Archive;
-    use tempdir::TempDir;
-    use tracing::warn;
-
-    use super::*;
-    use crate::rpc::DarkfidRpcClient;
-
-    /// Tests the adding of [`ContractMetaData`] to the store by adding
-    /// metadata, and verifying the inserted data matches the expected results.
-    #[test]
-    fn test_add_metadata() -> Result<()> {
-        // Setup test, returning initialized service
-        let service = setup()?;
-
-        // Unique identifier for contracts in tests
-        let contract_id: ContractId = *MONEY_CONTRACT_ID;
-
-        // Declare expected metadata used for test
-        let expected_metadata: ContractMetaData = ContractMetaData::new(
-            "Money Contract".to_string(),
-            "Money Contract Description".to_string(),
-        );
-
-        // Add the metadata
-        service.add_contract_metadata(&[contract_id], slice::from_ref(&expected_metadata))?;
-
-        // Get the metadata that was loaded as actual results
-        let actual_metadata = service.get_contract_metadata(&contract_id)?;
-
-        // Verify existence of loaded metadata
-        assert!(actual_metadata.is_some());
-
-        // Confirm actual metadata match expected results
-        assert_eq!(actual_metadata.unwrap(), expected_metadata.clone());
-
-        Ok(())
-    }
-
-    /// This test validates the loading and retrieval of native contract metadata. It sets up the
-    /// explorer service, loads native contract metadata, and then verifies metadata retrieval
-    /// for each native contract.
-    #[test]
-    fn test_load_native_contract_metadata() -> Result<()> {
-        // Setup test, returning initialized service
-        let service = setup()?;
-
-        // Load native contract metadata
-        service.load_native_contract_metadata()?;
-
-        // Define Contract IDs used to retrieve loaded metadata
-        let native_contract_ids = [*DAO_CONTRACT_ID, *DEPLOYOOOR_CONTRACT_ID, *MONEY_CONTRACT_ID];
-
-        // For each native contract, verify metadata was loaded
-        for contract_id in native_contract_ids.iter() {
-            let metadata = service.get_contract_metadata(contract_id)?;
-            assert!(metadata.is_some());
-        }
-
-        Ok(())
-    }
-
-    /// This test validates the loading, storage, and retrieval of native contract source code. It sets up the
-    /// explorer service, loads native contract sources, and then verifies both the source paths and content
-    /// for each native contract. The test compares the retrieved source paths and content against the expected
-    /// results from the corresponding tar archives.
-    #[test]
-    fn test_load_native_contracts() -> Result<()> {
-        // Setup test, returning initialized service
-        let service = setup()?;
-
-        // Load native contracts
-        service.load_native_contract_sources()?;
-
-        // Define contract archive paths
-        let native_contract_tars = [
-            "native_contracts_src/dao_contract_src.tar",
-            "native_contracts_src/deployooor_contract_src.tar",
-            "native_contracts_src/money_contract_src.tar",
-        ];
-
-        // Define Contract IDs to associate with each contract source archive
-        let native_contract_ids = [*DAO_CONTRACT_ID, *DEPLOYOOOR_CONTRACT_ID, *MONEY_CONTRACT_ID];
-
-        // Iterate archive and verify actual match expected results
-        for (&tar_file, &contract_id) in native_contract_tars.iter().zip(&native_contract_ids) {
-            // Verify that source paths match
-            verify_source_paths(&service, tar_file, contract_id)?;
-
-            // Verify that source content match
-            verify_source_content(&service, tar_file, contract_id)?;
-        }
-
-        Ok(())
-    }
-
-    /// This test validates the transformation of a [`ContractId`] into a [`ContractRecord`].
-    /// It sets up the explorer service, adds test metadata for a specific Contract ID, and then verifies the
-    /// correct transformation of this Contract ID into a ContractRecord.
-    #[test]
-    fn test_to_contract_record() -> Result<()> {
-        // Setup test, returning initialized service
-        let service = setup()?;
-
-        // Unique identifier for contracts in tests
-        let contract_id: ContractId = *MONEY_CONTRACT_ID;
-
-        // Declare expected metadata used for test
-        let expected_metadata: ContractMetaData = ContractMetaData::new(
-            "Money Contract".to_string(),
-            "Money Contract Description".to_string(),
-        );
-
-        // Load contract metadata used for test
-        service.add_contract_metadata(&[contract_id], slice::from_ref(&expected_metadata))?;
-
-        // Transform Contract ID to a `ContractRecord`
-        let contract_record = service.to_contract_record(&contract_id)?;
-
-        // Verify that name and description exist
-        assert!(
-            contract_record.name.is_some(),
-            "Expected to_contract_record to return a contract with name"
-        );
-        assert!(
-            contract_record.description.is_some(),
-            "Expected to_contract_record to return a contract with description"
-        );
-
-        // Verify that id, name, and description match expected results
-        assert_eq!(contract_id.to_string(), contract_record.id);
-        assert_eq!(expected_metadata.name, contract_record.name.unwrap());
-        assert_eq!(expected_metadata.description, contract_record.description.unwrap());
-
-        Ok(())
-    }
-
-    /// Sets up a test case for contract metadata store testing by initializing the logger
-    /// and returning an initialized [`ExplorerService`].
-    fn setup() -> Result<ExplorerService> {
-        // Initialize logger to show execution output
-        if setup_test_logger(
-            &["sled", "runtime", "net"],
-            false,
-            Level::Info,
-            //Level::Verbose,
-            //Level::Debug,
-            //Level::Trace,
-        )
-        .is_err()
-        {
-            warn!("Logger already initialized");
-        }
-
-        // Create a temporary directory for sled DB
-        let temp_dir = TempDir::new("test")?;
-
-        // Initialize a sled DB instance using the temporary directory's path
-        let db_path = temp_dir.path().join("sled_db");
-
-        // Initialize the explorer service
-        ExplorerService::new(
-            db_path.to_string_lossy().into_owned(),
-            Arc::new(DarkfidRpcClient::new()),
-        )
-    }
-
-    /// This Auxiliary function verifies that the loaded native contract source paths match the expected results
-    /// from a given contract archive. This function extracts source paths from the specified `tar_file`, retrieves
-    /// the actual paths for the [`ContractId`] from the ExplorerService, and compares them to ensure they match.
-    fn verify_source_paths(
-        service: &ExplorerService,
-        tar_file: &str,
-        contract_id: ContractId,
-    ) -> Result<()> {
-        // Read the tar file and extract source paths
-        let tar_bytes = std::fs::read(tar_file)?;
-        let mut expected_source_paths = extract_file_paths_from_tar(&tar_bytes)?;
-
-        // Retrieve and sort actual source paths for the provided Contract ID
-        let mut actual_source_paths = service.get_contract_source_paths(&contract_id)?;
-
-        // Sort paths to ensure they are in the same order needed for assert
-        expected_source_paths.sort();
-        actual_source_paths.sort();
-
-        // Verify actual source matches expected result
-        assert_eq!(
-            expected_source_paths, actual_source_paths,
-            "Mismatch between expected and actual source paths for tar file: {tar_file}"
-        );
-
-        Ok(())
-    }
-
-    /// This auxiliary function verifies that the loaded native contract source content matches the
-    /// expected results from a given contract source archive. It extracts source files from the specified
-    /// `tar_file`, retrieves the actual content for each file path using the [`ContractId`] from the
-    /// ExplorerService, and compares them to ensure the content match.
-    fn verify_source_content(
-        service: &ExplorerService,
-        tar_file: &str,
-        contract_id: ContractId,
-    ) -> Result<()> {
-        // Read the tar file
-        let tar_bytes = std::fs::read(tar_file)?;
-        let expected_source_paths = extract_file_paths_from_tar(&tar_bytes)?;
-
-        // Validate contents of tar archive source code content
-        for file_path in expected_source_paths {
-            // Get the source code content
-            let actual_source = service.get_contract_source_content(&contract_id, &file_path)?;
-
-            // Verify source content exists
-            assert!(
-                actual_source.is_some(),
-                "Actual source `{file_path}` is missing in the store."
-            );
-
-            // Read the source content from the tar archive
-            let expected_source = read_file_from_tar(tar_file, &file_path)?;
-
-            // Verify actual source matches expected results
-            assert_eq!(
-                actual_source.unwrap(),
-                expected_source,
-                "Actual source does not match expected results `{file_path}`."
-            );
-        }
-
-        Ok(())
-    }
-
-    /// Auxiliary function that reads the contents of specified `file_path` within a tar archive.
-    fn read_file_from_tar(tar_path: &str, file_path: &str) -> Result<String> {
-        let file = File::open(tar_path)?;
-        let mut archive = Archive::new(file);
-        for entry in archive.entries()? {
-            let mut entry = entry?;
-            if let Ok(path) = entry.path() {
-                if path == Path::new(file_path) {
-                    let mut content = String::new();
-                    entry.read_to_string(&mut content)?;
-                    return Ok(content);
-                }
-            }
-        }
-
-        Err(Custom(format!("File {file_path} not found in tar archive.")))
-    }
-
-    /// Auxiliary function that extracts all file paths from the given `tar_bytes` tar archive.
-    pub fn extract_file_paths_from_tar(tar_bytes: &[u8]) -> Result<Vec<String>> {
-        let cursor = Cursor::new(tar_bytes);
-        let mut archive = Archive::new(cursor);
-
-        // Collect paths from the tar archive
-        let mut file_paths = Vec::new();
-        for entry in archive.entries()? {
-            let entry = entry?;
-            let path = entry.path()?;
-
-            // Skip directories and only include files
-            if entry.header().entry_type().is_file() {
-                file_paths.push(path.to_string_lossy().to_string());
-            }
-        }
-
-        Ok(file_paths)
-    }
-}

+ 0 - 107
bin/explorer/explorerd/src/service/mod.rs

@@ -1,107 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use std::sync::Arc;
-
-use tracing::debug;
-
-use darkfi::Result;
-
-use crate::{rpc::DarkfidRpcClient, store::ExplorerDb};
-
-/// Handles core block-related functionality
-pub mod blocks;
-
-/// Implements functionality for smart contracts
-pub mod contracts;
-
-/// Powers metrics gathering and analytical capabilities
-pub mod statistics;
-
-/// Manages transaction data processing
-pub mod transactions;
-
-/// Manages synchronization with darkfid
-pub mod sync;
-
-/// Represents the service layer for the Explorer application, bridging the RPC layer and the database.
-/// It encapsulates explorer business logic and provides a unified interface for core functionalities,
-/// providing a clear separation of concerns between RPC handling and data management layers.
-///
-/// Core functionalities include:
-///
-/// - Data Transformation: Converting database data into structured responses suitable for RPC callers.
-/// - Blocks: Synchronization, retrieval, counting, and management.
-/// - Contracts: Handling native and user contract data, source code, tar files, and metadata.
-/// - Metrics: Providing metric-related data over the life of the chain.
-/// - Transactions: Synchronization, calculating gas data, retrieval, counting, and related block information.
-pub struct ExplorerService {
-    /// Explorer database instance
-    pub db: ExplorerDb,
-    /// JSON-RPC client used to execute requests to Darkfi blockchain nodes
-    pub darkfid_client: Arc<DarkfidRpcClient>,
-}
-
-impl ExplorerService {
-    /// Creates a new `ExplorerService` instance.
-    pub fn new(db_path: String, darkfid_client: Arc<DarkfidRpcClient>) -> Result<Self> {
-        // Initialize explorer database
-        let db = ExplorerDb::new(db_path)?;
-
-        Ok(Self { db, darkfid_client })
-    }
-
-    /// Initializes the explorer service by deploying native contracts and loading native contract
-    /// source code and metadata required for its operation.
-    pub async fn init(&self) -> Result<()> {
-        self.deploy_native_contracts().await?;
-        self.load_native_contract_sources()?;
-        self.load_native_contract_metadata()?;
-        Ok(())
-    }
-
-    /// Resets the explorer state to the specified height. If a genesis block height is provided,
-    /// all blocks and transactions are purged from the database. Otherwise, the state is reverted
-    /// to the given height. The explorer metrics are updated to reflect the updated blocks and
-    /// transactions up to the reset height, ensuring consistency. Returns a result indicating
-    /// success or an error if the operation fails.
-    pub fn reset_explorer_state(&self, height: u32) -> Result<()> {
-        debug!(target: "explorerd::reset_explorer_state", "Resetting explorer state to height: {height}");
-
-        // Check if a genesis block reset or to a specific height
-        match height {
-            // Reset for genesis height 0, purge blocks and transactions
-            0 => {
-                self.reset_blocks()?;
-                self.reset_transactions()?;
-                debug!(target: "explorerd::reset_explorer_state", "Reset explorer state to accept a new genesis block");
-            }
-            // Reset for all other heights
-            _ => {
-                self.reset_to_height(height)?;
-                debug!(target: "explorerd::reset_explorer_state", "Reset blocks to height: {height}");
-            }
-        }
-
-        // Reset gas metrics to the specified height to reflect the updated blockchain state
-        self.db.metrics_store.reset_gas_metrics(height)?;
-        debug!(target: "explorerd::reset_explorer_state", "Reset metrics store to height: {height}");
-
-        Ok(())
-    }
-}

+ 0 - 140
bin/explorer/explorerd/src/service/statistics.rs

@@ -1,140 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use tinyjson::JsonValue;
-
-use darkfi::{Error, Result};
-use darkfi_sdk::blockchain::block_epoch;
-
-use crate::{service::ExplorerService, store::metrics::GasMetrics};
-
-#[derive(Debug, Clone)]
-/// Structure representing basic statistic extracted from the database.
-pub struct BaseStatistics {
-    /// Current blockchain height
-    pub height: u32,
-    /// Current blockchain epoch (based on current height)
-    pub epoch: u8,
-    /// Blockchains' last block hash
-    pub last_block: String,
-    /// Blockchain total blocks
-    pub total_blocks: usize,
-    /// Blockchain total transactions
-    pub total_txs: usize,
-}
-
-impl BaseStatistics {
-    /// Auxiliary function to convert `BaseStatistics` into a `JsonValue` array.
-    pub fn to_json_array(&self) -> JsonValue {
-        JsonValue::Array(vec![
-            JsonValue::Number(self.height as f64),
-            JsonValue::Number(self.epoch as f64),
-            JsonValue::String(self.last_block.clone()),
-            JsonValue::Number(self.total_blocks as f64),
-            JsonValue::Number(self.total_txs as f64),
-        ])
-    }
-}
-
-/// Structure representing metrics extracted from the database.
-#[derive(Default)]
-pub struct MetricStatistics {
-    /// Metrics used to store explorer statistics
-    pub metrics: GasMetrics,
-}
-
-impl MetricStatistics {
-    pub fn new(metrics: GasMetrics) -> Self {
-        Self { metrics }
-    }
-
-    /// Auxiliary function to convert [`MetricStatistics`] into a [`JsonValue`] array.
-    pub fn to_json_array(&self) -> JsonValue {
-        JsonValue::Array(vec![
-            JsonValue::Number(self.metrics.avg_total_gas_used() as f64),
-            JsonValue::Number(self.metrics.total_gas.min as f64),
-            JsonValue::Number(self.metrics.total_gas.max as f64),
-            JsonValue::Number(self.metrics.avg_wasm_gas_used() as f64),
-            JsonValue::Number(self.metrics.wasm_gas.min as f64),
-            JsonValue::Number(self.metrics.wasm_gas.max as f64),
-            JsonValue::Number(self.metrics.avg_zk_circuits_gas_used() as f64),
-            JsonValue::Number(self.metrics.zk_circuits_gas.min as f64),
-            JsonValue::Number(self.metrics.zk_circuits_gas.max as f64),
-            JsonValue::Number(self.metrics.avg_signatures_gas_used() as f64),
-            JsonValue::Number(self.metrics.signatures_gas.min as f64),
-            JsonValue::Number(self.metrics.signatures_gas.max as f64),
-            JsonValue::Number(self.metrics.avg_deployments_gas_used() as f64),
-            JsonValue::Number(self.metrics.deployments_gas.min as f64),
-            JsonValue::Number(self.metrics.deployments_gas.max as f64),
-            JsonValue::Number(self.metrics.timestamp.inner() as f64),
-        ])
-    }
-}
-impl ExplorerService {
-    /// Fetches the latest [`BaseStatistics`] from the explorer database, or returns `None` if no block exists.
-    pub fn get_base_statistics(&self) -> Result<Option<BaseStatistics>> {
-        let last_block = self.last_block();
-        Ok(last_block
-            // Throw database error if last_block retrievals fails
-            .map_err(|e| {
-                Error::DatabaseError(format!(
-                    "[get_base_statistics] Retrieving last block failed: {e:?}"
-                ))
-            })?
-            // Calculate base statistics and return result
-            .map(|(height, header_hash)| {
-                let epoch = block_epoch(height);
-                let total_blocks = self.get_block_count();
-                let total_txs = self.get_transaction_count();
-                BaseStatistics { height, epoch, last_block: header_hash, total_blocks, total_txs }
-            }))
-    }
-
-    /// Fetches the latest metrics from the explorer database, returning a vector of
-    /// [`MetricStatistics`] if found, or an empty Vec if no metrics exist.
-    pub async fn get_metrics_statistics(&self) -> Result<Vec<MetricStatistics>> {
-        // Fetch all metrics from the metrics store, handling any potential errors
-        let metrics = self.db.metrics_store.get_all_metrics().map_err(|e| {
-            Error::DatabaseError(format!(
-                "[get_metrics_statistics] Retrieving metrics failed: {e:?}"
-            ))
-        })?;
-
-        // Transform the fetched metrics into `MetricStatistics`, collect them into a vector
-        let metric_statistics =
-            metrics.iter().map(|metrics| MetricStatistics::new(metrics.clone())).collect();
-
-        Ok(metric_statistics)
-    }
-
-    /// Fetches the latest metrics from the explorer database, returning [`MetricStatistics`] if found,
-    /// or zero-initialized defaults when not.
-    pub async fn get_latest_metrics_statistics(&self) -> Result<MetricStatistics> {
-        // Fetch the latest metrics, handling any potential errors
-        match self.db.metrics_store.get_last().map_err(|e| {
-            Error::DatabaseError(format!(
-                "[get_metrics_statistics] Retrieving latest metrics failed: {e:?}"
-            ))
-        })? {
-            // Transform metrics into `MetricStatistics` when found
-            Some((_, metrics)) => Ok(MetricStatistics::new(metrics)),
-            // Return default statistics when no metrics exist
-            None => Ok(MetricStatistics::default()),
-        }
-    }
-}

+ 0 - 418
bin/explorer/explorerd/src/service/sync.rs

@@ -1,418 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-//! # Sync Module
-//!
-//! The `sync` module is responsible for synchronizing the explorer's database with the Darkfi
-//! blockchain network. It ensures consistency between the explorer and the blockchain by
-//! fetching missing blocks, handling reorganizations (reorgs), and subscribing to live updates
-//! through Darkfi's JSON-RPC service.
-//!
-//! ## Responsibilities
-//!
-//! - **Block Synchronization**: Handles fetching and storing blocks from a Darkfi
-//!   blockchain node during startup or when syncing, ensuring the explorer stays synchronized
-//!   with the latest confirmed blocks.
-//! - **Real-Time Updates**: Subscribes to Darkfi's JSON-RPC notification service,
-//!   allowing the explorer to process and sync new blocks as they are confirmed.
-//! - **Reorg Handling**: Detects and resolves blockchain reorganizations by identifying
-//!   the last common block (in case of divergence) and re-aligning the explorer's state with the
-//!   latest blockchain state. Reorgs are an importnt part of synchronization because they prevent
-//!   syncing invalid or outdated states, ensuring the explorer maintains an accurate view of a
-//!   Darkfi blockchain network.
-
-use std::{sync::Arc, time::Instant};
-
-use tinyjson::JsonValue;
-use tracing::{debug, error, info, warn};
-use url::Url;
-
-use darkfi::{
-    blockchain::BlockInfo,
-    rpc::{
-        client::RpcClient,
-        jsonrpc::{JsonRequest, JsonResult},
-    },
-    system::{Publisher, StoppableTask, StoppableTaskPtr},
-    util::{encoding::base64, time::fmt_duration},
-    Error,
-};
-use darkfi_serial::deserialize_async;
-
-use crate::{service::ExplorerService, Explorerd};
-
-impl ExplorerService {
-    /// Synchronizes blocks between the explorer and a Darkfi blockchain node, ensuring
-    /// the database remains consistent by syncing any missing or outdated blocks.
-    ///
-    /// If provided `reset` is true, the explorer's blockchain-related and metric sled trees are purged
-    /// and syncing starts from the genesis block. The function also handles reorgs by re-aligning the
-    /// explorer state to the correct height when blocks are outdated. Returns a result indicating
-    /// success or failure.
-    ///
-    /// Reorg handling is delegated to the [`Self::reorg_blocks`] function, whose
-    /// documentation provides more details on the reorg process during block syncing.
-    pub async fn sync_blocks(&self, reset: bool) -> darkfi::Result<()> {
-        // Grab last synced block height from the explorer's database.
-        let last_synced_block = self.last_block().map_err(|e| {
-            let error_message = format!("[sync_blocks] Retrieving last synced block failed: {e:?}");
-            error!(target: "explorerd::rpc_blocks::sync_blocks", "{}", error_message);
-            Error::DatabaseError(error_message)
-        })?;
-
-        // Grab the last confirmed block height and hash from the darkfi node
-        let (last_darkfid_height, last_darkfid_hash) =
-            self.darkfid_client.get_last_confirmed_block().await?;
-
-        // Initialize the current height to sync from, starting from genesis block if last sync block does not exist
-        let (last_synced_height, last_synced_hash) = last_synced_block
-            .map_or((0, "".to_string()), |(height, header_hash)| (height, header_hash));
-
-        // Declare a mutable variable to track the current sync height while processing blocks
-        let mut current_height = last_synced_height;
-
-        info!(target: "explorerd::rpc_blocks::sync_blocks", "Syncing from block number: {current_height}");
-        info!(target: "explorerd::rpc_blocks::sync_blocks", "Last confirmed darkfid block: {last_darkfid_height} - {last_darkfid_hash}");
-
-        // A reorg is detected if the hash of the last synced block differs from the hash of the last confirmed block,
-        // unless the reset flag is set or the current height is 0
-        let reorg_detected = last_synced_hash != last_darkfid_hash && !reset && current_height != 0;
-
-        // If the reset flag is set, reset the explorer state and start syncing from the genesis block height.
-        // Otherwise, handle reorgs if detected, or proceed to the next block if not at the genesis height.
-        if reset {
-            self.reset_explorer_state(0)?;
-            current_height = 0;
-            info!(target: "explorerd::rpc_blocks::sync_blocks", "Reset explorer database based on set reset parameter");
-        } else if reorg_detected {
-            // Record the start time to measure the duration of potential reorg
-            let start_reorg_time = Instant::now();
-
-            // Process reorg
-            current_height = self.reorg_blocks(last_synced_height, last_darkfid_height).await?;
-
-            // Log only if a reorg occurred (i.e., the explorer wasn't merely catching up to Darkfi node blocks)
-            if current_height != last_synced_height {
-                info!(target: "explorerd::rpc_blocks::sync_blocks", "Completed reorg to height: {current_height} [{}]", fmt_duration(start_reorg_time.elapsed()));
-            }
-
-            // Prepare to sync the next block after reorg if not from genesis height
-            if current_height != 0 {
-                current_height += 1;
-            }
-        } else if current_height != 0 {
-            // Resume syncing from the block after the last synced height
-            current_height += 1;
-        }
-
-        // Record the sync start time to measure the total block sync duration
-        let sync_start_time = Instant::now();
-        // Track the number of blocks synced for reporting
-        let mut blocks_synced = 0;
-
-        // Sync blocks until the explorer is up to date with the last confirmed block
-        while current_height <= last_darkfid_height {
-            // Record the start time to measure the duration it took to sync the block
-            let block_sync_start = Instant::now();
-
-            // Retrieve the block from darkfi node by height
-            let block = match self.darkfid_client.get_block_by_height(current_height).await {
-                Ok(r) => r,
-                Err(e) => {
-                    let error_message = format!("[sync_blocks] RPC client request failed: {e:?}");
-                    error!(target: "explorerd::rpc_blocks::sync_blocks", "{}", error_message);
-                    return Err(Error::DatabaseError(error_message))
-                }
-            };
-
-            // Store the retrieved block in the explorer's database
-            if let Err(e) = self.put_block(&block).await {
-                let error_message = format!("[sync_blocks] Put block failed: {e:?}");
-                error!(target: "explorerd::rpc_blocks::sync_blocks", "{}", error_message);
-                return Err(Error::DatabaseError(error_message))
-            };
-
-            debug!(
-                target: "explorerd::rpc_blocks::sync_blocks",
-                "Synced block {current_height} [{}]",
-                fmt_duration(block_sync_start.elapsed())
-            );
-
-            // Increment the current height to sync the next block
-            current_height += 1;
-            // Increment the count of successfully synced blocks
-            blocks_synced += 1;
-        }
-
-        info!(
-            target: "explorerd::rpc_blocks::sync_blocks",
-            "Synced {blocks_synced} blocks: explorer blocks total {} [{}]",
-            self.db.blockchain.blocks.len(),
-            fmt_duration(sync_start_time.elapsed()),
-        );
-
-        Ok(())
-    }
-
-    /// Handles blockchain reorganizations (reorgs) during the explorer node's startup synchronization
-    /// with Darkfi nodes, ensuring the explorer provides a consistent and accurate view of the blockchain.
-    ///
-    /// A reorg occurs when the blocks stored by the blockchain nodes diverge from those stored by the explorer.
-    /// This function resolves inconsistencies by identifying the point of divergence, searching backward through
-    /// block heights, and comparing block hashes between the explorer database and the blockchain node. Once a
-    /// common block height is found, the explorer is re-aligned to that height.
-    ///
-    /// If no common block can be found, the explorer resets to the "genesis height," removing all blocks,
-    /// transactions, and metrics from its database to resynchronize with the canonical chain from the nodes.
-    ///
-    /// Returns the last height at which the explorer's state was successfully re-aligned with the blockchain.
-    async fn reorg_blocks(
-        &self,
-        last_synced_height: u32,
-        last_darkfid_height: u32,
-    ) -> darkfi::Result<u32> {
-        // Log reorg detection in the case that explorer height is greater or equal to height of darkfi node
-        if last_synced_height >= last_darkfid_height {
-            info!(target: "explorerd::rpc_blocks::process_sync_blocks_reorg",
-                "Reorg detected with heights: explorer.{last_synced_height} >= darkfid.{last_darkfid_height}");
-        }
-
-        // Declare a mutable variable to track the current height while searching for a common block
-        let mut cur_height = last_synced_height;
-        // Search for an explorer block that matches a darkfi node block
-        while cur_height > 0 {
-            let synced_block = self.get_block_by_height(cur_height)?;
-            debug!(target: "explorerd::rpc_blocks::process_sync_blocks_reorg", "Searching for common block: {cur_height}");
-
-            // Check if we found a synced block for current height being searched
-            if let Some(synced_block) = synced_block {
-                // Fetch the block from darkfi node to check for a match
-                match self.darkfid_client.get_block_by_height(cur_height).await {
-                    Ok(darkfid_block) => {
-                        // If hashes match, we've found the point of divergence
-                        if synced_block.header_hash == darkfid_block.hash().to_string() {
-                            // If hashes match but the cur_height differs from the last synced height, reset the explorer state
-                            if cur_height != last_synced_height {
-                                self.reset_explorer_state(cur_height)?;
-                                debug!(target: "explorerd::rpc_blocks::process_sync_blocks_reorg", "Completed reorg to height: {cur_height}");
-                            }
-                            break;
-                        } else {
-                            // Log reorg detection with height and header hash mismatch details
-                            if cur_height == last_synced_height {
-                                info!(
-                                    target: "explorerd::rpc_blocks::process_sync_blocks_reorg",
-                                    "Reorg detected at height {cur_height}: explorer.{} != darkfid.{}",
-                                    synced_block.header_hash,
-                                    darkfid_block.hash()
-                                );
-                            }
-                        }
-                    }
-                    // Continue searching for blocks that do not exist on darkfi nodes
-                    Err(Error::JsonRpcError((-32121, _))) => (),
-                    Err(e) => {
-                        let error_message =
-                            format!("[process_sync_blocks_reorg] RPC client request failed: {e:?}");
-                        error!(target: "explorerd::rpc_blocks::process_sync_blocks_reorg", "{}", error_message);
-                        return Err(Error::DatabaseError(error_message))
-                    }
-                }
-            }
-
-            // Move to previous block to search for a match
-            cur_height = cur_height.saturating_sub(1);
-        }
-
-        // Check if genesis block reorg is needed
-        if cur_height == 0 {
-            self.reset_explorer_state(0)?;
-        }
-
-        // Return the last height we reorged to
-        Ok(cur_height)
-    }
-}
-/// Subscribes to darkfid's JSON-RPC notification endpoint that serves
-/// new confirmed blocks. Upon receiving them, store them to the database.
-pub async fn subscribe_sync_blocks(
-    explorer: Arc<Explorerd>,
-    endpoint: Url,
-    ex: Arc<smol::Executor<'static>>,
-) -> darkfi::Result<(StoppableTaskPtr, StoppableTaskPtr)> {
-    // Grab last confirmed block
-    let (last_darkfid_height, last_darkfid_hash) =
-        explorer.darkfid_client.get_last_confirmed_block().await?;
-
-    // Grab last synced block
-    let (mut height, hash) = match explorer.service.last_block() {
-        Ok(Some((height, hash))) => (height, hash),
-        Ok(None) => (0, "".to_string()),
-        Err(e) => {
-            return Err(Error::DatabaseError(format!(
-                "[subscribe_blocks] Retrieving last synced block failed: {e:?}"
-            )))
-        }
-    };
-
-    // Evaluates whether there is a mismatch between the last confirmed block and the last synced block
-    let blocks_mismatch = (last_darkfid_height != height || last_darkfid_hash != hash) &&
-        last_darkfid_height != 0 &&
-        height != 0;
-
-    // Check if there is a mismatch, throwing an error to prevent operating in a potentially inconsistent state
-    if blocks_mismatch {
-        warn!(target: "explorerd::rpc_blocks::subscribe_blocks",
-        "Warning: Last synced block is not the last confirmed block: \
-        last_darkfid_height={last_darkfid_height}, last_synced_height={height}, last_darkfid_hash={last_darkfid_hash}, last_synced_hash={hash}");
-        warn!(target: "explorerd::rpc_blocks::subscribe_blocks", "You should first fully sync the blockchain, and then subscribe");
-        return Err(Error::DatabaseError(
-            "[subscribe_blocks] Blockchain not fully synced".to_string(),
-        ));
-    }
-
-    info!(target: "explorerd::rpc_blocks::subscribe_blocks", "Subscribing to receive notifications of incoming blocks");
-    let publisher = Publisher::new();
-    let subscription = publisher.clone().subscribe().await;
-    let _ex = ex.clone();
-    let subscriber_task = StoppableTask::new();
-    subscriber_task.clone().start(
-        // Weird hack to prevent lifetimes hell
-        async move {
-            let ex = _ex.clone();
-            let rpc_client = RpcClient::new(endpoint, ex).await?;
-            let req = JsonRequest::new("blockchain.subscribe_blocks", JsonValue::Array(vec![]));
-            rpc_client.subscribe(req, publisher).await
-        },
-        |res| async move {
-            match res {
-                Ok(()) => { /* Do nothing */ }
-                Err(e) => error!(target: "explorerd::rpc_blocks::subscribe_blocks", "[subscribe_blocks] JSON-RPC server error: {e:?}"),
-            }
-        },
-        Error::RpcServerStopped,
-        ex.clone(),
-    );
-    info!(target: "explorerd::rpc_blocks::subscribe_blocks", "Detached subscription to background");
-
-    let listener_task = StoppableTask::new();
-    listener_task.clone().start(
-        // Weird hack to prevent lifetimes hell
-        async move {
-            loop {
-                match subscription.receive().await {
-                    JsonResult::Notification(n) => {
-                        debug!(target: "explorerd::rpc_blocks::subscribe_blocks", "Got Block notification from darkfid subscription");
-                        if n.method != "blockchain.subscribe_blocks" {
-                            return Err(Error::UnexpectedJsonRpc(format!(
-                                "Got foreign notification from darkfid: {}",
-                                n.method
-                            )))
-                        }
-
-                        // Verify parameters
-                        if !n.params.is_array() {
-                            return Err(Error::UnexpectedJsonRpc(
-                                "Received notification params are not an array".to_string(),
-                            ))
-                        }
-                        let params = n.params.get::<Vec<JsonValue>>().unwrap();
-                        if params.is_empty() {
-                            return Err(Error::UnexpectedJsonRpc(
-                                "Notification parameters are empty".to_string(),
-                            ))
-                        }
-
-                        for param in params {
-                            let param = param.get::<String>().unwrap();
-                            let bytes = base64::decode(param).unwrap();
-
-                            let darkfid_block: BlockInfo = match deserialize_async(&bytes).await {
-                                Ok(b) => b,
-                                Err(e) => {
-                                    return Err(Error::UnexpectedJsonRpc(format!(
-                                        "[subscribe_blocks] Deserializing block failed: {e:?}"
-                                    )))
-                                },
-                            };
-                            info!(target: "explorerd::rpc_blocks::subscribe_blocks", "========================================================================================");
-                            info!(target: "explorerd::rpc_blocks::subscribe_blocks", "| Block Notification: {} |", darkfid_block.hash());
-                            info!(target: "explorerd::rpc_blocks::subscribe_blocks", "========================================================================================");
-
-                            // Store darkfi node block height for later use
-                            let darkfid_block_height = darkfid_block.header.height;
-
-                            // Check if we need to perform a reorg due to mismatch in block heights
-                            if darkfid_block_height <= height {
-                                info!(target: "explorerd::rpc_blocks::subscribe_blocks",
-                                    "Reorg detected with heights: darkfid.{darkfid_block_height} <= explorer.{height}");
-
-                                // Calculate the reset height
-                                let reset_height = darkfid_block_height.saturating_sub(1);
-
-                                // Record the start time to measure the duration of the reorg
-                                let start_reorg_time = Instant::now();
-
-                                // Execute the reorg by resetting the explorer state to reset height
-                                explorer.service.reset_explorer_state(reset_height)?;
-                                info!(target: "explorerd::rpc_blocks::subscribe_blocks", "Completed reorg to height: {reset_height} [{}]", fmt_duration(start_reorg_time.elapsed()));
-                            }
-
-
-                            // Record the start time to measure the duration to store the block
-                            let start_reorg_time = Instant::now();
-
-                            if let Err(e) = explorer.service.put_block(&darkfid_block).await {
-                                return Err(Error::DatabaseError(format!(
-                                    "[subscribe_blocks] Put block failed: {e:?}"
-                                )))
-                            }
-
-                            info!(target: "explorerd::rpc_blocks::subscribe_blocks", "Stored new block at height: {} [{}]", darkfid_block.header.height, fmt_duration(start_reorg_time.elapsed()));
-
-                            // Process the next block
-                            height = darkfid_block.header.height;
-                        }
-                    }
-
-                    JsonResult::Error(e) => {
-                        // Some error happened in the transmission
-                        return Err(Error::UnexpectedJsonRpc(format!("Got error from JSON-RPC: {e:?}")))
-                    }
-
-                    x => {
-                        // And this is weird
-                        return Err(Error::UnexpectedJsonRpc(format!(
-                            "Got unexpected data from JSON-RPC: {x:?}"
-                        )))
-                    }
-                }
-            };
-        },
-        |res| async move {
-            match res {
-                Ok(()) => { /* Do nothing */ }
-                Err(e) => error!(target: "explorerd::rpc_blocks::subscribe_blocks", "[subscribe_blocks] JSON-RPC server error: {e:?}"),
-            }
-        },
-        Error::RpcServerStopped,
-        ex,
-    );
-
-    Ok((subscriber_task, listener_task))
-}

+ 0 - 497
bin/explorer/explorerd/src/service/transactions.rs

@@ -1,497 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use std::collections::HashMap;
-
-use smol::io::Cursor;
-use tinyjson::JsonValue;
-use tracing::{debug, error};
-
-use darkfi::{
-    blockchain::{
-        BlockInfo, BlockchainOverlay, HeaderHash, SLED_PENDING_TX_ORDER_TREE, SLED_PENDING_TX_TREE,
-        SLED_TX_LOCATION_TREE, SLED_TX_TREE,
-    },
-    error::TxVerifyFailed,
-    runtime::vm_runtime::Runtime,
-    tx::Transaction,
-    util::time::Timestamp,
-    validator::fees::{circuit_gas_use, GasData, PALLAS_SCHNORR_SIGNATURE_FEE},
-    zk::VerifyingKey,
-    Error, Result,
-};
-use darkfi_sdk::{
-    crypto::{ContractId, PublicKey},
-    deploy::DeployParamsV1,
-    pasta::pallas,
-    tx::TransactionHash,
-};
-use darkfi_serial::{deserialize_async, serialize_async, AsyncDecodable, AsyncEncodable};
-
-use crate::{error::ExplorerdError, ExplorerService};
-
-#[derive(Debug, Clone)]
-/// Structure representing a `TRANSACTIONS_TABLE` record.
-pub struct TransactionRecord {
-    /// Transaction hash identifier
-    pub transaction_hash: String,
-    /// Header hash identifier of the block this transaction was included in
-    pub header_hash: String,
-    // TODO: Split the payload into a more easily readable fields
-    /// Transaction payload
-    pub payload: Transaction,
-    /// Time transaction was added to the block
-    pub timestamp: Timestamp,
-    /// Total gas used for processing transaction
-    pub total_gas_used: u64,
-    /// Gas used by WASM
-    pub wasm_gas_used: u64,
-    /// Gas used by ZK circuit operations
-    pub zk_circuit_gas_used: u64,
-    /// Gas used for creating the transaction signature
-    pub signature_gas_used: u64,
-    /// Gas used for deployments
-    pub deployment_gas_used: u64,
-}
-
-impl TransactionRecord {
-    /// Auxiliary function to convert a `TransactionRecord` into a `JsonValue` array.
-    pub fn to_json_array(&self) -> JsonValue {
-        JsonValue::Array(vec![
-            JsonValue::String(self.transaction_hash.clone()),
-            JsonValue::String(self.header_hash.clone()),
-            JsonValue::String(format!("{:?}", self.payload)),
-            JsonValue::String(self.timestamp.to_string()),
-            JsonValue::Number(self.total_gas_used as f64),
-            JsonValue::Number(self.wasm_gas_used as f64),
-            JsonValue::Number(self.zk_circuit_gas_used as f64),
-            JsonValue::Number(self.signature_gas_used as f64),
-            JsonValue::Number(self.deployment_gas_used as f64),
-        ])
-    }
-}
-
-impl ExplorerService {
-    /// Resets transactions in the database by clearing transaction-related trees, returning an Ok result on success.
-    pub fn reset_transactions(&self) -> Result<()> {
-        // Initialize transaction trees to reset
-        let trees_to_reset =
-            [SLED_TX_TREE, SLED_TX_LOCATION_TREE, SLED_PENDING_TX_TREE, SLED_PENDING_TX_ORDER_TREE];
-
-        // Iterate over each associated transaction tree and delete its contents
-        for tree_name in &trees_to_reset {
-            let tree = &self.db.blockchain.sled_db.open_tree(tree_name)?;
-            tree.clear()?;
-            let tree_name_str = std::str::from_utf8(tree_name)?;
-            debug!(target: "explorerd::blocks", "Successfully reset transaction tree: {tree_name_str}");
-        }
-
-        Ok(())
-    }
-
-    /// Provides the transaction count of all the transactions in the explorer database.
-    pub fn get_transaction_count(&self) -> usize {
-        self.db.blockchain.txs_len()
-    }
-
-    /// Fetches all known transactions from the database.
-    ///
-    /// This function retrieves all transactions stored in the database and transforms
-    /// them into a vector of [`TransactionRecord`]s. If no transactions are found,
-    /// it returns an empty vector.
-    pub fn get_transactions(&self) -> Result<Vec<TransactionRecord>> {
-        // Retrieve all transactions and handle any errors encountered
-        let txs = self.db.blockchain.transactions.get_all().map_err(|e| {
-            Error::DatabaseError(format!("[get_transactions] Trxs retrieval: {e:?}"))
-        })?;
-
-        // Transform the found `Transactions` into a vector of `TransactionRecords`
-        let txs_records = txs
-            .iter()
-            .map(|(_, tx)| self.to_tx_record(None, tx))
-            .collect::<Result<Vec<TransactionRecord>>>()?;
-
-        Ok(txs_records)
-    }
-
-    /// Fetches all transactions from the database for the given block `header_hash`.
-    ///
-    /// This function retrieves all transactions associated with the specified
-    /// block header hash. It first parses the header hash and then fetches
-    /// the corresponding [`BlockInfo`]. If the block is found, it transforms its
-    /// transactions into a vector of [`TransactionRecord`]s. If no transactions
-    /// are found, it returns an empty vector.
-    pub fn get_transactions_by_header_hash(
-        &self,
-        header_hash: &str,
-    ) -> Result<Vec<TransactionRecord>> {
-        // Parse header hash, returning an error if parsing fails
-        let header_hash = header_hash
-            .parse::<HeaderHash>()
-            .map_err(|_| ExplorerdError::InvalidHeaderHash(header_hash.to_string()))?;
-
-        // Fetch block by hash and handle encountered errors
-        let block = match self.db.blockchain.get_blocks_by_hash(&[header_hash]) {
-            Ok(blocks) => blocks.first().cloned().unwrap(),
-            Err(Error::BlockNotFound(_)) => return Ok(vec![]),
-            Err(e) => {
-                return Err(Error::DatabaseError(format!(
-                    "[get_transactions_by_header_hash] Block retrieval failed: {e:?}"
-                )))
-            }
-        };
-
-        // Transform block transactions into transaction records
-        block
-            .txs
-            .iter()
-            .map(|tx| self.to_tx_record(self.get_block_info(block.header.hash())?, tx))
-            .collect::<Result<Vec<TransactionRecord>>>()
-    }
-
-    /// Fetches a transaction given its header hash.
-    ///
-    /// This function retrieves the transaction associated with the provided
-    /// [`TransactionHash`] and transforms it into a [`TransactionRecord`] if found.
-    /// If no transaction is found, it returns `None`.
-    pub fn get_transaction_by_hash(
-        &self,
-        tx_hash: &TransactionHash,
-    ) -> Result<Option<TransactionRecord>> {
-        let tx_store = &self.db.blockchain.transactions;
-
-        // Attempt to retrieve the transaction using the provided hash handling any potential errors
-        let tx_opt = &tx_store.get(&[*tx_hash], false).map_err(|e| {
-            Error::DatabaseError(format!(
-                "[get_transaction_by_hash] Transaction retrieval failed: {e:?}"
-            ))
-        })?[0];
-
-        // Transform `Transaction` to a `TransactionRecord`, returning None if no transaction was found
-        tx_opt.as_ref().map(|tx| self.to_tx_record(None, tx)).transpose()
-    }
-
-    /// Fetches the [`BlockInfo`] associated with a given transaction hash.
-    ///
-    /// This auxiliary function first fetches the location of the transaction in the blockchain.
-    /// If the location is found, it retrieves the associated [`HeaderHash`] and then fetches
-    /// the block information corresponding to that header hash. The function returns the
-    /// [`BlockInfo`] if successful, or `None` if no location or header hash is found.
-    fn get_tx_block_info(&self, tx_hash: &TransactionHash) -> Result<Option<BlockInfo>> {
-        // Retrieve the location of the transaction
-        let location =
-            self.db.blockchain.transactions.get_location(&[*tx_hash], false).map_err(|e| {
-                Error::DatabaseError(format!(
-                    "[get_tx_block_info] Location retrieval failed: {e:?}"
-                ))
-            })?[0];
-
-        // Fetch the `HeaderHash` associated with the location
-        let header_hash = match location {
-            None => return Ok(None),
-            Some((block_height, _)) => {
-                self.db.blockchain.blocks.get_order(&[block_height], false).map_err(|e| {
-                    Error::DatabaseError(format!(
-                        "[get_tx_block_info] Block retrieval failed: {e:?}"
-                    ))
-                })?[0]
-            }
-        };
-
-        // Return the associated `BlockInfo` if the header hash is found; otherwise, return `None`.
-        match header_hash {
-            None => Ok(None),
-            Some(header_hash) => self.get_block_info(header_hash).map_err(|e| {
-                Error::DatabaseError(format!(
-                    "[get_tx_block_info] BlockInfo retrieval failed: {e:?}"
-                ))
-            }),
-        }
-    }
-
-    /// Fetches the [`BlockInfo`] associated with a given [`HeaderHash`].
-    ///
-    /// This auxiliary function attempts to retrieve the block information using
-    /// the specified [`HeaderHash`]. It returns the associated [`BlockInfo`] if found,
-    /// or `None` when not found.
-    fn get_block_info(&self, header_hash: HeaderHash) -> Result<Option<BlockInfo>> {
-        match self.db.blockchain.get_blocks_by_hash(&[header_hash]) {
-            Err(Error::BlockNotFound(_)) => Ok(None),
-            Ok(block_info) => Ok(block_info.into_iter().next()),
-            Err(e) => Err(Error::DatabaseError(format!(
-                "[get_transactions_by_header_hash] Block retrieval failed: {e:?}"
-            ))),
-        }
-    }
-
-    /// Calculates the gas data for a given transaction, returning a [`GasData`] instance detailing
-    /// various aspects of the gas usage.
-    pub async fn calculate_tx_gas_data(
-        &self,
-        tx: &Transaction,
-        verify_fee: bool,
-    ) -> Result<GasData> {
-        let tx_hash = tx.hash();
-
-        let overlay = BlockchainOverlay::new(&self.db.blockchain)?;
-
-        // Gas accumulators
-        let mut total_gas_used = 0;
-        let mut zk_circuit_gas_used = 0;
-        let mut wasm_gas_used = 0;
-        let mut deploy_gas_used = 0;
-        let mut gas_paid = 0;
-
-        // Table of public inputs used for ZK proof verification
-        let mut zkp_table = vec![];
-        // Table of public keys used for signature verification
-        let mut sig_table = vec![];
-
-        // Index of the Fee-paying call
-        let fee_call_idx = 0;
-
-        // Write the transaction calls payload data
-        let mut payload = vec![];
-        tx.calls.encode_async(&mut payload).await?;
-
-        // Map of ZK proof verifying keys for the transaction
-        let mut verifying_keys: HashMap<[u8; 32], HashMap<String, VerifyingKey>> = HashMap::new();
-        for call in &tx.calls {
-            verifying_keys.insert(call.data.contract_id.to_bytes(), HashMap::new());
-        }
-
-        let block_target = self.db.blockchain.blocks.get_last()?.0 + 1;
-
-        // We'll also take note of all the circuits in a Vec so we can calculate their verification cost.
-        let mut circuits_to_verify = vec![];
-
-        // Iterate over all calls to get the metadata
-        for (idx, call) in tx.calls.iter().enumerate() {
-            // Transaction must not contain a Pow reward call
-            if call.data.is_money_pow_reward() {
-                error!(target: "explorerd::calculate_tx_gas_data", "Reward transaction detected");
-                return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
-            }
-
-            let wasm = overlay.lock().unwrap().contracts.get(call.data.contract_id)?;
-            let mut runtime = Runtime::new(
-                &wasm,
-                overlay.clone(),
-                call.data.contract_id,
-                block_target,
-                block_target,
-                tx_hash,
-                idx as u8,
-            )?;
-
-            // Retrieve the runtime metadata
-            let metadata = runtime.metadata(&payload)?;
-
-            // Decode the metadata retrieved from the execution
-            let mut decoder = Cursor::new(&metadata);
-
-            // The tuple is (zkas_ns, public_inputs)
-            let zkp_pub: Vec<(String, Vec<pallas::Base>)> =
-                AsyncDecodable::decode_async(&mut decoder).await?;
-            let sig_pub: Vec<PublicKey> = AsyncDecodable::decode_async(&mut decoder).await?;
-
-            if decoder.position() != metadata.len() as u64 {
-                error!(
-                    target: "block_explorer::calculate_tx_gas_data",
-                    "[BLOCK_EXPLORER] Failed decoding entire metadata buffer for {tx_hash}:{idx}"
-                );
-                return Err(TxVerifyFailed::ErroneousTxs(vec![tx.clone()]).into())
-            }
-
-            // Here we'll look up verifying keys and insert them into the per-contract map.
-            for (zkas_ns, _) in &zkp_pub {
-                let inner_vk_map =
-                    verifying_keys.get_mut(&call.data.contract_id.to_bytes()).unwrap();
-
-                // TODO: This will be a problem in case of ::deploy, unless we force a different
-                // namespace and disable updating existing circuit. Might be a smart idea to do
-                // so in order to have to care less about being able to verify historical txs.
-                if inner_vk_map.contains_key(zkas_ns.as_str()) {
-                    continue
-                }
-
-                let (zkbin, vk) =
-                    overlay.lock().unwrap().contracts.get_zkas(&call.data.contract_id, zkas_ns)?;
-
-                inner_vk_map.insert(zkas_ns.to_string(), vk);
-                circuits_to_verify.push(zkbin);
-            }
-
-            zkp_table.push(zkp_pub);
-            sig_table.push(sig_pub);
-
-            // Execute the contract
-            let mut state_update = vec![call.data.data[0]];
-            state_update.append(&mut runtime.exec(&payload)?);
-
-            // Apply contract state update
-            runtime.apply(&state_update)?;
-
-            // Contracts are not included within blocks. They need to be deployed so that they can be accessed and utilized for fee data computation
-            if call.data.is_deployment()
-            /* DeployV1 */
-            {
-                // Deserialize the deployment parameters
-                let deploy_params: DeployParamsV1 = deserialize_async(&call.data.data[1..]).await?;
-                let deploy_cid = ContractId::derive_public(deploy_params.public_key);
-
-                // Instantiate the new deployment runtime
-                let mut deploy_runtime = Runtime::new(
-                    &deploy_params.wasm_bincode,
-                    overlay.clone(),
-                    deploy_cid,
-                    block_target,
-                    block_target,
-                    tx_hash,
-                    idx as u8,
-                )?;
-
-                deploy_runtime.deploy(&deploy_params.ix)?;
-
-                deploy_gas_used = deploy_runtime.gas_used();
-
-                // Append the used deployment gas
-                total_gas_used += deploy_gas_used;
-            }
-
-            // At this point we're done with the call and move on to the next one.
-            // Accumulate the WASM gas used.
-            wasm_gas_used = runtime.gas_used();
-
-            // Append the used wasm gas
-            total_gas_used += wasm_gas_used;
-        }
-
-        // The signature fee is tx_size + fixed_sig_fee * n_signatures
-        let signature_gas_used = (PALLAS_SCHNORR_SIGNATURE_FEE * tx.signatures.len() as u64) +
-            serialize_async(tx).await.len() as u64;
-
-        // Append the used signature gas
-        total_gas_used += signature_gas_used;
-
-        // The ZK circuit fee is calculated using a function in validator/fees.rs
-        for zkbin in circuits_to_verify.iter() {
-            zk_circuit_gas_used = circuit_gas_use(zkbin);
-
-            // Append the used zk circuit gas
-            total_gas_used += zk_circuit_gas_used;
-        }
-
-        if verify_fee {
-            // Deserialize the fee call to find the paid fee
-            let fee: u64 = match deserialize_async(&tx.calls[fee_call_idx].data.data[1..9]).await {
-                Ok(v) => v,
-                Err(e) => {
-                    error!(
-                        target: "block_explorer::calculate_tx_gas_data",
-                        "[VALIDATOR] Failed deserializing tx {tx_hash} fee call: {e}"
-                    );
-                    return Err(TxVerifyFailed::InvalidFee.into())
-                }
-            };
-
-            // TODO: This counts 1 gas as 1 token unit. Pricing should be better specified.
-            // Check that enough fee has been paid for the used gas in this transaction.
-            if total_gas_used > fee {
-                error!(
-                    target: "block_explorer::calculate_tx_gas_data",
-                    "[VALIDATOR] Transaction {tx_hash} has insufficient fee. Required: {total_gas_used}, Paid: {fee}");
-                return Err(TxVerifyFailed::InsufficientFee.into())
-            }
-            debug!(target: "block_explorer::calculate_tx_gas_data", "The gas paid for transaction {tx_hash}: {gas_paid}");
-
-            // Store paid fee
-            gas_paid = fee;
-        }
-
-        // Commit changes made to the overlay
-        overlay.lock().unwrap().overlay.lock().unwrap().apply()?;
-
-        let fee_data = GasData {
-            paid: gas_paid,
-            wasm: wasm_gas_used,
-            zk_circuits: zk_circuit_gas_used,
-            signatures: signature_gas_used,
-            deployments: deploy_gas_used,
-        };
-
-        debug!(target: "block_explorer::calculate_tx_gas_data", "The total gas usage for transaction {tx_hash}: {fee_data:?}");
-
-        Ok(fee_data)
-    }
-
-    /// Converts a [`Transaction`] and its associated block information into a [`TransactionRecord`].
-    ///
-    /// This auxiliary function first retrieves the gas data associated with the provided transaction.
-    /// If [`BlockInfo`] is not provided, it attempts to fetch it using the transaction's hash,
-    /// returning an error if the block information cannot be found. Upon success, the function
-    /// returns a [`TransactionRecord`] containing relevant details about the transaction.
-    fn to_tx_record(
-        &self,
-        block_info_opt: Option<BlockInfo>,
-        tx: &Transaction,
-    ) -> Result<TransactionRecord> {
-        // Fetch the gas data associated with the transaction
-        let gas_data_option = self.db.metrics_store.get_tx_gas_data(&tx.hash()).map_err(|e| {
-            Error::DatabaseError(format!(
-                "[to_tx_record] Failed to fetch the gas data associated with transaction {}: {e:?}",
-                tx.hash()
-            ))
-        })?;
-
-        // Unwrap the option, providing a default value when `None`
-        let gas_data = gas_data_option.unwrap_or_else(GasData::default);
-
-        // Process provided block_info option
-        let block_info = match block_info_opt {
-            // Use provided block_info when present
-            Some(block_info) => block_info,
-            // Fetch the block info associated with the transaction when block info not provided
-            None => {
-                match self.get_tx_block_info(&tx.hash())? {
-                    Some(block_info) => block_info,
-                    // If no associated block info found, throw an error as this should not happen
-                    None => {
-                        return Err(Error::BlockNotFound(format!(
-                            "[to_tx_record] Required `BlockInfo` was not found for transaction: {}",
-                            tx.hash()
-                        )))
-                    }
-                }
-            }
-        };
-
-        // Return transformed transaction record
-        Ok(TransactionRecord {
-            transaction_hash: tx.hash().to_string(),
-            header_hash: block_info.hash().to_string(),
-            timestamp: block_info.header.timestamp,
-            payload: tx.clone(),
-            total_gas_used: gas_data.total_gas_used(),
-            wasm_gas_used: gas_data.wasm,
-            zk_circuit_gas_used: gas_data.zk_circuits,
-            signature_gas_used: gas_data.signatures,
-            deployment_gas_used: gas_data.deployments,
-        })
-    }
-}

+ 0 - 456
bin/explorer/explorerd/src/store/contract_metadata.rs

@@ -1,456 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use std::sync::{Arc, Mutex, MutexGuard};
-
-use sled_overlay::{sled, SledDbOverlay};
-use tracing::debug;
-
-use darkfi::{blockchain::SledDbOverlayPtr, Error, Result};
-use darkfi_sdk::crypto::ContractId;
-use darkfi_serial::{async_trait, deserialize, serialize, SerialDecodable, SerialEncodable};
-
-/// Contract metadata tree name.
-pub const SLED_CONTRACT_METADATA_TREE: &[u8] = b"_contact_metadata";
-
-/// Contract source code tree name.
-pub const SLED_CONTRACT_SOURCE_CODE_TREE: &[u8] = b"_contact_source_code";
-
-/// Represents contract metadata containing additional contract information that is not stored on-chain.
-#[derive(Debug, Clone, Eq, PartialEq, SerialEncodable, SerialDecodable)]
-pub struct ContractMetaData {
-    pub name: String,
-    pub description: String,
-}
-
-impl ContractMetaData {
-    pub fn new(name: String, description: String) -> Self {
-        Self { name, description }
-    }
-}
-
-/// Represents a source file containing its file path as a string and its content as a vector of bytes.
-#[derive(Debug, Clone)]
-pub struct ContractSourceFile {
-    pub path: String,
-    pub content: String,
-}
-
-impl ContractSourceFile {
-    /// Creates a `ContractSourceFile` instance.
-    pub fn new(path: String, content: String) -> Self {
-        Self { path, content }
-    }
-}
-
-pub struct ContractMetaStore {
-    /// Pointer to the underlying sled database used by the store and its associated overlay.
-    pub sled_db: sled::Db,
-
-    /// Primary sled tree for storing contract metadata, utilizing [`ContractId`] as keys
-    /// and serialized [`ContractMetaData`] as values.
-    pub main: sled::Tree,
-
-    /// Sled tree for storing contract source code, utilizing source file paths as keys pre-appended with a contract id
-    /// and serialized contract source code [`ContractSourceFile`] content as values.
-    pub source_code: sled::Tree,
-}
-
-impl ContractMetaStore {
-    /// Creates a `ContractMetaStore` instance.
-    pub fn new(db: &sled::Db) -> Result<Self> {
-        let main = db.open_tree(SLED_CONTRACT_METADATA_TREE)?;
-        let source_code = db.open_tree(SLED_CONTRACT_SOURCE_CODE_TREE)?;
-
-        Ok(Self { sled_db: db.clone(), main, source_code })
-    }
-
-    /// Retrieves associated contract metadata for a given [`ContractId`],
-    /// returning an `Option` of [`ContractMetaData`] upon success.
-    pub fn get(&self, contract_id: &ContractId) -> Result<Option<ContractMetaData>> {
-        let opt = self.main.get(contract_id.to_string().as_bytes())?;
-        opt.map(|bytes| deserialize(&bytes).map_err(Error::from)).transpose()
-    }
-
-    /// Provides the number of stored [`ContractMetaData`].
-    pub fn len(&self) -> usize {
-        self.main.len()
-    }
-
-    /// Checks if there is contract metadata stored.
-    pub fn is_empty(&self) -> bool {
-        self.main.is_empty()
-    }
-
-    /// Retrieves all the source file paths associated for provided [`ContractId`].
-    ///
-    /// This function uses provided [`ContractId`] as a prefix to filter relevant paths
-    /// stored in the underlying sled tree, ensuring only files belonging to
-    /// the given contract ID are included. Returns a `Vec` of [`String`]
-    /// representing source code paths.
-    pub fn get_source_paths(&self, contract_id: &ContractId) -> Result<Vec<String>> {
-        let prefix = format!("{contract_id}/");
-
-        // Get all the source paths for provided `ContractId`
-        let mut entries = self
-            .source_code
-            .scan_prefix(&prefix)
-            .filter_map(|item| {
-                let (key, _) = item.ok()?;
-                let key_str = String::from_utf8(key.to_vec()).ok()?;
-                key_str.strip_prefix(&prefix).map(|path| path.to_string())
-            })
-            .collect::<Vec<String>>();
-
-        // Sort the entries to ensure a consistent order
-        entries.sort();
-
-        Ok(entries)
-    }
-
-    /// Retrieves a source content as a [`String`] given a [`ContractId`] and path.
-    pub fn get_source_content(
-        &self,
-        contract_id: &ContractId,
-        source_path: &str,
-    ) -> Result<Option<String>> {
-        let key = format!("{contract_id}/{source_path}");
-        match self.source_code.get(key.as_bytes())? {
-            Some(ivec) => Ok(Some(String::from_utf8(ivec.to_vec()).map_err(|e| {
-                Error::Custom(format!(
-                    "[get_source_content] Failed to retrieve source content: {e:?}"
-                ))
-            })?)),
-            None => Ok(None),
-        }
-    }
-
-    /// Adds contract source code [`ContractId`] and `Vec` of [`ContractSourceFile`]s to the store,
-    /// deleting existing source associated with the provided contract id before doing so.
-    ///
-    /// Delegates operation to [`ContractMetadataStoreOverlay::insert_source`],
-    /// whose documentation provides more details.
-    pub fn insert_source(
-        &self,
-        contract_id: &ContractId,
-        source: &[ContractSourceFile],
-    ) -> Result<()> {
-        let existing_source_paths = self.get_source_paths(contract_id)?;
-        let overlay = ContractMetadataStoreOverlay::new(self.sled_db.clone())?;
-        overlay.insert_source(contract_id, source, Some(&existing_source_paths))?;
-        Ok(())
-    }
-
-    /// Adds contract metadata using provided [`ContractId`] and [`ContractMetaData`] pairs to the store.
-    ///
-    /// Delegates operation to [`ContractMetadataStoreOverlay::insert_metadata`], whose documentation
-    /// provides more details.
-    pub fn insert_metadata(
-        &self,
-        contract_ids: &[ContractId],
-        metadata: &[ContractMetaData],
-    ) -> Result<()> {
-        let overlay = ContractMetadataStoreOverlay::new(self.sled_db.clone())?;
-        overlay.insert_metadata(contract_ids, metadata)?;
-        Ok(())
-    }
-}
-
-/// The `ContractMetadataStoreOverlay` provides write operations for managing contract metadata in
-/// underlying sled database. It supports inserting new [`ContractMetaData`] and contract source code
-/// [`ContractSourceFile`] content and deleting existing source code.
-struct ContractMetadataStoreOverlay {
-    /// Pointer to the overlay used for accessing and performing database write operations on the store.
-    overlay: SledDbOverlayPtr,
-}
-
-impl ContractMetadataStoreOverlay {
-    /// Instantiate a [`ContractMetadataStoreOverlay`] over the provided [`sled::Db`] instance.
-    pub fn new(db: sled::Db) -> Result<Self> {
-        // Create overlay pointer
-        let overlay = Arc::new(Mutex::new(SledDbOverlay::new(&db, vec![])));
-        Ok(Self { overlay: overlay.clone() })
-    }
-
-    /// Inserts [`ContractSourceFile`]s associated with provided [`ContractId`] into the store's
-    /// [`SLED_CONTRACT_SOURCE_CODE_TREE`], committing the changes upon success.
-    ///
-    /// This function locks the overlay, then inserts the provided source files into the store while
-    /// handling serialization and potential errors. The provided contract ID is used to create a key
-    /// for each source file by prepending the contract ID to each source code path. On success, the
-    /// contract source code is persisted and made available for use.
-    ///
-    /// If optional `source_paths_to_delete` is provided, the function first deletes the existing
-    /// source code associated with these paths before inserting the provided source code.
-    pub fn insert_source(
-        &self,
-        contract_id: &ContractId,
-        source: &[ContractSourceFile],
-        source_paths_to_delete: Option<&[String]>,
-    ) -> Result<()> {
-        // Obtain lock
-        let mut lock = self.lock(SLED_CONTRACT_SOURCE_CODE_TREE)?;
-
-        // Delete existing source when existing paths are provided
-        if let Some(paths_to_delete) = source_paths_to_delete {
-            self.delete_source(contract_id, paths_to_delete, &mut lock)?;
-        };
-
-        // Insert each source code file
-        for source_file in source.iter() {
-            // Create key by pre-pending contract id to the source code path
-            let key = format!("{contract_id}/{}", source_file.path);
-            // Insert the source code
-            lock.insert(
-                SLED_CONTRACT_SOURCE_CODE_TREE,
-                key.as_bytes(),
-                source_file.content.as_bytes(),
-            )?;
-            debug!(target: "explorerd::contract_meta_store::insert_source", "Inserted contract source for path {key}");
-        }
-
-        // Commit the changes
-        lock.apply()?;
-
-        Ok(())
-    }
-
-    /// Deletes source code associated with provided [`ContractId`] from the store's [`SLED_CONTRACT_SOURCE_CODE_TREE`],
-    /// committing the changes upon success.
-    ///
-    /// This auxiliary function locks the overlay, then removes the code associated with the provided
-    /// contract ID from the store, handling serialization and potential errors. The contract ID is
-    /// prepended to each source code path to create the keys for deletion. On success, the contract
-    /// source code is permanently deleted.
-    fn delete_source(
-        &self,
-        contract_id: &ContractId,
-        source_paths: &[String],
-        lock: &mut MutexGuard<SledDbOverlay>,
-    ) -> Result<()> {
-        // Delete each source file associated with provided paths
-        for path in source_paths.iter() {
-            // Create key by pre-pending contract id to the source code path
-            let key = format!("{contract_id}/{path}");
-            // Delete the source code
-            lock.remove(SLED_CONTRACT_SOURCE_CODE_TREE, key.as_bytes())?;
-            debug!(target: "explorerd::contract_meta_store::delete_source", "Deleted contract source for path {key}");
-        }
-
-        Ok(())
-    }
-
-    /// Inserts [`ContractId`] and [`ContractMetaData`] pairs into the store's [`SLED_CONTRACT_METADATA_TREE`],
-    /// committing the changes upon success.
-    ///
-    /// This function locks the overlay, verifies that the contract_ids and metadata arrays have matching lengths,
-    /// then inserts them into the store while handling serialization and potential errors. On success,
-    /// contract metadata is persisted and available for use.
-    pub fn insert_metadata(
-        &self,
-        contract_ids: &[ContractId],
-        metadata: &[ContractMetaData],
-    ) -> Result<()> {
-        let mut lock = self.lock(SLED_CONTRACT_METADATA_TREE)?;
-
-        // Ensure lengths of contract_ids and metadata arrays match
-        if contract_ids.len() != metadata.len() {
-            return Err(Error::Custom(String::from(
-                "The lengths of contract_ids and metadata arrays must match",
-            )));
-        }
-
-        // Insert each contract id and metadata pair
-        for (contract_id, metadata) in contract_ids.iter().zip(metadata.iter()) {
-            // Serialize the gas data
-            let serialized_metadata = serialize(metadata);
-
-            // Insert serialized gas data
-            lock.insert(
-                SLED_CONTRACT_METADATA_TREE,
-                contract_id.to_string().as_bytes(),
-                &serialized_metadata,
-            )?;
-            debug!(target: "explorerd::contract_meta_store::insert_metadata",
-                "Inserted contract metadata for contract_id {contract_id}: {metadata:?}");
-        }
-
-        // Commit the changes
-        lock.apply()?;
-
-        Ok(())
-    }
-
-    /// Acquires a lock on the database, opening a specified tree for write operations, returning a
-    /// [`MutexGuard<SledDbOverlay>`] representing the locked state.
-    pub fn lock(&self, tree_name: &[u8]) -> Result<MutexGuard<'_, SledDbOverlay>> {
-        // Lock the database, open tree, and return lock
-        let mut lock = self.overlay.lock().unwrap();
-        lock.open_tree(tree_name, true)?;
-        Ok(lock)
-    }
-}
-
-#[cfg(test)]
-///  This test module verifies the correct insertion and retrieval of contract metadata and source code.
-mod tests {
-    use std::slice;
-
-    use darkfi::util::logger::{setup_test_logger, Level};
-    use darkfi_sdk::crypto::MONEY_CONTRACT_ID;
-    use sled_overlay::sled::Config;
-    use tracing::warn;
-
-    use super::*;
-
-    // Test source paths data
-    const TEST_SOURCE_PATHS: &[&str] = &["test/source1.rs", "test/source2.rs"];
-
-    // Test source code data
-    const TEST_SOURCE_CONTENT: &[&str] =
-        &["fn main() { println!(\"Hello, world!\"); }", "fn add(a: i32, b: i32) -> i32 { a + b }"];
-
-    /// Tests the storing of contract source code by setting up the store, retrieving loaded source paths
-    /// and verifying that the retrieved paths match against expected results.
-    #[test]
-    fn test_add_contract_source() -> Result<()> {
-        // Setup test, returning initialized contract metadata store
-        let store = setup()?;
-
-        // Load source code tests data
-        let contract_id = load_source_code(&store)?;
-
-        // Initialize expected source paths
-        let expected_source_paths: Vec<String> =
-            TEST_SOURCE_PATHS.iter().map(|s| s.to_string()).collect();
-
-        // Retrieve actual loaded source files
-        let actual_source_paths = store.get_source_paths(contract_id)?;
-
-        // Verify that loaded source code matches expected results
-        assert_eq!(expected_source_paths, actual_source_paths);
-
-        Ok(())
-    }
-
-    /// Validates the retrieval of a contract source file from the metadata store by setting up the store,
-    /// loading test source code data, and verifying that loaded source contents match against
-    /// expected content.
-    #[test]
-    fn test_get_contract_source() -> Result<()> {
-        // Setup test, returning initialized contract metadata store
-        let store = setup()?;
-
-        // Load source code tests data
-        let contract_id = load_source_code(&store)?;
-
-        // Iterate through test data
-        for (source_path, expected_content) in
-            TEST_SOURCE_PATHS.iter().zip(TEST_SOURCE_CONTENT.iter())
-        {
-            // Get the content of the source path from the store
-            let actual_source = store.get_source_content(contract_id, source_path)?;
-
-            // Verify that the source code content is the store
-            assert!(actual_source.is_some(), "No content found for path: {source_path}");
-
-            // Validate that the source content matches expected results
-            assert_eq!(
-                actual_source.unwrap(),
-                expected_content.to_string(),
-                "Actual source does not match the expected results for path: {source_path}"
-            );
-        }
-
-        Ok(())
-    }
-
-    /// Tests the addition of [`ContractMetaData`] to the store by setting up the store, inserting
-    /// metadata, and verifying the inserted data matches the expected results.
-    #[test]
-    fn test_add_metadata() -> Result<()> {
-        // Setup test, returning initialized contract metadata store
-        let store = setup()?;
-
-        // Unique identifier for contracts in tests
-        let contract_id: ContractId = *MONEY_CONTRACT_ID;
-
-        // Declare expected metadata used for test
-        let expected_metadata: ContractMetaData = ContractMetaData::new(
-            "Money Contract".to_string(),
-            "Money Contract Description".to_string(),
-        );
-
-        // Add metadata for the source code to the test
-        store.insert_metadata(&[contract_id], slice::from_ref(&expected_metadata))?;
-
-        // Get the metadata content from the store
-        let actual_metadata = store.get(&contract_id)?;
-
-        // Verify that the metadata exists in the store
-        assert!(actual_metadata.is_some());
-
-        // Verify actual metadata matches expected results
-        assert_eq!(actual_metadata.unwrap(), expected_metadata.clone());
-
-        Ok(())
-    }
-
-    /// Sets up a test case for contract metadata store testing by initializing the logger
-    /// and returning an initialized [`ContractMetaStore`].
-    fn setup() -> Result<ContractMetaStore> {
-        // Initialize logger to show execution output
-        if setup_test_logger(
-            &["sled", "runtime", "net"],
-            false,
-            Level::Info,
-            //Level::Verbose,
-            //Level::Debug,
-            //Level::Trace
-        )
-        .is_err()
-        {
-            warn!("Logger already initialized");
-        }
-
-        // Initialize an in-memory sled db instance
-        let db = Config::new().temporary(true).open()?;
-
-        // Initialize the contract store
-        ContractMetaStore::new(&db)
-    }
-
-    /// Loads [`TEST_SOURCE_PATHS`] and [`TEST_SOURCE_CONTENT`] into the provided
-    /// [`ContractMetaStore`] to test source code insertion and retrieval.
-    fn load_source_code(store: &ContractMetaStore) -> Result<&'static ContractId> {
-        // Define the contract ID for testing
-        let contract_id = &MONEY_CONTRACT_ID;
-
-        // Define sample source files for testing using the shared paths and content
-        let test_sources: Vec<ContractSourceFile> = TEST_SOURCE_PATHS
-            .iter()
-            .zip(TEST_SOURCE_CONTENT.iter())
-            .map(|(path, content)| ContractSourceFile::new(path.to_string(), content.to_string()))
-            .collect();
-
-        // Add test source code to the store
-        store.insert_source(contract_id, &test_sources)?;
-
-        Ok(contract_id)
-    }
-}

+ 0 - 1378
bin/explorer/explorerd/src/store/metrics.rs

@@ -1,1378 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use std::{
-    fmt, slice,
-    sync::{Arc, Mutex, MutexGuard},
-};
-
-use sled_overlay::{sled, SledDbOverlay};
-use tracing::{debug, info};
-
-use darkfi::{
-    blockchain::SledDbOverlayPtr,
-    util::time::{DateTime, Timestamp},
-    validator::fees::GasData,
-    Error, Result,
-};
-use darkfi_sdk::{num_traits::ToBytes, tx::TransactionHash};
-use darkfi_serial::{async_trait, deserialize, serialize, SerialDecodable, SerialEncodable};
-
-/// Gas metrics tree name.
-pub const SLED_GAS_METRICS_TREE: &[u8] = b"_gas_metrics";
-
-/// Gas metrics `by_height` tree that contains all metrics by height.
-pub const SLED_GAS_METRICS_BY_HEIGHT_TREE: &[u8] = b"_gas_metrics_by_height";
-
-/// Transaction gas data tree name.
-pub const SLED_TX_GAS_DATA_TREE: &[u8] = b"_tx_gas_data";
-
-/// The time interval for [`GasMetricsKey`]s in the main tree, specified in seconds.
-/// Metrics are stored in hourly intervals (3600 seconds), meaning all metrics accumulated
-/// within a specific hour are stored using a key representing the start of that hour.
-pub const GAS_METRICS_KEY_TIME_INTERVAL: u64 = 3600;
-
-#[derive(Debug, Clone, Default, Eq, PartialEq, SerialEncodable, SerialDecodable)]
-/// Represents metrics used to capture key statistical data.
-pub struct Metrics {
-    /// An aggregate value that represents the sum of the metrics.
-    pub sum: u64,
-    /// The smallest value in the series of measured metrics.
-    pub min: u64,
-    /// The largest value in the series of measured metrics.
-    pub max: u64,
-}
-
-// Temporarily disable unused warnings until the store is integrated with the explorer
-#[allow(dead_code)]
-impl Metrics {
-    /// Constructs a [`Metrics`] instance with provided parameters.
-    pub fn new(sum: u64, min: u64, max: u64) -> Self {
-        Self { sum, min, max }
-    }
-}
-
-/// Structure for managing gas metrics across all transactions in the store.
-///
-/// This struct maintains running totals, extrema, and transaction counts to efficiently calculate
-/// metrics without the need to iterate through previous transactions when new data is added. It is used to build a
-/// comprehensive view of gas metrics across the blockchain's history, including total gas, WASM gas,
-/// ZK circuit gas, and signature gas. The structure allows for O(1) performance in calculating
-/// averages and updating min/max values.
-#[derive(Clone, Default, Eq, PartialEq, SerialEncodable, SerialDecodable)]
-pub struct GasMetrics {
-    /// Represents the total count of transactions tracked by the metrics store.
-    pub txs_count: u64,
-    /// Overall gas consumed metrics across all transactions.
-    pub total_gas: Metrics,
-    /// Gas used across all executed wasm transactions.
-    pub wasm_gas: Metrics,
-    /// Gas consumed across all zk circuit computations.
-    pub zk_circuits_gas: Metrics,
-    /// Gas used metrics related to signatures across transactions.
-    pub signatures_gas: Metrics,
-    /// Gas consumed for deployments across transactions.
-    pub deployments_gas: Metrics,
-    /// The time the metrics was calculated
-    pub timestamp: Timestamp,
-}
-
-// Temporarily disable unused warnings until the store is integrated with the explorer
-#[allow(dead_code)]
-impl GasMetrics {
-    /// Creates a [`GasMetrics`] instance.
-    pub fn new(
-        txs_count: u64,
-        total_gas: Metrics,
-        wasm_gas: Metrics,
-        zk_circuit_gas: Metrics,
-        signature_gas: Metrics,
-        deployment_gas: Metrics,
-        timestamp: Timestamp,
-    ) -> Self {
-        Self {
-            txs_count,
-            total_gas,
-            wasm_gas,
-            zk_circuits_gas: zk_circuit_gas,
-            signatures_gas: signature_gas,
-            deployments_gas: deployment_gas,
-            timestamp,
-        }
-    }
-
-    /// Provides the average of the total gas used.
-    pub fn avg_total_gas_used(&self) -> u64 {
-        self.total_gas.sum.checked_div(self.txs_count).unwrap_or_default()
-    }
-
-    /// Provides the average of the gas used across WASM transactions.
-    pub fn avg_wasm_gas_used(&self) -> u64 {
-        self.wasm_gas.sum.checked_div(self.txs_count).unwrap_or_default()
-    }
-
-    /// Provides the average of the gas consumed across Zero-Knowledge Circuit computations.
-    pub fn avg_zk_circuits_gas_used(&self) -> u64 {
-        self.zk_circuits_gas.sum.checked_div(self.txs_count).unwrap_or_default()
-    }
-
-    /// Provides the average of the gas used to sign transactions.
-    pub fn avg_signatures_gas_used(&self) -> u64 {
-        self.signatures_gas.sum.checked_div(self.txs_count).unwrap_or_default()
-    }
-
-    /// Provides the average of the gas used for deployments.
-    pub fn avg_deployments_gas_used(&self) -> u64 {
-        self.deployments_gas.sum.checked_div(self.txs_count).unwrap_or_default()
-    }
-
-    /// Adds new [`GasData`] to the existing accumulated values.
-    ///
-    /// This method updates running totals, transaction counts, and min/max values
-    /// for various gas metric categories. It accumulates new data without reading existing
-    /// averages, minimums, or maximums from the database to optimize performance.
-    pub fn add(&mut self, tx_gas_data: &[GasData]) {
-        for gas_data in tx_gas_data {
-            // Increment number of transactions included in stats
-            self.txs_count += 1;
-
-            // Update the statistics related to total gas
-            self.total_gas.sum += gas_data.total_gas_used();
-
-            // Update the statistics related to WASM gas
-            self.wasm_gas.sum += gas_data.wasm;
-
-            // Update the statistics related to ZK circuit gas
-            self.zk_circuits_gas.sum += gas_data.zk_circuits;
-
-            // Update the statistics related to signature gas
-            self.signatures_gas.sum += gas_data.signatures;
-
-            // Update the statistics related to deployment gas
-            self.deployments_gas.sum += gas_data.deployments;
-
-            if self.txs_count == 1 {
-                // For the first transaction, set min/max to the transaction values
-                self.total_gas.min = gas_data.total_gas_used();
-                self.total_gas.max = gas_data.total_gas_used();
-                self.wasm_gas.min = gas_data.wasm;
-                self.wasm_gas.max = gas_data.wasm;
-                self.zk_circuits_gas.min = gas_data.zk_circuits;
-                self.zk_circuits_gas.max = gas_data.zk_circuits;
-                self.signatures_gas.min = gas_data.signatures;
-                self.signatures_gas.max = gas_data.signatures;
-                self.deployments_gas.min = gas_data.deployments;
-                self.deployments_gas.max = gas_data.deployments;
-                return;
-            }
-
-            // For subsequent transactions, compare with min/max
-            self.total_gas.min = self.total_gas.min.min(gas_data.total_gas_used());
-            self.total_gas.max = self.total_gas.max.max(gas_data.total_gas_used());
-            self.wasm_gas.min = self.wasm_gas.min.min(gas_data.wasm);
-            self.wasm_gas.max = self.wasm_gas.max.max(gas_data.wasm);
-            self.zk_circuits_gas.min = self.zk_circuits_gas.min.min(gas_data.zk_circuits);
-            self.zk_circuits_gas.max = self.zk_circuits_gas.max.max(gas_data.zk_circuits);
-            self.signatures_gas.min = self.signatures_gas.min.min(gas_data.signatures);
-            self.signatures_gas.max = self.signatures_gas.max.max(gas_data.signatures);
-            self.deployments_gas.min = self.deployments_gas.min.min(gas_data.deployments);
-            self.deployments_gas.max = self.deployments_gas.max.max(gas_data.deployments);
-        }
-    }
-}
-
-/// Debug formatting support for [`GasMetrics`] instances to include averages.
-impl fmt::Debug for GasMetrics {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.debug_struct("GasMetrics")
-            .field("txs_count", &self.txs_count)
-            .field("avg_total_gas_used", &self.avg_total_gas_used())
-            .field("avg_wasm_gas_used", &self.avg_wasm_gas_used())
-            .field("avg_zk_circuits_gas_used", &self.avg_zk_circuits_gas_used())
-            .field("avg_signatures_gas_used", &self.avg_signatures_gas_used())
-            .field("avg_deployments_gas_used", &self.avg_deployments_gas_used())
-            .field("total_gas", &format_args!("{:?}", self.total_gas))
-            .field("wasm_gas", &format_args!("{:?}", self.wasm_gas))
-            .field("zk_circuits_gas", &format_args!("{:?}", self.zk_circuits_gas))
-            .field("signatures_gas", &format_args!("{:?}", self.signatures_gas))
-            .field("deployments_gas", &format_args!("{:?}", self.deployments_gas))
-            .field("timestamp", &self.timestamp)
-            .finish()
-    }
-}
-
-/// The `MetricStore` serves as the entry point for managing metrics,
-/// offering an API for fetching, inserting, and resetting metrics backed by a Sled database.
-///
-/// It organizes data into separate Sled trees, including main storage for gas metrics by a defined time interval,
-/// a tree containing metrics by height for handling reorgs, and a transaction-specific gas data tree.
-/// Different keys, such as gas metric keys, block heights, and transaction hashes, are used to handle
-/// various use cases.
-///
-/// The `MetricStore` utilizes an overlay pattern for write operations, allowing unified management of metrics,
-/// by internally delegating write-related actions like adding metrics and handling reorgs to [`MetricsStoreOverlay`].
-#[derive(Clone)]
-pub struct MetricsStore {
-    /// Pointer to the underlying sled database used by the store and its associated overlay
-    pub sled_db: sled::Db,
-
-    /// Primary sled tree for storing gas metrics, utilizing [`GasMetricsKey`] as keys and
-    /// serialized [`GasMetrics`] as values.
-    pub main: sled::Tree,
-
-    /// Sled tree for storing gas metrics by height, utilizing block `height` as keys
-    /// and serialized [`GasMetrics`] as values.
-    pub by_height: sled::Tree,
-
-    /// Sled tree for storing transaction gas data, utilizing [`TransactionHash`] inner value as keys
-    /// and serialized [`GasData`] as values.
-    pub tx_gas_data: sled::Tree,
-}
-
-// Temporarily disable unused warnings until the store is integrated with the explorer
-#[allow(dead_code)]
-impl MetricsStore {
-    /// Creates a [`MetricsStore`] instance by opening the necessary
-    /// trees in the provided sled database [`sled::Db`]
-    pub fn new(db: &sled::Db) -> Result<Self> {
-        let main = db.open_tree(SLED_GAS_METRICS_TREE)?;
-        let tx_gas_data = db.open_tree(SLED_TX_GAS_DATA_TREE)?;
-        let metrcs_by_height = db.open_tree(SLED_GAS_METRICS_BY_HEIGHT_TREE)?;
-
-        Ok(Self { sled_db: db.clone(), main, tx_gas_data, by_height: metrcs_by_height })
-    }
-
-    /// Fetches [`GasMetrics`]s associated with the provided slice of [`GasMetricsKey`]s.
-    pub fn get(&self, keys: &[GasMetricsKey]) -> Result<Vec<GasMetrics>> {
-        let mut ret = Vec::with_capacity(keys.len());
-        for key in keys {
-            if let Some(metrics_bytes) = self.main.get(key.to_sled_key())? {
-                let metrics = deserialize(&metrics_bytes).map_err(Error::from)?;
-                ret.push(metrics);
-            }
-        }
-        Ok(ret)
-    }
-
-    /// Fetches [`GasMetrics`]s associated with the provided slice of [`u32`] heights.
-    pub fn get_by_height(&self, heights: &[u32]) -> Result<Vec<GasMetrics>> {
-        let mut ret = Vec::with_capacity(heights.len());
-        for height in heights {
-            if let Some(metrics_bytes) = self.by_height.get(height.to_be_bytes())? {
-                let metrics = deserialize(&metrics_bytes).map_err(Error::from)?;
-                ret.push(metrics);
-            }
-        }
-        Ok(ret)
-    }
-
-    /// Fetches the most recent [`GasMetrics`] and its associated [`GasMetricsKey`] from the main tree,
-    /// returning `None` if no metrics are found.
-    pub fn get_last(&self) -> Result<Option<(GasMetricsKey, GasMetrics)>> {
-        self.main
-            .last()?
-            .map(|(key_bytes, metrics_bytes)| {
-                // Deserialize gas metrics key and value
-                let key = GasMetricsKey::from_sled_key(&key_bytes)?;
-                let metrics: GasMetrics = deserialize(&metrics_bytes).map_err(Error::from)?;
-                debug!(target: "explorerd::metrics_store::get_last", "Deserialized metrics at key {key}: {metrics:?}");
-                Ok((key, metrics))
-            })
-            .transpose()
-    }
-
-    /// Fetches all [`GasMetrics`] from the main tree without corresponding key, returning an empty `Vec`
-    /// if no metrics are found.
-    pub fn get_all_metrics(&self) -> Result<Vec<GasMetrics>> {
-        // Iterate through all metrics, deserialize each one, and collect results
-        self.main
-            .iter()
-            .map(|iter_result| match iter_result {
-                Ok((_, metrics_bytes)) => deserialize(&metrics_bytes).map_err(Error::from),
-                Err(e) => Err(Error::from(e)),
-            })
-            .collect()
-    }
-
-    /// Fetches the most recent [`GasMetrics`] and its associated `height` from the `by_height` tree, returning `None` if no metrics are found.
-    pub fn get_last_by_height(&self) -> Result<Option<(u32, GasMetrics)>> {
-        self.by_height
-            .last()?
-            .map(|(height_bytes, metrics_bytes)| {
-                // Deserialize height key and value
-                let key_bytes: [u8; 4] = height_bytes.as_ref().try_into().unwrap();
-                let height = u32::from_be_bytes(key_bytes);
-                let metrics: GasMetrics = deserialize(&metrics_bytes).map_err(Error::from)?;
-                debug!(target: "explorerd::metrics_store::get_last_by_height", "Deserialized metrics at height {height:?}: {metrics:?}");
-                Ok((height, metrics))
-            })
-            .transpose()
-    }
-
-    /// Fetches the [`GasData`] associated with the provided [`TransactionHash`], or `None` if no gas data is found.
-    pub fn get_tx_gas_data(&self, tx_hash: &TransactionHash) -> Result<Option<GasData>> {
-        // Query transaction gas data tree using provided hash
-        let opt = self.tx_gas_data.get(tx_hash.inner())?;
-
-        // Deserialize gas data, map error if needed, return result
-        opt.map(|value| deserialize(&value).map_err(Error::from)).transpose()
-    }
-
-    /// Adds gas metrics for a specific block of transactions to the store.
-    ///
-    /// This function takes block `height`, [`Timestamp`], with associated pairs of [`TransactionHash`] and [`GasData`],
-    /// and updates the accumulated gas metrics in the store. It handles the storage of metrics for both regular use and
-    /// blockchain reorganizations.
-    ///
-    /// Delegates operation to [`MetricsStoreOverlay::insert_gas_metrics`], whose documentation
-    /// provides more details.
-    pub fn insert_gas_metrics(
-        &self,
-        block_height: u32,
-        block_timestamp: &Timestamp,
-        tx_hashes: &[TransactionHash],
-        tx_gas_data: &[GasData],
-    ) -> Result<GasMetricsKey> {
-        let overlay = MetricsStoreOverlay::new(self.sled_db.clone())?;
-        overlay.insert_gas_metrics(block_height, block_timestamp, tx_hashes, tx_gas_data)
-    }
-
-    /// Resets the gas metrics in the store to a specified `height` [`u32`].
-    ///
-    /// This function reverts all gas metrics data after the given height, effectively
-    /// undoing changes made beyond that point. It's useful for handling blockchain
-    /// reorganizations.
-    ///
-    /// Delegates operation to [`MetricsStoreOverlay::reset_gas_metrics`], whose documentation
-    /// provides more details.
-    pub fn reset_gas_metrics(&self, height: u32) -> Result<()> {
-        let overlay = MetricsStoreOverlay::new(self.sled_db.clone())?;
-        overlay.reset_gas_metrics(height)
-    }
-
-    /// Checks if provided [`GasMetricsKey`] exists in the store's main tree.
-    pub fn contains(&self, key: &GasMetricsKey) -> Result<bool> {
-        Ok(self.main.contains_key(key.to_sled_key())?)
-    }
-
-    /// Provides the number of stored metrics in the main tree.
-    pub fn len(&self) -> usize {
-        self.main.len()
-    }
-
-    /// Provides the number of stored metrics by height.
-    pub fn len_by_height(&self) -> usize {
-        self.by_height.len()
-    }
-
-    /// Returns the number of transaction gas usage metrics stored.
-    pub fn len_tx_gas_data(&self) -> usize {
-        self.tx_gas_data.len()
-    }
-
-    /// Checks if there are any gas metrics stored.
-    pub fn is_empty(&self) -> bool {
-        self.main.is_empty()
-    }
-
-    /// Checks if transaction gas data metrics are stored.
-    pub fn is_empty_tx_gas_data(&self) -> bool {
-        self.tx_gas_data.is_empty()
-    }
-}
-
-/// The `MetricsStoreOverlay` provides write operations for managing metrics in conjunction with the
-/// underlying sled database. It supports inserting new [`GasData`] into the stored accumulated metrics,
-/// adding transaction gas data, and reverting metric changes after a specified height.
-struct MetricsStoreOverlay {
-    /// Pointer to the overlay used for accessing and performing database write operations to the store.
-    overlay: SledDbOverlayPtr,
-    /// Pointer managed by the [`MetricsStore`] that references the sled instance on which the overlay operates.
-    db: sled::Db,
-}
-
-impl MetricsStoreOverlay {
-    /// Instantiate a [`MetricsStoreOverlay`] over the provided
-    /// [`sled::Db`] instance.
-    pub fn new(db: sled::Db) -> Result<Self> {
-        // Create overlay pointer
-        let overlay = Arc::new(Mutex::new(SledDbOverlay::new(&db, vec![])));
-
-        // Open trees
-        overlay.lock().unwrap().open_tree(SLED_GAS_METRICS_TREE, true)?;
-        overlay.lock().unwrap().open_tree(SLED_GAS_METRICS_BY_HEIGHT_TREE, true)?;
-        overlay.lock().unwrap().open_tree(SLED_TX_GAS_DATA_TREE, true)?;
-
-        Ok(Self { overlay: overlay.clone(), db })
-    }
-
-    /// Adds the provided [`TransactionHash`] and [`GasData`] pairs to the accumulated [`GasMetrics`]
-    /// in the store's [`SLED_GAS_METRICS_BY_HEIGHT_TREE`] and [`SLED_GAS_METRICS_TREE`] trees, while
-    /// also storing transaction gas data in the [`SLED_TX_GAS_DATA_TREE`], committing all changes upon success.
-    ///
-    /// This function retrieves the latest recorded metrics, updates them with the new gas data, and
-    /// stores the accumulated result. It uses the provided `block_timestamp` to create a normalied time-sequenced
-    /// [`GasMetricsKey`] for metrics storage. The `block_height` is used as a key to store metrics by height
-    /// which are used to handle chain reorganizations. After updating the aggregate metrics, it stores
-    /// the transaction gas data for each transaction in the block.
-    ///
-    /// Returns the created [`GasMetricsKey`] that can be used to retrieve the metric upon success.
-    pub fn insert_gas_metrics(
-        &self,
-        block_height: u32,
-        block_timestamp: &Timestamp,
-        tx_hashes: &[TransactionHash],
-        tx_gas_data: &[GasData],
-    ) -> Result<GasMetricsKey> {
-        // Ensure lengths of tx_hashes and gas_data arrays match
-        if tx_hashes.len() != tx_gas_data.len() {
-            return Err(Error::Custom(String::from(
-                "The lengths of tx_hashes and gas_data arrays must match",
-            )));
-        }
-
-        // Ensure gas data is provided
-        if tx_gas_data.is_empty() {
-            return Err(Error::Custom(String::from("No transaction gas data was provided")));
-        }
-
-        // Lock the database
-        let mut lock = self.overlay.lock().unwrap();
-
-        // Retrieve latest recorded metrics, returning default if not exist
-        let mut metrics = match self.get_last_by_height(&mut lock)? {
-            None => GasMetrics::default(),
-            Some((_, metrics)) => metrics,
-        };
-
-        // Update the accumulated metrics with the provided transaction gas data
-        metrics.add(tx_gas_data);
-
-        // Update the time that the metrics was recorded
-        metrics.timestamp = *block_timestamp;
-
-        // Insert metrics by height
-        self.insert_by_height(&[block_height], &[metrics.clone()], &mut lock)?;
-
-        // Create metrics key based on block_timestamp
-        let metrics_key = GasMetricsKey::new(block_timestamp)?;
-
-        // Normalize metric timestamp based on the key's time interval
-        metrics.timestamp = GasMetricsKey::normalize_timestamp(block_timestamp)?;
-
-        // Insert the gas metrics using metrics key
-        self.insert(slice::from_ref(&metrics_key), &[metrics], &mut lock)?;
-
-        // Insert the transaction gas data for each transaction in the block
-        self.insert_tx_gas_data(tx_hashes, tx_gas_data, &mut lock)?;
-
-        // Commit the changes
-        lock.apply()?;
-
-        Ok(metrics_key)
-    }
-
-    /// Inserts [`TransactionHash`] and [`GasData`] pairs into the store's [`SLED_TX_GAS_DATA_TREE`],
-    /// committing the changes upon success.
-    ///
-    /// This function locks the overlay, verifies that the tx_hashes and gas_data arrays have matching lengths,
-    /// then inserts them into the store while handling serialization and potential errors. Returns a
-    /// successful result upon success.
-    fn insert_tx_gas_data(
-        &self,
-        tx_hashes: &[TransactionHash],
-        gas_data: &[GasData],
-        lock: &mut MutexGuard<SledDbOverlay>,
-    ) -> Result<()> {
-        // Ensure lengths of tx_hashes and gas_data arrays match
-        if tx_hashes.len() != gas_data.len() {
-            return Err(Error::Custom(String::from(
-                "The lengths of tx_hashes and gas_data arrays must match",
-            )));
-        }
-
-        // Insert each transaction hash and gas data pair
-        for (tx_hash, gas_data) in tx_hashes.iter().zip(gas_data.iter()) {
-            // Serialize the gas data
-            let serialized_gas_data = serialize(gas_data);
-
-            // Insert serialized gas data
-            lock.insert(SLED_TX_GAS_DATA_TREE, tx_hash.inner(), &serialized_gas_data)?;
-            debug!(target: "explorerd::metrics_store::insert_tx_gas_data", "Inserted gas data for transaction {tx_hash}: {gas_data:?}");
-        }
-
-        Ok(())
-    }
-
-    /// Resets gas metrics in the [`SLED_GAS_METRICS_TREE`] and [`SLED_GAS_METRICS_BY_HEIGHT_TREE`]
-    /// to a specified block height, undoing all entries after provided height and committing the
-    /// changes upon success.
-    ///
-    /// This function first obtains a lock on the overlay, then reverts changes by calling
-    /// [`Self::revert_by_height_metrics`] and [`Self::revert_metrics`]. Upon successful revert,
-    /// all modifications made after the specified height are permanently reverted.
-    pub fn reset_gas_metrics(&self, height: u32) -> Result<()> {
-        // Obtain lock
-        let mut lock = self.overlay.lock().unwrap();
-
-        // Revert the metrics by height
-        self.revert_by_height_metrics(height, &mut lock)?;
-
-        // Revert the main metrics entries now that `by_height` tree is reset
-        self.revert_metrics(&mut lock)?;
-
-        // Commit the changes
-        lock.apply()?;
-
-        Ok(())
-    }
-
-    /// Inserts [`GasMetricsKey`] and [`GasMetrics`] pairs into the store's [`SLED_GAS_METRICS_TREE`].
-    ///
-    /// This function verifies that the provided keys and metrics arrays have matching lengths,
-    /// then inserts each pair while handling serialization. Returns a successful result
-    /// if all insertions are completed without errors.
-    fn insert(
-        &self,
-        keys: &[GasMetricsKey],
-        metrics: &[GasMetrics],
-        lock: &mut MutexGuard<SledDbOverlay>,
-    ) -> Result<()> {
-        // Ensure lengths of keys and metrics match
-        if keys.len() != metrics.len() {
-            return Err(Error::Custom(String::from(
-                "The lengths of keys and metrics arrays must match",
-            )));
-        }
-
-        // Insert each metric corresponding to respective gas metrics key
-        for (key, metric) in keys.iter().zip(metrics.iter()) {
-            // Insert metric
-            lock.insert(SLED_GAS_METRICS_TREE, &key.to_sled_key(), &serialize(metric))?;
-            debug!(target: "explorerd::metrics_store::insert", "Added gas metrics using key {key}: {metric:?}");
-        }
-
-        Ok(())
-    }
-
-    /// Inserts provided [`u32`] height and [`GasMetrics`] pairs into the store's [`SLED_GAS_METRICS_BY_HEIGHT_TREE`].
-    ///
-    /// This function verifies matching lengths of provided heights and metrics arrays,
-    /// and inserts each pair while handling serialization and errors. Returns a successful result
-    /// if all insertions are completed without errors.
-    fn insert_by_height(
-        &self,
-        heights: &[u32],
-        metrics: &[GasMetrics],
-        lock: &mut MutexGuard<SledDbOverlay>,
-    ) -> Result<()> {
-        // Ensure lengths of heights and metrics match
-        if heights.len() != metrics.len() {
-            return Err(Error::Custom(String::from(
-                "The lengths of heights and metrics arrays must match",
-            )));
-        }
-
-        // Insert each metric corresponding to respective height
-        for (height, metric) in heights.iter().zip(metrics.iter()) {
-            // Serialize the metric and handle potential errors
-            let serialized_metric = serialize(metric);
-
-            // Insert the serialized metric
-            lock.insert(
-                SLED_GAS_METRICS_BY_HEIGHT_TREE,
-                &height.to_be_bytes(),
-                &serialized_metric,
-            )?;
-            debug!(target: "explorerd::metrics_store::insert_by_height", "Added gas metrics using height {height}: {metric:?}");
-        }
-
-        Ok(())
-    }
-
-    /// This function reverts gas metric entries in the [`SLED_GAS_METRICS_TREE`] to align
-    /// with the latest metrics state in the [`SLED_GAS_METRICS_BY_HEIGHT_TREE`].
-    ///
-    /// It first determines the target timestamp to revert to based on the latest entry
-    /// in the by_height tree timestamp. Then, it iteratively removes entries from the main metrics
-    /// tree that are newer than the target timestamp. Once all that is complete, it adds the latest
-    /// metrics by height to the main metrics tree, returning a successful result if revert processes
-    /// without error.
-    fn revert_metrics(&self, lock: &mut MutexGuard<SledDbOverlay>) -> Result<()> {
-        /*** Determine Metrics To Revert ***/
-
-        // Get the last metrics by height and determine the target timestamp to revert to
-        let latest_by_height = self.get_last_by_height(lock)?;
-        let target_timestamp = match &latest_by_height {
-            None => 0,
-            Some((_, metrics)) => GasMetricsKey::normalize_timestamp(&metrics.timestamp)?.inner(),
-        };
-
-        // Get the timestamp of the latest metrics entry in the metrics store
-        let mut current_timestamp = match self.get_last(lock)? {
-            None => return Ok(()),
-            Some((_, metrics)) => metrics.timestamp.inner(),
-        };
-
-        /*** Revert Main Tree Gas Metrics ***/
-
-        // Iterate through at most the total number of gas metric tree entries
-        for _ in 0..self.db.open_tree(SLED_GAS_METRICS_TREE)?.len() {
-            // Stop the loop if the current timestamp is less than or equal to the target timestamp,
-            // as there are no more entries to revert
-            if current_timestamp <= target_timestamp {
-                break;
-            }
-
-            // Create a `GasMetricsKey` for the current timestamp to locate the entry to be reverted.
-            let key_to_revert = GasMetricsKey::new(current_timestamp)?;
-
-            // Remove the corresponding entry from the gas metrics tree.
-            lock.remove(SLED_GAS_METRICS_TREE, &key_to_revert.to_sled_key())?;
-            info!(target: "explorerd:metrics_store:revert_metrics", "Successfully reverted metrics with key: {key_to_revert}");
-
-            // Move to the previous valid timestamp by subtracting the defined time interval
-            current_timestamp = current_timestamp.saturating_sub(GAS_METRICS_KEY_TIME_INTERVAL);
-        }
-
-        /*** Add the Latest Reverted Metrics To Main Tree ***/
-
-        // Retrieve the latest metrics from the `by_height` tree and normalize its timestamp so it can be added to the main tree.
-        // If there are no metrics in the `by_height` tree, we may have reset to 0, so return as there is nothing add.
-        let latest_metrics = match latest_by_height {
-            None => return Ok(()),
-            Some((_, mut metrics)) => {
-                metrics.timestamp = GasMetricsKey::normalize_timestamp(&metrics.timestamp)?;
-                metrics
-            }
-        };
-
-        // Add the latest metrics to the main tree based on latest reverted metrics by height
-        let gas_metrics_key = GasMetricsKey::new(&latest_metrics.timestamp)?;
-        self.insert(&[gas_metrics_key], &[latest_metrics], lock)?;
-
-        Ok(())
-    }
-
-    /// Reverts gas metric entries from [`SLED_GAS_METRICS_BY_HEIGHT_TREE`] to provided `height`.
-    ///
-    /// This function iterates through the entries in gas metrics by height tree and removes all entries
-    /// with heights greater than the specified `height`, effectively reverting all gas metrics beyond that point.
-    fn revert_by_height_metrics(
-        &self,
-        height: u32,
-        lock: &mut MutexGuard<SledDbOverlay>,
-    ) -> Result<()> {
-        // Retrieve the last stored block height
-        let (last_height, _) = match self.get_last_by_height(lock)? {
-            None => return Ok(()),
-            Some(v) => v,
-        };
-
-        // Return early if the requested height is after the last stored height
-        if height >= last_height {
-            return Ok(());
-        }
-
-        // Remove keys greater than `height`
-        while let Some((cur_height_bytes, _)) = lock.last(SLED_GAS_METRICS_BY_HEIGHT_TREE)? {
-            // Convert height bytes to u32
-            let cur_height = u32::from_be_bytes(cur_height_bytes.as_ref().try_into()?);
-
-            // Process all heights that are bigger than provided `height`
-            if cur_height <= height {
-                break;
-            }
-
-            // Remove height being reverted
-            lock.remove(SLED_GAS_METRICS_BY_HEIGHT_TREE, &cur_height_bytes)?;
-            info!(target: "explorerd:metrics_store:revert_by_height_metrics", "Successfully reverted metrics with height: {cur_height}");
-        }
-
-        Ok(())
-    }
-
-    /// Fetches the most recent gas metrics from [`SLED_GAS_METRICS_TREE`], returning an option
-    /// containing a metrics key [`GasMetricsKey`] and [`GasMetrics`] pair, or `None` if no metrics exist.
-    fn get_last(
-        &self,
-        lock: &mut MutexGuard<SledDbOverlay>,
-    ) -> Result<Option<(GasMetricsKey, GasMetrics)>> {
-        // Fetch and deserialize key and metric pair
-        lock.last(SLED_GAS_METRICS_TREE)?
-            .map(|(key_bytes, metrics_bytes)| {
-                // Deserialize the metrics key
-                let key = GasMetricsKey::from_sled_key(&key_bytes)?;
-                // Deserialize the stored gas metrics
-                let metrics: GasMetrics = deserialize(&metrics_bytes).map_err(Error::from)?;
-                Ok((key, metrics))
-            })
-            .transpose()
-    }
-
-    /// Fetches the most recent gas metrics from [`SLED_GAS_METRICS_BY_HEIGHT_TREE`], returning an option
-    /// containing a height [`u32`] and [`GasMetrics`] pair, or `None` if no metrics exist.
-    fn get_last_by_height(
-        &self,
-        lock: &mut MutexGuard<SledDbOverlay>,
-    ) -> Result<Option<(u32, GasMetrics)>> {
-        // Fetch and deserialize height and metric pair
-        lock.last(SLED_GAS_METRICS_BY_HEIGHT_TREE)?
-            .map(|(height_bytes, metrics_bytes)| {
-                // Deserialize the height
-                let key_bytes: [u8; 4] = height_bytes.as_ref().try_into().unwrap();
-                let height = u32::from_be_bytes(key_bytes);
-                // Deserialize the stored gas metrics
-                let metrics: GasMetrics = deserialize(&metrics_bytes).map_err(Error::from)?;
-                Ok((height, metrics))
-            })
-            .transpose()
-    }
-}
-
-/// Represents a key used to store and fetch metrics in the metrics store.
-///
-/// This struct provides methods for creating, serializing, and deserializing gas metrics keys.
-/// It supports creation from various time representations through the [`GasMetricsKeySource`] trait
-/// and offers conversion methods for use with a sled database.
-#[derive(Debug, Eq, PartialEq, Clone)]
-pub struct GasMetricsKey(pub DateTime);
-
-impl GasMetricsKey {
-    /// Creates a new [`GasMetricsKey`] from a source that implements [`GasMetricsKeySource`].
-    /// Depending on the use case, the key supports different input sources such as `Timestamp`, `u64` timestamp,
-    /// or `&str` timestamp to create the key.
-    pub fn new<T: GasMetricsKeySource>(source: T) -> Result<GasMetricsKey> {
-        source.to_key()
-    }
-
-    /// Gets the inner [`DateTime`] value.
-    pub fn inner(&self) -> &DateTime {
-        &self.0
-    }
-
-    /// Converts the [`GasMetricsKey`] into a key suitable for use with a sled database.
-    pub fn to_sled_key(&self) -> Vec<u8> {
-        // Create a new vector with a capacity of 28 bytes
-        let mut sled_key = Vec::with_capacity(28);
-
-        // Push the byte representations of each field into the vector
-        sled_key.extend_from_slice(&self.inner().year.to_be_bytes());
-        sled_key.extend_from_slice(&self.inner().month.to_be_bytes());
-        sled_key.extend_from_slice(&self.inner().day.to_be_bytes());
-        sled_key.extend_from_slice(&self.inner().hour.to_be_bytes());
-        sled_key.extend_from_slice(&self.inner().min.to_be_bytes());
-        sled_key.extend_from_slice(&self.inner().sec.to_be_bytes());
-        sled_key.extend_from_slice(&self.inner().nanos.to_be_bytes());
-
-        // Return sled key
-        sled_key
-    }
-
-    /// Converts a `sled` key into a [`GasMetricsKey`] by deserializing a slice of bytes.
-    pub fn from_sled_key(bytes: &[u8]) -> Result<Self> {
-        if bytes.len() != 28 {
-            return Err(Error::Custom(String::from("Invalid byte length for GasMetricsKey")));
-        }
-
-        // Deserialize byte representations into each field
-        let key = DateTime {
-            year: u32::from_be_bytes(bytes[0..4].try_into()?),
-            month: u32::from_be_bytes(bytes[4..8].try_into()?),
-            day: u32::from_be_bytes(bytes[8..12].try_into()?),
-            hour: u32::from_be_bytes(bytes[12..16].try_into()?),
-            min: u32::from_be_bytes(bytes[16..20].try_into()?),
-            sec: u32::from_be_bytes(bytes[20..24].try_into()?),
-            nanos: u32::from_be_bytes(bytes[24..28].try_into()?),
-        };
-
-        Ok(Self(key))
-    }
-
-    /// Normalizes the given [`DateTime`] to the start of hour.
-    pub fn normalize_date_time(date_time: DateTime) -> DateTime {
-        DateTime {
-            nanos: 0,
-            sec: 0,
-            min: 0,
-            hour: date_time.hour,
-            day: date_time.day,
-            month: date_time.month,
-            year: date_time.year,
-        }
-    }
-
-    /// Normalizes a given [`Timestamp`] to the start of the hour.
-    pub fn normalize_timestamp(timestamp: &Timestamp) -> Result<Timestamp> {
-        let remainder = timestamp.inner() % GAS_METRICS_KEY_TIME_INTERVAL;
-        timestamp.checked_sub(Timestamp::from_u64(remainder))
-    }
-}
-
-impl fmt::Display for GasMetricsKey {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        write!(f, "{}", self.inner())
-    }
-}
-
-/// Provides a unified method for creating new instances of GasMetricKeys using
-/// various time representations: [`Timestamp`], `u64` timestamp, or `&str` timestamp.
-pub trait GasMetricsKeySource {
-    fn to_key(&self) -> Result<GasMetricsKey>;
-}
-
-/// Implements [`GasMetricsKeySource`] for &[`Timestamp`], converting it to a [`GasMetricsKey`].
-impl GasMetricsKeySource for &Timestamp {
-    fn to_key(&self) -> Result<GasMetricsKey> {
-        let date_time = DateTime::from_timestamp(self.inner(), 0);
-        Ok(GasMetricsKey(GasMetricsKey::normalize_date_time(date_time)))
-    }
-}
-
-/// Implements [`GasMetricsKeySource`] for `u64`, converting it to a [`GasMetricsKey`].
-impl GasMetricsKeySource for u64 {
-    fn to_key(&self) -> Result<GasMetricsKey> {
-        let date_time = DateTime::from_timestamp(*self, 0);
-        Ok(GasMetricsKey(GasMetricsKey::normalize_date_time(date_time)))
-    }
-}
-
-/// Implements [`GasMetricsKeySource`] for string slices, converting a `&str` in the `YYYY-MM-DD HH:mm:ss UTC` format
-/// to a [`GasMetricsKey`]. Returns an [`Error::ParseFailed`] error if the provided timestamp string slice is invalid.
-impl GasMetricsKeySource for &str {
-    fn to_key(&self) -> Result<GasMetricsKey> {
-        let date_time = DateTime::from_timestamp_str(self)?;
-        Ok(GasMetricsKey(GasMetricsKey::normalize_date_time(date_time)))
-    }
-}
-
-#[cfg(test)]
-/// This test module verifies the correct insertion, retrieval, and reset of metrics in the store.
-/// It covers adding metrics, searching metrics by time and transaction hash, and resetting metrics with specified heights.
-mod tests {
-    use std::{
-        str::FromStr,
-        time::{Duration, SystemTime, UNIX_EPOCH},
-    };
-
-    use darkfi::util::{
-        logger::{setup_test_logger, Level},
-        time::DateTime,
-    };
-    use structopt::lazy_static::lazy_static;
-    use tracing::warn;
-
-    use super::*;
-
-    /// Number of heights to simulate.
-    const HEIGHT: u32 = 10;
-
-    /// Fixed timestamp in seconds since UNIX epoch.
-    const FIXED_TIMESTAMP: u64 = 1732042800;
-
-    /// [`FIXED_TIMESTAMP`] timestamp as a string in UTC format.
-    const FIXED_TIMESTAMP_STR: &str = "2024-11-19T19:00:00";
-
-    lazy_static! {
-        /// Test transaction hash.
-        pub static ref TX_HASH: TransactionHash = TransactionHash::from_str(
-            "92225ff00a3755d8df93c626b59f6e36cf021d85ebccecdedc38f3f1890a15fc"
-        ).expect("Invalid transaction hash");
-    }
-    /// Tests inserting gas metrics, verifying the correctness of stored metrics.
-    #[test]
-    fn test_insert_gas_metrics() -> Result<()> {
-        // Declare constants used for test
-        const EXPECTED_HEIGHT: usize = HEIGHT as usize - 1;
-
-        // Setup test, returning initialized metrics store
-        let store = setup()?;
-
-        // Load test data into the store and get the expected metrics results
-        let test_data = load_random_metrics(&store, |_, _| {})?;
-
-        // Verify metrics were inserted with the expected counts
-        assert_eq!(store.len(), EXPECTED_HEIGHT);
-
-        // Process height 0 test data separately
-        let mut test_data_iter = test_data.iter();
-
-        // For height 0, confirm there are no metrics stored in the store
-        if let Some(test_data_height0) = test_data_iter.next() {
-            let actual_height0 = store.get(&[GasMetricsKey::new(&test_data_height0.timestamp)?])?;
-            assert!(
-                actual_height0.is_empty(),
-                "Timestamp associated with height 0 should not have any metrics stored"
-            );
-        }
-
-        // Process remaining test data, verifying that each stored metric matches expected results
-        for expected in test_data_iter {
-            let actual = store.get(&[GasMetricsKey::new(&expected.timestamp)?])?;
-            let expected_normalized = normalize_metrics_timestamp(expected)?;
-            assert_eq!(&expected_normalized, &actual[0]);
-        }
-
-        Ok(())
-    }
-
-    /// Tests inserting gas metrics into the `by_height` tree, verifying the correctness of stored metrics.
-    #[test]
-    fn test_insert_by_height_gas_metrics() -> Result<()> {
-        // Declare constants used for test
-        const EXPECTED_HEIGHT: usize = HEIGHT as usize - 1;
-
-        // Setup test, returning initialized metrics store
-        let store = setup()?;
-
-        // Load test data into the store and get the expected metrics results
-        let test_data = load_random_metrics(&store, |_, _| {})?;
-
-        // Verify metrics were inserted with the expected counts
-        assert_eq!(store.len(), EXPECTED_HEIGHT);
-
-        // For height 0, confirm there are no metrics stored in metrics by height
-        let actual_height0 = store.get_by_height(&[0])?;
-        assert!(actual_height0.is_empty(), "Height 0 should not have any metrics stored");
-
-        // Process remaining heights, verifying that each stored metric matches expected results
-        for (height, expected) in (1..).zip(test_data.iter().skip(1)) {
-            let actual = store.get_by_height(&[height])?;
-            assert!(!actual.is_empty(), "No metrics found for height {height}");
-            assert_eq!(expected, &actual[0]);
-        }
-
-        Ok(())
-    }
-
-    /// Tests searching gas metrics by the hour, verifying the correct metrics are found
-    /// and match expected values.
-    #[test]
-    fn test_search_metrics_by_hour() -> Result<()> {
-        // Setup test, returning initialized metrics store
-        let store = setup()?;
-
-        // Load test data, initializing expected with the fourth loaded record
-        let expected = &load_random_metrics(&store, |_, _| {})?[3];
-
-        // Create search criteria based on the expected timestamp value
-        let search_criteria = DateTime::from_timestamp(expected.timestamp.inner(), 0);
-
-        // Search metrics by the hour
-        let actual_opt = store.main.iter().find_map(|res| {
-            res.ok().and_then(|(k, v)| {
-                let key = GasMetricsKey::from_sled_key(&k).ok()?;
-                if key.inner().hour == search_criteria.hour {
-                    deserialize::<GasMetrics>(&v).ok()
-                } else {
-                    None
-                }
-            })
-        });
-
-        // Verify the found metrics match expected results
-        assert!(actual_opt.is_some());
-        assert_eq!(normalize_metrics_timestamp(expected)?, actual_opt.unwrap());
-
-        Ok(())
-    }
-
-    /// Tests fetching gas metrics by a timestamp string, verifying the retrieved metrics
-    /// match expected values.
-    #[test]
-    fn test_get_metrics_by_timestamp_str() -> Result<()> {
-        // Setup test, returning initialized metrics store
-        let store = setup()?;
-
-        // Load fixed data needed for test, initializing expected with the first loaded record
-        let (expected, _) = &load_fixed_metrics(&store)?[0];
-
-        // Create gas metrics key using a test fixed timestamp
-        let gas_metrics_key = GasMetricsKey::new(FIXED_TIMESTAMP_STR)?;
-
-        // Verify the key retrieves the correct metrics and matches the expected value
-        let actual = store.get(&[gas_metrics_key])?;
-        assert_eq!(expected, &actual[0]);
-        Ok(())
-    }
-
-    /// Tests the insertion and retrieval of transaction gas data in the store, verifying expected results.
-    /// Additionally, it tests that transactions not found in the store correctly return a `None` result.
-    #[test]
-    fn test_tx_gas_data() -> Result<()> {
-        let tx_hash_not_found: TransactionHash = TransactionHash::from_str(
-            "93325ff00a3755d8df93c626b59f6e36cf021d85ebccecdedc38f3f1890a15fc",
-        )
-        .expect("Invalid hash");
-
-        // Setup test, returning initialized metrics store
-        let store = setup()?;
-        // Load data needed for test, initializing expected with the first loaded record
-        let (_, expected) = &load_fixed_metrics(&store)?[0];
-
-        // Verify that existing transaction is found
-        let actual_opt = store.get_tx_gas_data(&TX_HASH)?;
-        assert!(actual_opt.is_some());
-        assert_eq!(*expected, actual_opt.unwrap());
-
-        // Verify that transactions that do not exist return None result
-        let actual_not_found = store.get_tx_gas_data(&tx_hash_not_found)?;
-        assert_eq!(None, actual_not_found);
-
-        Ok(())
-    }
-
-    /// Tests resetting gas metrics within a specified height range, verifying that both the `by_height` and `main` trees
-    /// are properly set to the reset height.
-    #[test]
-    fn test_reset_metrics_within_height_range() -> Result<()> {
-        // Declare constants used for test
-        const RESET_HEIGHT: u32 = 6;
-
-        // Setup test, returning initialized metrics store
-        let store = setup()?;
-
-        // Load test data into the store and get the expected reset metrics result
-        let expected = load_reset_metrics(&store, RESET_HEIGHT)?;
-
-        // Reset metrics
-        store.reset_gas_metrics(RESET_HEIGHT)?;
-
-        // Fetch reset metrics by height
-        let actual_by_height_opt = store.get_last_by_height()?;
-        assert!(actual_by_height_opt.is_some(), "Expected get_last_by_height to return metrics");
-
-        // Verify metrics by height are properly reset
-        let (_, actual_by_height) = actual_by_height_opt.unwrap();
-        assert_eq!(&expected, &actual_by_height);
-
-        // Fetch reset main metrics
-        let actual_main_opt = store.get_last()?;
-        assert!(actual_main_opt.is_some(), "Expected get_last to return metrics");
-
-        // Verify main metrics are properly reset
-        let (_, actual_main_metrics) = actual_main_opt.unwrap();
-        assert_eq!(&normalize_metrics_timestamp(&expected)?, &actual_main_metrics);
-
-        Ok(())
-    }
-
-    /// Tests resetting the metrics store to height 0, ensuring it handles the operation gracefully without errors
-    /// and verifies that no metrics remain in the store afterward.
-    #[test]
-    fn test_reset_metrics_height_to_0() -> Result<()> {
-        // Declare constants used for test
-        const RESET_HEIGHT: u32 = 0;
-        const EXPECTED_RESET_HEIGHT: usize = 0;
-
-        // Setup test, returning initialized metrics store
-        let store = setup()?;
-
-        // Load reset test data needed for test
-        _ = load_reset_metrics(&store, RESET_HEIGHT)?;
-
-        // Reset metrics
-        store.reset_gas_metrics(RESET_HEIGHT)?;
-
-        // Verify metrics were reset with the expected counts
-        assert_eq!(store.len_by_height(), EXPECTED_RESET_HEIGHT);
-        assert_eq!(store.len(), EXPECTED_RESET_HEIGHT);
-
-        // Verify metrics by height are empty
-        let actual_by_height_opt = store.get_last_by_height()?;
-        assert!(actual_by_height_opt.is_none(), "Expected None from get_last_by_height");
-
-        // Confirm main metrics are empty
-        let actual_main_opt = store.get_last()?;
-        assert!(actual_main_opt.is_none(), "Expected None from get_last");
-
-        Ok(())
-    }
-
-    /// Tests that resetting beyond the number of available metrics does not change
-    /// the store and no errors are thrown since there are no metrics to reset.
-    #[test]
-    fn test_reset_metrics_beyond_height() -> Result<()> {
-        // Declare constants used for test
-        const RESET_HEIGHT: u32 = HEIGHT + 1;
-        const EXPECTED_RESET_HEIGHT: usize = HEIGHT as usize - 1;
-
-        // Setup test, returning initialized metrics store
-        let store = setup()?;
-
-        // Load reset test data needed for test, storing the expected result
-        let expected = load_reset_metrics(&store, RESET_HEIGHT)?;
-
-        // Reset metrics to given height
-        store.reset_gas_metrics(RESET_HEIGHT)?;
-
-        // Verify metrics were reset with the expected counts
-        assert_eq!(store.len_by_height(), EXPECTED_RESET_HEIGHT);
-        assert_eq!(store.len(), EXPECTED_RESET_HEIGHT);
-
-        // Verify that the last record for metrics by height is correctly reset
-        let actual_by_height_opt = store.get_last_by_height()?;
-        assert!(actual_by_height_opt.is_some(), "Expected get_last_by_height to return metrics");
-        let (_, actual_by_height) = actual_by_height_opt.unwrap();
-        assert_eq!(&expected, &actual_by_height);
-
-        // Verify that the last record for main metrics is correctly reset
-        let actual_main_opt = store.get_last()?;
-        assert!(actual_main_opt.is_some(), "Expected get_last to return metrics");
-        let (_, actual_main) = actual_main_opt.unwrap();
-        assert_eq!(&normalize_metrics_timestamp(&expected)?, &actual_main);
-
-        Ok(())
-    }
-
-    /// Tests resetting metrics at the last available height to verify that the code
-    /// can handle the boundary condition.
-    #[test]
-    fn test_reset_metrics_at_height() -> Result<()> {
-        // Declare constants used for test
-        const RESET_HEIGHT: u32 = HEIGHT;
-        const EXPECTED_RESET_HEIGHT: usize = HEIGHT as usize - 1;
-
-        // Setup test, returning initialized metrics store
-        let store = setup()?;
-
-        // Load reset test data needed for test
-        let expected = load_reset_metrics(&store, RESET_HEIGHT)?;
-
-        // Reset metrics to given height
-        store.reset_gas_metrics(RESET_HEIGHT)?;
-
-        // Verify metrics were reset with the expected counts
-        assert_eq!(store.len_by_height(), EXPECTED_RESET_HEIGHT);
-        assert_eq!(store.len(), EXPECTED_RESET_HEIGHT);
-
-        // Verify that the last record for metrics by height is correctly reset
-        let actual_by_height_opt = store.get_last_by_height()?;
-        assert!(actual_by_height_opt.is_some(), "Expected get_last_by_height to return metrics");
-        let (_, actual_by_height) = actual_by_height_opt.unwrap();
-        assert_eq!(&expected, &actual_by_height);
-
-        // Verify that the last record for main metrics is correctly reset
-        let actual_main_opt = store.get_last()?;
-        assert!(actual_main_opt.is_some(), "Expected get_last to return metrics");
-        let (_, actual_main) = actual_main_opt.unwrap();
-        assert_eq!(&normalize_metrics_timestamp(&expected)?, &actual_main);
-
-        Ok(())
-    }
-
-    /// Tests that resetting an empty metrics store gracefully handles
-    /// the operation without errors and ensures the store remains empty.
-    #[test]
-    fn test_reset_empty_store() -> Result<()> {
-        const RESET_HEIGHT: u32 = 6;
-
-        // Setup test, returning initialized metrics store
-        let store = setup()?;
-
-        // Reset metrics with an empty store
-        store.reset_gas_metrics(RESET_HEIGHT)?;
-
-        // Verify no metrics with the expected counts
-        assert_eq!(store.len_by_height(), 0);
-        assert_eq!(store.len(), 0);
-
-        // Verify that metrics by height is empty
-        let actual_by_height = store.get_last_by_height()?;
-        assert!(actual_by_height.is_none(), "Expected get_last_by_height to return None");
-
-        // Verify main metrics is empty
-        let actual_main = store.get_last()?;
-        assert!(actual_main.is_none(), "Expected get_last to return None");
-
-        Ok(())
-    }
-
-    /// Sets up a test case for metrics store testing by initializing the logger,
-    /// creating a temporary database, and returning an initialized metrics store.
-    fn setup() -> Result<MetricsStore> {
-        // Initialize logger to show execution output
-        if setup_test_logger(
-            &["sled", "runtime", "net"],
-            false,
-            Level::Info,
-            //Level::Verbose,
-            //Level::Debug,
-            //Level::Trace,
-        )
-        .is_err()
-        {
-            warn!("Logger already initialized");
-        }
-
-        // Create a temporary directory for the sled database
-        let db =
-            sled::Config::new().temporary(true).open().expect("Unable to open test sled database");
-
-        // Initialize the metrics store
-        let metrics_store = MetricsStore::new(&db.clone())?;
-
-        Ok(metrics_store)
-    }
-
-    /// Loads random test gas metrics data into the given metrics store, simulating height 0 as a
-    /// genesis block with no metrics.
-    ///
-    /// Computes the starting block timestamp from the current system time for the first metric,
-    /// then inserts each subsequent metric at intervals of [`GAS_METRICS_KEY_TIME_INTERVAL`],
-    /// resulting in metrics being inserted one hour apart. The function iterates through a predefined
-    /// height range, as defined by [`HEIGHT`], to accumulate and insert gas metrics. After each
-    /// metric is stored, the `metric_loaded` closure is invoked, allowing the caller to perform
-    /// specific actions as the data is loaded.
-    ///
-    /// NOTE: A fixed transaction hash is used to insert the metrics, as this test data is solely intended
-    /// to validate gas metrics and not transaction-specific gas data.
-    ///
-    /// Upon success, it returns a list of snapshots of the accumulated metrics that were loaded.
-    fn load_random_metrics<F>(
-        metrics_store: &MetricsStore,
-        mut metrics_loaded: F,
-    ) -> Result<Vec<GasMetrics>>
-    where
-        F: FnMut(u32, &GasMetrics),
-    {
-        // Calculate the start block timestamp
-        let start_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
-
-        // Define variables to store accumulated loaded data
-        let mut accumulated_metrics = GasMetrics::default();
-        let mut metrics = Vec::with_capacity(HEIGHT as usize);
-
-        // Iterate and load data
-        for height in 0..HEIGHT {
-            let timestamp_secs = (UNIX_EPOCH +
-                Duration::from_secs(start_time + height as u64 * GAS_METRICS_KEY_TIME_INTERVAL))
-            .duration_since(UNIX_EPOCH)?
-            .as_secs();
-
-            // Initialize simulated block_timestamp
-            let block_timestamp = Timestamp::from(timestamp_secs);
-            accumulated_metrics.timestamp = block_timestamp;
-
-            // Simulate genesis block, metrics are stored after height 0
-            if height > 0 {
-                let tx_gas_data = random_gas_data(height as u64 + start_time);
-                accumulated_metrics.add(slice::from_ref(&tx_gas_data));
-                metrics_store.insert_gas_metrics(
-                    height,
-                    &block_timestamp,
-                    &[*TX_HASH],
-                    &[tx_gas_data],
-                )?;
-            }
-
-            // Invoke passed in metrics loaded closure
-            metrics_loaded(height, &accumulated_metrics);
-
-            // Add a snapshot of the accumulated metrics
-            metrics.push(accumulated_metrics.clone());
-        }
-
-        Ok(metrics)
-    }
-
-    /// Loads fixed test data into the metrics store using fixed timestamps,
-    /// returning snapshots of accumulated [`GasMetrics`] with corresponding [`GasData`]
-    /// used to update the metrics.
-    ///
-    /// Currently, this function only loads a single record but is designed to be extendable
-    /// to insert additional records in the future without affecting the method's return signature,
-    /// making it suitable for use in tests.
-    fn load_fixed_metrics(metrics_store: &MetricsStore) -> Result<Vec<(GasMetrics, GasData)>> {
-        // Convert the fixed timestamp constant to a Timestamp object
-        let fixed_timestamp = Timestamp::from_u64(FIXED_TIMESTAMP);
-
-        // Initialize an empty GasMetrics object to accumulate the data
-        let height: u32 = 1;
-        let mut accumulated_metrics = GasMetrics::default();
-        let mut metrics_vec = Vec::with_capacity(HEIGHT as usize);
-
-        // Initialize the block_timestamp using the fixed timestamp
-        let block_timestamp = fixed_timestamp;
-        accumulated_metrics.timestamp = block_timestamp;
-
-        // Generate random gas data for the given height
-        let gas_data = random_gas_data(height as u64);
-        accumulated_metrics.add(slice::from_ref(&gas_data));
-
-        // Insert the gas metrics into the metrics store
-        metrics_store.insert_gas_metrics(
-            height,
-            &block_timestamp,
-            &[*TX_HASH],
-            slice::from_ref(&gas_data),
-        )?;
-        metrics_vec.push((accumulated_metrics, gas_data));
-
-        Ok(metrics_vec)
-    }
-
-    /// Loads reset test data into the store, returning the accumulated gas metrics at the specified reset height.
-    fn load_reset_metrics(metrics_store: &MetricsStore, reset_height: u32) -> Result<GasMetrics> {
-        let mut reset_metrics = GasMetrics::default();
-
-        // Load metrics, passing in a closure to store the reset metrics
-        _ = load_random_metrics(metrics_store, |height, acc_metrics| {
-            // Store accumulated metrics at reset height
-            if reset_height == height || reset_height >= HEIGHT {
-                reset_metrics = acc_metrics.clone();
-            }
-        })?;
-
-        Ok(reset_metrics)
-    }
-
-    /// Generates random [`GasData`] based on the provided seed value, allowing for the simulation
-    /// of varied gas data values.
-    fn random_gas_data(seed: u64) -> GasData {
-        /// Defines a limit for gas data values.
-        const GAS_LIMIT: u64 = 100_000;
-
-        // Initialize gas usage with the provided seed
-        let mut gas_used = seed;
-
-        // Closure to generate a random gas value
-        let mut random_gas = || {
-            // Introduce variability using the seed and current gas_used
-            let variation = seed.wrapping_add(gas_used);
-            gas_used = gas_used.wrapping_mul(6364136223846793005).wrapping_add(variation);
-            gas_used
-        };
-
-        // Create GasData with random values constrained by GAS_LIMIT
-        GasData {
-            paid: random_gas() % GAS_LIMIT,
-            wasm: random_gas() % GAS_LIMIT,
-            zk_circuits: random_gas() % GAS_LIMIT,
-            signatures: random_gas() % GAS_LIMIT,
-            deployments: random_gas() % GAS_LIMIT,
-        }
-    }
-
-    /// Normalizes the [`GasMetrics`] timestamp to the start of the hour for test comparisons.
-    fn normalize_metrics_timestamp(metrics: &GasMetrics) -> Result<GasMetrics> {
-        let mut normalized_metrics = metrics.clone();
-        normalized_metrics.timestamp = GasMetricsKey::normalize_timestamp(&metrics.timestamp)?;
-        Ok(normalized_metrics)
-    }
-}

+ 0 - 82
bin/explorer/explorerd/src/store/mod.rs

@@ -1,82 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use std::collections::HashMap;
-
-use lazy_static::lazy_static;
-use sled_overlay::sled;
-use tracing::info;
-
-use darkfi::{blockchain::Blockchain, error::Result, util::path::expand_path};
-
-use darkfi_sdk::crypto::{DAO_CONTRACT_ID, DEPLOYOOOR_CONTRACT_ID, MONEY_CONTRACT_ID};
-
-use crate::store::{contract_metadata::ContractMetaStore, metrics::MetricsStore};
-
-/// Stores, manages, and provides access to explorer metrics
-pub mod metrics;
-
-/// Stores, manages, and provides access to contract metadata
-pub mod contract_metadata;
-
-/// Represents the explorer database backed by a `sled` database connection, responsible for maintaining
-/// persistent state required for blockchain exploration. It serves as the core data layer for the Explorer application,
-/// storing and managing blockchain data, metrics, and contract-related information.
-pub struct ExplorerDb {
-    /// The main `sled` database connection used for data storage and retrieval
-    pub sled_db: sled::Db,
-    /// Local copy of the Darkfi blockchain used for block synchronization and exploration
-    pub blockchain: Blockchain,
-    /// Store for tracking chain-related metrics
-    pub metrics_store: MetricsStore,
-    /// Store for managing contract metadata, source code, and related data
-    pub contract_meta_store: ContractMetaStore,
-}
-
-impl ExplorerDb {
-    /// Creates a new `ExplorerDb` instance
-    pub fn new(db_path: String) -> Result<Self> {
-        let db_path = expand_path(db_path.as_str())?;
-        let sled_db = sled::open(&db_path)?;
-        let blockchain = Blockchain::new(&sled_db)?;
-        let metrics_store = MetricsStore::new(&sled_db)?;
-        let contract_meta_store = ContractMetaStore::new(&sled_db)?;
-        info!(target: "explorerd", "Initialized explorer database {}: block count: {}, tx count: {}", db_path.display(), blockchain.len(), blockchain.txs_len());
-        Ok(Self { sled_db, blockchain, metrics_store, contract_meta_store })
-    }
-}
-
-// Contract source archives used to bootstrap native contracts during explorer startup
-lazy_static! {
-    pub static ref NATIVE_CONTRACT_SOURCE_ARCHIVES: HashMap<String, &'static [u8]> = {
-        let mut src_map = HashMap::new();
-        src_map.insert(
-            MONEY_CONTRACT_ID.to_string(),
-            &include_bytes!("../../native_contracts_src/money_contract_src.tar")[..],
-        );
-        src_map.insert(
-            DAO_CONTRACT_ID.to_string(),
-            &include_bytes!("../../native_contracts_src/dao_contract_src.tar")[..],
-        );
-        src_map.insert(
-            DEPLOYOOOR_CONTRACT_ID.to_string(),
-            &include_bytes!("../../native_contracts_src/deployooor_contract_src.tar")[..],
-        );
-        src_map
-    };
-}

+ 0 - 205
bin/explorer/explorerd/src/test_utils/mod.rs

@@ -1,205 +0,0 @@
-/* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2026 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <https://www.gnu.org/licenses/>.
- */
-
-use std::sync::{Arc, Mutex};
-
-use lazy_static::lazy_static;
-use smol::Executor;
-use tempdir::TempDir;
-use tinyjson::JsonValue;
-use tracing::warn;
-use url::Url;
-
-use darkfi::{
-    rpc::{
-        jsonrpc::{ErrorCode, JsonRequest, JsonResult},
-        server::RequestHandler,
-    },
-    util::logger::{setup_test_logger, Level},
-};
-
-use crate::Explorerd;
-
-// Defines a global `Explorerd` instance shared across all tests
-lazy_static! {
-    static ref EXPLORERD_INSTANCE: Mutex<Option<Arc<Explorerd>>> = Mutex::new(None);
-}
-
-#[cfg(test)]
-/// Sets up the `Explorerd` instance for testing, ensuring a single instance is initialized only
-/// once and shared among subsequent setup calls.
-pub fn setup() -> Arc<Explorerd> {
-    let mut instance = EXPLORERD_INSTANCE.lock().expect("Failed to lock EXPLORERD_INSTANCE mutex");
-
-    if instance.is_none() {
-        // Initialize logger for the first time
-        if setup_test_logger(
-            &["sled", "runtime", "net"],
-            false,
-            Level::Info,
-            //Level::Verbose,
-            //Level::Debug,
-            //Level::Trace
-        )
-        .is_err()
-        {
-            warn!("Logger already initialized");
-        }
-
-        // Prepare parameters for Explorerd::new
-        let temp_dir = TempDir::new("explorerd").expect("Failed to create temp dir");
-        let db_path_buf = temp_dir.path().join("explorerd_0");
-        let db_path =
-            db_path_buf.to_str().expect("Failed to convert db_path to string").to_string();
-        let darkfid_endpoint = Url::parse("http://127.0.0.1:28345").expect("Invalid URL");
-        let executor = Arc::new(Executor::new());
-
-        // Block on the async function to resolve Explorerd::new
-        let explorerd = smol::block_on(Explorerd::new(db_path, darkfid_endpoint, executor))
-            .expect("Failed to initialize Explorerd instance");
-
-        // Store the initialized instance in the global Mutex
-        *instance = Some(Arc::new(explorerd));
-    }
-
-    // Return a clone of the shared instance
-    Arc::clone(instance.as_ref().unwrap())
-}
-
-/// Auxiliary function that validates the correct handling of an invalid JSON-RPC parameter. It
-/// prepares a JSON-RPC request with the provided method and params. It then sends the request using
-/// the [`Explorerd::handle_request`] function of the provided [`Explorerd`] instance. Verifies the
-/// response is an error, matching the expected error code and message.
-pub async fn validate_invalid_rpc_parameter(
-    explorerd: &Explorerd,
-    method_name: &str,
-    params: &[JsonValue],
-    expected_error_code: i32,
-    expected_error_message: &str,
-) {
-    // Prepare an invalid JSON-RPC request with the provided `params`
-    let request = JsonRequest {
-        id: 1,
-        jsonrpc: "2.0",
-        method: method_name.to_string(),
-        params: JsonValue::Array(params.to_vec()),
-    };
-
-    // Call `handle_request` on the Explorerd instance
-    let response = explorerd.handle_request(request).await;
-
-    // Verify response is a `JsonError` with the appropriate error code and message
-    match response {
-        JsonResult::Error(actual_error) => {
-            assert_eq!(actual_error.error.message, expected_error_message);
-            assert_eq!(actual_error.error.code, expected_error_code);
-        }
-        _ => panic!(
-            "Expected a JSON error response for method: {method_name}, but got something else"
-        ),
-    }
-}
-
-/// Auxiliary function that validates the handling of non-empty parameters when they are supposed
-/// to be empty for the given RPC `method`. It uses the provided [`Explorerd`] instance to ensure
-/// that unexpected non-empty parameters result in the expected error for invalid parameters.
-pub async fn validate_empty_rpc_parameters(explorerd: &Explorerd, method: &str) {
-    // Prepare a JSON-RPC request for `ping_darkfid`
-    let request = JsonRequest {
-        id: 1,
-        jsonrpc: "2.0",
-        method: method.to_string(),
-        params: JsonValue::Array(vec![JsonValue::String("non_empty_param".to_string())]),
-    };
-
-    // Call `handle_request` on the Explorerd instance.
-    let response = explorerd.handle_request(request).await;
-
-    // Verify the response is a `JsonError` with the `PingFailed` error code
-    match response {
-        JsonResult::Error(actual_error) => {
-            let expected_error_code = ErrorCode::InvalidParams.code();
-            let expected_error_msg =
-                "Parameters not permited, received: \"[\\\"non_empty_param\\\"]\"";
-            assert_eq!(actual_error.error.code, expected_error_code);
-            assert_eq!(actual_error.error.message, expected_error_msg);
-        }
-        _ => panic!("Expected a JSON object for the response, but got something else"),
-    }
-}
-
-/// Auxiliary function that validates the handling of an invalid contract ID when calling the specified
-/// JSON-RPC method, ensuring appropriate error responses from provided [`Explorerd`].
-pub fn validate_invalid_rpc_contract_id(explorerd: &Explorerd, method: &str) {
-    validate_invalid_rpc_hash_parameter(explorerd, method, "contract_id", "Invalid contract ID");
-}
-
-/// Auxiliary function that validates the handling of an invalid header hash when calling the specified
-/// JSON-RPC `method`, ensuring appropriate error responses from provided [`Explorerd`].
-pub fn validate_invalid_rpc_header_hash(explorerd: &Explorerd, method: &str) {
-    validate_invalid_rpc_hash_parameter(explorerd, method, "header_hash", "Invalid header hash");
-}
-
-/// Auxiliary function that validates the handling of an invalid tx hash when calling the specified JSON-RPC
-/// `method`, ensuring appropriate error responses from provided [`Explorerd`].
-pub fn validate_invalid_rpc_tx_hash(explorerd: &Explorerd, method: &str) {
-    validate_invalid_rpc_hash_parameter(explorerd, method, "tx_hash", "Invalid tx hash");
-}
-
-/// Auxiliary function that validates the correct handling of invalid hash parameters
-/// when calling the given RPC `method` using the provided [`Explorerd`]. This includes checks for
-/// missing parameters, incorrect parameter types, and invalid hash values, ensuring it returns
-/// error responses matching the expected error codes and messages.
-fn validate_invalid_rpc_hash_parameter(
-    explorerd: &Explorerd,
-    method: &str,
-    parameter_name: &str,
-    invalid_hash_value_message: &str,
-) {
-    smol::block_on(async {
-        // Test for missing `parameter_name` parameter
-        validate_invalid_rpc_parameter(
-            explorerd,
-            method,
-            &[],
-            ErrorCode::InvalidParams.code(),
-            &format!("Parameter '{parameter_name}' at index 0 is missing"),
-        )
-        .await;
-
-        // Test for invalid `parameter_name` parameter type
-        validate_invalid_rpc_parameter(
-            explorerd,
-            method,
-            &[JsonValue::Number(123.0)],
-            ErrorCode::InvalidParams.code(),
-            &format!("Parameter '{parameter_name}' is not a valid string"),
-        )
-        .await;
-
-        // Test for invalid `contract_id` value
-        validate_invalid_rpc_parameter(
-            explorerd,
-            method,
-            &[JsonValue::String("0x0222".to_string())],
-            ErrorCode::InvalidParams.code(),
-            &format!("{invalid_hash_value_message}: 0x0222"),
-        )
-        .await;
-    });
-}

+ 0 - 2
bin/explorer/site/.gitignore

@@ -1,2 +0,0 @@
-/venv
-/__pycache__

+ 0 - 76
bin/explorer/site/Makefile

@@ -1,76 +0,0 @@
-BIN := venv/bin/activate
-PYTHON := python
-
-export LOG_HOME := $(shell echo ~/.local/share/darkfi/explorer_site)
-
-help:
-	@echo "Explorer Site Makefile Commands:"
-	@echo ""
-	@echo "Installation and cleanup:"
-	@echo "  make install       - Install Python dependencies in a virtual environment"
-	@echo "  make clean         - Remove the virtual environment and installed dependencies"
-	@echo ""
-	@echo "Server management:"
-	@echo "  make start-localnet - Start explorer site on localnet environment"
-	@echo "  make start-testnet  - Start explorer site on testnet environment"
-	@echo "  make start-mainnet  - Start explorer site on mainnet environment"
-	@echo "  make stop           - Stop running explorer site"
-
-install: $(BIN)
-
-# Create the virtual environment and install dependencies
-$(BIN): requirements.txt
-	@echo "Installing dependencies..."
-	@if [ ! -d venv ]; then \
-		$(PYTHON) -m venv venv; \
-	fi; \
-	. venv/bin/activate && pip install -q -r requirements.txt
-
-# Remove virtual environment
-clean:
-	@rm -rf venv
-	@echo "Cleaned the virtual environment!"
-
-# Start the server for the specified network (localnet, testnet, mainnet)
-start-%: install
-	@if [ "$*" != "localnet" ] && [ "$*" != "testnet" ] && [ "$*" != "mainnet" ]; then \
-		echo "Error: Unsupported environment '$*'. Supported values are 'localnet', 'testnet', and 'mainnet'."; \
-		exit 1; \
-	fi
-	@if [ -f flask.pid ] && [ "$*" = "localnet" ]; then \
-		echo "Explorer site is already running (PID=$$(cat flask.pid)). Stop it first before starting."; \
-		exit 1; \
-	elif [ -f gunicorn.pid ] && { [ "$*" = "testnet" ] || [ "$*" = "mainnet" ]; }; then \
-		echo "Explorer site is already running (PID=$$(cat gunicorn.pid)). Stop it first before starting."; \
-		exit 1; \
-	fi
-	@. venv/bin/activate && if [ "$*" = "testnet" ] || [ "$*" = "mainnet" ]; then \
-		FLASK_ENV=$* gunicorn --config gunicorn_config.py wsgi:app & PID=$$!; \
-		echo $$PID > gunicorn.pid; \
-		echo "Explorer site started on $* network (PID=$$PID)"; \
-		if [ "$*" = "testnet" ]; then \
-			echo "See site logfile $(LOG_HOME)/testnet/app.log for server startup details"; \
-		fi; \
-		if [ "$*" = "mainnet" ]; then \
-			echo "See site logfile $(LOG_HOME)/mainnet/app.log for server startup details"; \
-		fi; \
-	else \
-		FLASK_ENV=$* python -m flask run & PID=$$!; \
-		echo $$PID > flask.pid; \
-		echo "Started explorer site on $* network (PID=$$PID)"; \
-	fi
-
-# Stop the explorer sites that are running
-stop:
-	@if [ -f flask.pid ] || [ -f gunicorn.pid ]; then \
-		[ -f flask.pid ] && kill $$(cat flask.pid) 2>/dev/null || true; \
-		rm -f flask.pid; \
-		[ -f gunicorn.pid ] && kill $$(cat gunicorn.pid) 2>/dev/null || true; \
-		rm -f gunicorn.pid; \
-		echo "Stopped running explorer sites"; \
-	else \
-		echo "An explorer site is not running, nothing to stop."; \
-	fi
-
-# Declare PHONY targets
-.PHONY: all start-% install clean stop-server

+ 0 - 186
bin/explorer/site/README.md

@@ -1,186 +0,0 @@
-# DarkFi Explorer Site
-
-The Explorer Site is a python based web application that serves as the user interface for viewing DarkFi blockchain data. It provides an interactive way to explore blockchain information, gas analytics, and DarkFi native contract source code, connecting with explorer nodes for real-time data.
-
-## Key Features
-
-- **Explorer Node Integration**: Enables real-time blockchain data retrieval, ensuring a consistent and accurate view of blockchain data across supported networks.
-- **View Blockchain Data**: View and navigate block and transaction data.
-- **Gas Analytics**: Access detailed gas usage metrics across the blockchain and per-transaction basis.
-- **Contract Source Code Navigation**: Inspect native contract source code implementations directly.
-
-## Network Status
-
-The testnet and mainnet configurations serve as placeholders in preparation for their respective launch. When starting the site using these environment configurations, the site will connect to an Explorer Node that point to their respective darkfid networks, but currently only display the network's genesis block.
-
-In addition, testnet and mainnet configurations are currently using development servers and work is ongoing for production-like setups.
-
-## Prerequisites
-- Python 3.12
-- Make
-- Explorerd - Explorer node must be installed and running for the network configuration you are running the site against. See [Explorerd README](../explorerd/README.md) for more details.
-
-## Quick-Start Guide
-
-Run the following to get the explorer site running using pre-configured networks that will automatically install dependencies, configure your environment, and start the site server. Please make sure that explorerd is running for the site configuration that you are launching.
-
-### Start Localnet Site
-
-```sh
-# Launch site using the localnet explorer node configuration  
-make start-localnet
-```
-
-### Start Testnet Site
-
-```sh
-# Launch site using the latest testnet explorer node configuration 
-make start-testnet
-```
-
-### Start Mainnet Site
-
-```sh
-# Launch site using the latest mainnet explorer node configuration 
-make start-mainnet
-```
-
-> Once started, navigate to http://127.0.0.1:5000 in your browser to navigate the site.
-
-### Stopping the Site
-
-```sh
-# Stop the running explorer site
-make stop
-```
-
-### Getting Help
-```shell
-make help
-```
-
-## Detailed Guide
-
-### Installation
-
-Install dependencies:
-```sh
-make install
-```
-
-### Configuration
-
-The application uses a TOML configuration file to manage different environment settings located at [Site Config](site_config.toml).
-
-#### Example Configuration
-
-Below is an example configuration for `localnet`.
-
-```toml
-[localnet]
-# Explorer daemon JSON-RPC endpoint URL
-explorer_rpc_url = "127.0.0.1"
-
-# Explorer daemon JSON-RPC port
-explorer_rpc_port = 14567
-
-# Path to store log files
-log_path = "~/.local/share/darkfi/explorer_site/localnet"
-```
-
-#### Pre-Configured Networks
-
-The Explorer Site supports the following pre-configured `explorerd` environments out of the box.
-- **`localnet`**: An environment for testing and running `explorerd` locally.
-- **`testnet`**: A testing environment for validating the site's functionality prior to mainnet (pending availability).
-- **`mainnet`**: The live production environment connected to the canonical DarkFi blockchain network (pending availability).
-
-> Once the DarkFi blockchain testnet and mainnet are fully available, the Explorer Site will show more than just the genesis blocks for these networks.
-
-Each network environment corresponds to a pre-configured explorerd configuration defined in [Explorerd Config](../explorerd/explorerd_config.toml).
-
-#### Custom Configurations
-
-The Explorer Site supports custom configurations to connect to an Explorer Node, whether running locally on the same machine or remotely on a different network. Proper alignment between the Explorer Site and Explorer Node configurations is essential to ensure the site displays blockchain data.
-
-##### Connecting to a Remote Explorer Node
-
-To enable a remote setup, such as for designers testing their UI design, the Explorer Site can be configured to connect to a remote Explorer Node.
-
-To connect to a remote node, configure the Explorer RPC URL and port in the `site_config.toml` file. Ensure that the domain address and port correspond to the remote node's settings.
-
-**Example Configuration for a Testnet**:
-
-`site_config.toml`:
-
-```toml
-[testnet]
-explorer_rpc_url = "remote-explorer-node.com"
-explorer_rpc_port = 80
-```
-
-Replace `remote-explorer-node.com` with the domain of your remote node and adjust the port as needed for your specific setup.
-
-##### Connecting to a Local Explorer Node
-
-When running the Explorer Node and Site on the same machine with a custom configuration, make sure the RPC URL and port in `site_config.toml` match the `rpc_listen` settings in `explorerd_config.toml` to establish connectivity with the node.
-
-**Example Local Configuration Alignment**:
-
-- **`site_config.toml`**:
-
-  ```toml
-  [localnet]
-  explorer_rpc_url = "127.0.0.1"
-  explorer_rpc_port = 14567
-  ```
-
-- **`explorerd_config.toml`**:
-
-  ```toml
-  [network_config."localnet"]
-  rpc_listen = "tcp://127.0.0.1:14567"
-  ```
-
-### Running the Application
-
-Launch the application using the Flask server:
-
-```sh
-FLASK_ENV=<environment> python -m flask run
-```
-
-Where `<environment>` can be:
-- `localnet` - To run locally.
-- `testnet` - For testing environment.
-- `mainnet` - For mainnet environment.
-
-### Logging
-
-The Explorer Site provides logs that can be inspected to resolve issues and understand runtime behavior. The logging behavior adapts based on the environment setting. In `localnet`, logs are written to both console and files to assist with development and debugging, while `testnet` uses standard file handlers for log files only. For production use, `mainnet` employs rotating file handlers that maintain logs.
-
-#### Log Locations
-
-The log files are stored in environment-specific directories:
-
-| Environment | Log Path |
-|------------|----------|
-| Localnet | `~/.local/share/darkfi/explorer_site/localnet` |
-| Testnet | `~/.local/share/darkfi/explorer_site/testnet` |
-| Mainnet | `~/.local/share/darkfi/explorer_site/mainnet` |
-
-#### Log Output
-
-The logging system maintains two log files in each environment directory:
-
-| Log File | Purpose |
-|----------|---------|
-| `app.log` | Application logs and HTTP requests |
-| `error.log` | Application errors |
-
-#### Log Level Configuration
-
-The logging level can be set using the `LOG_LEVEL` environment variable:
-```sh
-LOG_LEVEL=DEBUG FLASK_ENV=localnet python -m flask run
-```

+ 0 - 136
bin/explorer/site/app.py

@@ -1,136 +0,0 @@
-# This file is part of DarkFi (https://dark.fi)
-#
-# Copyright (C) 2020-2026 Dyne.org foundation
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <https://www.gnu.org/licenses/>.
-
-"""
-Module: app.py
-
-This module initializes the DarkFi explorer Flask application by registering various blueprints for handling routes
-related to blocks, contracts, transactions, search, and the explore section, including the home page. It also defines
-error handlers, ensuring appropriate responses for these common HTTP errors.
-"""
-
-import os
-import tomli
-
-from flask import Flask, render_template
-
-from blueprints.explore import explore_bp
-from blueprints.block import block_bp
-from blueprints.contract import contract_bp
-from blueprints.transaction import transaction_bp
-
-import log
-
-def create_app():
-    """
-    Creates and configures the DarkFi explorer Flask application.
-
-    This function creates and initializes the explorer the Flask app,
-    registering applicable blueprints for handling explorer-related routes,
-    and defining error handling for common HTTP errors. It returns a fully
-    configured Flask application instance.
-    """
-    app = Flask(__name__)
-
-    # Retrieve and store network
-    network = os.getenv("FLASK_ENV", "localnet")
-    app.config['NETWORK'] = network
-
-    # Load the app TOML configuration
-    load_toml_config(app, network)
-
-    # Setup logger
-    log.setup_logger(app, network)
-
-    # Register Blueprints
-    app.register_blueprint(explore_bp)
-    app.register_blueprint(block_bp)
-    app.register_blueprint(contract_bp)
-    app.register_blueprint(transaction_bp)
-
-    # Define page not found error handler
-    @app.errorhandler(404)
-    def page_not_found(e):
-        """
-        Handles 404 errors by rendering a custom 404 error page when a requested page is not found,
-        returning a rendered template along with a 404 status code.
-
-        Args:
-            e: The error object associated with the 404 error.
-        """
-        # Render the custom 404 error page
-        return render_template('404.html'), 404
-
-    # Define internal server error handler
-    @app.errorhandler(500)
-    def internal_server_error(e):
-        """
-        Handles 500 errors by logging the error and returning the app's 500 error page.
-
-        This function logs the error with its stack trace, file name, and line number
-        to help with debugging. It then renders the '500.html' template and returns it
-        along with a 500 HTTP status code.
-
-        Args:
-            e: The error object associated with the 500 error.
-        """
-        # Log the error
-        app.error_logger.exception("An unexpected error occurred")
-
-        # Render the custom 500 error page
-        return render_template('500.html'), 500
-
-    # Log that we started the site
-    app.logger.info("=" * 60)
-    app.logger.info("Started Explorer Site")
-    app.logger.info("=" * 60)
-    app.logger.info(f"Network: {network}")
-    app.logger.info(f"Explorer Node Endpoint: {app.config['explorer_rpc_url']}:{app.config['explorer_rpc_port']}")
-    app.logger.info(f"Log Path: {app.config['log_path']}")
-    app.logger.info("=" * 60)
-
-    return app
-
-def load_toml_config(app, network="localnet", config_path="site_config.toml"):
-    """
-    Loads environment-specific key-value pairs from a TOML configuration file into `app.config`.
-
-    Args:
-        app (Flask): The Flask application.
-        network (str): The name of the network section to load (default is "localnet").
-        config_path (str): The path to the TOML configuration file.
-
-    Raises:
-        FileNotFoundError: If the configuration file cannot be found.
-        KeyError: If the specified environment section does not exist.
-    """
-
-    # Verify that the configuration file exists
-    if not os.path.isfile(config_path):
-        raise FileNotFoundError(f"Configuration file '{config_path}' not found.")
-
-    # Open and parse the configuration file (TOML)
-    with open(config_path, "rb") as f:
-        config = tomli.load(f)
-
-    # Ensure the specified network section exists in the configuration
-    if network not in config:
-        raise KeyError(f"Network '{network}' not found in {config_path}")
-
-    # Load the environment specific configurations into app.config
-    for key, value in config[network].items():
-        app.config[key] = value

+ 0 - 36
bin/explorer/site/blueprints/__init__.py

@@ -1,36 +0,0 @@
-# This file is part of DarkFi (https://dark.fi)
-#
-# Copyright (C) 2020-2026 Dyne.org foundation
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <https://www.gnu.org/licenses/>.
-
-"""
-Explorer blueprint package initializer.
-
-This module imports and exposes blueprints for used by the explorer
-flask application.
-
-Exposed Blueprints:
-    - explore_bp: The explorer application blueprint.
-    - block_bp: The blueprint for block-related functionality.
-    - contract_bp: The blueprint for contract-related functionality.
-    - transaction_bp: The blueprint for transaction-related functionality.
-"""
-from .explore import explore_bp
-from .block import block_bp
-from .contract import contract_bp
-from .transaction import transaction_bp
-
-# Expose blueprints for importing
-__all__ = ["explore_bp", "block_bp", "contract_bp", "transaction_bp"]

+ 0 - 48
bin/explorer/site/blueprints/block.py

@@ -1,48 +0,0 @@
-# This file is part of DarkFi (https://dark.fi)
-#
-# Copyright (C) 2020-2026 Dyne.org foundation
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <https://www.gnu.org/licenses/>.
-
-"""
-Blueprint: block_bp
-
-This module defines a Flask blueprint (`block_bp`) for handling block-related functionality,
-serving as a primary location for Flask code related to routes and features associated with blocks.
-"""
-
-from flask import Blueprint, render_template
-
-import rpc
-
-# Create block blueprint
-block_bp = Blueprint("block", __name__)
-
-@block_bp.route('/block/<header_hash>')
-async def block(header_hash):
-    """
-    Retrieves and displays details of a specific block and its associated transactions based
-    on the provided header hash using RPC calls to the explorer daemon, returning a rendered template.
-
-    Path Args:
-        header_hash (str): The header hash of the block to retrieve.
-    """
-    # Fetch the block details
-    block = await rpc.get_block(header_hash)
-
-    # Fetch transactions associated with the block
-    transactions = await rpc.get_block_transactions(header_hash)
-
-    # Render the template with the block details and associated transactions
-    return render_template('block.html', block=block, transactions=transactions)

+ 0 - 94
bin/explorer/site/blueprints/contract.py

@@ -1,94 +0,0 @@
-# This file is part of DarkFi (https://dark.fi)
-#
-# Copyright (C) 2020-2026 Dyne.org foundation
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <https://www.gnu.org/licenses/>.
-
-"""
-Blueprint: contract_bp
-
-This module defines a Flask blueprint (`contract_bp`) for handling contract-related functionality,
-serving as a primary location for Flask code related to routes and related features associated with contracts.
-"""
-
-from flask import request, render_template, Blueprint
-
-from pygments import highlight
-from pygments.lexers import RustLexer
-from pygments.formatters import HtmlFormatter
-
-import rpc
-
-# Create contract blueprint
-contract_bp = Blueprint("contract", __name__)
-
-@contract_bp.route('/contract/<contract_id>')
-async def contract_source_list(contract_id):
-    """
-    Fetches and displays a list of source files for the specified contract using an RPC
-    call to the explorer daemon, returning a rendered template with the source code
-    files associated with the contract.
-
-    Args:
-        contract_id (str): The Contract ID to fetch the source files for.
-
-    Query Params:
-        name (str, optional): The contract name to display alongside the source files.
-    """
-    # Obtain the contract name to display with the source
-    contract_name = request.args.get('name')
-
-    # Fetch the source file list associated with the contract
-    source_paths = await rpc.get_contract_source_paths(contract_id)
-
-    # Returned rendered contract source list
-    return render_template('contract_source_list.html', contract_id=contract_id, source_paths=source_paths, contract_name=contract_name)
-
-@contract_bp.route('/contract/source/<contract_id>/<path:source_path>')
-async def contract_source(contract_id, source_path):
-    """
-    Fetches and displays the source code for a specific file of a contract using an RPC
-    call to the explorer daemon, returning a rendered template with syntax-highlighted
-    source code.
-
-    Path Args:
-        contract_id (str): The Contract ID to fetch the source file for.
-        source_path (str): The path of the specific source file within the contract.
-
-    Query Params:
-        name (str, optional): The contract name to display alongside the source code.
-    """
-    # Obtain the contract name to display with the source
-    contract_name = request.args.get('name')
-
-    # Retrieve the contract source code
-    raw_source = await rpc.get_contract_source(contract_id, source_path)
-
-    # Style the source code
-    formatter = HtmlFormatter(style='friendly', linenos=True)
-    source = highlight(raw_source, RustLexer(), formatter)
-
-    # Generate css for styled source code
-    pygments_css = formatter.get_style_defs()
-
-    # Returned rendered contract source code page
-    return render_template(
-        'contract_source.html',
-        source=source,
-        contract_id=contract_id,
-        source_path=source_path,
-        pygments_css=pygments_css,
-        contract_name=contract_name
-    )
-

+ 0 - 94
bin/explorer/site/blueprints/explore.py

@@ -1,94 +0,0 @@
-# This file is part of DarkFi (https://dark.fi)
-#
-# Copyright (C) 2020-2026 Dyne.org foundation
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <https://www.gnu.org/licenses/>.
-
-"""
-Blueprint: explorer_bp
-
-This module defines a Flask blueprint (`explore_bp`) for managing the general functionality of the explorer application.
-It serves as the primary location for Flask routes related to the home page, search functionality, and other general features.
-"""
-
-from flask import Blueprint, render_template, request
-
-import rpc
-
-# Create explore blueprint
-explore_bp = Blueprint("explore", __name__)
-@explore_bp.route('/', methods=["GET"])
-async def index():
-    """
-    Fetches and displays the explorer home page content using multiple RPC calls to the
-    explorer daemon, retrieving the last 10 blocks, basic statistics, metric statistics,
-    and DarkFi native contracts.
-
-    Upon success, it returns a rendered template with recent blocks, basic statistics,
-    latest metric statistics (if available), and native contracts.
-    """
-    # Fetch the latest 10 blocks
-    blocks = await rpc.get_last_n_blocks(10)
-
-    # Retrieve basic statistics summarizing the overall chain data
-    basic_stats = await rpc.get_basic_statistics()
-
-    # Fetch the metric statistics
-    metric_stats = await rpc.get_metric_statistics()
-
-    # Determine if metrics exist
-    has_metrics = metric_stats and isinstance(metric_stats, list)
-
-    # Get the latest metric statistics or return empty metrics
-    latest_metric_stats = metric_stats[-1] if has_metrics else [0] * 15
-
-    # Retrieve the native contracts
-    native_contracts = await rpc.get_native_contracts()
-
-    # Render the explorer home page
-    return render_template(
-        'index.html',
-        blocks=blocks,
-        basic_stats=basic_stats,
-        metric_stats=latest_metric_stats,
-        native_contracts=native_contracts,
-    )
-
-@explore_bp.route('/search', methods=['GET', 'POST'])
-async def search():
-    """
-    Searches for a block or transaction based on the provided hash using RPC calls to the
-    explorer daemon. It retrieves relevant data using the search hash from provided query parameter,
-    returning a rendered template that displays either block details with associated transactions
-    or transaction details, depending on the search result.
-
-    Query Params:
-        search_hash (str): The hash of the block or transaction to search for.
-    """
-    # Get the search hash
-    search_hash = request.args.get('search_hash', '')
-
-    # Fetch the block corresponding to the search hash
-    block = await rpc.get_block(search_hash)
-
-    # Fetch transactions associated with the block
-    transactions = await rpc.get_block_transactions(search_hash)
-
-    if transactions:
-        # Render block details with associated transactions if found
-        return render_template('block.html', block=block, transactions=transactions)
-    else:
-        # Fetch transaction details if no transactions are found for the block
-        transaction = await rpc.get_transaction(search_hash)
-        return render_template('transaction.html', transaction=transaction)

+ 0 - 45
bin/explorer/site/blueprints/transaction.py

@@ -1,45 +0,0 @@
-# This file is part of DarkFi (https://dark.fi)
-#
-# Copyright (C) 2020-2026 Dyne.org foundation
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <https://www.gnu.org/licenses/>.
-
-"""
-Blueprint: transaction_bp
-
-This module defines a Flask blueprint (`transaction_bp`) for handling transaction-related functionality,
-serving as a primary location for Flask code related to routes and related features associated with transactions.
-"""
-
-from flask import Blueprint, render_template
-
-import rpc
-
-# Create transaction blueprint
-transaction_bp = Blueprint("transaction", __name__)
-
-@transaction_bp.route('/tx/<transaction_hash>')
-async def transaction(transaction_hash):
-    """
-    Retrieves transaction details based on the provided hash using RPC calls to the explorer daemon
-    and returns a rendered template displaying the information.
-
-    Path Args:
-        transaction_hash (str): The hash of the transaction to retrieve.
-    """
-    # Fetch the transaction details
-    transaction = await rpc.get_transaction(transaction_hash)
-
-    # Render the template using the fetched transaction details
-    return render_template('transaction.html', transaction=transaction)

+ 0 - 31
bin/explorer/site/gunicorn_config.py

@@ -1,31 +0,0 @@
-## explorer gunicorn configuration
-
-# Bind address and port
-bind = "127.0.0.1:8000"
-
-# Number of worker processes
-workers = 1
-
-# Number of threads per worker
-threads = 2
-
-# Type of worker class
-worker_class = "gthread"
-
-# Maximum number of pending connections
-backlog = 2048
-
-# Timeout for workers (in seconds)
-timeout = 30
-
-# PID file location
-pidfile = "gunicorn.pid"
-
-# Log configuration
-import os
-environment = os.getenv("FLASK_ENV", "testnet")
-log_home = os.getenv("LOG_HOME", "/tmp")
-log_path = os.path.join(log_home, environment, "app.log")
-accesslog = log_path
-errorlog = log_path
-loglevel = "info"

+ 0 - 183
bin/explorer/site/log.py

@@ -1,183 +0,0 @@
-# This file is part of DarkFi (https://dark.fi)
-#
-# Copyright (C) 2020-2026 Dyne.org foundation
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <https://www.gnu.org/licenses/>.
-
-import os
-import logging
-
-from logging.handlers import RotatingFileHandler
-
-"""
-Module: log.py
-
-This module provides functionality to setup logging for the explorer Flask application.
-"""
-
-def setup_logger(app, env):
-    """
-    Sets up logging for the explorer Flask app by setting up error, application, and request logging.
-
-    The error logger captures application errors and logs them to a dedicated error log file. The application
-    logger handles general application-level logs such as debug or informational messages. Additionally, the
-    request logger, derived from the Werkzeug logger, manages HTTP request logs and directs them to the
-    same file as the application logger.
-
-    The overall log level is determined by the LOG_LEVEL environment variable, defaulting to INFO if the
-    variable is not set or contains an invalid value. The path where logs are stored is obtained from the
-    application's TOML configuration file under the 'log_path' entry. If not specified, it defaults to the
-    current directory.
-
-    Args:
-        app (Flask): The Flask application instance.
-        env (str): The environment (e.g., 'localnet', 'mainnet', 'testnet', etc.).
-    """
-    log_path = app.config.get('log_path', '.')
-
-    # Expand the path if home directory is specified
-    log_path = os.path.expanduser(log_path)
-
-    # Ensure the log path exists or create it if not
-    if not os.path.exists(log_path):
-        try:
-            os.makedirs(log_path)
-            print(f"created log dir: {log_path}")
-        except OSError as e:
-            raise RuntimeError(f"Unable to create log directory at '{log_path}': {e}")
-
-    # Get log level from environment variable, default to INFO
-    log_level_name = os.environ.get('LOG_LEVEL', 'INFO').upper()
-    try:
-        log_level = getattr(logging, log_level_name)
-    except AttributeError:
-        if log_level_name:
-            app.logger.warning(f"Invalid LOG_LEVEL '{log_level_name}'. Defaulting to INFO.")
-        log_level = logging.INFO
-
-    # App logger setup
-    app_logger = setup_app_logger(log_path, env, log_level)
-    app.logger = app_logger
-
-    # Request logger setup
-    app_log_file = os.path.join(log_path, 'app.log')
-    setup_request_logger(app_log_file, env, log_level)
-
-    # Error logger setup
-    error_logger = setup_error_logger(log_path, env)
-    app.error_logger = error_logger
-
-def setup_error_logger(log_path, env):
-    """
-    Configures the error logger to capture application errors, returning an error logger instance.
-
-    Args:
-        log_path (str): Path to the directory where logs are stored.
-        env (str): The application environment.
-
-    Returns:
-        logging.Logger: Configured error logger instance.
-    """
-    error_logger = logging.getLogger('error_logger')
-    error_log_file = os.path.join(log_path, 'error.log')
-    error_handler = initialize_log_handler(error_log_file, env)
-    error_handler.setLevel(logging.ERROR)
-    error_formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]')
-    error_handler.setFormatter(error_formatter)
-    error_logger.addHandler(error_handler)
-    error_logger.setLevel(logging.ERROR)
-    error_logger.propagate = False
-
-    add_console_handler_if_localnet(env, error_logger, logging.ERROR)
-
-    return error_logger
-
-def setup_app_logger(log_path, env, log_level=logging.INFO):
-    """
-    Configures the app logger for general application logging, returning an app logger instance.
-
-    Args:
-        log_path (str): Path to the directory where logs are stored.
-        env (str): The application environment.
-        log_level (int): The logging level (default is INFO).
-    """
-    app_logger = logging.getLogger('app_logger')
-    app_log_file = os.path.join(log_path, 'app.log')
-    app_handler = initialize_log_handler(app_log_file, env)
-    app_handler.setLevel(log_level)
-    app_formatter = logging.Formatter('%(asctime)s %(message)s')
-    app_handler.setFormatter(app_formatter)
-    app_logger.addHandler(app_handler)
-    app_logger.setLevel(log_level)
-    app_logger.propagate = False
-
-    add_console_handler_if_localnet(env, app_logger, log_level)
-
-    return app_logger
-
-def setup_request_logger(log_file, env, log_level=logging.INFO):
-    """
-    Configures the request logger to handle HTTP request logs based on the specified environment.
-
-    If the environment is set to 'localnet', HTTP requests are logged to the console to facilitate
-    local development and debugging. For all other environments, such as 'testnet' or 'mainnet',
-    HTTP requests are logged to the specified log file, ensuring that logs are persisted in a location
-    appropriate for testing or production use.
-
-    Args:
-        log_file (str): Path to the log file where HTTP requests should be logged.
-        env (str): The application environment (e.g., 'localnet', 'testnet', 'mainnet').
-        log_level (int): The logging level (default is INFO).
-    """
-    # Get the werkzeug logger that logs requests
-    request_logger = logging.getLogger('werkzeug')
-    request_logger.setLevel(log_level)
-    request_logger.propagate = False
-
-    file_handler = logging.FileHandler(log_file)
-    file_handler.setLevel(log_level)
-    request_logger.addHandler(file_handler)
-
-    add_console_handler_if_localnet(env, request_logger, log_level)
-
-def initialize_log_handler(log_file, env):
-    """
-    Initializes and returns a log handler based on the environment.
-
-    Args:
-        log_file (str): Path to the log file.
-        env (str): The environment (e.g., 'mainnet', 'testnet', etc.).
-    """
-    if env == "mainnet":
-        return RotatingFileHandler(log_file, maxBytes=100_000_000, backupCount=5)
-    else:
-        return logging.FileHandler(log_file)
-
-def add_console_handler_if_localnet(env, logger, log_level=logging.INFO):
-    """
-    Adds a console handler to the given logger if the environment is 'localnet'.
-
-    Args:
-    env (str): The current environment (e.g., 'localnet', 'mainnet').
-    logger (logging.Logger): The logger to which the console handler should be added.
-    log_level (int): The logging level for the console handler.
-    """
-
-    # If localnet, also log to console
-    if env == 'localnet':
-        console_handler = logging.StreamHandler()
-        console_handler.setLevel(log_level)
-        formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
-        console_handler.setFormatter(formatter)
-        logger.addHandler(console_handler)

+ 0 - 4
bin/explorer/site/requirements.txt

@@ -1,4 +0,0 @@
-flask[async]
-Pygments
-tomli
-gunicorn

+ 0 - 145
bin/explorer/site/rpc.py

@@ -1,145 +0,0 @@
-# This file is part of DarkFi (https://dark.fi)
-#
-# Copyright (C) 2020-2026 Dyne.org foundation
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <https://www.gnu.org/licenses/>.
-
-"""
-Module: rpc.py
-
-This module provides an asynchronous interface for interacting with the DarkFi explorer daemon
-using JSON-RPC. It includes functionality to create a communication channel, send requests,
-and handle responses from the server.
-"""
-
-import asyncio, json, random
-
-from flask import abort, current_app
-
-class Channel:
-    """Class representing the channel with the JSON-RPC server."""
-    def __init__(self, reader, writer):
-        """Initialize the channel with a reader and writer."""
-        self.reader = reader
-        self.writer = writer
-
-    async def readline(self):
-        """Read a line from the channel, closing it if the connection is lost."""
-        if not (line := await self.reader.readline()):
-            self.writer.close()
-            return None
-        return line[:-1].decode()  # Strip the newline
-
-    async def receive(self):
-        """Receive and decode a message from the channel."""
-        if (plaintext := await self.readline()) is None:
-            return None
-
-        message = plaintext
-        response = json.loads(message)
-        return response
-
-    async def send(self, obj):
-        """Send a JSON-encoded object to the channel."""
-        message = json.dumps(obj)
-        data = message.encode()
-
-        self.writer.write(data + b"\n")
-        await self.writer.drain()
-
-async def create_channel(server_name, port):
-    """
-     Creates a channel used to send RPC requests to the DarkFi explorer daemon.
-    """
-    try:
-        reader, writer = await asyncio.open_connection(server_name, port)
-    except ConnectionRefusedError:
-        print(
-            f"Error: Connection Refused to '{server_name}:{port}', Either because the daemon is down, is currently syncing or wrong url.")
-        abort(500)
-    channel = Channel(reader, writer)
-    return channel
-
-async def query(method, params):
-    """
-     Execute a request towards the JSON-RPC server by constructing a JSON-RPC
-     request and sending it to the server. It handles connection errors and server responses,
-     returning the result of the query or raising an error if the request fails.
-    """
-    # Create the channel to send RPC request
-    channel = await create_channel(current_app.config['explorer_rpc_url'], current_app.config['explorer_rpc_port'])
-
-    # Prepare request
-    request = {
-        "id": random.randint(0, 2 ** 32),
-        "method": method,
-        "params": params,
-        "jsonrpc": "2.0",
-    }
-
-    # Send request and await response
-    await channel.send(request)
-    response = await channel.receive()
-
-    # Closed connect returns None
-    if response is None:
-        print("error: connection with server was closed")
-        abort(500)
-
-    # Erroneous query is handled with not found
-    if "error" in response:
-        error = response["error"]
-        errcode, errmsg = error["code"], error["message"]
-        print(f"error: {errcode} - {errmsg}")
-        abort(404)
-
-    return response["result"]
-
-async def get_last_n_blocks(n: str):
-    """Retrieves the last n blocks."""
-    return await query("blocks.get_last_n_blocks", [n])
-
-async def get_basic_statistics():
-    """Retrieves basic statistics."""
-    return await query("statistics.get_basic_statistics", [])
-
-async def get_metric_statistics():
-    """Retrieves metrics statistics."""
-    return await query("statistics.get_metric_statistics", [])
-
-async def get_block(header_hash: str):
-    """Retrieves block information for a given header hash."""
-    return await query("blocks.get_block_by_hash", [header_hash])
-
-async def get_block_transactions(header_hash: str):
-    """Retrieves transactions associated with a given block header hash."""
-    return await query("transactions.get_transactions_by_header_hash", [header_hash])
-
-
-async def get_transaction(transaction_hash: str):
-    """Retrieves transaction information for a given transaction hash."""
-    return await query("transactions.get_transaction_by_hash", [transaction_hash])
-
-async def get_native_contracts():
-    """Retrieves native contracts."""
-    return await query("contracts.get_native_contracts", [])
-
-
-async def get_contract_source_paths(contract_id: str):
-    """Retrieves contract source code paths for a given contract ID."""
-    return await query("contracts.get_contract_source_code_paths", [contract_id])
-
-async def get_contract_source(contract_id: str, source_path):
-    """Retrieves the contract source file for a given contract ID and source path."""
-    return await query("contracts.get_contract_source", [contract_id, source_path])

+ 0 - 34
bin/explorer/site/site_config.toml

@@ -1,34 +0,0 @@
-# This file is part of DarkFi (https://dark.fi)
-#
-# Copyright (C) 2020-2026 Dyne.org foundation
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <https://www.gnu.org/licenses/>.
-
-# Localnet Configuration
-[localnet]
-explorer_rpc_url = "127.0.0.1"
-explorer_rpc_port = 14567
-log_path = "~/.local/share/darkfi/explorer_site/localnet"
-
-# Testnet configuration
-[testnet]
-explorer_rpc_url = "127.0.0.1"
-explorer_rpc_port = 14667
-log_path = "~/.local/share/darkfi/explorer_site/testnet"
-
-# Mainnet configuration
-[mainnet]
-explorer_rpc_url = "127.0.0.1"
-explorer_rpc_port = 14767
-log_path = "~/.local/share/darkfi/explorer_site/mainnet"

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 5
bin/explorer/site/static/bootstrap.min.css


BIN
bin/explorer/site/static/font-files/Chomsky.otf


BIN
bin/explorer/site/static/font-files/Chomsky.woff


BIN
bin/explorer/site/static/font-files/Chomsky.woff2


BIN
bin/explorer/site/static/font-files/IBMPlexMonoExtraLight.otf


BIN
bin/explorer/site/static/font-files/IBMPlexMonoExtraLight.woff


BIN
bin/explorer/site/static/font-files/IBMPlexMonoExtraLight.woff2


BIN
bin/explorer/site/static/font-files/IBMPlexSansArabic-Regular.woff


BIN
bin/explorer/site/static/font-files/IBMPlexSansArabic-Regular.woff2


BIN
bin/explorer/site/static/font-files/Inter-Bold.woff


BIN
bin/explorer/site/static/font-files/Inter-BoldItalic.woff


BIN
bin/explorer/site/static/font-files/Inter-Italic.woff


BIN
bin/explorer/site/static/font-files/Inter-Regular.woff


BIN
bin/explorer/site/static/font-files/Inter-SemiBold.woff


BIN
bin/explorer/site/static/font-files/Inter-SemiBoldItalic.woff


BIN
bin/explorer/site/static/font-files/InterDisplay-Black.woff


BIN
bin/explorer/site/static/font-files/InterDisplay-BlackItalic.woff


BIN
bin/explorer/site/static/font-files/InterDisplay-ExtraLight.woff


BIN
bin/explorer/site/static/font-files/InterDisplay-ExtraLight.woff2


BIN
bin/explorer/site/static/font-files/ReemKufi-Bold.woff


BIN
bin/explorer/site/static/font-files/ReemKufi-Bold.woff2


BIN
bin/explorer/site/static/font-files/weissrundgotisch.regular.woff


BIN
bin/explorer/site/static/font-files/weissrundgotisch.regular.woff2


+ 0 - 208
bin/explorer/site/static/img/darkfi.svg

@@ -1,208 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
-<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
-	 viewBox="0 0 687.3 179" style="enable-background:new 0 0 687.3 179;" xml:space="preserve">
-<style type="text/css">
-	.st0{fill:#FFFFFF;}
-</style>
-<desc>Generated with Qt</desc>
-<g transform="matrix(1.45797,0,0,1.45797,502.142,172.042)">
-</g>
-<g transform="matrix(1.45797,0,0,1.45797,502.142,172.042)">
-</g>
-<g transform="matrix(1.45797,0,0,1.45797,502.142,172.042)">
-</g>
-<g transform="matrix(1.45797,0,0,1.45797,502.142,172.042)">
-</g>
-<g transform="matrix(1.45797,0,0,1.45797,502.142,172.042)">
-</g>
-<g transform="matrix(1.45797,0,0,1.45797,502.142,172.042)">
-</g>
-<g transform="matrix(1.45797,0,0,1.45797,502.142,172.042)">
-</g>
-<g transform="matrix(1.45797,0,0,1.45797,502.142,172.042)">
-</g>
-<g transform="matrix(1.45797,0,0,1.45797,502.142,172.042)">
-</g>
-<g transform="matrix(1.45797,0,0,1.45797,502.142,172.042)">
-</g>
-<g transform="matrix(1.45797,0,0,1.45797,502.142,172.042)">
-</g>
-<g transform="matrix(1.45797,0,0,1.45797,502.142,172.042)">
-</g>
-<g transform="matrix(1.45797,0,0,1.45797,502.142,172.042)">
-</g>
-<g transform="matrix(1.45797,0,0,1.45797,502.142,172.042)">
-</g>
-<g transform="matrix(1.45797,0,0,1.45797,502.142,172.042)">
-</g>
-<g transform="matrix(1.45797,0,0,1.45797,502.142,172.042)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,548.797,376.392)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,548.797,376.392)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,548.797,376.392)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,688.949,376.392)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,688.949,376.392)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,688.949,376.392)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,777.034,376.392)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,777.034,376.392)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,777.034,376.392)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,834.513,376.392)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,834.513,376.392)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,834.513,376.392)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,834.513,376.392)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,938.46,376.392)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,938.46,376.392)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,938.46,376.392)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,938.46,376.392)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,1054.91,376.392)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,1054.91,376.392)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,1054.91,376.392)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,1054.91,376.392)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,548.797,579.808)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,548.797,579.808)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,548.797,579.808)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,688.949,579.808)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,688.949,579.808)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,688.949,579.808)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,777.034,579.808)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,777.034,579.808)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,777.034,579.808)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,834.513,579.808)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,834.513,579.808)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,834.513,579.808)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,834.513,579.808)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,938.46,579.808)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,938.46,579.808)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,938.46,579.808)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,954.51,579.808)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,954.51,579.808)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,954.51,579.808)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,954.51,579.808)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,1070.96,579.808)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,1070.96,579.808)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,1070.96,579.808)">
-</g>
-<g transform="matrix(0.18662,0,0,-0.18662,1070.96,579.808)">
-</g>
-<g transform="matrix(1.45797,0,0,1.45797,502.142,172.042)">
-</g>
-<g transform="matrix(1.45797,0,0,1.45797,502.142,172.042)">
-</g>
-<g transform="matrix(1.45797,0,0,1.45797,502.142,172.042)">
-</g>
-<g transform="matrix(1.45797,0,0,1.45797,502.142,172.042)">
-</g>
-<g transform="matrix(1.45797,0,0,1.45797,502.142,172.042)">
-</g>
-<g transform="matrix(1.45797,0,0,1.45797,502.142,172.042)">
-</g>
-<g transform="matrix(1.45797,0,0,1.45797,502.142,172.042)">
-</g>
-<g transform="matrix(1,0,0,1,0,0)">
-</g>
-<g transform="matrix(1,0,0,1,0,0)">
-</g>
-<g transform="matrix(1.45797,0,0,1.45797,502.142,172.042)">
-</g>
-<g transform="matrix(1.45797,0,0,1.45797,502.142,172.042)">
-</g>
-<g transform="matrix(1,0,0,1,0,0)">
-	<path vector-effect="non-scaling-stroke" class="st0" d="M295.6,49H287c-2.3,2.1-4.5,4.4-5.8,7.3c-1.4,3-1.5,6.4-1.5,9.6v11.8v15.4
-		c0,3,0,5.9-0.8,8.8c-0.8,2.9-2.1,5.6-3.8,8c-5.3,7.6-14.7,12.6-23.8,14.9l-1.4-3.6c1.8-1.1,3.5-2.6,4.9-4.2
-		c2.4-3.2,3.6-7.1,4.1-11.1l0.3-33.1c0-1.7,0-3.3,0.3-4.9c0.3-1.7,0.9-3.2,1.8-4.7c1.8-2.7,4.5-4.9,7.3-6.5l12.9-7.7h-24.1
-		c-1.4,0-2.9,0-4.1-0.6c-1.4-0.5-2.6-1.2-3.5-2.3c-1.2-1.5-2.1-3.3-2.6-5.2c-0.5-2-0.8-3.9-0.8-5.8c-0.2-3.9,0.5-7.9,1.4-11.7
-		l3.3,0.2c0.3,1.2,0.8,2.3,1.5,3.3c0.6,0.9,1.7,1.7,2.7,2.3c0.9,0.5,2.1,0.9,3.3,1.1c1.2,0.2,2.4,0.2,3.5,0.2h38.5
-		c6.2,0,12.7,0.3,18.5,2.7c5.9,2.4,10.9,6.7,14.7,11.8c6.4,8.9,8.9,20,9.7,30.9c0.5,10.5-0.8,21.1-3.6,31.2
-		c-1.7,5.5-3.8,10.9-6.2,16.1l-37.9,20.9c-7.3-5.3-16.2-8.3-25.2-8.5c-11.5-0.3-19.1,2.9-27,11.8h-4.2c9.3-14.3,17-24.4,34.4-27.9
-		c13.8-2.7,29.6,1.8,40.8,10.5c3-7.9,5.3-16.1,6.8-24.4c1.2-7.9,1.8-15.6,1.5-23.5c0-8.5-2.6-19-8.2-25.6c-2.4-2.7-5.6-5-9.1-6.2
-		C302.5,49.3,299,49,295.6,49"/>
-</g>
-<g transform="matrix(1,0,0,1,0,0)">
-	<path vector-effect="non-scaling-stroke" class="st0" d="M400.3,123.1V80.6l-23.4-15l-16.7,8.8c-1.8,0.9-3.5,1.8-5,3.2
-		c-1.4,1.2-2.6,2.7-3.3,4.4c-0.8,1.7-1.2,3.6-1.1,5.5c0.2,2,0.8,3.9,1.8,5.6c2.1,3.2,5.5,5.5,8.9,7.1c0.8,0.3,1.4,0.8,2.1,1.1
-		c-3.5,1.8-6.8,4.1-9.9,6.7c-2.1,1.8-4.1,4.1-5.6,6.5s-2.4,5.2-2.6,8c-0.3,4.4,1.5,8.9,4.2,12.3c3.9,4.9,9.9,7.9,16.1,8.6l16.1-11.7
-		l11.5,11.7l16.1-11.2L400.3,123.1 M381.3,102.4c-2.7-0.6-5.5-1.2-8.2-2.1c-1.8-0.6-3.6-1.2-5.5-1.8c-1.1-0.5-2-0.9-3-1.4
-		c-2.7-1.4-5.3-3-7.3-5.3c-1.4-1.7-2.3-3.6-2.3-5.8c0-1.5,0.5-2.9,1.2-4.2c0.8-1.4,1.7-2.4,2.9-3.3c0.9-0.8,2.1-1.4,3.2-1.8l19,11.7
-		C381.3,93,381.3,97.7,381.3,102.4 M381.3,106.8c0,7.7,0,15.5,0,23.2c-1.7,0.5-3.3,0.6-5,0.3c-2.1-0.5-4.2-1.5-5.9-3
-		c-1.7-1.5-2.9-3.2-3.9-5.2c-2.1-4.1-3.2-8.8-2.4-13.3c0.3-2.3,1.2-4.5,2.4-6.5c2,0.6,3.9,1.4,5.9,2
-		C375.4,105.1,378.3,105.8,381.3,106.8"/>
-</g>
-<g transform="matrix(1,0,0,1,0,0)">
-	<path vector-effect="non-scaling-stroke" class="st0" d="M453.5,123.7l-8.2,5.5l-5.9-4.7V79.8c2.1,2.1,4.7,3.8,7.4,5
-		c1.8,0.8,3.6,1.4,5.6,1.4s3.8-0.5,5.6-1.2c2.3-1.1,4.1-2.7,5.5-4.9c1.4-2.1,2.3-4.4,2.9-6.8c0.8-2.7,1.4-5.5,1.7-8.3h-2.9
-		c-1.4,2.3-3.6,4.2-7.9,4.2c-3.6,0-6.1-2-7.4-4.2h-1.7l-10.5,12l-11.2-12l-15.8,11.1l8.5,8.6v39.9L415,129l18.8,14.6l22.6-16.4
-		L453.5,123.7"/>
-</g>
-<g transform="matrix(1,0,0,1,0,0)">
-	<path vector-effect="none" class="st0" d="M544,125.3l-6.5,4.4l-29.7-44.3l1.4-3c2.1,2,4.9,3.6,7.6,4.4c2.3,0.8,4.9,0.9,7.3,0.6
-		c2.4-0.3,4.7-1.2,6.7-2.6c2.1-1.5,3.9-3.5,5.3-5.6c1.4-2.1,2.3-4.7,3-7.1c0.6-2.1,1.1-4.2,1.4-6.4h-2.9c-0.9,1.7-2.3,3.2-3.9,4.2
-		c-1.7,1.1-3.5,1.5-5.5,1.5c-2.1,0-4.1-0.3-5.9-1.4s-3.2-2.6-4.2-4.4h-1.5l-22.4,34.9l29.7,43.7l23.4-15.3L544,125.3"/>
-</g>
-<g transform="matrix(1,0,0,1,0,0)">
-	<path vector-effect="non-scaling-stroke" class="st0" d="M492.5,124.5V26.4h-2.9l-24.7,17.4l7.1,7.9c0.2,0.8,0.2,1.5,0.2,2.3v69.1
-		l-5,3.2l18.8,16.4l16.1-10.8L492.5,124.5"/>
-</g>
-<g transform="matrix(1,0,0,1,0,0)">
-	<path vector-effect="none" class="st0" d="M565,93.4h-26.4l13.2,23L565,93.4"/>
-</g>
-<path class="st0" d="M610.3,42c7.4,7.4,14.3,10,22.9,11.1l22-17.7l-3.6-3.2l-4.2,3.2c-12.6,0-25.5-5.9-29-11.5L589,49.8v20.6h-6.5
-	l-14.1,12v3.3c21.4,0,3.3,0,20.6,0v30.2c0,9.9-0.2,14.7-1.5,17.7c-4.1-1.5-8.6-2.9-15.3-2c-17.1,2.3-29.1,16.7-33.5,25.8h3.8
-	c0,0,4.9-8.9,19.3-8.9c5,0,9.3,1.7,12.3,3.9c0,0,28.1-25.3,36.4-35.6V85.7h12.4l19.7-12.9v-3h-32.1V42H610.3z"/>
-<g transform="matrix(1,0,0,1,0,0)">
-	<path vector-effect="none" class="st0" d="M683.8,125.3l-6.7,4.4l-5-5.2V78.7l-11.7-13.8l-15.2,10.3l7.9,9.4v39.9l-4.9,3.5
-		l17.3,15.5l21.8-15.3L683.8,125.3"/>
-</g>
-<g transform="matrix(1,0,0,1,0,0)">
-	<path vector-effect="none" class="st0" d="M661.6,34.5l11.3,12.3L660.7,58l-11.3-12.3L661.6,34.5"/>
-</g>
-<g>
-	<path class="st0" d="M103.3,38.4c-12.4,0-22.4,10-22.4,22.4s10,22.4,22.4,22.4s22.4-10,22.4-22.4S115.7,38.4,103.3,38.4z M103.3,71
-		c-5.6,0-10.1-4.5-10.1-10.1s4.5-10.1,10.1-10.1s10.1,4.5,10.1,10.1S108.9,71,103.3,71z"/>
-	<path class="st0" d="M0,0l103.3,179L206.6,0H0L0,0z M103.3,92.8c-20.1,0-37.2-16-45.9-32c8.7-16,25.8-32,45.9-32s37.2,16,45.9,32
-		C140.5,76.8,123.5,92.8,103.3,92.8z"/>
-</g>
-</svg>

BIN
bin/explorer/site/static/img/darkfi05.jpg


BIN
bin/explorer/site/static/img/darkfi25.jpg


BIN
bin/explorer/site/static/img/icon.png


BIN
bin/explorer/site/static/img/sunset2.png


+ 0 - 1079
bin/explorer/site/static/style.css

@@ -1,1079 +0,0 @@
-@font-face {
-  font-family: 'Inter';
-  font-style: normal;
-  font-weight: 400;
-  font-display: swap;
-  src: url("font-files/Inter-Regular.woff2?v=3.19") format("woff2"),
-  url("font-files/Inter-Regular.woff?v=3.19") format("woff");
-}
-
-@font-face {
-  font-family: 'Inter';
-  font-style: italic;
-  font-weight: 400;
-  font-display: swap;
-  src: url("font-files/Inter-Italic.woff2?v=3.19") format("woff2"),
-  url("font-files/Inter-Italic.woff?v=3.19") format("woff");
-}
-
-@font-face {
-  font-family: 'Inter';
-  font-style: normal;
-  font-weight: 700;
-  font-display: swap;
-  src: url("font-files/Inter-Bold.woff2?v=3.19") format("woff2"),
-  url("font-files/Inter-Bold.woff?v=3.19") format("woff");
-}
-
-@font-face {
-  font-family: 'Inter';
-  font-style: italic;
-  font-weight: 700;
-  font-display: swap;
-  src: url("font-files/Inter-BoldItalic.woff2?v=3.19") format("woff2"),
-  url("font-files/Inter-BoldItalic.woff?v=3.19") format("woff");
-}
-
-@font-face {
-  font-family: 'InterDisplay';
-  font-style: normal;
-  font-weight: 900;
-  font-display: swap;
-  src: url("font-files/InterDisplay-Black.woff2?v=3.19") format("woff2"),
-  url("font-files/InterDisplay-Black.woff?v=3.19") format("woff");
-}
-
-@font-face {
-  font-family: 'InterDisplay';
-  font-style: italic;
-  font-weight: 900;
-  font-display: swap;
-  src: url("font-files/InterDisplay-BlackItalic.woff2?v=3.19") format("woff2"),
-  url("font-files/InterDisplay-BlackItalic.woff?v=3.19") format("woff");
-}
-
-@font-face {
-  font-family: 'InterDisplay';
-  font-style: normal;
-  font-weight: 200;
-  font-display: swap;
-  src: url("font-files/InterDisplay-ExtraLight.woff2?v=3.19") format("woff2"),
-  url("font-files/InterDisplay-ExtraLight.woff?v=3.19") format("woff");
-}
-
-@font-face {
-  font-family: 'Chomsky';
-  font-style: normal;
-  font-weight: 700;
-  font-display: swap;
-  src: url("font-files/Chomsky.woff2?v=3.19") format("woff2"),
-  url("font-files/Chomsky.woff?v=3.19") format("woff");
-}
-
-@font-face {
-  font-family: 'IBMPlexMono';
-  font-style: normal;
-  font-weight: 400;
-  font-display: swap;
-  src: url("font-files/IBMPlexMono-Light.woff2") format("woff2"),
-  url("font-files/IBMPlexMono-Light.woff?v=3.19") format("woff");
-}
-
-@font-face {
-  font-family: 'WeissRundgotisch';
-  font-style: normal;
-  font-weight: 400;
-  font-display: swap;
-  src: url("font-files/weissrundgotisch.regular.woff2") format("woff2"),
-  url("font-files/weissrundgotisch.regular.woff?v=3.19") format("woff");
-}
-
-@font-face {
-  font-family: 'IBMPlexSansArabic';
-  font-style: normal;
-  font-weight: 400;
-  font-display: swap;
-  src: url("font-files/IBMPlexSansArabic-Regular.woff2") format("woff2"),
-  url("font-files/IBMPlexSansArabic-Regular.woff?v=3.19") format("woff");
-}
-
-@font-face {
-  font-family: 'ReemKufi';
-  font-style: normal;
-  font-weight: 700;
-  font-display: swap;
-  src: url("font-files/ReemKufi-Bold.woff2") format("woff2"),
-  url("font-files/ReemKufi-Bold.woff?v=3.19") format("woff");
-}
-
-/* -------------------------------------------------------------------------- */
-/* Colors                                                                     */
-/* -------------------------------------------------------------------------- */
-
-:root {
-  --color-background-page: #181a1b;
-  --color-background-table-header: #181818;
-  --color-input-primary: #AFA99E;
-  --color-input-background: #3B3B3B;
-  --color-border-primary: #83796B;
-  --color-source-code-link: #D1CDC7;
-  --color-source-code-link-bg: #393939;
-  --color-source-code-link-hover-bg: #313131;
-  --color-text-primary: #D1CDC7;
-}
-
-/* -------------------------------------------------------------------------- */
-/* To Categorize                                                              */
-/* -------------------------------------------------------------------------- */
-
-html {
-  font-family: "Inter", sans-serif;
-  font-size: 16px;
-  text-size-adjust: none;
-  -webkit-text-size-adjust: none;
-}
-
-body {
-  margin: 0;
-  overflow-x: hidden;
-  padding-bottom: 80px;
-  background-color: #000;
-}
-
-p {
-  font-family: 'IBMPlexMono', monospace;
-  font-style: normal;
-  font-weight: 400;
-}
-
-h1 {
-  font-family: 'InterDisplay', sans-serif;
-  font-style: normal;
-  font-weight: 900;
-}
-
-h3 a {
-  color: #00ffff;
-  text-decoration: none;
-}
-
-h3 a:hover {
-  color: #4c9fb5;
-}
-
-.description {
-  margin-top: 30px;
-}
-
-.main-text {
-  padding-bottom: 70px;
-  background-color: #001111;
-  background-position: bottom;
-  background-repeat: no-repeat;
-}
-
-.border-st {
-  border-radius: 0;
-}
-
-.border-l1 {
-  font-size: 56px;
-  text-align: center;
-  text-shadow: 0px 0px 5px #0ff;
-  line-height: 1.3em;
-  padding-left: 100px;
-  padding-right: 100px;
-}
-
-.border-l {
-  font-size: 36px;
-  text-align: center;
-  font-weight: 100;
-  letter-spacing: 1px;
-  color: #005b60;
-  /* text-shadow: 0px 0px 2px #0ff; */
-  line-height: 1.3em;
-  padding-top: 70px;
-  padding-left: 180px;
-  padding-right: 180px;
-}
-
-.border-f {
-  border-left-width: 1px;
-  border-left-style: solid;
-  border-left-color: #0ff;
-  text-shadow: 0px 0px 5px #0ff;
-  line-height: 1.1em;
-  padding-left: 48px;
-}
-
-.border-l2 {
-  border-left-width: 1px;
-  border-left-style: solid;
-  border-left-color: #0ff;
-  font-family: 'WeissRundgotisch', blackletter;
-  font-style: normal;
-  font-weight: 400;
-  font-size: 2em;
-  line-height: .85em;
-}
-
-.border-l3 {
-  /* text-shadow: 0px 0px 5px #0ff; */
-  line-height: 1.1em;
-  /*filter:blur(20px);*/
-  /*transition: ease-in-out 10s;*/
-}
-
-a:hover {
-  text-decoration: none;
-}
-
-.mobile {
-  display: none;
-}
-
-.news {
-  padding-top: 48px;
-  padding-bottom: 48px;
-}
-
-.info {
-  font-family: 'IBMPlexMono', monospace;
-  font-style: normal;
-  font-weight: 400;
-  font-size: 12px;
-  letter-spacing: 0.12em;
-  border-left: 1px solid #0ff;
-}
-
-.dl-bt {
-  display: inline-block;
-  background-color: #000;
-  transition: ease-in-out 0.15s;
-  border: 1px solid #00282e;
-  color: #fff;
-  font-family: 'IBMPlexMono', monospace;
-  font-weight: 400;
-}
-
-.dl-bt:hover {
-  background-color: #000c0d;
-  border: 1px solid #00282e;
-  color: #82b2b5;
-}
-
-.button-insights, .button-insights span {
-  transition: ease .2s;
-  color: #80ffff;
-
-}
-
-.button-insights:hover {
-  background: #180606;
-}
-
-.button-insights:hover span {
-  color: #0ff;
-}
-
-.title-block {
-  background-position: bottom;
-  background-repeat: no-repeat;
-}
-
-footer {
-  background-position: bottom;
-  background-repeat: no-repeat;
-  height: 10vw;
-  bottom: 0;
-  width: 100%;
-}
-
-.navbar-toggler {
-  border-radius: 0px;
-  border-color: #6c757d;
-  opacity: 1;
-}
-
-.navbar {
-  padding: 20px;
-  padding-top: 6px;
-  padding-bottom: 6px;
-  background: #000;
-  position: sticky;
-  top: 0;
-  z-index: 999;
-  margin: 0 auto;
-  width: 100%;
-  background-color: #000;
-}
-
-.nav-item a {
-  margin-left: 10px;
-  font-family: 'IBMPlexMono', monospace;
-  font-weight: 400;
-  font-size: .8em;
-  color: #fff;
-}
-
-.nav-link {
-  font-size: .6em;
-  color: #fff;
-}
-
-.nav-item a:hover {
-  color: red;
-}
-
-.a {
-  background-color: rgba(0, 0, 0, 0);
-  transition: ease-in-out 0.2s;
-}
-
-.b {
-  background-color: #000;
-  border-bottom: 1px solid #6c757d;
-  padding-top: 0px;
-  padding-bottom: 0px;
-}
-
-.b .nav-link {
-  border: 0;
-}
-
-.b .navbar-brand img {
-  max-height: 30px;
-  padding: 0;
-}
-
-.b .border-st {
-  padding-bottom: 0;
-  padding-top: 0;
-}
-
-.quote {
-  border-left: 1px solid #0ff;
-  background-color: #001615;
-  padding-top: 15px;
-  padding-bottom: 15px;
-  padding-right: 15px;
-}
-
-.quote-src {
-  font-family: 'IBMPlexMono', monospace;
-  font-size: .8em;
-  color: #fff;
-  display: inline-block;
-  border-left: 1px solid #0ff;
-  padding-top: 10px;
-}
-
-.btn:hover {
-  color: #000;
-  background-color: #000;
-}
-
-a:hover .btn {
-  color: #000;
-}
-
-.icon-sm {
-  vertical-align: middle;
-  width: 30px;
-}
-
-/* Manifesto */
-
-.p-text {
-  font-family: 'Inter';
-  font-style: normal;
-  font-weight: 400;
-  padding: 48px;
-}
-
-h4 {
-  font-size: 2em;
-  padding-left: 0.5em;
-  border-left-width: 1px;
-  border-left-style: solid;
-  border-left-color: #0ff;
-
-  font-family: 'InterDisplay', sans-serif;
-  font-style: normal;
-  font-weight: 800;
-}
-
-.p-text a {
-  color: #0ff;
-  border-bottom: 1px solid #0ff;
-  padding-bottom: 2px;
-  transition: ease-in-out 0.3s;
-}
-
-.p-text a:hover {
-  color: #00ffa3;
-  border-bottom: 1px solid #00ffa3;
-}
-
-.l-ph {
-  background: url("img/manifesto.jpg") fixed;
-  background-position: right;
-}
-
-.l-ph-ar {
-  background: url("img/manifesto.jpg") fixed;
-  background-position: left;
-}
-
-#manifesto {
-  max-width: 700px;
-}
-
-.quote-code {
-  font-family: 'IBMPlexMono', monospace;
-  font-style: normal;
-  font-weight: 400;
-  display: block;
-  padding: 10px;
-}
-
-code {
-  color: #fff;
-  background: #001615;
-  padding: 2px;
-  font-size: 1.1em;
-  color: #D8E8E2;
-}
-
-#language {
-  font-family: 'IBMPlexMono', monospace;
-  font-style: normal;
-  font-weight: 400;
-  color: #a0a0a0;
-  margin-bottom: 30px;
-}
-
-#language a {
-  color: #a0a0a0;
-  border-bottom: 1px solid #a0a0a0;
-}
-
-#language a:hover {
-  color: #fff;
-  border-bottom: 1px solid #fff;
-}
-
-/* Manifesto Arabic */
-
-.p-text-ar {
-  font-family: 'IBMPlexSansArabic';
-  font-style: normal;
-  font-weight: 400;
-  font-size: 1.1em;
-  padding: 48px;
-}
-
-.quote-ar {
-  border-right: 1px solid #0ff;
-  background-color: #001615;
-  padding-top: 15px;
-  padding-bottom: 15px;
-  padding-right: 15px;
-}
-
-.quote-src-ar {
-  font-family: 'IBMPlexMono', monospace;
-  font-size: 1em;
-  color: #fff;
-  display: inline-block;
-  border-right: 1px solid #0ff;
-  padding-top: 10px;
-}
-
-.h4-ar {
-  font-size: 2em;
-  padding-right: 0.5em;
-  border-right-width: 1px;
-  border-right-style: solid;
-  border-right-color: #0ff;
-  border-left-width: 0;
-  border-left-style: none;
-  font-family: 'InterDisplay', sans-serif;
-  font-style: normal;
-  font-weight: black;
-}
-
-/* Overview */
-
-.lead-text {
-  padding: 48px;
-}
-
-.source-list {
-  border-left: 1px solid #00ffff;
-}
-
-/* Community */
-
-.comm {
-  padding-left: 48px;
-}
-
-h5 {
-  font-size: 2em;
-  padding-left: 0.5em;
-  border-left-width: 1px;
-  border-left-style: solid;
-  border-left-color: #0ff;
-
-  font-family: 'InterDisplay', sans-serif;
-  font-style: normal;
-  font-weight: 800;
-}
-
-h5 a {
-  color: #fff;
-  border-bottom: 1px solid #0ff;
-  transition: ease-in-out 0.3s;
-}
-
-h5 a:hover {
-  color: #f00;
-  border-bottom: 1px solid #f00;
-}
-
-/* Media */
-
-.media-text {
-  font-family: Weissrundgotisch;
-  color: #005458;
-  text-shadow: 0 0 5px #002e4a;
-  -webkit-text-stroke: .5px #001f1f;
-}
-
-/* Download */
-
-.dl-info {
-  font-family: 'IBMPlexMono', monospace;
-  font-style: normal;
-  font-weight: 400;
-  font-size: 15px;
-  letter-spacing: 0.12em;
-  border-left: 1px solid #0ff;
-}
-
-.navbarb {
-  display: none;
-}
-
-.mail {
-  display: inline-block;
-}
-
-/* Responsive */
-
-@media (min-width: 1281px) {
-  .page-container {
-    max-width: 1280px;
-    min-height: 100vh;
-  }
-}
-
-@media (max-width: 1280px) and (min-width: 768px) {
-  .page-container {
-    max-width: 90%;
-    min-height: 100vh;
-  }
-}
-
-@media (max-width: 991px) {
-
-  .border-l1 {
-    padding-left: 60px;
-    padding-right: 60px;
-  }
-
-  .news {
-    padding-top: 0px;
-    padding-bottom: 48px;
-  }
-
-  .border-l {
-    padding-left: 100px;
-    padding-right: 100px;
-  }
-
-}
-
-@media (max-width: 767px) {
-  .page-container {
-    max-width: 95%;
-    min-height: auto;
-  }
-
-  /* Lower viewport-specific adjustments */
-  .border-st {
-    background-color: #000;
-  }
-
-  .border-l {
-    padding-left: 70px;
-    padding-right: 70px;
-  }
-
-  .navbarb {
-    display: block;
-    margin: 0;
-    padding: 0;
-  }
-
-  .img-dl {
-    display: none;
-  }
-
-  .dl-info-v {
-    display: none;
-  }
-
-  .navbar-nav {
-    margin: auto;
-  }
-}
-
-@media (max-width: 730px) {
-  .lead-text {
-    padding: 24px;
-  }
-
-  .display-1 {
-    font-size: 10vw;
-  }
-
-  .space,
-  .desktop {
-    display: none;
-  }
-
-  .mobile {
-    display: block;
-  }
-
-  .border-l {
-    padding-left: 20px;
-    padding-right: 20px;
-    font-size: 7vw;
-  }
-
-  .border-f,
-  .border-l3 {
-    border-left-width: 1px;
-  }
-
-  .media-text {
-    font-size: 1.7em;
-  }
-}
-
-@media (max-width: 590px) {
-  .navbar-brand img {
-    max-height: 40px;
-  }
-
-  .about {
-    font-size: 4.5vw;
-  }
-
-  footer {
-    height: 60vw;
-  }
-
-  .icon-sm {
-    max-width: 20px;
-    vertical-align: middle;
-  }
-
-  .p-text {
-    font-size: 14px;
-    padding: 24px;
-  }
-}
-
-@media (max-width: 400px) {
-  .comm {
-    padding-left: 30px;
-  }
-}
-
-@media (max-width: 350px) {
-  .navbar-brand img {
-    max-height: 30px;
-  }
-
-  .border-l1 {
-    padding-left: 20px;
-    padding-right: 20px;
-  }
-
-  .border-l {
-    padding-left: 10px;
-    padding-right: 10px;
-  }
-}
-
-/* ___________________ */
-
-.container2 {
-  max-width: 1050px;
-  width: 90%;
-  margin: auto;
-}
-
-.navbar2 {
-  width: 100%;
-}
-
-.nav-container2 {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  height: 30px;
-}
-
-.navbar2 .menu-items {
-  display: flex;
-}
-
-.navbar2 .nav-container2 li {
-  list-style: none;
-}
-
-.navbar2 .nav-container2 a {
-  text-decoration: none;
-  color: #0e2431;
-  font-weight: 500;
-  font-size: 1.2rem;
-  padding: 0.7rem;
-}
-
-/*.navbar2 .nav-container2 a:hover{
-}*/
-
-.nav-container2 {
-  display: block;
-  position: relative;
-}
-
-.nav-container2 .checkbox {
-  position: absolute;
-  display: block;
-  height: 40px;
-  width: 40px;
-  top: 0px;
-  right: 0px;
-  z-index: 5;
-  opacity: 0;
-  cursor: pointer;
-}
-
-.nav-container2 .hamburger-lines {
-  display: block;
-  height: 15px;
-  width: 20px;
-  position: absolute;
-  top: 12px;
-  right: 0px;
-  z-index: 2;
-  display: flex;
-  flex-direction: column;
-  justify-content: space-between;
-}
-
-.nav-container2 .hamburger-lines .line {
-  display: block;
-  height: 2px;
-  width: 100%;
-  border-radius: 0;
-  background: #fff;
-}
-
-.nav-container2 .hamburger-lines .line1 {
-  transform-origin: 0% 0%;
-  /*  transition: transform 0.4s ease-in-out;*/
-}
-
-.nav-container2 .hamburger-lines .line2 {
-  /*  transition: transform 0.2s ease-in-out;*/
-}
-
-.nav-container2 .hamburger-lines .line3 {
-  transform-origin: 0% 100%;
-  /*  transition: transform 0.4s ease-in-out;*/
-}
-
-.menu-items {
-  background: #000;
-  margin-top: -10px;
-  /*height: 100vh;*/
-  width: 150px;
-  transform: translate(150%);
-  display: flex;
-  flex-direction: column;
-  margin-left: 0;
-  margin-right: 0;
-  padding-top: 50px;
-  transition: transform 0.5s ease-in-out;
-  border-top: 1px solid #fff;
-  border-left: 1px solid #fff;
-  border-right: 1px solid #fff;
-}
-
-.navbar2 .menu-items li {
-  display: inline-block;
-  width: 100%;
-  padding: 5px;
-  margin-left: auto;
-  margin-right: auto;
-  border-bottom: 1px solid #fff;
-}
-
-.navbar2 .menu-items li a {
-  color: #fff;
-  font-size: 1rem;
-}
-
-.nav-container2 input[type="checkbox"]:checked ~ .menu-items {
-  transform: translateX(0);
-}
-
-.nav-container2 input[type="checkbox"]:checked ~ .hamburger-lines .line1 {
-  transform: rotate(45deg);
-}
-
-.nav-container2 input[type="checkbox"]:checked ~ .hamburger-lines .line2 {
-  transform: scaleY(0);
-}
-
-.nav-container2 input[type="checkbox"]:checked ~ .hamburger-lines .line3 {
-  transform: rotate(-45deg);
-}
-
-.nav-container2 input[type="checkbox"]:checked ~ .logo {
-  display: none;
-}
-
-/* Container holding an image with text on top */
-.background-container {
-  position: relative;
-  text-align: center;
-  color: white;
-}
-
-/* Top centered text inside a background-container */
-.top-centered {
-  position: absolute;
-  top: 25%;
-  left: 50%;
-  transform: translate(-50%, -25%);
-}
-
-/* -------------------------------------------------------------------------- */
-/* Page Layout                                                                */
-/* -------------------------------------------------------------------------- */
-
-.page-container {
-  display: flex;
-  flex-direction: column;
-  margin-left: auto;
-  margin-right: auto;
-  position: relative;
-  background-color: var(--color-background-page);
-}
-
-/* -------------------------------------------------------------------------- */
-/* Page Content Styles                                                        */
-/* -------------------------------------------------------------------------- */
-
-.content-container {
-  padding: 24px;
-  color: var(--color-text-primary);
-}
-
-.content-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
-
-.content-section {
-  width: 100%;
-  padding-top: 20px;
-  padding-bottom: 20px;
-}
-
-.content-section-header {
-  font-family: 'InterDisplay', sans-serif;
-  font-weight: 900;
-  letter-spacing: 0.035em;
-  border: none;
-  display: block;
-  margin-bottom: 16px;
-  font-size: 24px;
-}
-
-.content-divider {
-  border: thin solid var(--color-border-primary);
-}
-
-/* -------------------------------------------------------------------------- */
-/* Search                                                                     */
-/* -------------------------------------------------------------------------- */
-
-.search-container {
-  display: flex;
-  align-items: center;
-  justify-content: start;
-  gap: 20px;
-  min-width: max-content;
-}
-
-.search-input {
-  color: var(--color-input-primary);
-  background-color: var(--color-input-background);
-  border: 1px solid var(--color-border-primary);
-  width: 64ch;
-  padding-left: 5px;
-  height: 30px;
-}
-
-/* -------------------------------------------------------------------------- */
-/* Table Styles                                                               */
-/* -------------------------------------------------------------------------- */
-
-.table {
-  width: 100%;
-  margin: 0 auto;
-  border-collapse: collapse;
-  border-spacing: 0;
-  border: 1px solid var(--color-border-primary);
-  overflow: hidden;
-  color: var(--color-text-primary);
-}
-
-.table td {
-  padding: 3px 20px;
-  border-top: 1px var(--color-border-primary) solid;
-  border-bottom: 1px var(--color-border-primary) solid;
-}
-
-.table thead {
-  background: var(--color-background-table-header);
-}
-
-.table thead td {
-  font-weight: 700;
-}
-
-.table thead th {
-  padding: 3px 20px;
-  border-top: 1px var(--color-border-primary) solid;
-  border-bottom: 1px var(--color-border-primary) solid;
-}
-
-.table thead tr {
-  border: 1px var(--color-border-primary) solid;
-}
-
-/* -------------------------------------------------------------------------- */
-/* Native Contracts                                                           */
-/* -------------------------------------------------------------------------- */
-
-.contract-id {
-  width: calc(27ch + 12px);
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-}
-
-.contract-name {
-  width: calc(9ch + 12px);
-  padding: 6px;
-}
-
-.contract-description {
-  width: auto;
-}
-
-/* -------------------------------------------------------------------------- */
-/* Source Code                                                                */
-/* -------------------------------------------------------------------------- */
-
-.source-code-container {
-  display: flex;
-  flex-direction: column;
-}
-
-.source-code-display-box {
-  padding: 16px;
-  border: 1px var(--color-border-primary) solid;
-  overflow: hidden;
-  box-sizing: border-box;
-}
-
-.source-path {
-  align-self: flex-start;
-  margin-bottom: 12px;
-  margin-right: 16px;
-}
-
-.source-code-list {
-  list-style-type: none;
-  padding-left: 0;
-}
-
-.source-code-list-item {
-  margin-bottom: 8px;
-}
-
-.source-file-link {
-  color: var(--color-source-code-link);
-  text-decoration: none;
-  font-weight: bold;
-  display: block;
-  padding: 8px 12px;
-  background-color: var(--color-source-code-link-bg);
-  border-radius: 2px;
-  transition: background-color 0.3s ease;
-}
-
-.source-file-link:hover {
-  background-color: var(--color-source-code-link-hover-bg);
-  color: var(--color-source-code-link);
-}
-
-.pre-source {
-  overflow: hidden;
-  white-space: pre-wrap;
-  word-wrap: break-word;
-  margin: 0;
-  padding: 0;
-  line-height: 1;
-  border: none;
-}
-
-.pre-source table {
-  width: 100%;
-  border-collapse: collapse;
-}
-
-.pre-source td {
-  padding-right: 2px;
-}
-
-pre, pre code {
-  color: var(--color-text-primary);
-}
-
-code {
-  color: var(--color-text-primary);
-}

+ 0 - 35
bin/explorer/site/templates/404.html

@@ -1,35 +0,0 @@
-<!--
- This file is part of DarkFi (https://dark.fi)
-
- Copyright (C) 2020-2026 Dyne.org foundation
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program.  If not, see <https://www.gnu.org/licenses/>.
--->
-
-{% extends "base.html" %}
-{% block title %}Not found!{% endblock %}
-
-{% block content %}
-
-    <div class="background-container">
-        <img src="{{ url_for('static', filename='img/darkfi25.jpg') }}" style="width:100%;">
-        <div class="top-centered">
-            <h1 class="display-4 text-white border-l3" style="font-family:'Weissrundgotisch';line-height:.9em;">
-                Is this what you are looking for?
-            </h1>
-        </div>
-    </div>
-    
-{% endblock %}
-

+ 0 - 35
bin/explorer/site/templates/500.html

@@ -1,35 +0,0 @@
-<!--
- This file is part of DarkFi (https://dark.fi)
-
- Copyright (C) 2020-2026 Dyne.org foundation
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program.  If not, see <https://www.gnu.org/licenses/>.
--->
-
-{% extends "base.html" %}
-{% block title %}Not found!{% endblock %}
-
-{% block content %}
-
-    <div class="background-container">
-        <img src="{{ url_for('static', filename='img/darkfi05.jpg') }}" style="width:100%">
-        <div class="top-centered">
-            <h1 class="display-4 text-white border-l3" style="font-family:'Weissrundgotisch';line-height:.9em">
-                Reality fractured beyond repair!
-            </h1>
-        </div>
-    </div>
-    
-{% endblock %}
-

+ 0 - 194
bin/explorer/site/templates/base.html

@@ -1,194 +0,0 @@
-<!--
- This file is part of DarkFi (https://dark.fi)
-
- Copyright (C) 2020-2026 Dyne.org foundation
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program.  If not, see <https://www.gnu.org/licenses/>.
--->
-
-<!DOCTYPE html>
-<html lang="en">
-
-<head>
-    <title>DarkFi &ndash; {% block title %}{% endblock %}</title>
-
-    <link rel="icon" type="image/png" href="{{ url_for('static', filename= 'img/icon.png') }}"/>
-
-    <!-- Latest compiled and minified CSS -->
-    <link rel="stylesheet" href="{{ url_for('static', filename= 'bootstrap.min.css') }}">
-
-    <link rel="stylesheet" href="{{ url_for('static', filename= 'style.css') }}"/>
-
-    <meta charset="utf-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1">
-</head>
-
-<body>
-
-<div class="page-container">
-    <nav class="navbar navbar-expand-md navbar-dark tedt border-bottom border-secondary">
-        <a class="navbar-brand" href="https://dark.fi/">
-            <img src="{{ url_for('static', filename= 'img/darkfi.svg') }}" style="height:40px;"/>
-        </a>
-
-        <!--- Network -->
-        <div class="text-light">
-            {{ config.NETWORK }}
-        </div>
-
-        <div class="collapse navbar-collapse nav justify-content-end" id="collapsibleNavbar" style="height:40px;">
-            <ul class="navbar-nav">
-                <li class="nav-item">
-                    <a class="nav-link text-light" href="/">Explorer</a>
-                </li>
-
-                <!--Twitter-->
-                <li class="nav-item" style="padding-left:10px;">
-                    <div class="icon-sm">
-                        <a href="https://twitter.com/DarkFiSquad" class="">
-                            <svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg"
-                                 xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
-                                 viewBox="0 0 100 100"
-                                 style="enable-background:new 0 0 100 100; max-width:30px; min-width:15px;"
-                                 xml:space="preserve">
-                                <style type="text/css">
-                                    .st0 {
-                                        fill: #3a3a3a;
-                                    }
-                                </style>
-                                <g id="layer1" transform="translate(-539.17946,-568.85777)">
-                                    <path id="path3611" class="st0"
-                                          d="M570.9,659c37.3,0,57.7-30.9,57.7-57.7c0-0.9,0-1.8-0.1-2.6c4-2.9,7.4-6.4,10.1-10.5
-                                           c-3.6,1.6-7.5,2.7-11.6,3.2c4.2-2.5,7.4-6.5,8.9-11.2c-3.9,2.3-8.3,4-12.9,4.9c-3.7-3.9
-                                           -9-6.4-14.8-6.4c-11.2,0-20.3,9.1-20.3,20.3c0,1.6,0.2,3.1,0.5,4.6c-16.8-0.8-31.8-8.9
-                                           -41.8-21.2c-1.7,3-2.7,6.5-2.7,10.2c0,7,3.6,13.2,9,16.9c-3.3-0.1-6.5-1-9.2-2.5c0,0.1,0,0.2,0,
-                                           0.3c0,9.8,7,18,16.3,19.9c-1.7,0.5-3.5,0.7-5.3,0.7c-1.3,0-2.6-0.1-3.8-0.4c2.6,8.1,10.1,13.9,
-                                           18.9,14.1c-6.9,5.4-15.7,8.7-25.2,8.7c-1.6,0-3.2-0.1-4.8-0.3C548.7,655.7,559.4,659,570.9,659"/>
-                                </g>
-                            </svg>
-                        </a>
-                    </div>
-                </li>
-
-                <!--Codeberg-->
-                <li class="nav-item" style="padding-left:10px;">
-                    <div class="icon-sm">
-                        <a href="https://codeberg.org/darkrenaissance/darkfi" class="">
-                            <svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg"
-                                 xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
-                                 viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;"
-                                 xml:space="preserve">
-                                <style type="text/css">
-                                    .st0 {
-                                        fill: #3A3A3A;
-                                    }
-                                </style>
-                                <g id="g370484" transform="matrix(0.06551432,0,0,0.06551432,-2.232417,-1.431776)">
-                                    <g>
-                                        <path id="path6733-5_00000147220371580420933290000015044290908562073483_"
-                                              class="st0"
-                                              d="M797.5,506.6c-2.6,0-4.7,1.6-4.7,3.6c0,0.2,0,0.4,0.1,0.6l253.7,1037.5c104.2-35.5,
-                                                 265.5-150.8,346-256.8l-591-783.2C800.8,507.2,799.2,506.6,797.5,506.6z"/>
-                                        <path id="path360787_00000175324937253379690650000018049397027717501353_"
-                                              class="st0"
-                                              d="M797.3,59C375.7,59,34.1,400.6,34.1,822.1c0,143.4,40.4,283.8,
-                                                116.5,405.3l636.3-822.6c4.6-5.9,16.1-5.9,20.6,0l636.3,822.6c76.2-121.5,
-                                                116.6-262,116.6-405.4C1560.5,400.6,1218.8,59,797.3,59L797.3,59z"/>
-                                    </g>
-                                </g>
-                            </svg>
-                        </a>
-                    </div>
-                </li>
-
-                <!--Darkirc-->
-                <li class="nav-item" style="padding-left:10px;">
-                    <div class="icon-sm">
-                        <a href="https://dark.fi/book/misc/darkirc/darkirc.html" class="">
-                            <svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg"
-                                 xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
-                                 viewBox="0 0 100 100"
-                                 style="enable-background:new 0 0 100 100; max-width:30px; min-width:15px;"
-                                 xml:space="preserve">
-                                <style type="text/css">
-                                    .st0 {
-                                        fill: #3a3a3a;
-                                    }
-                                </style>
-                                <path class="st0" d="M0,0v100h100V0H0z M16.2,30c0-2,1.2-3.6,4.5-3.6c3.4,0,4.5,1.5,4.5,3.6v1.5c0,2-1.2,3.6-4.5,3.6
-                                                    c-3.4,0-4.5-1.6-4.5-3.6V30z M32.3,64.8H8.6v-5.9H17V44.3H8.6v-5.9h15.9v20.5h7.8V64.8z M63.1,45.4h-5.6c-3.7,0-5.7,2.5-5.7,5.7v7.8
-                                                    h8.7v5.9H38.2v-5.9h6.1V44.3h-6.1v-5.9h13.6v7.4h0.4c1-3.9,3.2-7.4,8.4-7.4h2.5V45.4z M81.4,65.4c-8.3,0-13.3-5.3-13.3-13.8
-                                                    c0-8.5,5-13.8,13.3-13.8c5.8,0,9.1,2.6,11,6.4l-5.8,3.2c-0.9-2-2.3-3.6-5.2-3.6C78,43.8,76,46,76,49.5v4.3c0,3.5,1.9,5.6,5.5,5.6
-                                                    c2.9,0,4.4-1.5,5.6-3.7l5.7,3.3C90.9,62.8,87.3,65.4,81.4,65.4z"/>
-                            </svg>
-                        </a>
-                    </div>
-                </li>
-
-                <!--Telegram-->
-                <li class="nav-item" style="padding-left:10px; padding-right:10px;">
-                    <div class="icon-sm">
-                        <a href="https://t.me/darkfichat" class="">
-                            <svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg"
-                                 xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
-                                 viewBox="0 0 100 100"
-                                 style="enable-background:new 0 0 100 100; max-width:30px; min-width:15px;"
-                                 xml:space="preserve">
-                                <style type="text/css">
-                                    .st0 {
-                                        fill: #3a3a3a;
-                                    }
-                                </style>
-                                <path class="st0" d="M50,0C22.4,0,0,22.4,0,50s22.4,50,50,50s50-22.4,50-50S77.6,0,50,0z M73.8,32.5c-0.1,2-1.4,9.1-2.5,16.8
-                                                    L67.9,72c0,0-0.3,3.3-2.9,3.9c-2.6,0.6-6.5-2-7.2-2.6c-0.6-0.4-10.8-6.9-14.5-10.1c-1-0.9-2.2-2.6,0.1-4.6l15.2-14.5
-                                                    c1.7-1.7,3.5-5.8-3.8-0.9L34.7,56.9c0,0-2.3,1.4-6.6,0.1l-9.4-2.9c0,0-3.5-2.2,2.5-4.3c14.5-6.8,32.2-13.7,48-20.2
-                                                    C69.1,29.6,74.3,27.6,73.8,32.5z"/>
-                            </svg>
-                        </a>
-                    </div>
-                </li>
-            </ul>
-        </div>
-
-        <div class="navbarb">
-            <div class="navbar2">
-                <div class="container2 nav-container2">
-                    <input class="checkbox" type="checkbox" name="" id=""/>
-                    <div class="hamburger-lines">
-                        <span class="line line1"></span>
-                        <span class="line line2"></span>
-                        <span class="line line3"></span>
-                    </div>
-                    <div class="menu-items">
-                        <li class="topnav"><a href="/">Explorer</a></li>
-                        <li><a href="https://twitter.com/DarkFiSquad">Twitter</a></li>
-                        <li><a href="https://codeberg.org/darkrenaissance/darkfi">Codeberg</a></li>
-                        <li><a href="https://dark.fi/book/misc/darkirc/darkirc.html">Darkirc</a>
-                        </li>
-                        <li><a href="https://t.me/darkfichat">Telegram</a></li>
-                    </div>
-                </div>
-            </div>
-        </div>
-    </nav>
-
-    <div>
-        {% block content %}{% endblock %}
-    </div>
-
-    <footer></footer>
-</div>
-
-</body>
-

+ 0 - 60
bin/explorer/site/templates/block.html

@@ -1,60 +0,0 @@
-<!--
- This file is part of DarkFi (https://dark.fi)
-
- Copyright (C) 2020-2026 Dyne.org foundation
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program.  If not, see <https://www.gnu.org/licenses/>.
--->
-
-{% extends "base.html" %}
-{% block title %}Block {{ block[0] }}{% endblock %}
-
-{% block content %}
-
-<div class="content-container">
-
-    <!-- Block Details Header -->
-    <div class="content-header" style="padding-bottom: 8px">
-        <h2 class="content-section-header">Block Details</h2>
-        <div class="">{{ block[0] }}</div>
-    </div>
-
-    <!-- Block Details -->
-    <ul>
-        <li><strong>Version:</strong> {{ block[1] }}</li>
-        <li><strong>Previous:</strong> <a href="../block/{{ block[2] }}">{{ block[2] }}</a></li>
-        <li><strong>Height:</strong> {{ block[3] }}</li>
-        <li><strong>Timestamp:</strong> {{ block[4] }}</li>
-        <li><strong>Nonce:</strong> {{ block[5] }}</li>
-        <li><strong>Transactions Root:</strong> {{ block[6] }}</li>
-        <li><strong>State Root:</strong> {{ block[7] }}</li>
-        <li><strong>Proof of Work type:</strong> {{ block[8] }}</li>
-        <li><strong>Signature:</strong> {{ block[9] }}</li>
-    </ul>
-
-    <!-- Page Content Divider -->
-    <hr class="content-divider"/>
-
-    <!-- Block Transactions -->
-    <div class="content-section" style="padding-top: 12px">
-        <h2 class="content-section-header">Block Transactions</h2>
-        <ul>
-            {% for transaction in transactions %}
-            <li><a href="../tx/{{ transaction[0] }}">{{ transaction[0] }}</a></li>
-            {% endfor %}
-        </ul>
-    </div>
-</div>
-{% endblock %}
-

+ 0 - 46
bin/explorer/site/templates/contract_source.html

@@ -1,46 +0,0 @@
-<!--
- This file is part of DarkFi (https://dark.fi)
-
- Copyright (C) 2020-2026 Dyne.org foundation
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program.  If not, see <https://www.gnu.org/licenses/>.
--->
-
-{% extends "base.html" %}
-{% block title %}Contract {{ contract_id }}{% endblock %}
-
-{% block content %}
-<div class="content-container">
-
-    <!-- Page Content Header -->
-    <div class="content-header">
-        <h2 class="content-section-header">{{ contract_name }} Contract Source Code</h2>
-        <div class="">{{ contract_id }}</div>
-    </div>
-
-    <!-- Source Code -->
-    <div class="source-code-container">
-        <div class="source-path">
-            {{ source_path }}
-        </div>
-        <div class="source-code-display-box">
-            <!--Include Pygments CSS for syntax highlighting-->
-            <style>{{ pygments_css|safe }}</style>
-            <!-- Display the highlighted code -->
-            <pre class="pre-source">{{ source|safe }}</pre>
-        </div>
-    </div>
-</div>
-{% endblock %}
-

+ 0 - 55
bin/explorer/site/templates/contract_source_list.html

@@ -1,55 +0,0 @@
-<!--
- This file is part of DarkFi (https://dark.fi)
-
- Copyright (C) 2020-2026 Dyne.org foundation
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program.  If not, see <https://www.gnu.org/licenses/>.
--->
-
-{% extends "base.html" %}
-{% block title %}Contract {{ contract_id }}{% endblock %}
-
-{% block content %}
-<div class="content-container">
-
-    <!-- Page Content Header -->
-    <div class="content-header">
-        <h2 class="content-section-header">{{ contract_name }} Contract Source Code</h2>
-        <div>{{ contract_id }}</div>
-    </div>
-
-    <!-- Source File List -->
-    <div>
-        {% if source_paths %}
-        <!-- Source Code List -->
-        <ul class="source-code-list">
-            {% for path in source_paths %}
-            <li class="source-code-list-item">
-                <a href="/contract/source/{{ contract_id }}/{{ path }}?name={{ contract_name|urlencode }}"
-                   class="source-file-link">
-                    {{ path }}
-                </a>
-            </li>
-            {% endfor %}
-        </ul>
-        {% else %}
-        <!-- No source code associated with contract -->
-        <div>
-            There is no source files are associated with this contract.
-        </div>
-        {% endif %}
-    </div>
-</div>
-
-{% endblock %}

+ 0 - 147
bin/explorer/site/templates/index.html

@@ -1,147 +0,0 @@
-<!--
- This file is part of DarkFi (https://dark.fi)
-
- Copyright (C) 2020-2026 Dyne.org foundation
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program.  If not, see <https://www.gnu.org/licenses/>.
--->
-
-{% extends "base.html" %}
-{% block title %}Explorer{% endblock %}
-
-{% block content %}
-<div class="content-container">
-
-    <!-- Explorer Search -->
-    <div class="search-container">
-        <h1>Explore</h1>
-        <form action="/search">
-            <input class="search-input" type="text" placeholder="Search for a block or transaction.." name="search_hash">
-        </form>
-    </div>
-
-    <!--Network Summary -->
-    <div class="content-section" style="padding-bottom: 0px;">
-        <h2 class="content-section-header">Network Summary</h2>
-        <ul>
-            <li>Height: {{ basic_stats[0] }}</li>
-            <li>Epoch: {{ basic_stats[1] }}</li>
-            <li>Last block: <a href="block/{{ basic_stats[2] }}">{{ basic_stats[2] }}</a></li>
-            <li>Total blocks: {{ basic_stats[3] }}</li>
-            <li>Total transactions: {{ basic_stats[4] }}</li>
-        </ul>
-    </div>
-
-    <!--Latest Blocks -->
-    <div class="content-section">
-        <h2 class="content-section-header">Latest Blocks</h2>
-        <table class="table">
-            <thead>
-            <tr>
-                <th>Height</th>
-                <th>Hash</th>
-                <th>Timestamp</th>
-            </tr>
-            </thead>
-            <tbody>
-            {% for block in blocks %}
-            <tr>
-                <td>{{ block[3] }}</td>
-                <td><a href="block/{{ block[0] }}">{{ block[0] }}</a></td>
-                <td>{{ block[4] }}</td>
-            </tr>
-            {% endfor %}
-            </tbody>
-        </table>
-    </div>
-
-    <!--Total Network Gas Consumption -->
-    <div class="content-section">
-        <h2 class="content-section-header">Total Network Gas Consumption</h2>
-        {% if metric_stats %}
-        <table class="table">
-            <thead>
-            <tr>
-                <th></th>
-                <th>Average</th>
-                <th>Minimum</th>
-                <th>Maximum</th>
-            </tr>
-            </thead>
-            <tbody>
-            <tr>
-                <td>Total</td>
-                <td>{{ metric_stats[0] }}</td>
-                <td>{{ metric_stats[1] }}</td>
-                <td>{{ metric_stats[2] }}</td>
-            </tr>
-            <tr>
-                <td>WASM</td>
-                <td>{{ metric_stats[3] }}</td>
-                <td>{{ metric_stats[4] }}</td>
-                <td>{{ metric_stats[5] }}</td>
-            </tr>
-            <tr>
-                <td>ZK Circuits</td>
-                <td>{{ metric_stats[6] }}</td>
-                <td>{{ metric_stats[7] }}</td>
-                <td>{{ metric_stats[8] }}</td>
-            </tr>
-            <tr>
-                <td>Signatures</td>
-                <td>{{ metric_stats[9] }}</td>
-                <td>{{ metric_stats[10] }}</td>
-                <td>{{ metric_stats[11] }}</td>
-            </tr>
-            <tr>
-                <td>Deployments</td>
-                <td>{{ metric_stats[12] }}</td>
-                <td>{{ metric_stats[13] }}</td>
-                <td>{{ metric_stats[14] }}</td>
-            </tr>
-            </tbody>
-        </table>
-        {% else %}
-        Gas consumption details are not available.
-        {% endif %}
-    </div>
-
-    <!--Native Contracts -->
-    <div class="content-section">
-        <h2 class="content-section-header">Native Contracts</h2>
-        <table class="table">
-            <thead>
-            <tr class="table-header">
-                <th class="contract-id">Contract Id</th>
-                <th class="contract-name text-center">Name</th>
-                <th class="contract-description">Description</th>
-            </tr>
-            </thead>
-            <tbody>
-            {% for contract in native_contracts %}
-            <tr class="table-row">
-                <td class="contract-id">
-                    <a href="contract/{{ contract[0] }}?name={{ contract[1]|urlencode }}">{{ contract[0] }}</a>
-                </td>
-                <td class="contract-name text-center">{{ contract[1] }}</td>
-                <td class="contract-description">{{ contract[2] }}</td>
-            </tr>
-            {% endfor %}
-            </tbody>
-        </table>
-    </div>
-
-</div>
-{% endblock %}
-

+ 0 - 55
bin/explorer/site/templates/transaction.html

@@ -1,55 +0,0 @@
-<!--
- This file is part of DarkFi (https://dark.fi)
-
- Copyright (C) 2020-2026 Dyne.org foundation
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program.  If not, see <https://www.gnu.org/licenses/>.
--->
-
-{% extends "base.html" %}
-{% block title %}Transaction {{ transaction[0] }}{% endblock %}
-
-{% block content %}
-<div class="content-container">
-
-    <!-- Page Content Header -->
-    <div class="content-header" style="padding-bottom: 8px">
-        <h1 class="content-section-header">Transaction Details</h1>
-        <div class="">{{ transaction[0] }}</div>
-    </div>
-
-    <!-- Transaction Details -->
-    <ul>
-        <li><strong>Timestamp:</strong> {{ transaction[3] }}</li>
-        <li><strong>Block:</strong> <a href="../block/{{ transaction[1] }}">{{ transaction[1] }}</a></li>
-        <li><strong>Payload:</strong> {{ transaction[2] }}</li>
-    </ul>
-
-    <!-- Page Content Divider -->
-    <hr class="content-divider"/>
-
-    <!-- Transaction Gas Consumption -->
-    <div class="content-section" style="padding-top: 12px;">
-        <h2 class="content-section-header">Gas Consumption</h2>
-        <ul>
-            <li><strong>Total:</strong> {{ transaction[4] }}</li>
-            <li><strong>WASM:</strong> {{ transaction[5] }}</li>
-            <li><strong>ZK Circuits:</strong> {{ transaction[6] }}</li>
-            <li><strong>Signatures:</strong> {{ transaction[7] }}</li>
-            <li><strong>Deployments:</strong> {{ transaction[8] }}</li>
-        </ul>
-    </div>
-</div>
-{% endblock %}
-

+ 0 - 28
bin/explorer/site/wsgi.py

@@ -1,28 +0,0 @@
-# This file is part of DarkFi (https://dark.fi)
-#
-# Copyright (C) 2020-2026 Dyne.org foundation
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <https://www.gnu.org/licenses/>.
-
-"""
-Module: wsgi.py
-
-This module acts as the entry point for the Gunicorn server to run the Flask application.
-It initializes and exposes the Flask application created by the `create_app()` factory
-function from the `app` module.
-"""
-
-from app import create_app
-
-app = create_app()

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است