use crate::database::Database;
use crate::schema::{
scalars::{BlockId, U64},
tx::types::Transaction,
};
use crate::{
database::KvStoreError,
model::fuel_block::{BlockHeight, FuelBlockDb},
state::IterDirection,
};
use async_graphql::{
connection::{query, Connection, Edge, EmptyFields},
Context, Object,
};
use chrono::{DateTime, Utc};
use fuel_storage::Storage;
use itertools::Itertools;
use std::borrow::Cow;
use std::convert::TryInto;
use super::scalars::Address;
pub struct Block(pub(crate) FuelBlockDb);
#[Object]
impl Block {
async fn id(&self) -> BlockId {
self.0.id().into()
}
async fn height(&self) -> U64 {
self.0.headers.height.into()
}
async fn transactions(&self, ctx: &Context<'_>) -> async_graphql::Result<Vec<Transaction>> {
let db = ctx.data_unchecked::<Database>().clone();
self.0
.transactions
.iter()
.map(|tx_id| {
Ok(Transaction(
Storage::<fuel_types::Bytes32, fuel_tx::Transaction>::get(&db, tx_id)
.and_then(|v| v.ok_or(KvStoreError::NotFound))?
.into_owned(),
))
})
.collect()
}
async fn time(&self) -> DateTime<Utc> {
self.0.headers.time
}
async fn producer(&self) -> Address {
self.0.headers.producer.into()
}
}
#[derive(Default)]
pub struct BlockQuery;
#[Object]
impl BlockQuery {
async fn block(
&self,
ctx: &Context<'_>,
#[graphql(desc = "id of the block")] id: Option<BlockId>,
#[graphql(desc = "height of the block")] height: Option<U64>,
) -> async_graphql::Result<Option<Block>> {
let db = ctx.data_unchecked::<Database>();
let id = match (id, height) {
(Some(_), Some(_)) => {
return Err(async_graphql::Error::new(
"Can't provide both an id and a height",
))
}
(Some(id), None) => id.into(),
(None, Some(height)) => {
let height: u64 = height.into();
if height == 0 {
return Err(async_graphql::Error::new(
"Genesis block isn't implemented yet",
));
} else {
db.get_block_id(height.try_into()?)?
.ok_or("block height non-existent")?
}
}
(None, None) => return Err(async_graphql::Error::new("missing either id or height")),
};
let block = Storage::<fuel_types::Bytes32, FuelBlockDb>::get(db, &id)?
.map(|b| Block(b.into_owned()));
Ok(block)
}
async fn blocks(
&self,
ctx: &Context<'_>,
first: Option<i32>,
after: Option<String>,
last: Option<i32>,
before: Option<String>,
) -> async_graphql::Result<Connection<usize, Block, EmptyFields, EmptyFields>> {
let db = ctx.data_unchecked::<Database>().clone();
query(
after,
before,
first,
last,
|after: Option<usize>, before: Option<usize>, first, last| async move {
let (records_to_fetch, direction) = if let Some(first) = first {
(first, IterDirection::Forward)
} else if let Some(last) = last {
(last, IterDirection::Reverse)
} else {
(0, IterDirection::Forward)
};
let start;
let end;
if direction == IterDirection::Forward {
start = after;
end = before;
} else {
start = before;
end = after;
}
let mut blocks = db.all_block_ids(start.map(Into::into), Some(direction));
let mut started = None;
if start.is_some() {
started = blocks.next();
}
let blocks = blocks
.take_while(|r| {
if let (Ok(b), Some(end)) = (r, end) {
if b.0 == end.into() {
return false;
}
}
true
})
.take(records_to_fetch);
let mut blocks: Vec<(BlockHeight, fuel_types::Bytes32)> = blocks.try_collect()?;
if direction == IterDirection::Forward {
blocks.reverse();
}
let blocks: Vec<Cow<FuelBlockDb>> = blocks
.iter()
.map(|(_, id)| {
Storage::<fuel_types::Bytes32, FuelBlockDb>::get(&db, id)
.transpose()
.ok_or(KvStoreError::NotFound)?
})
.try_collect()?;
let mut connection =
Connection::new(started.is_some(), records_to_fetch <= blocks.len());
connection.append(
blocks
.into_iter()
.map(|item| Edge::new(item.headers.height.to_usize(), item.into_owned())),
);
Ok(connection)
},
)
.await
.map(|conn| conn.map_node(Block))
}
}