Skip to content

Commit 54154fb

Browse files
feat: add iota-names-indexer crate (#6975)
Adds an IOTA-Names indexer to provide metrics through Prometheus. --------- Co-authored-by: Thoralf Müller <[email protected]>
1 parent badf04b commit 54154fb

File tree

11 files changed

+305
-0
lines changed

11 files changed

+305
-0
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
/crates/iota-move-build/ @iotaledger/vm-language
4646
/crates/iota-move-lsp/ @iotaledger/vm-language
4747
/crates/iota-names/ @iotaledger/dev-tools
48+
/crates/iota-names-indexer/ @iotaledger/dev-tools
4849
/crates/iota-network/ @iotaledger/node
4950
/crates/iota-network-stack/ @iotaledger/node
5051
/crates/iota-node/ @iotaledger/node

.github/crates-filters.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ iota-move-lsp:
9696
- "crates/iota-move-lsp/**"
9797
iota-names:
9898
- "crates/iota-names/**"
99+
iota-names-indexer:
100+
- "crates/iota-names-indexer/**"
99101
iota-network:
100102
- "crates/iota-network/**"
101103
iota-network-stack:

Cargo.lock

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ members = [
113113
"crates/iota-move-build",
114114
"crates/iota-move-lsp",
115115
"crates/iota-names",
116+
"crates/iota-names-indexer",
116117
"crates/iota-network",
117118
"crates/iota-network-stack",
118119
"crates/iota-node",
@@ -408,6 +409,7 @@ iota-move = { path = "crates/iota-move" }
408409
iota-move-build = { path = "crates/iota-move-build" }
409410
iota-move-lsp = { path = "crates/iota-move-lsp" }
410411
iota-names = { path = "crates/iota-names" }
412+
iota-names-indexer = { path = "crates/iota-names-indexer" }
411413
iota-network = { path = "crates/iota-network" }
412414
iota-network-stack = { path = "crates/iota-network-stack" }
413415
iota-node = { path = "crates/iota-node" }

crates/iota-names-indexer/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
progress_store

crates/iota-names-indexer/Cargo.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[package]
2+
name = "iota-names-indexer"
3+
version.workspace = true
4+
authors = ["IOTA Foundation <[email protected]>"]
5+
edition = "2021"
6+
license = "Apache-2.0"
7+
publish = false
8+
9+
[dependencies]
10+
# external dependencies
11+
anyhow.workspace = true
12+
async-trait.workspace = true
13+
prometheus.workspace = true
14+
tokio.workspace = true
15+
tokio-util.workspace = true
16+
17+
# internal dependencies
18+
iota-data-ingestion-core.workspace = true
19+
iota-metrics.workspace = true
20+
iota-names.workspace = true
21+
iota-types.workspace = true

crates/iota-names-indexer/README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# IOTA-Names indexer
2+
3+
Indexer to collect metrics about IOTA-Names.
4+
5+
## Testing
6+
7+
### Run a local network
8+
9+
```bash
10+
iota start --force-regenesis --with-faucet --committee-size 2
11+
```
12+
13+
### Deploy the IOTA-Names packages
14+
15+
https://github.com/iotaledger/iota-names/tree/develop/scripts#setup-iota-names-locally
16+
In the `iota-names` directory, run:
17+
18+
```bash
19+
pnpm ts-node init/init.ts localnet
20+
```
21+
22+
### Start the indexer
23+
24+
```bash
25+
cargo run
26+
```
27+
28+
### Start prometheus
29+
30+
```bash
31+
# Create persistent volume for your data
32+
docker volume create prometheus-data
33+
# Start Prometheus container
34+
docker run \
35+
-p 9090:9090 \
36+
-v "$(pwd)/prometheus.yml:/etc/prometheus/prometheus.yml" \
37+
-v prometheus-data:/prometheus \
38+
prom/prometheus
39+
```
40+
41+
### Start Grafana
42+
43+
```bash
44+
sudo /bin/systemctl start grafana-server
45+
```
46+
47+
Set env variables:
48+
49+
JSON_FILE="./localnet.json"
50+
51+
export IOTA_NAMES_PACKAGE_ADDRESS=$(jq -r '.packageId' "$JSON_FILE")
52+
export IOTA_NAMES_OBJECT_ID=$(jq -r '.iotaNames' "$JSON_FILE")
53+
export IOTA_NAMES_PAYMENTS_PACKAGE_ADDRESS=$(jq -r '.paymentsPackageId' "$JSON_FILE")
54+
export IOTA_NAMES_REGISTRY_ID=$(jq -r '.registryTableId' "$JSON_FILE")
55+
export IOTA_NAMES_REVERSE_REGISTRY_ID=$(jq -r '.reverseRegistryTableId' "$JSON_FILE")
56+
57+
echo "IOTA_NAMES_PACKAGE_ADDRESS=$IOTA_NAMES_PACKAGE_ADDRESS"
58+
echo "IOTA_NAMES_OBJECT_ID=$IOTA_NAMES_OBJECT_ID"
59+
echo "IOTA_NAMES_PAYMENTS_PACKAGE_ADDRESS=$IOTA_NAMES_PAYMENTS_PACKAGE_ADDRESS"
60+
echo "IOTA_NAMES_REGISTRY_ID=$IOTA_NAMES_REGISTRY_ID"
61+
echo "IOTA_NAMES_REVERSE_REGISTRY_ID=$IOTA_NAMES_REVERSE_REGISTRY_ID"
62+
63+
cargo run --features=iota-names name register test.iota
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
scrape_configs:
2+
- job_name: "iota_names"
3+
static_configs:
4+
- targets: ["172.17.0.1:9184"]

crates/iota-names-indexer/src/main.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright (c) 2025 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
mod metrics;
5+
mod worker;
6+
7+
use std::{path::PathBuf, sync::Arc};
8+
9+
use anyhow::Result;
10+
use iota_data_ingestion_core::{
11+
DataIngestionMetrics, FileProgressStore, IndexerExecutor, ReaderOptions, WorkerPool,
12+
};
13+
use iota_names::config::IotaNamesConfig;
14+
use tokio_util::sync::CancellationToken;
15+
16+
use self::{
17+
metrics::{IotaNamesMetrics, METRICS, start_prometheus_server},
18+
worker::IotaNamesWorker,
19+
};
20+
21+
#[tokio::main]
22+
async fn main() -> Result<()> {
23+
let registry = start_prometheus_server();
24+
25+
METRICS.get_or_init(|| Arc::new(IotaNamesMetrics::new(&registry)));
26+
let metrics = DataIngestionMetrics::new(&registry);
27+
28+
let progress_store = FileProgressStore::new("./progress_store").await?;
29+
30+
let cancel_token = CancellationToken::new();
31+
let mut executor = IndexerExecutor::new(progress_store, 1, metrics, cancel_token);
32+
33+
let worker = IotaNamesWorker::new(IotaNamesConfig::from_env().unwrap_or_default());
34+
let worker_pool = WorkerPool::new(
35+
worker,
36+
"iota_names_reader".to_string(),
37+
1,
38+
Default::default(),
39+
);
40+
41+
executor.register(worker_pool).await.unwrap();
42+
executor
43+
.run(
44+
PathBuf::from("./chk".to_string()), /* path to a local directory where checkpoints
45+
* are stored. */
46+
Some("http://localhost:9000/api/v1".to_string()),
47+
vec![], // optional remote store access options.
48+
ReaderOptions::default(), // remote_read_batch_size.
49+
)
50+
.await
51+
.unwrap();
52+
53+
// To get the metrics open: http://localhost:9184/metrics
54+
55+
Ok(())
56+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) 2025 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use std::{
5+
net::{IpAddr, Ipv4Addr, SocketAddr},
6+
sync::{Arc, OnceLock},
7+
};
8+
9+
use prometheus::{IntGauge, Registry, register_int_gauge_with_registry};
10+
11+
pub(crate) struct IotaNamesMetrics {
12+
pub total_name_records: IntGauge,
13+
}
14+
15+
impl IotaNamesMetrics {
16+
pub fn new(registry: &Registry) -> Self {
17+
Self {
18+
total_name_records: register_int_gauge_with_registry!(
19+
"total_name_records",
20+
"The total number of name records in the registry",
21+
registry,
22+
)
23+
.unwrap(),
24+
}
25+
}
26+
}
27+
28+
pub(crate) static METRICS: OnceLock<Arc<IotaNamesMetrics>> = OnceLock::new();
29+
30+
pub(crate) fn start_prometheus_server() -> Registry {
31+
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9184);
32+
33+
iota_metrics::start_prometheus_server(addr).default_registry()
34+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright (c) 2025 IOTA Stiftung
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
use std::sync::Arc;
5+
6+
use anyhow::Result;
7+
use async_trait::async_trait;
8+
use iota_data_ingestion_core::Worker;
9+
use iota_names::config::IotaNamesConfig;
10+
use iota_types::{
11+
Identifier,
12+
effects::{TransactionEffects, TransactionEffectsAPI},
13+
event::Event,
14+
execution_status::ExecutionStatus,
15+
full_checkpoint_content::CheckpointData,
16+
transaction::{ProgrammableTransaction, TransactionData, TransactionKind},
17+
};
18+
19+
use crate::metrics::METRICS;
20+
21+
pub(crate) struct IotaNamesWorker {
22+
config: IotaNamesConfig,
23+
}
24+
25+
impl IotaNamesWorker {
26+
pub(crate) fn new(config: IotaNamesConfig) -> Self {
27+
Self { config }
28+
}
29+
30+
fn process_event(&self, event: &Event) -> Result<(), anyhow::Error> {
31+
if event.type_.address == self.config.package_address.into() {
32+
// TODO temporarily allowed until there are more even types
33+
#[allow(clippy::collapsible_if)]
34+
if event.type_.name == Identifier::new("IotaNamesRegistryEvent")? {
35+
// TODO: init from prometheus storage to not always start from 0
36+
METRICS
37+
.get()
38+
.expect("metrics global should be initialized")
39+
.total_name_records
40+
.add(1);
41+
// TODO: deserialize to get the name lengths
42+
// let register_event =
43+
// bcs::from_bytes::<IotaNamesRegistryEvent>(&
44+
// event_bcs_bytes)?;
45+
// println!("Register event: {register_event:#?}");
46+
}
47+
}
48+
49+
Ok(())
50+
}
51+
52+
fn process_ptb(&self, _ptb: &ProgrammableTransaction) -> Result<(), anyhow::Error> {
53+
let _module = Identifier::new("payment")?; // TODO: Make const
54+
let _function = Identifier::new("register")?;
55+
56+
// if ptb.commands.iter().any(|cmd| {
57+
// if let Command::MoveCall(call) = cmd {
58+
// call.package == ObjectID::from(self.config.package_address)
59+
// && call.module == module
60+
// && call.function == function
61+
// } else {
62+
// false
63+
// }
64+
// }) {}
65+
66+
Ok(())
67+
}
68+
}
69+
70+
#[async_trait]
71+
impl Worker for IotaNamesWorker {
72+
type Message = ();
73+
type Error = anyhow::Error;
74+
75+
async fn process_checkpoint(
76+
&self,
77+
checkpoint: Arc<CheckpointData>, // TODO change to &?
78+
) -> Result<Self::Message, Self::Error> {
79+
println!(
80+
"Processing checkpoint: {}",
81+
checkpoint.checkpoint_summary.sequence_number
82+
);
83+
84+
for transaction in &checkpoint.transactions {
85+
let TransactionEffects::V1(effects) = &transaction.effects;
86+
87+
if *effects.status() != ExecutionStatus::Success {
88+
continue;
89+
}
90+
91+
if let Some(events) = &transaction.events {
92+
for event in events.data.iter() {
93+
self.process_event(event)?;
94+
}
95+
}
96+
97+
let TransactionData::V1(data) = &transaction.transaction.intent_message().value;
98+
99+
if let TransactionKind::ProgrammableTransaction(ptb) = &data.kind {
100+
self.process_ptb(ptb)?;
101+
}
102+
}
103+
104+
Ok(())
105+
}
106+
}

0 commit comments

Comments
 (0)