From 10d6985d76d30fcd3695f8bbaf50c596c096ce76 Mon Sep 17 00:00:00 2001
From: librelois <elois@ifee.fr>
Date: Tue, 19 Nov 2019 00:52:59 +0100
Subject: [PATCH] [feat] gva: blocks: add inputs interval and step & add
 negative paging

---
 lib/modules/gva/resources/schema.gql          |  11 +-
 lib/modules/gva/src/db.rs                     |  18 +-
 lib/modules/gva/src/schema.rs                 |  15 +-
 lib/modules/gva/src/schema/entities/block.rs  |   4 +-
 lib/modules/gva/src/schema/inputs.rs          |  19 ++
 .../gva/src/schema/inputs/block_interval.rs   | 129 +++++++++
 lib/modules/gva/src/schema/inputs/paging.rs   | 242 ++++++++++++++++
 lib/modules/gva/src/schema/paging.rs          | 195 -------------
 lib/modules/gva/src/schema/queries/block.rs   |   2 +-
 lib/modules/gva/src/schema/queries/blocks.rs  | 268 +++++++++++++++---
 lib/modules/gva/src/schema/queries/current.rs |   2 +-
 11 files changed, 651 insertions(+), 254 deletions(-)
 create mode 100644 lib/modules/gva/src/schema/inputs.rs
 create mode 100644 lib/modules/gva/src/schema/inputs/block_interval.rs
 create mode 100644 lib/modules/gva/src/schema/inputs/paging.rs
 delete mode 100644 lib/modules/gva/src/schema/paging.rs

diff --git a/lib/modules/gva/resources/schema.gql b/lib/modules/gva/resources/schema.gql
index 163060c7..ed35e883 100644
--- a/lib/modules/gva/resources/schema.gql
+++ b/lib/modules/gva/resources/schema.gql
@@ -8,7 +8,7 @@ type Query {
   node: Node! @juniper(ownership: "owned")
   current: Block @juniper(ownership: "owned")
   block(number: Int!): Block @juniper(ownership: "owned")
-  blocks(paging: Paging): [Block!]! @juniper(ownership: "owned")
+  blocks(interval: BlockInterval, paging: Paging, step: Int = 1): [Block!]! @juniper(ownership: "owned")
 }
 
 type Mutation {
@@ -19,12 +19,15 @@ type Mutation {
 # Inputs
 #################################
 
+input BlockInterval {
+  from: Int # default value: 0
+  # If toBlock is null, current block number is used
+  to: Int
+}
+
 input Paging {
   pageNumber: Int # default value: 0
 	pageSize: Int # default value: 50
-  fromBlock: Int # default value: 0
-  # If toBlock is null, current block number is used
-  toBlock: Int
 }
 
 #################################
diff --git a/lib/modules/gva/src/db.rs b/lib/modules/gva/src/db.rs
index 9fcfac64..efbc0f05 100644
--- a/lib/modules/gva/src/db.rs
+++ b/lib/modules/gva/src/db.rs
@@ -20,7 +20,6 @@ pub use durs_bc_db_reader::DbError;
 use dubp_common_doc::{BlockNumber, Blockstamp};
 use durs_bc_db_reader::blocks::DbBlock;
 use durs_bc_db_reader::{BcDbRo, DbReadable};
-use std::ops::Range;
 
 #[cfg(test)]
 use mockall::predicate::*;
@@ -35,8 +34,10 @@ pub(crate) trait BcDbTrait {
         &self,
         block_number: BlockNumber,
     ) -> Result<Option<DbBlock>, DbError>;
-    fn get_db_blocks_in_local_blockchain(&self, range: Range<u32>)
-        -> Result<Vec<DbBlock>, DbError>;
+    fn get_db_blocks_in_local_blockchain(
+        &self,
+        numbers: Vec<BlockNumber>,
+    ) -> Result<Vec<DbBlock>, DbError>;
 }
 
 impl<'a> BcDbTrait for BcDbRo {
@@ -70,16 +71,13 @@ impl<'a> BcDbTrait for BcDbRo {
     }
     fn get_db_blocks_in_local_blockchain(
         &self,
-        range: Range<u32>,
+        numbers: Vec<BlockNumber>,
     ) -> Result<Vec<DbBlock>, DbError> {
         self.read(|r| {
-            range
+            numbers
+                .into_iter()
                 .filter_map(|n| {
-                    match durs_bc_db_reader::blocks::get_db_block_in_local_blockchain(
-                        self,
-                        r,
-                        BlockNumber(n),
-                    ) {
+                    match durs_bc_db_reader::blocks::get_db_block_in_local_blockchain(self, r, n) {
                         Ok(Some(db_block)) => Some(Ok(db_block)),
                         Ok(None) => None,
                         Err(e) => Some(Err(e)),
diff --git a/lib/modules/gva/src/schema.rs b/lib/modules/gva/src/schema.rs
index 18bc5550..a2a7c0cb 100644
--- a/lib/modules/gva/src/schema.rs
+++ b/lib/modules/gva/src/schema.rs
@@ -16,7 +16,7 @@
 // ! Module define GraphQl schema
 
 mod entities;
-mod paging;
+pub mod inputs;
 mod queries;
 
 use self::entities::block::Block;
@@ -62,9 +62,20 @@ impl QueryFields for Query {
         &self,
         executor: &Executor<'_, Context>,
         trail: &QueryTrail<'_, Block, Walked>,
+        block_interval_opt: Option<BlockInterval>,
         paging_opt: Option<Paging>,
+        mut step: i32,
     ) -> FieldResult<Vec<Block>> {
-        queries::blocks::execute(executor, trail, paging_opt)
+        if step <= 0 {
+            step = 1;
+        }
+        queries::blocks::execute(
+            executor,
+            trail,
+            paging_opt,
+            block_interval_opt,
+            step as usize,
+        )
     }
 }
 
diff --git a/lib/modules/gva/src/schema/entities/block.rs b/lib/modules/gva/src/schema/entities/block.rs
index fee7d248..13aeb7a8 100644
--- a/lib/modules/gva/src/schema/entities/block.rs
+++ b/lib/modules/gva/src/schema/entities/block.rs
@@ -63,8 +63,8 @@ impl super::super::BlockFields for Block {
     }
 }
 
-impl Block {
-    pub fn from_db_block(db_block: DbBlock) -> Block {
+impl From<DbBlock> for Block {
+    fn from(db_block: DbBlock) -> Block {
         Block {
             version: db_block.block.version() as i32,
             currency: db_block.block.currency().to_string(),
diff --git a/lib/modules/gva/src/schema/inputs.rs b/lib/modules/gva/src/schema/inputs.rs
new file mode 100644
index 00000000..4fb601be
--- /dev/null
+++ b/lib/modules/gva/src/schema/inputs.rs
@@ -0,0 +1,19 @@
+//  Copyright (C) 2017-2019  The AXIOM TEAM Association.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+// ! Schema inputs methods
+
+mod block_interval;
+pub mod paging;
diff --git a/lib/modules/gva/src/schema/inputs/block_interval.rs b/lib/modules/gva/src/schema/inputs/block_interval.rs
new file mode 100644
index 00000000..8b56aad8
--- /dev/null
+++ b/lib/modules/gva/src/schema/inputs/block_interval.rs
@@ -0,0 +1,129 @@
+//  Copyright (C) 2017-2019  The AXIOM TEAM Association.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+// ! BlockInterval input methods
+
+use super::super::BlockInterval;
+use crate::db::BcDbTrait;
+use durs_bc_db_reader::DbError;
+use std::ops::RangeInclusive;
+
+const DEFAULT_START: usize = 0;
+const END_WHEN_EMPTY_BLOCKCHAIN: usize = 0;
+
+impl BlockInterval {
+    fn get_default_end<DB: BcDbTrait>(db: &DB) -> Result<usize, DbError> {
+        if let Some(current_blockstamp) = db.get_current_blockstamp()? {
+            Ok(current_blockstamp.id.0 as usize)
+        } else {
+            Ok(END_WHEN_EMPTY_BLOCKCHAIN)
+        }
+    }
+    pub(crate) fn get_range<DB: BcDbTrait>(
+        db: &DB,
+        block_interval_opt: Option<BlockInterval>,
+    ) -> Result<RangeInclusive<usize>, DbError> {
+        if let Some(block_interval) = block_interval_opt {
+            let start = if let Some(from) = block_interval.from {
+                if from.is_negative() {
+                    0
+                } else {
+                    from as usize
+                }
+            } else {
+                DEFAULT_START
+            };
+            let mut end = if let Some(to) = block_interval.to {
+                if to.is_negative() {
+                    0
+                } else {
+                    to as usize
+                }
+            } else {
+                Self::get_default_end(db)?
+            };
+            if start > end {
+                end = start;
+            }
+            Ok(start..=end)
+        } else {
+            Ok(DEFAULT_START..=Self::get_default_end(db)?)
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::*;
+    use crate::db::MockBcDbTrait;
+    use dubp_common_doc::{BlockHash, BlockNumber, Blockstamp};
+
+    #[test]
+    fn test_block_interval_get_range_with_short_bc() -> Result<(), DbError> {
+        let mut mock_db = MockBcDbTrait::new();
+        mock_db
+            .expect_get_current_blockstamp()
+            .times(1)
+            .returning(|| {
+                Ok(Some(Blockstamp {
+                    id: BlockNumber(42),
+                    hash: BlockHash(dup_crypto::hashs::Hash::default()),
+                }))
+            });
+        assert_eq! {
+            0..=42,
+            BlockInterval::get_range(&mock_db, None)?
+        }
+        Ok(())
+    }
+
+    #[test]
+    fn test_block_interval_get_range_with_long_bc() -> Result<(), DbError> {
+        let mut mock_db = MockBcDbTrait::new();
+        mock_db
+            .expect_get_current_blockstamp()
+            .times(2)
+            .returning(|| {
+                Ok(Some(Blockstamp {
+                    id: BlockNumber(750),
+                    hash: BlockHash(dup_crypto::hashs::Hash::default()),
+                }))
+            });
+
+        assert_eq! {
+            0..=750,
+            BlockInterval::get_range(&mock_db, None)?
+        }
+
+        assert_eq! {
+            500..=750,
+            BlockInterval::get_range(&mock_db, Some(BlockInterval {
+                from: Some(500),
+                to: None,
+            }))?
+        }
+
+        assert_eq! {
+            500..=700,
+            BlockInterval::get_range(&mock_db, Some(BlockInterval {
+                from: Some(500),
+                to: Some(700),
+            }))?
+        }
+
+        Ok(())
+    }
+}
diff --git a/lib/modules/gva/src/schema/inputs/paging.rs b/lib/modules/gva/src/schema/inputs/paging.rs
new file mode 100644
index 00000000..7642c1a3
--- /dev/null
+++ b/lib/modules/gva/src/schema/inputs/paging.rs
@@ -0,0 +1,242 @@
+//  Copyright (C) 2017-2019  The AXIOM TEAM Association.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+// ! Paging input methods
+
+use super::super::Paging;
+use std::ops::Range;
+
+const DEFAULT_PAGE_NUMBER_I32: i32 = 0;
+const DEFAULT_PAGE_NUMBER: isize = 0;
+const DEFAULT_PAGE_SIZE: usize = 50;
+
+const MIN_PAGE_SIZE: i32 = 1;
+const MAX_PAGE_SIZE: i32 = 500;
+
+#[derive(Debug, PartialEq)]
+pub struct FilledPaging {
+    pub page_number: isize,
+    pub page_size: usize,
+}
+
+impl Default for FilledPaging {
+    fn default() -> Self {
+        FilledPaging {
+            page_number: DEFAULT_PAGE_NUMBER,
+            page_size: DEFAULT_PAGE_SIZE,
+        }
+    }
+}
+
+impl From<Option<Paging>> for FilledPaging {
+    fn from(paging_opt: Option<Paging>) -> Self {
+        if let Some(paging) = paging_opt {
+            FilledPaging {
+                page_number: paging.page_number.unwrap_or(DEFAULT_PAGE_NUMBER_I32) as isize,
+                page_size: if let Some(page_size) = paging.page_size {
+                    if page_size < MIN_PAGE_SIZE {
+                        MIN_PAGE_SIZE as usize
+                    } else if page_size > MAX_PAGE_SIZE {
+                        MAX_PAGE_SIZE as usize
+                    } else {
+                        page_size as usize
+                    }
+                } else {
+                    DEFAULT_PAGE_SIZE
+                },
+            }
+        } else {
+            FilledPaging::default()
+        }
+    }
+}
+
+impl FilledPaging {
+    pub(crate) fn get_page_range(&self, count_elems: usize, step: usize) -> Range<usize> {
+        let page_extended_size = self.page_size * step;
+        let mut count_pages = count_elems / page_extended_size;
+        if count_elems % page_extended_size > 0 {
+            count_pages += 1;
+        }
+        let page_number = if self.page_number.is_negative() {
+            std::cmp::max(0, count_pages as isize - self.page_number.abs()) as usize
+        } else {
+            self.page_number as usize
+        };
+
+        Range {
+            start: std::cmp::min(count_elems, page_number * page_extended_size),
+            end: std::cmp::min(count_elems, (page_number + 1) * page_extended_size),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::*;
+
+    #[test]
+    fn test_default() {
+        assert_eq!(
+            FilledPaging {
+                page_number: DEFAULT_PAGE_NUMBER,
+                page_size: DEFAULT_PAGE_SIZE,
+            },
+            FilledPaging::default(),
+        )
+    }
+
+    #[test]
+    fn test_from_none_paging() {
+        assert_eq!(
+            FilledPaging {
+                page_number: DEFAULT_PAGE_NUMBER,
+                page_size: DEFAULT_PAGE_SIZE,
+            },
+            FilledPaging::from(None),
+        )
+    }
+
+    #[test]
+    fn test_from_some_paging() {
+        assert_eq!(
+            FilledPaging {
+                page_number: 0,
+                page_size: 10,
+            },
+            FilledPaging::from(Some(Paging {
+                page_number: None,
+                page_size: Some(10)
+            })),
+        );
+        assert_eq!(
+            FilledPaging {
+                page_number: 1,
+                page_size: 50,
+            },
+            FilledPaging::from(Some(Paging {
+                page_number: Some(1),
+                page_size: None
+            })),
+        );
+        assert_eq!(
+            FilledPaging {
+                page_number: 1,
+                page_size: 10,
+            },
+            FilledPaging::from(Some(Paging {
+                page_number: Some(1),
+                page_size: Some(10)
+            })),
+        )
+    }
+
+    #[test]
+    fn test_get_page_range() {
+        assert_eq!(
+            Range { start: 10, end: 20 },
+            FilledPaging {
+                page_number: 1,
+                page_size: 10,
+            }
+            .get_page_range(5_000, 1),
+        );
+        assert_eq!(
+            Range {
+                start: 4_980,
+                end: 4_990
+            },
+            FilledPaging {
+                page_number: -2,
+                page_size: 10,
+            }
+            .get_page_range(5_000, 1),
+        );
+        assert_eq!(
+            Range { start: 10, end: 15 },
+            FilledPaging {
+                page_number: 1,
+                page_size: 10,
+            }
+            .get_page_range(15, 1),
+        );
+        assert_eq!(
+            Range { start: 15, end: 15 },
+            FilledPaging {
+                page_number: 2,
+                page_size: 10,
+            }
+            .get_page_range(15, 1),
+        );
+        assert_eq!(
+            Range { start: 20, end: 40 },
+            FilledPaging {
+                page_number: 1,
+                page_size: 10,
+            }
+            .get_page_range(5_000, 2),
+        );
+        assert_eq!(
+            Range {
+                start: 4_980,
+                end: 5_000
+            },
+            FilledPaging {
+                page_number: -1,
+                page_size: 10,
+            }
+            .get_page_range(5_000, 2),
+        );
+        assert_eq!(
+            Range { start: 0, end: 400 },
+            FilledPaging {
+                page_number: -1,
+                page_size: 500,
+            }
+            .get_page_range(400, 2),
+        );
+        assert_eq!(
+            Range {
+                start: 0,
+                end: 1_000
+            },
+            FilledPaging {
+                page_number: -3,
+                page_size: 400,
+            }
+            .get_page_range(1_000, 5),
+        );
+        assert_eq!(
+            Range {
+                start: 2_000,
+                end: 3_000
+            },
+            FilledPaging {
+                page_number: -1,
+                page_size: 400,
+            }
+            .get_page_range(3_000, 5),
+        );
+        assert_eq!(
+            Range { start: 40, end: 80 },
+            FilledPaging {
+                page_number: -2,
+                page_size: 40,
+            }
+            .get_page_range(100, 1),
+        );
+    }
+}
diff --git a/lib/modules/gva/src/schema/paging.rs b/lib/modules/gva/src/schema/paging.rs
deleted file mode 100644
index 4b85efa8..00000000
--- a/lib/modules/gva/src/schema/paging.rs
+++ /dev/null
@@ -1,195 +0,0 @@
-//  Copyright (C) 2017-2019  The AXIOM TEAM Association.
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as
-// published by the Free Software Foundation, either version 3 of the
-// License, or (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program.  If not, see <https://www.gnu.org/licenses/>.
-
-// ! Schema paging input
-
-use super::Paging;
-use crate::db::BcDbTrait;
-use durs_bc_db_reader::DbError;
-use std::ops::Range;
-
-const DEFAULT_PAGE_NUMBER: i32 = 0;
-const DEFAULT_PAGE_SIZE: i32 = 50;
-const DEFAULT_FROM_BLOCK: i32 = 0;
-
-const MAX_PAGE_SIZE: i32 = 500;
-
-/// Paging with all values filled in
-pub struct FilledPaging {
-    page_number: usize,
-    page_size: usize,
-    from_block: u32,
-    to_block: u32,
-}
-
-#[inline]
-fn i32_opt_to_positive_i32(int_opt: Option<i32>, default: i32) -> i32 {
-    if let Some(int) = int_opt {
-        if int < 0 {
-            0
-        } else {
-            int
-        }
-    } else {
-        default
-    }
-}
-
-impl FilledPaging {
-    pub(crate) fn new<DB: BcDbTrait>(db: &DB, paging_opt: Option<Paging>) -> Result<Self, DbError> {
-        if let Some(paging) = paging_opt {
-            Ok(FilledPaging {
-                page_number: i32_opt_to_positive_i32(paging.page_number, DEFAULT_PAGE_NUMBER)
-                    as usize,
-                page_size: std::cmp::min(
-                    MAX_PAGE_SIZE,
-                    i32_opt_to_positive_i32(paging.page_size, DEFAULT_PAGE_SIZE),
-                ) as usize,
-                from_block: i32_opt_to_positive_i32(paging.from_block, DEFAULT_FROM_BLOCK) as u32,
-                to_block: if let Some(to_block) = paging.to_block {
-                    if to_block < 0 {
-                        0
-                    } else {
-                        to_block as u32
-                    }
-                } else {
-                    Self::get_default_to_block(db)?
-                },
-            })
-        } else {
-            Ok(FilledPaging {
-                page_number: DEFAULT_PAGE_NUMBER as usize,
-                page_size: DEFAULT_PAGE_SIZE as usize,
-                from_block: DEFAULT_FROM_BLOCK as u32,
-                to_block: Self::get_default_to_block(db)?,
-            })
-        }
-    }
-    fn get_default_to_block<DB: BcDbTrait>(db: &DB) -> Result<u32, DbError> {
-        if let Some(current_blockstamp) = db.get_current_blockstamp()? {
-            Ok(current_blockstamp.id.0)
-        } else {
-            Ok(0)
-        }
-    }
-    pub fn get_range(&self) -> Range<u32> {
-        Range {
-            start: self.from_block + (self.page_number * self.page_size) as u32,
-            end: std::cmp::min(
-                self.to_block + 1,
-                self.from_block + ((self.page_number + 1) * self.page_size) as u32,
-            ),
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-
-    use super::*;
-    use crate::db::MockBcDbTrait;
-    use dubp_common_doc::{BlockHash, BlockNumber, Blockstamp};
-
-    #[test]
-    fn test_i32_opt_to_positive_i32() {
-        assert_eq!(3, i32_opt_to_positive_i32(Some(3), 1));
-        assert_eq!(0, i32_opt_to_positive_i32(Some(-2), 1));
-        assert_eq!(50, i32_opt_to_positive_i32(None, 50));
-        assert_eq!(0, i32_opt_to_positive_i32(Some(0), 1));
-    }
-
-    #[test]
-    fn test_filled_paging_range_with_short_bc() -> Result<(), DbError> {
-        let mut mock_db = MockBcDbTrait::new();
-        mock_db
-            .expect_get_current_blockstamp()
-            .times(1)
-            .returning(|| {
-                Ok(Some(Blockstamp {
-                    id: BlockNumber(42),
-                    hash: BlockHash(dup_crypto::hashs::Hash::default()),
-                }))
-            });
-
-        let filled_paging = FilledPaging::new(&mock_db, None)?;
-        assert_eq! {
-            Range {
-                start: 0,
-                end: 43,
-            },
-            filled_paging.get_range()
-        }
-        Ok(())
-    }
-
-    #[test]
-    fn test_filled_paging_range() -> Result<(), DbError> {
-        let mut mock_db = MockBcDbTrait::new();
-        mock_db
-            .expect_get_current_blockstamp()
-            .times(3)
-            .returning(|| {
-                Ok(Some(Blockstamp {
-                    id: BlockNumber(750),
-                    hash: BlockHash(dup_crypto::hashs::Hash::default()),
-                }))
-            });
-
-        let filled_paging = FilledPaging::new(&mock_db, None)?;
-        assert_eq! {
-            Range {
-                start: 0,
-                end: 50,
-            },
-            filled_paging.get_range()
-        }
-
-        let filled_paging = FilledPaging::new(
-            &mock_db,
-            Some(Paging {
-                page_number: Some(1),
-                page_size: Some(100),
-                from_block: Some(500),
-                to_block: None,
-            }),
-        )?;
-        assert_eq! {
-            Range {
-                start: 600,
-                end: 700,
-            },
-            filled_paging.get_range()
-        }
-
-        let filled_paging = FilledPaging::new(
-            &mock_db,
-            Some(Paging {
-                page_number: Some(2),
-                page_size: Some(100),
-                from_block: Some(500),
-                to_block: None,
-            }),
-        )?;
-        assert_eq! {
-            Range {
-                start: 700,
-                end: 751,
-            },
-            filled_paging.get_range()
-        }
-
-        Ok(())
-    }
-}
diff --git a/lib/modules/gva/src/schema/queries/block.rs b/lib/modules/gva/src/schema/queries/block.rs
index 4ad02dd8..24ce1018 100644
--- a/lib/modules/gva/src/schema/queries/block.rs
+++ b/lib/modules/gva/src/schema/queries/block.rs
@@ -40,7 +40,7 @@ pub(crate) fn execute(
         .get_db()
         .get_db_block_in_local_blockchain(block_number)
         .map_err(db_err_to_juniper_err)
-        .map(|db_block_opt| db_block_opt.map(Block::from_db_block))
+        .map(|db_block_opt| db_block_opt.map(Into::into))
 }
 
 #[cfg(test)]
diff --git a/lib/modules/gva/src/schema/queries/blocks.rs b/lib/modules/gva/src/schema/queries/blocks.rs
index fa88927b..51199636 100644
--- a/lib/modules/gva/src/schema/queries/blocks.rs
+++ b/lib/modules/gva/src/schema/queries/blocks.rs
@@ -19,8 +19,10 @@ use super::db_err_to_juniper_err;
 use crate::context::Context;
 use crate::db::BcDbTrait;
 use crate::schema::entities::block::Block;
-use crate::schema::paging;
+use crate::schema::inputs::paging::FilledPaging;
+use crate::schema::BlockInterval;
 use crate::schema::Paging;
+use dubp_common_doc::BlockNumber;
 use durs_bc_db_reader::blocks::DbBlock;
 use juniper::Executor;
 use juniper::FieldResult;
@@ -30,37 +32,47 @@ pub(crate) fn execute(
     executor: &Executor<'_, Context>,
     _trail: &QueryTrail<'_, Block, Walked>,
     paging_opt: Option<Paging>,
+    block_interval_opt: Option<BlockInterval>,
+    step: usize,
 ) -> FieldResult<Vec<Block>> {
     let db = executor.context().get_db();
 
+    // Get interval
+    let interval =
+        BlockInterval::get_range(db, block_interval_opt).map_err(db_err_to_juniper_err)?;
+
+    // Get blocks numbers that respect filters
+    let blocks_numbers: Vec<BlockNumber> =
+        interval.clone().map(|i| BlockNumber(i as u32)).collect(); // TODO
+
+    // Apply interval
+    let blocks_numbers: Vec<BlockNumber> = blocks_numbers
+        .into_iter()
+        .filter(|n| interval.contains(&(n.0 as usize)))
+        .collect();
+    let count = blocks_numbers.len();
+
+    // Apply paging and step
+    let paging = FilledPaging::from(paging_opt);
+    let page_range = paging.get_page_range(count, step);
+    let blocks_numbers: Vec<BlockNumber> = page_range
+        .step_by(step)
+        .map(|i| blocks_numbers[i])
+        .collect();
+
+    // Get blocks
     let blocks: Vec<DbBlock> = db
-        .get_db_blocks_in_local_blockchain(
-            paging::FilledPaging::new(db, paging_opt)
-                .map_err(db_err_to_juniper_err)?
-                .get_range(),
-        )
+        .get_db_blocks_in_local_blockchain(blocks_numbers)
         .map_err(db_err_to_juniper_err)?;
 
-    Ok(blocks.into_iter().map(Block::from_db_block).collect())
-
-    /*let db: &BcDbRo = &executor.context().get_db();
-    db.read(|r| {
-        paging::FilledPaging::new(db, paging_opt)?
-            .get_range()
-            .filter_map(|n| match block::get_block(db, r, BlockNumber(n)) {
-                Ok(Some(db_block)) => Some(Ok(db_block)),
-                Ok(None) => None,
-                Err(e) => Some(Err(e)),
-            })
-            .collect::<Result<Vec<Block>, DbError>>()
-    })
-    .map_err(db_err_to_juniper_err)*/
+    Ok(blocks.into_iter().map(Into::into).collect())
 }
 
 #[cfg(test)]
 mod tests {
     use crate::db::MockBcDbTrait;
     use crate::schema::queries::tests;
+    use dubp_block_doc::block::v10::BlockDocumentV10;
     use dubp_block_doc::block::BlockDocument;
     use dubp_blocks_tests_tools::mocks::gen_empty_timed_block_v10;
     use dubp_common_doc::traits::Document;
@@ -70,12 +82,8 @@ mod tests {
     use durs_bc_db_reader::blocks::DbBlock;
     use mockall::predicate::eq;
     use serde_json::json;
-    use std::ops::Range;
-
-    #[test]
-    fn test_graphql_blocks() {
-        let mut mock_db = MockBcDbTrait::new();
 
+    fn block_0() -> BlockDocumentV10 {
         let mut block_0 = gen_empty_timed_block_v10(
             Blockstamp {
                 id: BlockNumber(0),
@@ -85,6 +93,20 @@ mod tests {
             Hash::default(),
         );
         block_0.issuers = vec![pubkey('A')];
+        block_0
+    }
+    fn block_0_json() -> serde_json::Value {
+        json!({
+            "commonTime": 1_488_987_127.0,
+            "currency": "test_currency",
+            "hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
+            "issuer": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
+            "number": 0,
+            "version": 10
+        })
+    }
+
+    fn block_1() -> BlockDocumentV10 {
         let mut block_1 = gen_empty_timed_block_v10(
             Blockstamp {
                 id: BlockNumber(1),
@@ -94,7 +116,21 @@ mod tests {
             Hash::default(),
         );
         block_1.issuers = vec![pubkey('B')];
-        let mut current_block = gen_empty_timed_block_v10(
+        block_1
+    }
+    fn block_1_json() -> serde_json::Value {
+        json!({
+            "commonTime": 1_488_987_128.0,
+            "currency": "test_currency",
+            "hash": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
+            "issuer": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
+            "number": 1,
+            "version": 10
+        })
+    }
+
+    fn block_2() -> BlockDocumentV10 {
+        let mut block_2 = gen_empty_timed_block_v10(
             Blockstamp {
                 id: BlockNumber(2),
                 hash: BlockHash(hash('C')),
@@ -102,7 +138,73 @@ mod tests {
             1_488_987_129,
             Hash::default(),
         );
-        current_block.issuers = vec![pubkey('C')];
+        block_2.issuers = vec![pubkey('C')];
+        block_2
+    }
+    fn block_2_json() -> serde_json::Value {
+        json!({
+            "commonTime": 1_488_987_129.0,
+            "currency": "test_currency",
+            "hash": "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC",
+            "issuer": "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC",
+            "number": 2,
+            "version": 10
+        })
+    }
+
+    fn block_3() -> BlockDocumentV10 {
+        let mut block_3 = gen_empty_timed_block_v10(
+            Blockstamp {
+                id: BlockNumber(3),
+                hash: BlockHash(hash('D')),
+            },
+            1_488_987_130,
+            Hash::default(),
+        );
+        block_3.issuers = vec![pubkey('D')];
+        block_3
+    }
+    fn block_3_json() -> serde_json::Value {
+        json!({
+            "commonTime": 1_488_987_130.0,
+            "currency": "test_currency",
+            "hash": "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
+            "issuer": "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD",
+            "number": 3,
+            "version": 10
+        })
+    }
+
+    fn block_4() -> BlockDocumentV10 {
+        let mut block_4 = gen_empty_timed_block_v10(
+            Blockstamp {
+                id: BlockNumber(4),
+                hash: BlockHash(hash('E')),
+            },
+            1_488_987_131,
+            Hash::default(),
+        );
+        block_4.issuers = vec![pubkey('E')];
+        block_4
+    }
+    fn block_4_json() -> serde_json::Value {
+        json!({
+            "commonTime": 1_488_987_131.0,
+            "currency": "test_currency",
+            "hash": "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE",
+            "issuer": "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE",
+            "number": 4,
+            "version": 10
+        })
+    }
+
+    #[test]
+    fn test_graphql_blocks_from_2() {
+        let mut mock_db = MockBcDbTrait::new();
+
+        let block_2 = block_2();
+        let block_3 = block_3();
+        let current_block = block_4();
 
         let current_blockstamp = current_block.blockstamp();
         mock_db
@@ -112,15 +214,15 @@ mod tests {
 
         mock_db
             .expect_get_db_blocks_in_local_blockchain()
-            .with(eq(Range { start: 0, end: 3 }))
+            .with(eq(vec![BlockNumber(2), BlockNumber(3), BlockNumber(4)]))
             .returning(move |_| {
                 Ok(vec![
                     DbBlock {
-                        block: BlockDocument::V10(block_0.clone()),
+                        block: BlockDocument::V10(block_2.clone()),
                         expire_certs: None,
                     },
                     DbBlock {
-                        block: BlockDocument::V10(block_1.clone()),
+                        block: BlockDocument::V10(block_3.clone()),
                         expire_certs: None,
                     },
                     DbBlock {
@@ -134,7 +236,53 @@ mod tests {
 
         tests::test_gql_query(
             schema,
-            "{ blocks { commonTime, currency, hash, issuer, number, version } }",
+            "{ blocks(interval: { from: 2 }) { commonTime, currency, hash, issuer, number, version } }",
+            json!({
+                "data": {
+                    "blocks": [
+                        block_2_json(),
+                        block_3_json(),
+                        block_4_json(),
+                    ]
+                }
+            }),
+        );
+    }
+
+    #[test]
+    fn test_graphql_blocks_with_step_2() {
+        let mut mock_db = MockBcDbTrait::new();
+
+        let block_0 = block_0();
+        let current_block = block_2();
+
+        let current_blockstamp = current_block.blockstamp();
+        mock_db
+            .expect_get_current_blockstamp()
+            .times(1)
+            .returning(move || Ok(Some(current_blockstamp)));
+
+        mock_db
+            .expect_get_db_blocks_in_local_blockchain()
+            .with(eq(vec![BlockNumber(0), BlockNumber(2)]))
+            .returning(move |_| {
+                Ok(vec![
+                    DbBlock {
+                        block: BlockDocument::V10(block_0.clone()),
+                        expire_certs: None,
+                    },
+                    DbBlock {
+                        block: BlockDocument::V10(current_block.clone()),
+                        expire_certs: None,
+                    },
+                ])
+            });
+
+        let schema = tests::setup(mock_db);
+
+        tests::test_gql_query(
+            schema,
+            "{ blocks(step: 2) { commonTime, currency, hash, issuer, number, version } }",
             json!({
                 "data": {
                     "blocks": [{
@@ -145,14 +293,6 @@ mod tests {
                         "number": 0,
                         "version": 10
                     },
-                    {
-                        "commonTime": 1_488_987_128.0,
-                        "currency": "test_currency",
-                        "hash": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
-                        "issuer": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
-                        "number": 1,
-                        "version": 10
-                    },
                     {
                         "commonTime": 1_488_987_129.0,
                         "currency": "test_currency",
@@ -165,4 +305,54 @@ mod tests {
             }),
         );
     }
+
+    #[test]
+    fn test_graphql_blocks() {
+        let mut mock_db = MockBcDbTrait::new();
+
+        let block_0 = block_0();
+        let block_1 = block_1();
+        let current_block = block_2();
+
+        let current_blockstamp = current_block.blockstamp();
+        mock_db
+            .expect_get_current_blockstamp()
+            .times(1)
+            .returning(move || Ok(Some(current_blockstamp)));
+
+        mock_db
+            .expect_get_db_blocks_in_local_blockchain()
+            .with(eq(vec![BlockNumber(0), BlockNumber(1), BlockNumber(2)]))
+            .returning(move |_| {
+                Ok(vec![
+                    DbBlock {
+                        block: BlockDocument::V10(block_0.clone()),
+                        expire_certs: None,
+                    },
+                    DbBlock {
+                        block: BlockDocument::V10(block_1.clone()),
+                        expire_certs: None,
+                    },
+                    DbBlock {
+                        block: BlockDocument::V10(current_block.clone()),
+                        expire_certs: None,
+                    },
+                ])
+            });
+
+        let schema = tests::setup(mock_db);
+
+        tests::test_gql_query(
+            schema,
+            "{ blocks { commonTime, currency, hash, issuer, number, version } }",
+            json!({
+                "data": {
+                    "blocks": [
+                    block_0_json(),
+                    block_1_json(),
+                    block_2_json()]
+                }
+            }),
+        );
+    }
 }
diff --git a/lib/modules/gva/src/schema/queries/current.rs b/lib/modules/gva/src/schema/queries/current.rs
index 771e750e..076e702b 100644
--- a/lib/modules/gva/src/schema/queries/current.rs
+++ b/lib/modules/gva/src/schema/queries/current.rs
@@ -32,7 +32,7 @@ pub(crate) fn execute(
         .get_db()
         .get_current_block()
         .map_err(db_err_to_juniper_err)
-        .map(|db_block_opt| db_block_opt.map(Block::from_db_block))
+        .map(|db_block_opt| db_block_opt.map(Into::into))
 }
 
 #[cfg(test)]
-- 
GitLab