mirror of
https://github.com/logos-blockchain/logos-blockchain-block-explorer-template.git
synced 2026-01-02 05:03:07 +00:00
Standardize env variables. Revamp README.
This commit is contained in:
parent
64578fba42
commit
687cecdac7
@ -5,11 +5,10 @@ COPY . /app
|
||||
WORKDIR /app
|
||||
|
||||
# Environment variables
|
||||
ENV NODE_COMPOSE_FILEPATH=/app/docker-compose.yml
|
||||
ENV PYTHONPATH=/app:/app/src
|
||||
ENV UV_INSTALL_DIR=/usr/local/bin
|
||||
ENV NODE_API=http
|
||||
ENV NODE_MANAGER=noop
|
||||
ENV NBE_NODE_API=http
|
||||
ENV NBE_NODE_MANAGER=noop
|
||||
|
||||
# Package manager and dependencies
|
||||
# RUN apt-get update && apt-get install -y curl git
|
||||
|
||||
152
README.md
152
README.md
@ -1,23 +1,141 @@
|
||||
# Nomos Block Explorer
|
||||
|
||||
## Assumptions
|
||||
There are a few assumptions made to facilitate the development of the PoC:
|
||||
- One block per slot.
|
||||
- If a range has been backfilled, it has been fully successfully backfilled.
|
||||
- Backfilling strategy assumes there's, at most, one gap to fill.
|
||||
This is a Proof of Concept (PoC) for a block explorer for the Nomos blockchain.
|
||||
|
||||
## TODO
|
||||
- Better backfilling
|
||||
- Upsert on backfill
|
||||
- Change Sqlite -> Postgres
|
||||
- Performance improvements on API and DB calls
|
||||
- Fix assumptions, so we don't rely on them
|
||||
- DbRepository interfaces
|
||||
- Setup DB Migrations
|
||||
- Tests
|
||||
- Fix assumption of 1 block per slot
|
||||
- Log colouring
|
||||
- Handle reconnections:
|
||||
## Features
|
||||
|
||||
- Frontend (React-like SPA)
|
||||
- Client-side routing with Home, Block, and Transaction pages.
|
||||
- Home: Live stream of the latest Blocks and Transactions.
|
||||
- Block: Details of a Block, including a list of its transactions.
|
||||
- Transaction: Details of a Transaction.
|
||||
- Backend (FastAPI)
|
||||
- API
|
||||
- REST API to query Blocks and Transactions.
|
||||
- SSE API to stream live Blocks (and its transactions).
|
||||
- Node Management
|
||||
- Pluggable API (e.g. `fake`, `http`) to query nodes.
|
||||
- Pluggable Manager (e.g. `noop`, `docker`) to manage local nodes.
|
||||
- Simple backfilling mechanism to populate historical blocks.
|
||||
|
||||
## Architecture
|
||||
|
||||
The Nomos Block Explorer follows a three-tier architecture with a clear separation of concerns:
|
||||
|
||||
### High-Level Overview
|
||||
|
||||
```mermaid
|
||||
graph LR;
|
||||
A[Nomos<br/>Node] -->|REST/SSE| B["Backend<br/>(FastAPI)"]
|
||||
B -->|REST/SSE| C["Frontend<br/>(Preact)"]
|
||||
B <--> D["Database<br/>(SQLite)"]
|
||||
```
|
||||
|
||||
### Components
|
||||
|
||||
#### 1. Frontend (`/static`)
|
||||
- **Framework**: Preact (lightweight React alternative)
|
||||
- **Routing**: Client-side SPA routing
|
||||
- **Architecture**: Component-based with functional components
|
||||
- **Communication**: REST API calls and Server-Sent Events (SSE) for real-time updates
|
||||
|
||||
#### 2. Backend (`/src`)
|
||||
- **Framework**: FastAPI (Python async web framework)
|
||||
- **API Layer** (`/src/api`): Serializers, REST and streaming endpoints
|
||||
- **Core** (`/src/core`): Application setup, configuration, base types and mixins
|
||||
- **Database Layer** (`/src/db`): Repository pattern for data access
|
||||
- **Models** (`/src/models`): Domain models (Block, Transaction, Header, etc.)
|
||||
- **Node Integration** (`/src/node`):
|
||||
- **API**: Pluggable adapters to communicate with Nomos nodes (`fake`, `http`) and serializers
|
||||
- **Manager**: Pluggable node lifecycle management (`noop`, `docker`)
|
||||
|
||||
#### 3. Data Flow
|
||||
|
||||
1. **Node Updates**: On startup, the backend starts listening for new blocks from the node and stores them in the database
|
||||
2. **Backfilling**: After at least one block is in the database, the backend fetches historical blocks from the node and stores them
|
||||
3. **Client Updates**: Frontend subscribes to SSE endpoints for real-time block and transaction updates
|
||||
4. **Data Access**: All queries route through repository classes for consistent data access
|
||||
|
||||
#### 4. Key Design Patterns
|
||||
|
||||
- **Repository Pattern**: Abstraction layer for database operations (`BlockRepository`, `TransactionRepository`)
|
||||
- **Strategy Pattern**: Pluggable Node API implementations (fake for testing, HTTP for production)
|
||||
- **Adapter Pattern**: Serializers convert between Node API formats and internal domain models
|
||||
- **Observer Pattern**: SSE streams for pushing real-time updates to clients
|
||||
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.14
|
||||
- UV Package Manager
|
||||
|
||||
### Optional
|
||||
|
||||
- Docker: To run a local node.
|
||||
|
||||
## How to run
|
||||
|
||||
1. Install the dependencies:
|
||||
```bash
|
||||
uv sync
|
||||
```
|
||||
|
||||
2. Run the block explorer:
|
||||
```bash
|
||||
python src/main.py
|
||||
```
|
||||
|
||||
- You can optionally run it via Docker with:
|
||||
```bash
|
||||
docker build -t nomos-block-explorer . && docker run -p 8000:8000 nomos-block-explorer
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
The block explorer is configured through environment variables. The following variables are available:
|
||||
```dotenv
|
||||
NBE_LOG_LEVEL=DEBUG # DEBUG, INFO, WARNING, ERROR, CRITICAL
|
||||
|
||||
NBE_DEBUG=true # Randomizes transactions in BlockSerializer
|
||||
|
||||
NBE_NODE_MANAGER=noop # noop, docker
|
||||
NBE_NODE_COMPOSE_FILEPATH=/path/to/docker-compose.yml # Only used if NODE_MANAGER=docker
|
||||
|
||||
NBE_NODE_API=http # fake, http
|
||||
NBE_NODE_API_HOST=localhost # Only used if NODE_API=http
|
||||
NBE_NODE_API_PORT=18080 # Only used if NODE_API=http
|
||||
NBE_NODE_API_TIMEOUT=60 # Only used if NODE_API=http
|
||||
NBE_NODE_API_PROTOCOL=http # Only used if NODE_API=http
|
||||
|
||||
NBE_HOST=0.0.0.0 # Block Explorer's listening host
|
||||
NBE_PORT=8000 # Block Explorer's listening port
|
||||
```
|
||||
If running the Block Explorer with Docker, these can be overridden.
|
||||
|
||||
## Considerations
|
||||
|
||||
This PoC makes simplifications to focus on the core features:
|
||||
- Each slot has exactly one block.
|
||||
- When backfilling, the block explorer will only backfill from the earliest block's slot to genesis.
|
||||
|
||||
## Ideas and improvements
|
||||
|
||||
- Fix aforementioned assumptions
|
||||
- Backfilling
|
||||
- Make requests concurrently
|
||||
- Backfill all slots
|
||||
- Upsert received blocks and transactions
|
||||
- Database
|
||||
- Update to Postgres
|
||||
- Add migrations management
|
||||
- Add relevant indexes to columns
|
||||
- Add interfaces to database repositories: `BlockRepository` and `TransactionRepository`
|
||||
- Add tests
|
||||
- Colour logs by level
|
||||
- Reconnections
|
||||
- Failures to connect to Node
|
||||
- Timeouts
|
||||
- Stream closed
|
||||
- Frontend
|
||||
- Add a block / transaction search bar
|
||||
- Make pages work with block/transaction hash, rather than the `id`
|
||||
|
||||
@ -2,6 +2,7 @@ from asyncio import Task, gather
|
||||
from typing import Literal, Optional
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic import Field
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
from starlette.datastructures import State
|
||||
|
||||
@ -18,15 +19,15 @@ ENV_FILEPATH = DIR_REPO.joinpath(".env")
|
||||
class NBESettings(BaseSettings):
|
||||
model_config = SettingsConfigDict(env_file=ENV_FILEPATH, extra="ignore")
|
||||
|
||||
node_compose_filepath: str
|
||||
node_compose_filepath: Optional[str] = Field(alias="NBE_NODE_COMPOSE_FILEPATH", default=None)
|
||||
|
||||
node_api: Literal["http", "fake"]
|
||||
node_manager: Literal["docker", "noop"]
|
||||
node_api: Literal["http", "fake"] = Field(alias="NBE_NODE_API")
|
||||
node_manager: Literal["docker", "noop"] = Field(alias="NBE_NODE_MANAGER")
|
||||
|
||||
node_api_host: str = "127.0.0.1"
|
||||
node_api_port: int = 18080
|
||||
node_api_timeout: int = 60
|
||||
node_api_protocol: str = "http"
|
||||
node_api_host: str = Field(alias="NBE_NODE_API_HOST", default="127.0.0.1")
|
||||
node_api_port: int = Field(alias="NBE_NODE_API_PORT", default=18080)
|
||||
node_api_timeout: int = Field(alias="NBE_NODE_API_TIMEOUT", default=60)
|
||||
node_api_protocol: str = Field(alias="NBE_NODE_API_PROTOCOL", default="http")
|
||||
|
||||
|
||||
class NBEState(State):
|
||||
|
||||
@ -13,9 +13,8 @@ from utils.protocols import FromRandom
|
||||
|
||||
|
||||
def _should_randomize_transactions():
|
||||
is_debug = getenv("DEBUG", "False").lower() == "true"
|
||||
is_debug__randomize_transactions = getenv("DEBUG__RANDOMIZE_TRANSACTIONS", "False").lower() == "true"
|
||||
return is_debug and is_debug__randomize_transactions
|
||||
is_debug = getenv("NBE_DEBUG", "False").lower() == "true"
|
||||
return is_debug
|
||||
|
||||
|
||||
def _get_random_transactions() -> List[SignedTransactionSerializer]:
|
||||
@ -34,7 +33,7 @@ class BlockSerializer(NbeSerializer, FromRandom):
|
||||
def model_validate_json(cls, *args, **kwargs) -> Self:
|
||||
self = super().model_validate_json(*args, **kwargs)
|
||||
if _should_randomize_transactions():
|
||||
logger.debug("DEBUG and DEBUG__RANDOMIZE_TRANSACTIONS are enabled, randomizing Block's transactions.")
|
||||
logger.debug("DEBUG flag is enabled, randomizing Block's transactions.")
|
||||
self.transactions = _get_random_transactions()
|
||||
return self
|
||||
|
||||
|
||||
@ -16,6 +16,9 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class DockerModeManager(NodeManager):
|
||||
def __init__(self, settings: "NBESettings"):
|
||||
if not settings.node_compose_filepath:
|
||||
raise ValueError("Node compose filepath environment variable is not set.")
|
||||
|
||||
self.client: DockerClient = DockerClient(
|
||||
client_type="docker",
|
||||
compose_files=[settings.node_compose_filepath],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user