Skip to content

feat: add iota-names-indexer crate #6975

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
/crates/iota-move-build/ @iotaledger/vm-language
/crates/iota-move-lsp/ @iotaledger/vm-language
/crates/iota-names/ @iotaledger/dev-tools
/crates/iota-names-indexer/ @iotaledger/dev-tools
/crates/iota-network/ @iotaledger/node
/crates/iota-network-stack/ @iotaledger/node
/crates/iota-node/ @iotaledger/node
Expand Down
2 changes: 2 additions & 0 deletions .github/crates-filters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ iota-move-lsp:
- "crates/iota-move-lsp/**"
iota-names:
- "crates/iota-names/**"
iota-names-indexer:
- "crates/iota-names-indexer/**"
iota-network:
- "crates/iota-network/**"
iota-network-stack:
Expand Down
15 changes: 15 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ members = [
"crates/iota-move-build",
"crates/iota-move-lsp",
"crates/iota-names",
"crates/iota-names-indexer",
"crates/iota-network",
"crates/iota-network-stack",
"crates/iota-node",
Expand Down Expand Up @@ -408,6 +409,7 @@ iota-move = { path = "crates/iota-move" }
iota-move-build = { path = "crates/iota-move-build" }
iota-move-lsp = { path = "crates/iota-move-lsp" }
iota-names = { path = "crates/iota-names" }
iota-names-indexer = { path = "crates/iota-names-indexer" }
iota-network = { path = "crates/iota-network" }
iota-network-stack = { path = "crates/iota-network-stack" }
iota-node = { path = "crates/iota-node" }
Expand Down
1 change: 1 addition & 0 deletions crates/iota-names-indexer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
progress_store
21 changes: 21 additions & 0 deletions crates/iota-names-indexer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "iota-names-indexer"
version.workspace = true
authors = ["IOTA Foundation <[email protected]>"]
edition = "2021"
license = "Apache-2.0"
publish = false

[dependencies]
# external dependencies
anyhow.workspace = true
async-trait.workspace = true
prometheus.workspace = true
tokio.workspace = true
tokio-util.workspace = true

# internal dependencies
iota-data-ingestion-core.workspace = true
iota-metrics.workspace = true
iota-names.workspace = true
iota-types.workspace = true
63 changes: 63 additions & 0 deletions crates/iota-names-indexer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# IOTA-Names indexer

Indexer to collect metrics about IOTA-Names.

## Testing

### Run a local network

```bash
iota start --force-regenesis --with-faucet --committee-size 2
```

### Deploy the IOTA-Names packages

https://github.com/iotaledger/iota-names/tree/develop/scripts#setup-iota-names-locally
In the `iota-names` directory, run:

```bash
pnpm ts-node init/init.ts localnet
```

### Start the indexer

```bash
cargo run
```

### Start prometheus

```bash
# Create persistent volume for your data
docker volume create prometheus-data
# Start Prometheus container
docker run \
-p 9090:9090 \
-v "$(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml" \
-v prometheus-data:/prometheus \
prom/prometheus
```

### Start Grafana

```bash
sudo /bin/systemctl start grafana-server
```

Set env variables:

JSON_FILE="./localnet.json"

export IOTA_NAMES_PACKAGE_ADDRESS=$(jq -r '.packageId' "$JSON_FILE")
export IOTA_NAMES_OBJECT_ID=$(jq -r '.iotaNames' "$JSON_FILE")
export IOTA_NAMES_PAYMENTS_PACKAGE_ADDRESS=$(jq -r '.paymentsPackageId' "$JSON_FILE")
export IOTA_NAMES_REGISTRY_ID=$(jq -r '.registryTableId' "$JSON_FILE")
export IOTA_NAMES_REVERSE_REGISTRY_ID=$(jq -r '.reverseRegistryTableId' "$JSON_FILE")

echo "IOTA_NAMES_PACKAGE_ADDRESS=$IOTA_NAMES_PACKAGE_ADDRESS"
echo "IOTA_NAMES_OBJECT_ID=$IOTA_NAMES_OBJECT_ID"
echo "IOTA_NAMES_PAYMENTS_PACKAGE_ADDRESS=$IOTA_NAMES_PAYMENTS_PACKAGE_ADDRESS"
echo "IOTA_NAMES_REGISTRY_ID=$IOTA_NAMES_REGISTRY_ID"
echo "IOTA_NAMES_REVERSE_REGISTRY_ID=$IOTA_NAMES_REVERSE_REGISTRY_ID"

cargo run --features=iota-names name register test.iota
4 changes: 4 additions & 0 deletions crates/iota-names-indexer/prometheus.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
scrape_configs:
- job_name: "iota_names"
static_configs:
- targets: ["172.17.0.1:9184"]
56 changes: 56 additions & 0 deletions crates/iota-names-indexer/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) 2025 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

mod metrics;
mod worker;

use std::{path::PathBuf, sync::Arc};

use anyhow::Result;
use iota_data_ingestion_core::{
DataIngestionMetrics, FileProgressStore, IndexerExecutor, ReaderOptions, WorkerPool,
};
use iota_names::config::IotaNamesConfig;
use tokio_util::sync::CancellationToken;

use self::{
metrics::{IotaNamesMetrics, METRICS, start_prometheus_server},
worker::IotaNamesWorker,
};

#[tokio::main]
async fn main() -> Result<()> {
let registry = start_prometheus_server();

METRICS.get_or_init(|| Arc::new(IotaNamesMetrics::new(&registry)));
let metrics = DataIngestionMetrics::new(&registry);

let progress_store = FileProgressStore::new("./progress_store").await?;

let cancel_token = CancellationToken::new();
let mut executor = IndexerExecutor::new(progress_store, 1, metrics, cancel_token);

let worker = IotaNamesWorker::new(IotaNamesConfig::from_env().unwrap_or_default());
let worker_pool = WorkerPool::new(
worker,
"iota_names_reader".to_string(),
1,
Default::default(),
);

executor.register(worker_pool).await.unwrap();
executor
.run(
PathBuf::from("./chk".to_string()), /* path to a local directory where checkpoints
* are stored. */
Some("http://localhost:9000/api/v1".to_string()),
vec![], // optional remote store access options.
ReaderOptions::default(), // remote_read_batch_size.
)
.await
.unwrap();

// To get the metrics open: http://localhost:9184/metrics

Ok(())
}
34 changes: 34 additions & 0 deletions crates/iota-names-indexer/src/metrics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) 2025 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use std::{
net::{IpAddr, Ipv4Addr, SocketAddr},
sync::{Arc, OnceLock},
};

use prometheus::{IntGauge, Registry, register_int_gauge_with_registry};

pub(crate) struct IotaNamesMetrics {
pub total_name_records: IntGauge,
}

impl IotaNamesMetrics {
pub fn new(registry: &Registry) -> Self {
Self {
total_name_records: register_int_gauge_with_registry!(
"total_name_records",
"The total number of name records in the registry",
registry,
)
.unwrap(),
}
}
}

pub(crate) static METRICS: OnceLock<Arc<IotaNamesMetrics>> = OnceLock::new();

pub(crate) fn start_prometheus_server() -> Registry {
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9184);

iota_metrics::start_prometheus_server(addr).default_registry()
}
106 changes: 106 additions & 0 deletions crates/iota-names-indexer/src/worker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright (c) 2025 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use std::sync::Arc;

use anyhow::Result;
use async_trait::async_trait;
use iota_data_ingestion_core::Worker;
use iota_names::config::IotaNamesConfig;
use iota_types::{
Identifier,
effects::{TransactionEffects, TransactionEffectsAPI},
event::Event,
execution_status::ExecutionStatus,
full_checkpoint_content::CheckpointData,
transaction::{ProgrammableTransaction, TransactionData, TransactionKind},
};

use crate::metrics::METRICS;

pub(crate) struct IotaNamesWorker {
config: IotaNamesConfig,
}

impl IotaNamesWorker {
pub(crate) fn new(config: IotaNamesConfig) -> Self {
Self { config }
}

fn process_event(&self, event: &Event) -> Result<(), anyhow::Error> {
if event.type_.address == self.config.package_address.into() {
// TODO temporarily allowed until there are more even types
#[allow(clippy::collapsible_if)]
if event.type_.name == Identifier::new("IotaNamesRegistryEvent")? {
// TODO: init from prometheus storage to not always start from 0
METRICS
.get()
.expect("metrics global should be initialized")
.total_name_records
.add(1);
// TODO: deserialize to get the name lengths
// let register_event =
// bcs::from_bytes::<IotaNamesRegistryEvent>(&
// event_bcs_bytes)?;
// println!("Register event: {register_event:#?}");
}
}

Ok(())
}

fn process_ptb(&self, _ptb: &ProgrammableTransaction) -> Result<(), anyhow::Error> {
let _module = Identifier::new("payment")?; // TODO: Make const
let _function = Identifier::new("register")?;

// if ptb.commands.iter().any(|cmd| {
// if let Command::MoveCall(call) = cmd {
// call.package == ObjectID::from(self.config.package_address)
// && call.module == module
// && call.function == function
// } else {
// false
// }
// }) {}

Ok(())
}
}

#[async_trait]
impl Worker for IotaNamesWorker {
type Message = ();
type Error = anyhow::Error;

async fn process_checkpoint(
&self,
checkpoint: Arc<CheckpointData>, // TODO change to &?
) -> Result<Self::Message, Self::Error> {
println!(
"Processing checkpoint: {}",
checkpoint.checkpoint_summary.sequence_number
);

for transaction in &checkpoint.transactions {
let TransactionEffects::V1(effects) = &transaction.effects;

if *effects.status() != ExecutionStatus::Success {
continue;
}

if let Some(events) = &transaction.events {
for event in events.data.iter() {
self.process_event(event)?;
}
}

let TransactionData::V1(data) = &transaction.transaction.intent_message().value;

if let TransactionKind::ProgrammableTransaction(ptb) = &data.kind {
self.process_ptb(ptb)?;
}
}

Ok(())
}
}
Loading