Skip to content

Commit

Permalink
Add several block endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
gonzalezzfelipe committed Feb 6, 2025
1 parent 7ded3f9 commit 5f60353
Show file tree
Hide file tree
Showing 10 changed files with 446 additions and 36 deletions.
8 changes: 8 additions & 0 deletions src/serve/minibf/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,18 @@ pub async fn serve(
.mount(
"/",
routes![
// Addresses
routes::addresses::address::utxo::route,
routes::addresses::address::utxo::asset::route,
// Blocks
routes::blocks::latest::route,
routes::blocks::latest::txs::route,
routes::blocks::hash_or_number::route,
routes::blocks::hash_or_number::addresses::route,
routes::blocks::hash_or_number::next::route,
routes::blocks::hash_or_number::previous::route,
routes::blocks::hash_or_number::txs::route,
routes::blocks::slot::slot_number::route,
],
)
.launch()
Expand Down
99 changes: 99 additions & 0 deletions src/serve/minibf/routes/blocks/hash_or_number/addresses.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use itertools::Itertools;
use pallas::ledger::traverse::MultiEraBlock;
use rocket::{get, http::Status, State};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

use crate::{
state::LedgerStore,
wal::{redb::WalStore, ReadUtils, WalReader},
};

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct BlockAddress {
address: String,
transactions: Vec<BlockAddressTx>,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)]
pub struct BlockAddressTx {
tx_hash: String,
}

#[get("/blocks/<hash_or_number>/addresses", rank = 2)]
pub fn route(
hash_or_number: String,
wal: &State<WalStore>,
ledger: &State<LedgerStore>,
) -> Result<rocket::serde::json::Json<Vec<BlockAddress>>, Status> {
let maybe_raw = wal
.crawl_from(None)
.map_err(|_| Status::ServiceUnavailable)?
.into_blocks()
.find(|maybe_raw| match maybe_raw {
Some(raw) => match MultiEraBlock::decode(&raw.body) {
Ok(block) => {
block.hash().to_string() == hash_or_number
|| block.number().to_string() == hash_or_number
}
Err(_) => false,
},
None => false,
});

match maybe_raw {
Some(Some(raw)) => {
let block = MultiEraBlock::decode(&raw.body).map_err(|_| Status::ServiceUnavailable)?;
let mut addresses = HashMap::new();

for tx in block.txs() {
// Handle inputs
let utxos = ledger
.get_utxos(tx.inputs().iter().map(Into::into).collect())
.map_err(|_| Status::ServiceUnavailable)?;

for (_, eracbor) in utxos {
let parsed =
pallas::ledger::traverse::MultiEraOutput::decode(eracbor.0, &eracbor.1)
.map_err(|_| Status::InternalServerError)?;
let address = parsed
.address()
.map_err(|_| Status::ServiceUnavailable)?
.to_bech32()
.map_err(|_| Status::ServiceUnavailable)?;
let tx_hash = tx.hash().to_string();
addresses
.entry(address.to_string())
.or_insert_with(Vec::new)
.push(BlockAddressTx { tx_hash });
}

// Handle outputs
for output in tx.outputs() {
let address = output
.address()
.map_err(|_| Status::ServiceUnavailable)?
.to_bech32()
.map_err(|_| Status::ServiceUnavailable)?;
let tx_hash = tx.hash().to_string();
addresses
.entry(address.to_string())
.or_insert_with(Vec::new)
.push(BlockAddressTx { tx_hash });
}
}

Ok(rocket::serde::json::Json(
addresses
.into_iter()
.sorted_by_key(|(address, _)| address.clone())
.map(|(address, transactions)| BlockAddress {
address,
transactions: transactions.into_iter().unique().collect(),
})
.collect(),
))
}
_ => Err(Status::NotFound),
}
}
33 changes: 33 additions & 0 deletions src/serve/minibf/routes/blocks/hash_or_number/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use pallas::ledger::traverse::wellknown::GenesisValues;
use rocket::{get, http::Status, State};
use std::sync::Arc;

use crate::{ledger::pparams::Genesis, wal::redb::WalStore};

use super::Block;

pub mod addresses;
pub mod next;
pub mod previous;
pub mod txs;

#[get("/blocks/<hash_or_number>", rank = 2)]
pub fn route(
hash_or_number: String,
genesis: &State<Arc<Genesis>>,
wal: &State<WalStore>,
) -> Result<rocket::serde::json::Json<Block>, Status> {
let Some(magic) = genesis.shelley.network_magic else {
return Err(Status::ServiceUnavailable);
};

let Some(values) = GenesisValues::from_magic(magic as u64) else {
return Err(Status::ServiceUnavailable);
};

let block = Block::find_in_wal(wal, &hash_or_number, &values)?;
match block {
Some(block) => Ok(rocket::serde::json::Json(block)),
None => Err(Status::NotFound),
}
}
53 changes: 53 additions & 0 deletions src/serve/minibf/routes/blocks/hash_or_number/next.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use pallas::ledger::traverse::{wellknown::GenesisValues, MultiEraBlock};
use rocket::{get, http::Status, State};
use std::sync::Arc;

use crate::{
ledger::pparams::Genesis,
wal::{redb::WalStore, ReadUtils, WalReader},
};

use super::Block;

#[get("/blocks/<hash_or_number>/next", rank = 2)]
pub fn route(
hash_or_number: String,
genesis: &State<Arc<Genesis>>,
wal: &State<WalStore>,
) -> Result<rocket::serde::json::Json<Block>, Status> {
let Some(magic) = genesis.shelley.network_magic else {
return Err(Status::ServiceUnavailable);
};

let Some(values) = GenesisValues::from_magic(magic as u64) else {
return Err(Status::ServiceUnavailable);
};

let iterator = wal
.crawl_from(None)
.map_err(|_| Status::ServiceUnavailable)?
.into_blocks();

let mut prev = None;
for raw in iterator.flatten() {
let block = MultiEraBlock::decode(&raw.body).map_err(|_| Status::ServiceUnavailable)?;
if block.hash().to_string() == hash_or_number
|| block.number().to_string() == hash_or_number
{
break;
} else {
prev = Some(raw.hash.to_string());
}
}
match prev {
Some(block) => {
match Block::find_in_wal(wal, &block, &values)
.map_err(|_| Status::ServiceUnavailable)?
{
Some(block) => Ok(rocket::serde::json::Json(block)),
None => Err(Status::NotFound),
}
}
None => Err(Status::NotFound),
}
}
55 changes: 55 additions & 0 deletions src/serve/minibf/routes/blocks/hash_or_number/previous.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use pallas::ledger::traverse::{wellknown::GenesisValues, MultiEraBlock};
use rocket::{get, http::Status, State};
use std::sync::Arc;

use crate::{
ledger::pparams::Genesis,
wal::{redb::WalStore, ReadUtils, WalReader},
};

use super::Block;

#[get("/blocks/<hash_or_number>/previous", rank = 2)]
pub fn route(
hash_or_number: String,
genesis: &State<Arc<Genesis>>,
wal: &State<WalStore>,
) -> Result<rocket::serde::json::Json<Block>, Status> {
let Some(magic) = genesis.shelley.network_magic else {
return Err(Status::ServiceUnavailable);
};

let Some(values) = GenesisValues::from_magic(magic as u64) else {
return Err(Status::ServiceUnavailable);
};

// Reversed iterator
let iterator = wal
.crawl_from(None)
.map_err(|_| Status::ServiceUnavailable)?
.rev()
.into_blocks();

let mut next = None;
for raw in iterator.flatten() {
let block = MultiEraBlock::decode(&raw.body).map_err(|_| Status::ServiceUnavailable)?;
if block.hash().to_string() == hash_or_number
|| block.number().to_string() == hash_or_number
{
break;
} else {
next = Some(raw.hash.to_string());
}
}
match next {
Some(block) => {
match Block::find_in_wal(wal, &block, &values)
.map_err(|_| Status::ServiceUnavailable)?
{
Some(block) => Ok(rocket::serde::json::Json(block)),
None => Err(Status::NotFound),
}
}
None => Err(Status::NotFound),
}
}
35 changes: 35 additions & 0 deletions src/serve/minibf/routes/blocks/hash_or_number/txs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use pallas::ledger::traverse::MultiEraBlock;
use rocket::{get, http::Status, State};

use crate::wal::{redb::WalStore, ReadUtils, WalReader};

#[get("/blocks/<hash_or_number>/txs", rank = 2)]
pub fn route(
hash_or_number: String,
wal: &State<WalStore>,
) -> Result<rocket::serde::json::Json<Vec<String>>, Status> {
let maybe_raw = wal
.crawl_from(None)
.map_err(|_| Status::ServiceUnavailable)?
.into_blocks()
.find(|maybe_raw| match maybe_raw {
Some(raw) => match MultiEraBlock::decode(&raw.body) {
Ok(block) => {
block.hash().to_string() == hash_or_number
|| block.number().to_string() == hash_or_number
}
Err(_) => false,
},
None => false,
});

match maybe_raw {
Some(Some(raw)) => {
let block = MultiEraBlock::decode(&raw.body).map_err(|_| Status::ServiceUnavailable)?;
Ok(rocket::serde::json::Json(
block.txs().iter().map(|tx| tx.hash().to_string()).collect(),
))
}
_ => Err(Status::NotFound),
}
}
39 changes: 16 additions & 23 deletions src/serve/minibf/routes/blocks/latest/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
pub mod txs;

use pallas::ledger::traverse::{wellknown::GenesisValues, MultiEraBlock};
use pallas::ledger::traverse::wellknown::GenesisValues;
use rocket::{get, http::Status, State};
use std::sync::Arc;

Expand All @@ -10,39 +10,32 @@ use crate::{
wal::{redb::WalStore, WalReader},
};

#[get("/blocks/latest")]
#[get("/blocks/latest", rank = 1)]
pub fn route(
genesis: &State<Arc<Genesis>>,
wal: &State<WalStore>,
) -> Result<rocket::serde::json::Json<Block>, Status> {
let Some(magic) = genesis.shelley.network_magic else {
return Err(Status::ServiceUnavailable);
};

let Some(values) = GenesisValues::from_magic(magic as u64) else {
return Err(Status::ServiceUnavailable);
};

let tip = wal.find_tip().map_err(|_| Status::ServiceUnavailable)?;
match tip {
None => Err(Status::ServiceUnavailable),
Some((_, point)) => {
let raw_block = wal
let raw = wal
.read_block(&point)
.map_err(|_| Status::ServiceUnavailable)?;
let block =
MultiEraBlock::decode(&raw_block.body).map_err(|_| Status::ServiceUnavailable)?;

let Some(magic) = genesis.shelley.network_magic else {
return Err(Status::ServiceUnavailable);
};
let Some(values) = GenesisValues::from_magic(magic as u64) else {
return Err(Status::ServiceUnavailable);
};

let (epoch, epoch_slot) = block.epoch(&values);
Ok(rocket::serde::json::Json(Block {
slot: Some(block.slot()),
hash: block.hash().to_string(),
tx_count: block.tx_count() as u64,
size: block.size() as u64,
epoch: Some(epoch),
epoch_slot: Some(epoch_slot),
height: Some(block.number()),
..Default::default()
}))
match Block::find_in_wal(wal, &raw.hash.to_string(), &values) {
Ok(Some(block)) => Ok(rocket::serde::json::Json(block)),
Ok(None) => Err(Status::NotFound),
Err(_) => Err(Status::ServiceUnavailable),
}
}
}
}
Loading

0 comments on commit 5f60353

Please sign in to comment.