Commit 6f7a0ae9 authored by Pascal Engélibert's avatar Pascal Engélibert 🚴
Browse files

[feat] gva: blocks

parent 94c24755
Pipeline #11168 passed with stages
in 25 minutes and 57 seconds
......@@ -15,6 +15,26 @@
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 {
pub fn block(
&self,
......@@ -23,6 +43,108 @@ impl DbsReader {
) -> KvResult<Option<duniter_dbs::BlockMetaV2>> {
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(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)]
......@@ -30,6 +152,7 @@ mod tests {
use super::*;
use duniter_dbs::databases::bc_v2::BcV2DbWritable;
use duniter_gva_db::GvaV1DbWritable;
use std::num::NonZeroUsize;
#[test]
fn test_block() -> KvResult<()> {
......@@ -49,4 +172,60 @@ mod tests {
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 {
page_info: PageInfo<BlockNumber>,
) -> KvResult<PagedData<duniter_gva_dbs_reader::uds_of_pubkey::UdsWithSum>>;
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>(
&self,
bc_db: &BcDb,
......
......@@ -14,6 +14,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::*;
use duniter_gva_dbs_reader::PagedData;
#[derive(Default)]
pub(crate) struct BlockQuery;
......@@ -35,27 +36,103 @@ impl BlockQuery {
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)]
mod tests {
use super::BlockNumber;
use crate::tests::*;
use duniter_dbs::BlockMetaV2;
use duniter_gva_dbs_reader::{block::BlockCursor, PagedData};
#[tokio::test]
async fn test_block() -> anyhow::Result<()> {
async fn test_block_by_number() -> anyhow::Result<()> {
let mut dbs_reader = MockDbsReader::new();
dbs_reader
.expect_block()
.withf(|_, s| s.0 == 0)
.times(1)
.returning(|_, _| Ok(Some(duniter_dbs::BlockMetaV2::default())));
.returning(|_, _| Ok(Some(BlockMetaV2::default())));
let schema = create_schema(dbs_reader)?;
assert_eq!(
exec_graphql_request(&schema, r#"{ blockByNumber(number: 0) {number} }"#).await?,
serde_json::json!({
"data": {
"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(),
}
}
}
})
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment