Skip to content
Snippets Groups Projects
Commit 3c63a312 authored by Pascal Engélibert's avatar Pascal Engélibert :bicyclist: Committed by Éloïs
Browse files

[feat] gva: blocks

parent 94c24755
Branches
No related tags found
No related merge requests found
...@@ -15,6 +15,26 @@ ...@@ -15,6 +15,26 @@
use crate::*; use crate::*;
#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
pub struct BlockCursor {
pub number: BlockNumber,
}
impl std::fmt::Display for BlockCursor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.number)
}
}
impl FromStr for BlockCursor {
type Err = WrongCursor;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self {
number: s.parse().map_err(|_| WrongCursor)?,
})
}
}
impl DbsReader { impl DbsReader {
pub fn block( pub fn block(
&self, &self,
...@@ -23,6 +43,108 @@ impl DbsReader { ...@@ -23,6 +43,108 @@ impl DbsReader {
) -> KvResult<Option<duniter_dbs::BlockMetaV2>> { ) -> KvResult<Option<duniter_dbs::BlockMetaV2>> {
bc_db.blocks_meta().get(&number) bc_db.blocks_meta().get(&number)
} }
pub fn blocks(
&self,
bc_db: &BcV2DbRo<FileBackend>,
page_info: PageInfo<BlockCursor>,
) -> KvResult<PagedData<Vec<(BlockCursor, duniter_dbs::BlockMetaV2)>>> {
let last_block_number = bc_db
.blocks_meta()
.iter_rev(.., |it| it.values().next_res())?
.ok_or_else(|| KvError::Custom("Empty blockchain".into()))?
.number;
let first_cursor_opt = if page_info.not_all() {
Some(BlockCursor {
number: BlockNumber(0),
})
} else {
None
};
let last_cursor_opt = if page_info.not_all() {
Some(BlockCursor {
number: BlockNumber(last_block_number),
})
} else {
None
};
let k_min = U32BE(if page_info.order {
page_info.pos.map_or_else(|| 0, |pos| pos.number.0)
} else {
page_info.limit_opt.map_or_else(
|| 0,
|limit| {
page_info
.pos
.map_or_else(last_block_number + 1, |pos| pos.number.0)
.saturating_sub(limit.get() as u32 - 1)
},
)
});
let k_max = U32BE(if page_info.order {
page_info.limit_opt.map_or_else(
|| last_block_number + 1,
|limit| {
page_info.pos.map_or_else(
|| limit.get() as u32,
|pos| pos.number.0.saturating_add(limit.get() as u32),
)
},
)
} else {
page_info.pos.map_or_else(
|| last_block_number + 1,
|pos| pos.number.0.saturating_add(1),
)
});
let blocks: Vec<(BlockCursor, duniter_dbs::BlockMetaV2)> = if page_info.order {
bc_db.blocks_meta().iter(k_min..k_max, blocks_inner)?
} else {
bc_db.blocks_meta().iter_rev(k_min..k_max, blocks_inner)?
};
Ok(PagedData {
has_next_page: has_next_page(
blocks
.iter()
.map(|(block_cursor, _block)| block_cursor.into()),
last_cursor_opt,
page_info,
page_info.order,
),
has_previous_page: has_previous_page(
blocks
.iter()
.map(|(block_cursor, _block)| block_cursor.into()),
first_cursor_opt,
page_info,
page_info.order,
),
data: blocks,
})
}
}
fn blocks_inner<I>(blocks_iter: I) -> KvResult<Vec<(BlockCursor, duniter_dbs::BlockMetaV2)>>
where
I: Iterator<Item = KvResult<(U32BE, BlockMetaV2)>>,
{
blocks_iter
.map(|block_res| {
block_res.map(|block| {
(
BlockCursor {
number: BlockNumber(block.0 .0),
},
block.1,
)
})
})
.collect()
} }
#[cfg(test)] #[cfg(test)]
...@@ -30,6 +152,7 @@ mod tests { ...@@ -30,6 +152,7 @@ mod tests {
use super::*; use super::*;
use duniter_dbs::databases::bc_v2::BcV2DbWritable; use duniter_dbs::databases::bc_v2::BcV2DbWritable;
use duniter_gva_db::GvaV1DbWritable; use duniter_gva_db::GvaV1DbWritable;
use std::num::NonZeroUsize;
#[test] #[test]
fn test_block() -> KvResult<()> { fn test_block() -> KvResult<()> {
...@@ -49,4 +172,60 @@ mod tests { ...@@ -49,4 +172,60 @@ mod tests {
Ok(()) Ok(())
} }
#[test]
fn test_blocks() -> KvResult<()> {
let bc_db = duniter_dbs::databases::bc_v2::BcV2Db::<Mem>::open(MemConf::default())?;
let gva_db = duniter_gva_db::GvaV1Db::<Mem>::open(MemConf::default())?;
let bc_db_ro = bc_db.get_ro_handler();
let db_reader = create_dbs_reader(unsafe { std::mem::transmute(&gva_db.get_ro_handler()) });
for i in 0..20 {
bc_db.blocks_meta_write().upsert(
U32BE(i),
duniter_dbs::BlockMetaV2 {
number: i,
..Default::default()
},
)?;
}
let blocks = db_reader.blocks(
&bc_db_ro,
PageInfo {
pos: Some(BlockCursor {
number: BlockNumber(10),
}),
order: true,
limit_opt: NonZeroUsize::new(3),
},
)?;
assert_eq!(blocks.data.len(), 3);
assert_eq!(blocks.data[0].1.number, 10);
assert_eq!(blocks.data[1].1.number, 11);
assert_eq!(blocks.data[2].1.number, 12);
assert!(blocks.has_previous_page);
assert!(blocks.has_next_page);
let blocks = db_reader.blocks(
&bc_db_ro,
PageInfo {
pos: Some(BlockCursor {
number: BlockNumber(10),
}),
order: false,
limit_opt: NonZeroUsize::new(3),
},
)?;
assert_eq!(blocks.data.len(), 3);
assert_eq!(blocks.data[0].1.number, 10);
assert_eq!(blocks.data[1].1.number, 9);
assert_eq!(blocks.data[2].1.number, 8);
assert!(blocks.has_previous_page);
assert!(blocks.has_next_page);
Ok(())
}
} }
...@@ -101,6 +101,7 @@ mod tests { ...@@ -101,6 +101,7 @@ mod tests {
page_info: PageInfo<BlockNumber>, page_info: PageInfo<BlockNumber>,
) -> KvResult<PagedData<duniter_gva_dbs_reader::uds_of_pubkey::UdsWithSum>>; ) -> KvResult<PagedData<duniter_gva_dbs_reader::uds_of_pubkey::UdsWithSum>>;
fn block(&self, bc_db: &BcV2DbRo<FileBackend>, number: U32BE) -> KvResult<Option<BlockMetaV2>>; fn block(&self, bc_db: &BcV2DbRo<FileBackend>, number: U32BE) -> KvResult<Option<BlockMetaV2>>;
fn blocks(&self, bc_db: &BcV2DbRo<FileBackend>, page_info: PageInfo<duniter_gva_dbs_reader::block::BlockCursor>) -> KvResult<PagedData<Vec<(duniter_gva_dbs_reader::block::BlockCursor, BlockMetaV2)>>>;
fn find_inputs<BcDb: 'static + BcV2DbReadable, TxsMpDb: 'static + TxsMpV2DbReadable>( fn find_inputs<BcDb: 'static + BcV2DbReadable, TxsMpDb: 'static + TxsMpV2DbReadable>(
&self, &self,
bc_db: &BcDb, bc_db: &BcDb,
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::*; use crate::*;
use duniter_gva_dbs_reader::PagedData;
#[derive(Default)] #[derive(Default)]
pub(crate) struct BlockQuery; pub(crate) struct BlockQuery;
...@@ -35,27 +36,103 @@ impl BlockQuery { ...@@ -35,27 +36,103 @@ impl BlockQuery {
Ok(block.map(|block| BlockMeta::from(&block))) Ok(block.map(|block| BlockMeta::from(&block)))
} }
/// Get blocks
async fn blocks(
&self,
ctx: &async_graphql::Context<'_>,
#[graphql(desc = "pagination", default)] pagination: Pagination,
) -> async_graphql::Result<Connection<String, BlockMeta, EmptyFields, EmptyFields>> {
let QueryContext { is_whitelisted } = ctx.data::<QueryContext>()?;
let page_info = Pagination::convert_to_page_info(pagination, *is_whitelisted)?;
let data = ctx.data::<GvaSchemaData>()?;
let dbs_reader = data.dbs_reader();
let PagedData {
data: blocks,
has_next_page,
has_previous_page,
} = data
.dbs_pool
.execute(move |dbs| dbs_reader.blocks(&dbs.bc_db_ro, page_info))
.await??;
let mut conn = Connection::new(has_previous_page, has_next_page);
conn.append(blocks.into_iter().map(|(block_cursor, block)| {
Edge::new(block_cursor.to_string(), BlockMeta::from(block))
}));
Ok(conn)
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::BlockNumber;
use crate::tests::*; use crate::tests::*;
use duniter_dbs::BlockMetaV2;
use duniter_gva_dbs_reader::{block::BlockCursor, PagedData};
#[tokio::test] #[tokio::test]
async fn test_block() -> anyhow::Result<()> { async fn test_block_by_number() -> anyhow::Result<()> {
let mut dbs_reader = MockDbsReader::new(); let mut dbs_reader = MockDbsReader::new();
dbs_reader dbs_reader
.expect_block() .expect_block()
.withf(|_, s| s.0 == 0) .withf(|_, s| s.0 == 0)
.times(1) .times(1)
.returning(|_, _| Ok(Some(duniter_dbs::BlockMetaV2::default()))); .returning(|_, _| Ok(Some(BlockMetaV2::default())));
let schema = create_schema(dbs_reader)?; let schema = create_schema(dbs_reader)?;
assert_eq!( assert_eq!(
exec_graphql_request(&schema, r#"{ blockByNumber(number: 0) {number} }"#).await?, exec_graphql_request(&schema, r#"{ blockByNumber(number: 0) {number} }"#).await?,
serde_json::json!({ serde_json::json!({
"data": { "data": {
"blockByNumber": { "blockByNumber": {
"number": duniter_dbs::BlockMetaV2::default().number, "number": BlockMetaV2::default().number,
}
}
})
);
Ok(())
}
#[tokio::test]
async fn test_blocks() -> anyhow::Result<()> {
let mut dbs_reader = MockDbsReader::new();
dbs_reader.expect_blocks().times(1).returning(|_, _| {
Ok(PagedData {
data: vec![(
BlockCursor {
number: BlockNumber(0),
},
BlockMetaV2::default(),
)],
has_next_page: false,
has_previous_page: false,
})
});
let schema = create_schema(dbs_reader)?;
assert_eq!(
exec_graphql_request(
&schema,
r#"{ blocks{pageInfo{startCursor,endCursor},edges{node{number}}} }"#
)
.await?,
serde_json::json!({
"data": {
"blocks": {
"edges": [
{
"node": {
"number": BlockMetaV2::default().number,
}
}
],
"pageInfo": {
"endCursor": BlockMetaV2::default().number.to_string(),
"startCursor": BlockMetaV2::default().number.to_string(),
}
} }
} }
}) })
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment