diff --git a/Cargo.lock b/Cargo.lock
index 335797456d9a61fea4f92e07c37f298968ed9c84..85d67faab71989609734bd2faffc0535f2e25801 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -730,6 +730,17 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "duniter-bc-db"
+version = "0.1.0"
+dependencies = [
+ "dubp-common",
+ "duniter-dbs",
+ "log",
+ "resiter",
+ "unwrap",
+]
+
 [[package]]
 name = "duniter-dbex"
 version = "0.1.0"
@@ -791,6 +802,8 @@ dependencies = [
  "dubp-documents-parser",
  "dubp-wallet",
  "dubp-wot",
+ "duniter-bc-db",
+ "duniter-dbs",
  "flate2",
  "flexi_logger",
  "log",
@@ -1835,6 +1848,12 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "resiter"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd69ab1e90258b7769f0b5c46bfd802b8206d0707ced4ca4b9d5681b744de1be"
+
 [[package]]
 name = "ring"
 version = "0.16.15"
diff --git a/Cargo.toml b/Cargo.toml
index 90a051b9c4c06f1dd6f307707e18fe04c1974128..057d7afd9153bc7918f34b6ccc9b1fe0eb83a225 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -32,6 +32,7 @@ members = [
     "rust-bins/duniter-dbex",
     "rust-bins/xtask",
     "rust-libs/dubp-wot",
+    "rust-libs/duniter-bc-db",
     "rust-libs/duniter-dbs",
     "rust-libs/tools/kv_typed",
     "rust-libs/tools/kv_typed_code_gen"
diff --git a/app/lib/dal/fileDAL.ts b/app/lib/dal/fileDAL.ts
index 4207f81fc1748bc9f52d522409d503d036df2029..ba6c9ca3d0234b9d8c034509c51a2092db1f0755 100644
--- a/app/lib/dal/fileDAL.ts
+++ b/app/lib/dal/fileDAL.ts
@@ -233,12 +233,12 @@ export class FileDAL implements ServerDAO {
     return this.metaDAL.getVersion();
   }
 
-  writeFileOfBlock(block: DBBlock) {
-    return this.blockDAL.saveBlock(block);
+  writeFileOfBlock(block: DBBlock): void {
+    this.blockDAL.saveBlock(block);
   }
 
-  writeSideFileOfBlock(block: DBBlock) {
-    return this.blockDAL.saveSideBlock(block);
+  writeSideFileOfBlock(block: DBBlock): void {
+    this.blockDAL.saveSideBlock(block);
   }
 
   listAllPeers() {
@@ -1305,12 +1305,12 @@ export class FileDAL implements ServerDAO {
     return this.msDAL.savePendingMembership(ms);
   }
 
-  async saveBlockInFile(block: DBBlock) {
+  async saveBlockInFile(block: DBBlock): Promise<void> {
     await this.writeFileOfBlock(block);
   }
 
-  saveSideBlockInFile(block: DBBlock) {
-    return this.writeSideFileOfBlock(block);
+  saveSideBlockInFile(block: DBBlock): void {
+    this.writeSideFileOfBlock(block);
   }
 
   async saveTxsInFiles(
diff --git a/app/lib/dal/indexDAL/abstract/software/ForksDAO.ts b/app/lib/dal/indexDAL/abstract/software/ForksDAO.ts
index 27bb1ca2b3b4b29e874a622a2e7c01e2e1c9afed..70665f9e4372cc42a96d42d2388ba71cdde87bc3 100644
--- a/app/lib/dal/indexDAL/abstract/software/ForksDAO.ts
+++ b/app/lib/dal/indexDAL/abstract/software/ForksDAO.ts
@@ -14,7 +14,7 @@
 import { DBBlock } from "../../../../db/DBBlock";
 
 export interface ForksDAO {
-  saveSideBlock(block: DBBlock): Promise<DBBlock>;
+  saveSideBlock(block: DBBlock): Promise<void>;
 
   setSideBlock(number: number, previousBlock: DBBlock | null): Promise<void>;
 
diff --git a/app/lib/dal/indexDAL/leveldb/LevelDBBlockchain.ts b/app/lib/dal/indexDAL/leveldb/LevelDBBlockchain.ts
index 03a9dc5e6b4e53e20e4ebdb42326d32fccd0e7fa..5a52f331d0e0db5a4ffc13a5a9a8a9e6543f0510 100644
--- a/app/lib/dal/indexDAL/leveldb/LevelDBBlockchain.ts
+++ b/app/lib/dal/indexDAL/leveldb/LevelDBBlockchain.ts
@@ -275,11 +275,10 @@ export class LevelDBBlockchain extends LevelDBTable<DBBlock>
     return this.get(LevelDBBlockchain.trimKey(block.number));
   }
 
-  async saveSideBlock(block: DBBlock): Promise<DBBlock> {
+  async saveSideBlock(block: DBBlock): Promise<void> {
     const k = LevelDBBlockchain.trimForkKey(block.number, block.hash);
     block.fork = true;
     await this.forks.put(k, block);
-    return this.forks.get(k);
   }
 
   async setSideBlock(
diff --git a/neon/native/Cargo.toml b/neon/native/Cargo.toml
index 3f9b5be73008eb2c63001132a7fb958d51996854..b27f78066a1d8e1358abfaf3b1474d5d8a72375c 100644
--- a/neon/native/Cargo.toml
+++ b/neon/native/Cargo.toml
@@ -22,6 +22,8 @@ dubp-documents = { version = "0.25.2" }
 dubp-documents-parser = { version = "0.25.2" }
 dubp-wallet = { version = "0.25.2" }
 dubp-wot = { path = "../../rust-libs/dubp-wot" }
+duniter-bc-db = { path = "../../rust-libs/duniter-bc-db" }
+duniter-dbs = { path = "../../rust-libs/duniter-dbs"  }
 flate2 = "1.0.16"
 flexi_logger = { version = "=0.16.0", default-features = false, features = ["compress"] }
 log = "0.4.11"
diff --git a/neon/native/src/dbs.rs b/neon/native/src/dbs.rs
new file mode 100644
index 0000000000000000000000000000000000000000..f6097cf7456aa06c259e3f059f029e7ef5470dd5
--- /dev/null
+++ b/neon/native/src/dbs.rs
@@ -0,0 +1,160 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// 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/>.
+
+use crate::into_neon_res;
+use dubp_common::crypto::hashs::Hash;
+use dubp_common::prelude::*;
+use duniter_bc_db::DuniterBcDb;
+use duniter_dbs::BlockDbV1;
+use neon::declare_types;
+use neon::prelude::*;
+use std::path::PathBuf;
+
+pub struct RustDbs {
+    bc_db: DuniterBcDb,
+}
+
+declare_types! {
+    pub class JsDbs for RustDbs {
+        init(mut cx) {
+            if let Some(arg0) = cx.argument_opt(0) {
+                if arg0.is_a::<JsString>() {
+                    let home_path = arg0
+                        .downcast::<JsString>()
+                        .or_throw(&mut cx)?
+                        .value();
+                    let bc_db = into_neon_res(&mut cx, DuniterBcDb::open(Some(&PathBuf::from(home_path))))?;
+                    Ok(RustDbs { bc_db })
+                } else {
+                    cx.throw_type_error("arg0 must be a string")
+                }
+            } else {
+                let bc_db = into_neon_res(&mut cx, DuniterBcDb::open(None))?;
+                Ok(RustDbs { bc_db })
+            }
+        }
+        method saveBlock(mut cx) {
+            let block_js = cx.argument::<JsValue>(0)?;
+            let block: BlockDbV1 = neon_serde::from_value(&mut cx, block_js)?;
+
+            let this = cx.this();
+            let res = {
+                let guard = cx.lock();
+                let rust_db = this.borrow(&guard);
+                rust_db.bc_db.save_block(block)
+            };
+            match res {
+                Ok(()) => Ok(cx.undefined().upcast()),
+                Err(e) => cx.throw_error(format!("{}", e)),
+            }
+        }
+        method saveSideBlock(mut cx) {
+            let block_js = cx.argument::<JsValue>(0)?;
+            let block: BlockDbV1 = neon_serde::from_value(&mut cx, block_js)?;
+
+            let this = cx.this();
+            let res = {
+                let guard = cx.lock();
+                let rust_db = this.borrow(&guard);
+                rust_db.bc_db.save_side_block(block)
+            };
+            match res {
+                Ok(()) => Ok(cx.undefined().upcast()),
+                Err(e) => cx.throw_error(format!("{}", e)),
+            }
+        }
+        method dropOnForkBlocksAbove(mut cx) {
+            let block_number = BlockNumber(cx.argument::<JsNumber>(0)?.value() as u32);
+
+            let this = cx.this();
+            let res = {
+                let guard = cx.lock();
+                let rust_db = this.borrow(&guard);
+                rust_db.bc_db.drop_on_fork_blocks_above(block_number)
+            };
+            match res {
+                Ok(()) => Ok(cx.undefined().upcast()),
+                Err(e) => cx.throw_error(format!("{}", e)),
+            }
+        }
+        method getAbsoluteBlock(mut cx) {
+            let block_number = BlockNumber(cx.argument::<JsNumber>(0)?.value() as u32);
+            let block_hash = cx.argument::<JsString>(1)?.value();
+            let block_hash = BlockHash(into_neon_res(&mut cx, Hash::from_hex(&block_hash))?);
+
+            let this = cx.this();
+            let res = {
+                let guard = cx.lock();
+                let rust_db = this.borrow(&guard);
+                rust_db.bc_db.get_absolute_block(Blockstamp { number: block_number, hash: block_hash })
+            };
+            match res {
+                Ok(block_opt) => if let Some(block) = block_opt {
+                    Ok(neon_serde::to_value(&mut cx, &block)?)
+                } else {
+                    Ok(cx.null().upcast())
+                },
+                Err(e) => cx.throw_error(format!("{}", e)),
+            }
+        }
+        method getBlock(mut cx) {
+            let block_number = BlockNumber(cx.argument::<JsNumber>(0)?.value() as u32);
+
+            let this = cx.this();
+            let res = {
+                let guard = cx.lock();
+                let rust_db = this.borrow(&guard);
+                rust_db.bc_db.get_block(block_number)
+            };
+            match res {
+                Ok(block_opt) => if let Some(block) = block_opt {
+                    Ok(neon_serde::to_value(&mut cx, &block)?)
+                } else {
+                    Ok(cx.null().upcast())
+                },
+                Err(e) => cx.throw_error(format!("{}", e)),
+            }
+        }
+        method getBlocks(mut cx) {
+            let start = BlockNumber(cx.argument::<JsNumber>(0)?.value() as u32);
+            let end = BlockNumber(cx.argument::<JsNumber>(1)?.value() as u32);
+
+            let this = cx.this();
+            let res = {
+                let guard = cx.lock();
+                let rust_db = this.borrow(&guard);
+                rust_db.bc_db.get_blocks(start, end)
+            };
+            match res {
+                Ok(blocks) => Ok(neon_serde::to_value(&mut cx, &blocks)?),
+                Err(e) => cx.throw_error(format!("{}", e)),
+            }
+        }
+        method getCountOfBlocksIssuedBy(mut cx) {
+            let issuer = cx.argument::<JsString>(0)?.value();
+
+            let this = cx.this();
+            let res = {
+                let guard = cx.lock();
+                let rust_db = this.borrow(&guard);
+                rust_db.bc_db.get_count_of_blocks_issued_by(issuer)
+            };
+            match res {
+                Ok(count) => Ok(cx.number(count as f64).upcast()),
+                Err(e) => cx.throw_error(format!("{}", e)),
+            }
+        }
+    }
+}
diff --git a/neon/native/src/lib.rs b/neon/native/src/lib.rs
index 0fde2b572783bb433627466d337bf2dc1e4828a2..e867f58b1a1c6d751d3b70072ec160cc32c827c2 100644
--- a/neon/native/src/lib.rs
+++ b/neon/native/src/lib.rs
@@ -26,19 +26,21 @@
 )]
 
 mod crypto;
+mod dbs;
 mod logger;
 mod transaction;
 mod wot;
 
 use neon::{prelude::*, register_module};
+use std::fmt::Display;
 
-fn into_neon_res<'c, C: Context<'c>, T, S: AsRef<str>>(
+fn into_neon_res<'c, C: Context<'c>, T, S: Display>(
     context: &mut C,
     rust_result: Result<T, S>,
 ) -> NeonResult<T> {
     match rust_result {
         Ok(value) => Ok(value),
-        Err(e) => context.throw_error(e),
+        Err(e) => context.throw_error(format!("{}", e)),
     }
 }
 
@@ -51,6 +53,7 @@ register_module!(mut cx, {
     cx.export_function("sha256", crate::crypto::sha256)?;
     cx.export_function("verify", crate::crypto::verify)?;
     cx.export_class::<crate::crypto::JsKeyPair>("Ed25519Signator")?;
+    cx.export_class::<crate::dbs::JsDbs>("RustDbs")?;
     cx.export_class::<crate::logger::JsLogger>("RustLogger")?;
     cx.export_function(
         "rawTxParseAndVerify",
diff --git a/rust-libs/duniter-bc-db/Cargo.toml b/rust-libs/duniter-bc-db/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..b95ed2c92082383aa93eeb9dadddadeab71f582f
--- /dev/null
+++ b/rust-libs/duniter-bc-db/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "duniter-bc-db"
+version = "0.1.0"
+authors = ["librelois <elois@duniter.org>"]
+license = "AGPL-3.0"
+edition = "2018"
+
+[dependencies]
+dubp-common = { version = "0.25.2", features = ["crypto_scrypt"] }
+duniter-dbs = { path = "../../rust-libs/duniter-dbs"  }
+log = "0.4.11"
+resiter = "0.4.0"
+
+[dev-dependencies]
+unwrap = "1.2.1"
diff --git a/rust-libs/duniter-bc-db/src/lib.rs b/rust-libs/duniter-bc-db/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..5f71f085bcbc14452beb97dc934b958d5325072f
--- /dev/null
+++ b/rust-libs/duniter-bc-db/src/lib.rs
@@ -0,0 +1,286 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// 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/>.
+
+mod write_block;
+
+use dubp_common::crypto::hashs::Hash;
+use dubp_common::crypto::keys::{
+    ed25519::{PublicKey, Signature},
+    PublicKey as _, Signature as _,
+};
+use dubp_common::prelude::*;
+use duniter_dbs::{
+    kv_typed::backend::leveldb::{LevelDb, LevelDbConf},
+    kv_typed::backend::memory::{Mem, MemConf},
+    kv_typed::prelude::*,
+    AllKeyV1, BcV1Db, BcV1DbReadable, BcV1DbWritable, BlockDbV1, BlockNumberKeyV1, BlockstampKeyV1,
+    PubKeyAndSigV1, PubKeyKeyV1,
+};
+use resiter::filter::Filter;
+use std::path::Path;
+
+pub enum DuniterBcDb {
+    File(BcV1Db<LevelDb>),
+    Mem(BcV1Db<Mem>),
+}
+
+#[inline(always)]
+fn e(msg: &'static str) -> KvError {
+    KvError::DeserError(msg.to_owned())
+}
+
+fn pk(p: &str) -> KvResult<PubKeyKeyV1> {
+    Ok(PubKeyKeyV1(
+        PublicKey::from_base58(p).map_err(|_| e("Wrong block"))?,
+    ))
+}
+
+fn sig(p: &str) -> KvResult<Signature> {
+    Ok(Signature::from_base64(p).map_err(|_| e("Wrong block"))?)
+}
+
+impl DuniterBcDb {
+    pub fn open(home_path_opt: Option<&Path>) -> KvResult<Self> {
+        if let Some(home_path) = home_path_opt {
+            let bc_db_path = home_path.join("data/leveldb");
+            let bc_db: BcV1Db<LevelDb> = BcV1Db::open(LevelDbConf::path(bc_db_path))?;
+            Ok(DuniterBcDb::File(bc_db))
+        } else {
+            Ok(DuniterBcDb::Mem(BcV1Db::open(MemConf::default())?))
+        }
+    }
+
+    /*
+     * WRITE BLOCKS
+     */
+
+    pub fn save_block(&self, block: BlockDbV1) -> KvResult<()> {
+        match self {
+            DuniterBcDb::File(db) => write_block::write_main_block(db, block),
+            DuniterBcDb::Mem(db) => write_block::write_main_block(db, block),
+        }
+    }
+    pub fn save_side_block(&self, block: BlockDbV1) -> KvResult<()> {
+        match self {
+            DuniterBcDb::File(db) => write_block::write_fork_block(db, block),
+            DuniterBcDb::Mem(db) => write_block::write_fork_block(db, block),
+        }
+    }
+    pub fn drop_on_fork_blocks_above(&self, number: BlockNumber) -> KvResult<()> {
+        let number = BlockNumberKeyV1(number);
+        let keys_res: KvResult<Vec<BlockNumberKeyV1>> = match self {
+            DuniterBcDb::File(db) => db.main_blocks().iter(number..).keys().collect(),
+            DuniterBcDb::Mem(db) => db.main_blocks().iter(number..).keys().collect(),
+        };
+        for key in keys_res? {
+            match self {
+                DuniterBcDb::File(db) => db.main_blocks_write().remove(key)?,
+                DuniterBcDb::Mem(db) => db.main_blocks_write().remove(key)?,
+            }
+        }
+        Ok(())
+    }
+
+    /*
+     * READ BLOCKS
+     */
+
+    pub fn get_absolute_block(&self, blockstamp: Blockstamp) -> KvResult<Option<BlockDbV1>> {
+        if let Some(block) = self.get_block(blockstamp.number)? {
+            if block.hash == blockstamp.hash.0.to_hex() {
+                Ok(Some(block))
+            } else {
+                self.get_fork_block(blockstamp)
+            }
+        } else {
+            self.get_fork_block(blockstamp)
+        }
+    }
+    pub fn get_block(&self, number: BlockNumber) -> KvResult<Option<BlockDbV1>> {
+        match self {
+            DuniterBcDb::File(db) => db.main_blocks().get(&BlockNumberKeyV1(number)),
+            DuniterBcDb::Mem(db) => db.main_blocks().get(&BlockNumberKeyV1(number)),
+        }
+    }
+    pub fn get_blocks(&self, start: BlockNumber, end: BlockNumber) -> KvResult<Vec<BlockDbV1>> {
+        let start = BlockNumberKeyV1(start);
+        let end = BlockNumberKeyV1(end);
+        match self {
+            DuniterBcDb::File(db) => db.main_blocks().iter(start..=end).values().collect(),
+            DuniterBcDb::Mem(db) => db.main_blocks().iter(start..=end).values().collect(),
+        }
+    }
+    pub fn get_count_of_blocks_issued_by(&self, issuer: String) -> KvResult<usize> {
+        Ok(match self {
+            DuniterBcDb::File(db) => db
+                .main_blocks()
+                .iter(..)
+                .values()
+                .filter_map(Result::ok)
+                .filter(|b| b.issuer == issuer)
+                .count(),
+            DuniterBcDb::Mem(db) => db
+                .main_blocks()
+                .iter(..)
+                .values()
+                .filter_map(Result::ok)
+                .filter(|b| b.issuer == issuer)
+                .count(),
+        })
+    }
+    pub fn get_current_block(&self) -> KvResult<Option<BlockDbV1>> {
+        match self {
+            DuniterBcDb::File(db) => db.main_blocks().iter(..).values().reverse().next_res(),
+            DuniterBcDb::Mem(db) => db.main_blocks().iter(..).values().reverse().next_res(),
+        }
+    }
+    pub fn get_fork_block(&self, blockstamp: Blockstamp) -> KvResult<Option<BlockDbV1>> {
+        match self {
+            DuniterBcDb::File(db) => db.fork_blocks().get(&BlockstampKeyV1(blockstamp)),
+            DuniterBcDb::Mem(db) => db.fork_blocks().get(&BlockstampKeyV1(blockstamp)),
+        }
+    }
+    pub fn get_next_fork_blocks(&self, blockstamp: Blockstamp) -> KvResult<Vec<BlockDbV1>> {
+        let start = BlockstampKeyV1(Blockstamp {
+            number: BlockNumber(blockstamp.number.0 + 1),
+            hash: BlockHash(Hash::default()),
+        });
+        let end = BlockstampKeyV1(Blockstamp {
+            number: BlockNumber(blockstamp.number.0 + 1),
+            hash: BlockHash(Hash([255u8; 32])),
+        });
+        match self {
+            DuniterBcDb::File(db) => db
+                .fork_blocks()
+                .iter(start..=end)
+                .values()
+                .filter_ok(|b| b.previous_hash == Some(blockstamp.hash.0.to_hex()))
+                .collect(),
+            DuniterBcDb::Mem(db) => db
+                .fork_blocks()
+                .iter(start..=end)
+                .values()
+                .filter_ok(|b| b.previous_hash == Some(blockstamp.hash.0.to_hex()))
+                .collect(),
+        }
+    }
+    pub fn get_potential_fork_blocks(
+        &self,
+        number_start: BlockNumber,
+        median_time_start: u64,
+        max_number: BlockNumber,
+    ) -> KvResult<Vec<BlockDbV1>> {
+        let start = BlockstampKeyV1(Blockstamp {
+            number: BlockNumber(number_start.0 + 1),
+            hash: BlockHash(Hash::default()),
+        });
+        let end = BlockstampKeyV1(Blockstamp {
+            number: BlockNumber(max_number.0 + 1),
+            hash: BlockHash(Hash([255u8; 32])),
+        });
+        match self {
+            DuniterBcDb::File(db) => db
+                .fork_blocks()
+                .iter(start..=end)
+                .values()
+                .filter_ok(|b| b.median_time >= median_time_start)
+                .collect(),
+            DuniterBcDb::Mem(db) => db
+                .fork_blocks()
+                .iter(start..=end)
+                .values()
+                .filter_ok(|b| b.median_time >= median_time_start)
+                .collect(),
+        }
+    }
+    pub fn get_potential_roots(&self) -> KvResult<Vec<BlockDbV1>> {
+        let start = BlockstampKeyV1(Blockstamp {
+            number: BlockNumber(0),
+            hash: BlockHash(Hash::default()),
+        });
+        let end = BlockstampKeyV1(Blockstamp {
+            number: BlockNumber(0),
+            hash: BlockHash(Hash([255u8; 32])),
+        });
+        match self {
+            DuniterBcDb::File(db) => db.fork_blocks().iter(start..=end).values().collect(),
+            DuniterBcDb::Mem(db) => db.fork_blocks().iter(start..=end).values().collect(),
+        }
+    }
+    pub fn last_block_of_issuer(&self, issuer: String) -> KvResult<Option<BlockDbV1>> {
+        match self {
+            DuniterBcDb::File(db) => db
+                .main_blocks()
+                .iter(..)
+                .values()
+                .reverse()
+                .filter_ok(|b| b.issuer == issuer)
+                .next_res(),
+            DuniterBcDb::Mem(db) => db
+                .main_blocks()
+                .iter(..)
+                .values()
+                .reverse()
+                .filter_ok(|b| b.issuer == issuer)
+                .next_res(),
+        }
+    }
+}
+
+#[cfg(test)]
+impl DuniterBcDb {
+    fn mem(&self) -> &BcV1Db<Mem> {
+        if let DuniterBcDb::Mem(db) = self {
+            db
+        } else {
+            unreachable!()
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_write_block() -> KvResult<()> {
+        let db = DuniterBcDb::open(None)?;
+
+        let block = BlockDbV1 {
+            certifications: vec![
+                "J3hNuPirTEoxBQ7Z9gL5NCqQfb8vMGD9YRyhTJKoixAX:12JDJibbgZPRfD6Hoecg8rQRvCR5VYrMnqzfhQYmAr3k".to_owned(),
+                "D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx:2ny7YAdmzReQxAayyJZsyVYwYhVyax2thKcGknmQy5nQ".to_owned()
+            ],
+            hash: "55FBB918864D56922100211DAEF9CB71AC412BE10CC16B3A7AE56E10FB3A7569".to_owned(),
+            ..Default::default()
+        };
+        assert_eq!(db.mem().fork_blocks().count()?, 0);
+        db.save_side_block(block.clone())?;
+        assert_eq!(db.mem().fork_blocks().count()?, 1);
+        db.save_block(block.clone())?;
+        assert_eq!(db.mem().fork_blocks().count()?, 0);
+
+        if let DuniterBcDb::Mem(db) = db {
+            assert_eq!(db.mb_certs().count()?, 5);
+            assert_eq!(
+                db.main_blocks().get(&BlockNumberKeyV1(BlockNumber(0)))?,
+                Some(block)
+            );
+            Ok(())
+        } else {
+            unreachable!()
+        }
+    }
+}
diff --git a/rust-libs/duniter-bc-db/src/write_block.rs b/rust-libs/duniter-bc-db/src/write_block.rs
new file mode 100644
index 0000000000000000000000000000000000000000..d60b3b57964d6fabf3ef10e669f61c7dd3d8104e
--- /dev/null
+++ b/rust-libs/duniter-bc-db/src/write_block.rs
@@ -0,0 +1,144 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// 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/>.
+
+use crate::*;
+
+macro_rules! add_block_number {
+    ($block_key:ident, $db:ident, $K:ty, $k:ident, $col_ro:ident, $col_rw:ident) => {
+        let mut blocks_numbers = $db.$col_ro().get(&$k)?.unwrap_or_default();
+        println!("blocks_numbers={:?} (k={:?})", blocks_numbers, $k);
+        blocks_numbers.0.push($block_key.0);
+        $db.$col_rw().upsert($k, blocks_numbers)?;
+        let key_all = <$K>::all();
+        let mut all = $db.$col_ro().get(&key_all)?.unwrap_or_default();
+        println!("all={:?}", all);
+        all.0.push($block_key.0);
+        $db.$col_rw().upsert(key_all, all)?;
+    };
+}
+
+pub(super) fn write_main_block<B: Backend>(db: &BcV1Db<B>, block: BlockDbV1) -> KvResult<()> {
+    let block_key = BlockNumberKeyV1(BlockNumber(block.number as u32));
+    for idty in &block.identities {
+        let idty_issuer = pk(idty.split(':').next().ok_or_else(|| e("Wrong block"))?)?;
+        add_block_number!(
+            block_key,
+            db,
+            PubKeyKeyV1,
+            idty_issuer,
+            mb_idty,
+            mb_idty_write
+        );
+    }
+    for cert in &block.certifications {
+        let mut cert = cert.split(':');
+        let cert_issuer = pk(cert.next().ok_or_else(|| e("Wrong block"))?)?;
+        add_block_number!(
+            block_key,
+            db,
+            PubKeyKeyV1,
+            cert_issuer,
+            mb_certs,
+            mb_certs_write
+        );
+        let cert_receiver = pk(cert.next().ok_or_else(|| e("Wrong block"))?)?;
+        let mut blocks_numbers = db.mb_certs().get(&cert_receiver)?.unwrap_or_default();
+        blocks_numbers.0.push(block_key.0);
+        db.mb_certs_write().upsert(cert_receiver, blocks_numbers)?;
+    }
+    for joiner in &block.joiners {
+        let issuer = pk(joiner.split(':').next().ok_or_else(|| e("Wrong block"))?)?;
+        add_block_number!(
+            block_key,
+            db,
+            PubKeyKeyV1,
+            issuer,
+            mb_joiners,
+            mb_joiners_write
+        );
+    }
+    for active in &block.actives {
+        let issuer = pk(active.split(':').next().ok_or_else(|| e("Wrong block"))?)?;
+        add_block_number!(
+            block_key,
+            db,
+            PubKeyKeyV1,
+            issuer,
+            mb_actives,
+            mb_actives_write
+        );
+    }
+    for leaver in &block.leavers {
+        let issuer = pk(leaver.split(':').next().ok_or_else(|| e("Wrong block"))?)?;
+        add_block_number!(
+            block_key,
+            db,
+            PubKeyKeyV1,
+            issuer,
+            mb_leavers,
+            mb_leavers_write
+        );
+    }
+    for excluded in &block.excluded {
+        let issuer = pk(excluded)?;
+        add_block_number!(
+            block_key,
+            db,
+            PubKeyKeyV1,
+            issuer,
+            mb_excluded,
+            mb_excluded_write
+        );
+    }
+    for revoked in &block.revoked {
+        let mut revoked = revoked.split(':');
+        let issuer = pk(revoked.next().ok_or_else(|| e("Wrong block"))?)?;
+        let sig = sig(revoked.next().ok_or_else(|| e("Wrong block"))?)?;
+        let k = PubKeyAndSigV1(issuer.0, sig);
+        add_block_number!(
+            block_key,
+            db,
+            PubKeyAndSigV1,
+            k,
+            mb_revoked,
+            mb_revoked_write
+        );
+    }
+    if block.dividend.is_some() {
+        let mut all = db.mb_dividends().get(&AllKeyV1)?.unwrap_or_default();
+        all.0.push(block_key.0);
+        db.mb_dividends_write().upsert(AllKeyV1, all)?;
+    }
+    if !block.transactions.is_empty() {
+        let mut all = db.mb_transactions().get(&AllKeyV1)?.unwrap_or_default();
+        all.0.push(block_key.0);
+        db.mb_transactions_write().upsert(AllKeyV1, all)?;
+    }
+    let blockstamp = BlockstampKeyV1(Blockstamp {
+        number: block_key.0,
+        hash: BlockHash(Hash::from_hex(&block.hash).map_err(|_| e("Wrong block"))?),
+    });
+    db.main_blocks_write().upsert(block_key, block)?;
+    db.fork_blocks_write().remove(blockstamp)?;
+    Ok(())
+}
+
+pub(super) fn write_fork_block<B: Backend>(db: &BcV1Db<B>, block: BlockDbV1) -> KvResult<()> {
+    let blockstamp = BlockstampKeyV1(Blockstamp {
+        number: BlockNumber(block.number as u32),
+        hash: BlockHash(Hash::from_hex(&block.hash).map_err(|_| e("Wrong block"))?),
+    });
+    db.fork_blocks_write().upsert(blockstamp, block)
+}
diff --git a/rust-libs/duniter-dbs/src/keys/blockstamp.rs b/rust-libs/duniter-dbs/src/keys/blockstamp.rs
index 36cf85b1ccb65c075489b82244b5ba498d45e646..0bccbbc49bf5586a20b249aa42bd7b1cb30a33b3 100644
--- a/rust-libs/duniter-dbs/src/keys/blockstamp.rs
+++ b/rust-libs/duniter-dbs/src/keys/blockstamp.rs
@@ -16,7 +16,7 @@
 use crate::*;
 
 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)]
-pub struct BlockstampKeyV1(Blockstamp);
+pub struct BlockstampKeyV1(pub Blockstamp);
 
 impl KeyAsBytes for BlockstampKeyV1 {
     fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
diff --git a/rust-libs/duniter-dbs/src/keys/pubkey_and_sig.rs b/rust-libs/duniter-dbs/src/keys/pubkey_and_sig.rs
index 65934750d8e4aee2e073d9cc2aa6a876788fd104..8613c359586ba083ad6c14d1cc496f109fb522fb 100644
--- a/rust-libs/duniter-dbs/src/keys/pubkey_and_sig.rs
+++ b/rust-libs/duniter-dbs/src/keys/pubkey_and_sig.rs
@@ -16,7 +16,7 @@
 use crate::*;
 
 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
-pub struct PubKeyAndSigV1(PublicKey, Signature);
+pub struct PubKeyAndSigV1(pub PublicKey, pub Signature);
 
 impl PubKeyAndSigV1 {
     pub fn all() -> Self {
diff --git a/rust-libs/duniter-dbs/src/values/block_number_array_db.rs b/rust-libs/duniter-dbs/src/values/block_number_array_db.rs
index dd4a97937f92f055b886320079cf0ca16280506e..8c4739b7ce5cd4dd77b65ed94ee28dd5fe5b54d4 100644
--- a/rust-libs/duniter-dbs/src/values/block_number_array_db.rs
+++ b/rust-libs/duniter-dbs/src/values/block_number_array_db.rs
@@ -15,14 +15,14 @@
 
 use crate::*;
 
-#[derive(Debug, Deserialize, PartialEq, Serialize)]
+#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
 pub struct BlockNumberArrayV1(pub SmallVec<[BlockNumber; 1]>);
 
 impl ValueAsBytes for BlockNumberArrayV1 {
     fn as_bytes<T, F: FnMut(&[u8]) -> KvResult<T>>(&self, mut f: F) -> KvResult<T> {
         let json_string =
             serde_json::to_string(self).map_err(|e| KvError::DeserError(format!("{}", e)))?;
-        f(format!("[{}]", json_string).as_bytes())
+        f(json_string.as_bytes())
     }
 }