diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f4d5fb44ba448e79d489e891b871d78860ec6ec3..d5a5a5ecafa65be7c50f7f67f1426bca3826a3e8 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,7 +1,7 @@
 stages:
     - build_and_tests
-    - clippy
     - fmt
+    - clippy
     - publish
     
 before_script:
@@ -11,8 +11,9 @@ build_and_tests:stable:
   stage: build_and_tests
   tags:
     - redshift-rs-stable
-  script:
-    - cargo test --all-features
+  script: 
+    - cargo build --features strict
+    - cargo test --all
     
 build_and_tests:beta:
   stage: build_and_tests
@@ -20,7 +21,8 @@ build_and_tests:beta:
     - redshift-rs-beta
   script:
     - rustup update
-    - cargo test --all-features
+    - cargo build --features strict
+    - cargo test --all
   when: manual
   allow_failure: true
     
@@ -30,33 +32,34 @@ build_and_tests:nightly:
   tags:
     - redshift-rs-nightly
   script:
-    - cargo test --all-features
+    - cargo build --features strict
+    - cargo test --all
   when: manual
   allow_failure: true
   
-clippy:
-  stage: clippy
+fmt:
+  stage: fmt
   image: rustlang/rust:nightly
   tags:
     - redshift-rs-nightly
   before_script:
     - export PATH="$HOME/.cargo/bin:$PATH"
-    - cargo install --force clippy --verbose
+    - cargo install --force rustfmt-nightly
   script:
-    - cargo clippy --all -- -D warnings --verbose
-  allow_failure: true  
-  
-fmt:
-  stage: fmt
+    - cargo fmt -- --check
+  allow_failure: true
+
+clippy:
+  stage: clippy
   image: rustlang/rust:nightly
   tags:
     - redshift-rs-nightly
   before_script:
     - export PATH="$HOME/.cargo/bin:$PATH"
-    - cargo install --force rustfmt-nightly
+    - cargo install --force clippy --verbose
   script:
-    - cargo fmt -- --write-mode=diff
-  allow_failure: true
+    - cargo clippy --all -- -D warnings --verbose
+  allow_failure: true  
 
 publish:crate:
   stage: publish
@@ -70,4 +73,21 @@ publish:crate:
   only:
     - tags
   allow_failure: false
+  when: manual
+
+pages:
+  stage: publish
+  tags:
+    - redshift-rs-stable
+  before_script:
+    - export PATH="$HOME/.cargo/bin:$PATH"
+  script:
+    - cargo doc
+    - mv target/doc public
+    - ls public
+  artifacts:
+    untracked: true
+    paths:
+      - public
+  allow_failure: true
   when: manual
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index 0b4444798383eaf1f6a355dd8a39fe9f28e7cdfa..7679a35cbf9582871ab3c10de679551ebf5b011a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6,6 +6,14 @@ dependencies = [
  "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "ansi_term"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "arrayvec"
 version = "0.4.7"
@@ -14,11 +22,38 @@ dependencies = [
  "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "atty"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "base58"
 version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "base64"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "byteorder 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "base64"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "byteorder 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "base64"
 version = "0.8.0"
@@ -37,6 +72,11 @@ dependencies = [
  "serde 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "bitflags"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "bitflags"
 version = "1.0.3"
@@ -47,11 +87,67 @@ name = "byteorder"
 version = "1.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "bytes"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "byteorder 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "cfg-if"
 version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "chrono"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "num-integer 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "clap"
+version = "2.31.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "crossbeam-deque"
 version = "0.2.0"
@@ -61,6 +157,15 @@ dependencies = [
  "crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "crossbeam-deque"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "crossbeam-epoch 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crossbeam-utils 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "crossbeam-epoch"
 version = "0.3.1"
@@ -75,6 +180,19 @@ dependencies = [
  "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "crossbeam-epoch"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crossbeam-utils 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "crossbeam-utils"
 version = "0.2.2"
@@ -83,6 +201,75 @@ dependencies = [
  "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "crossbeam-utils"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "dtoa"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "duniter-blockchain"
+version = "0.1.0"
+dependencies = [
+ "duniter-conf 0.1.0",
+ "duniter-crypto 0.1.2",
+ "duniter-dal 0.1.0",
+ "duniter-documents 0.7.1",
+ "duniter-message 0.1.0",
+ "duniter-module 0.1.0",
+ "duniter-network 0.1.0",
+ "duniter-wotb 0.8.0-a0.6",
+ "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pbr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sqlite 0.23.9 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "duniter-conf"
+version = "0.1.0"
+dependencies = [
+ "duniter-crypto 0.1.2",
+ "duniter-module 0.1.0",
+ "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "duniter-core"
+version = "0.1.0"
+dependencies = [
+ "clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "duniter-blockchain 0.1.0",
+ "duniter-conf 0.1.0",
+ "duniter-crypto 0.1.2",
+ "duniter-message 0.1.0",
+ "duniter-module 0.1.0",
+ "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "simplelog 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sqlite 0.23.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "duniter-crypto"
 version = "0.1.2"
@@ -93,6 +280,27 @@ dependencies = [
  "serde 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "duniter-dal"
+version = "0.1.0"
+dependencies = [
+ "duniter-crypto 0.1.2",
+ "duniter-documents 0.7.1",
+ "duniter-module 0.1.0",
+ "duniter-network 0.1.0",
+ "duniter-wotb 0.8.0-a0.6",
+ "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sqlite 0.23.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "websocket 0.20.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "duniter-documents"
 version = "0.7.1"
@@ -107,6 +315,62 @@ dependencies = [
  "serde 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "duniter-message"
+version = "0.1.0"
+dependencies = [
+ "duniter-crypto 0.1.2",
+ "duniter-dal 0.1.0",
+ "duniter-documents 0.7.1",
+ "duniter-module 0.1.0",
+ "duniter-network 0.1.0",
+ "serde 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "duniter-module"
+version = "0.1.0"
+dependencies = [
+ "duniter-crypto 0.1.2",
+ "duniter-documents 0.7.1",
+ "serde 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "duniter-network"
+version = "0.1.0"
+dependencies = [
+ "duniter-crypto 0.1.2",
+ "duniter-documents 0.7.1",
+ "duniter-module 0.1.0",
+ "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "duniter-tui"
+version = "0.1.0"
+dependencies = [
+ "duniter-conf 0.1.0",
+ "duniter-crypto 0.1.2",
+ "duniter-dal 0.1.0",
+ "duniter-documents 0.7.1",
+ "duniter-message 0.1.0",
+ "duniter-module 0.1.0",
+ "duniter-network 0.1.0",
+ "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "duniter-wotb"
 version = "0.8.0-a0.6"
@@ -118,11 +382,61 @@ dependencies = [
  "serde_derive 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "duniter-ws2p"
+version = "0.1.0"
+dependencies = [
+ "duniter-conf 0.1.0",
+ "duniter-crypto 0.1.2",
+ "duniter-dal 0.1.0",
+ "duniter-documents 0.7.1",
+ "duniter-message 0.1.0",
+ "duniter-module 0.1.0",
+ "duniter-network 0.1.0",
+ "duniter-wotb 0.8.0-a0.6",
+ "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sqlite 0.23.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "websocket 0.20.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "durs"
+version = "0.1.0"
+dependencies = [
+ "clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "duniter-core 0.1.0",
+ "duniter-tui 0.1.0",
+ "duniter-ws2p 0.1.0",
+ "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "websocket 0.20.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "either"
 version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "fuchsia-zircon"
 version = "0.3.3"
@@ -137,16 +451,92 @@ name = "fuchsia-zircon-sys"
 version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "futures"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "gcc"
 version = "0.3.54"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "httparse"
+version = "1.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "hyper"
+version = "0.10.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "httparse 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
+ "traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "idna"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "iovec"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "kernel32-sys"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "language-tags"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "lazy_static"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "lazy_static"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "lazycell"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "libc"
 version = "0.2.40"
@@ -158,30 +548,170 @@ version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
-name = "memchr"
-version = "2.0.1"
+name = "log"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "log"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "matches"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "memchr"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "mime"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "mio"
+version = "0.6.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazycell 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "net2 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
+ "slab 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "miow"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "net2 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "native-tls"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "openssl 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "schannel 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "security-framework 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
+ "security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "net2"
+version = "0.2.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "nodrop"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "num-integer"
+version = "0.1.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "num_cpus"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "openssl"
+version = "0.9.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "openssl-sys 0.9.30 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
+ "cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "vcpkg 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "memoffset"
-version = "0.2.1"
+name = "pbr"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
 
 [[package]]
-name = "nodrop"
-version = "0.1.12"
+name = "percent-encoding"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
-name = "num_cpus"
-version = "1.8.0"
+name = "pkg-config"
+version = "0.3.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
-]
 
 [[package]]
 name = "proc-macro2"
@@ -245,6 +775,14 @@ name = "redox_syscall"
 version = "0.1.37"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "redox_termios"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "regex"
 version = "0.2.11"
@@ -265,6 +803,14 @@ dependencies = [
  "ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "remove_dir_all"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "rust-crypto"
 version = "0.2.36"
@@ -287,11 +833,45 @@ name = "safemem"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "schannel"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "scoped-tls"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "scopeguard"
 version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "security-framework"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "serde"
 version = "1.0.50"
@@ -307,6 +887,68 @@ dependencies = [
  "syn 0.13.7 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "serde_json"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "sha1"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "simplelog"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "term 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "sqlite"
+version = "0.23.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sqlite3-sys 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "sqlite3-src"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "sqlite3-sys"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sqlite3-src 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "strsim"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "syn"
 version = "0.13.7"
@@ -317,6 +959,42 @@ dependencies = [
  "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "tempdir"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "term"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "byteorder 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "termion"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "thread_local"
 version = "0.3.5"
@@ -326,6 +1004,14 @@ dependencies = [
  "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "threadpool"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "time"
 version = "0.1.39"
@@ -336,11 +1022,182 @@ dependencies = [
  "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "tokio"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mio 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-executor 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-fs 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-io 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-reactor 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-tcp 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-threadpool 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-timer 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-udp 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-core"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bytes 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mio 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)",
+ "scoped-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-executor 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-io 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-reactor 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-timer 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-executor"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-fs"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-io 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-threadpool 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-io"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bytes 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-reactor"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mio 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)",
+ "slab 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-executor 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-io 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-tcp"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bytes 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mio 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-io 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-reactor 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-threadpool"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "crossbeam-deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-executor 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-timer"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-executor 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-tls"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-io 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-udp"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bytes 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mio 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-io 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-reactor 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "traitobject"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "typeable"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "ucd-util"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "unicase"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "unicode-xid"
 version = "0.1.0"
@@ -354,16 +1211,67 @@ dependencies = [
  "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "url"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "utf8-ranges"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "vcpkg"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "vec_map"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "version_check"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "void"
 version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "websocket"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "byteorder 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bytes 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-io 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "winapi"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "winapi"
 version = "0.3.4"
@@ -373,6 +1281,11 @@ dependencies = [
  "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "winapi-build"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "winapi-i686-pc-windows-gnu"
 version = "0.4.0"
@@ -383,29 +1296,85 @@ name = "winapi-x86_64-pc-windows-gnu"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "ws2_32-sys"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "yaml-rust"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [metadata]
 "checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4"
+"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
 "checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef"
+"checksum atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2fc4a1aa4c24c0718a250f0681885c1af91419d242f29eb8f2ab28502d80dbd1"
 "checksum base58 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83"
+"checksum base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "30e93c03064e7590d0466209155251b90c22e37fab1daf2771582598b5827557"
+"checksum base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "96434f987501f0ed4eb336a411e0631ecd1afa11574fe148587adc4ff96143c9"
 "checksum base64 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c4a342b450b268e1be8036311e2c613d7f8a7ed31214dff1cc3b60852a3168d"
 "checksum bincode 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9a6301db0b49fb63551bc15b5ae348147101cdf323242b93ec7546d5002ff1af"
+"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
 "checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789"
 "checksum byteorder 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "73b5bdfe7ee3ad0b99c9801d58807a9dbc9e09196365b0203853b99889ab3c87"
+"checksum bytes 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "2f1d50c876fb7545f5f289cd8b2aee3f359d073ae819eed5d6373638e2c61e59"
+"checksum cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)" = "0ebb87d1116151416c0cf66a0e3fb6430cccd120fd6300794b4dfaa050ac40ba"
 "checksum cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "405216fd8fe65f718daa7102ea808a946b6ce40c742998fbfd3463645552de18"
+"checksum chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1cce36c92cb605414e9b824f866f5babe0a0368e39ea07393b9b63cf3844c0e6"
+"checksum clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f16b89cbb9ee36d87483dc939fe9f1e13c05898d56d7b230a0d4dff033a536"
+"checksum core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25bfd746d203017f7d5cbd31ee5d8e17f94b6521c7af77ece6c9e4b2d4b16c67"
+"checksum core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "065a5d7ffdcbc8fa145d6f0746f3555025b9097a9e9cda59f7467abae670c78d"
 "checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3"
+"checksum crossbeam-deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fe8153ef04a7594ded05b427ffad46ddeaf22e63fd48d42b3e1e3bb4db07cae7"
 "checksum crossbeam-epoch 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "927121f5407de9956180ff5e936fe3cf4324279280001cd56b669d28ee7e9150"
+"checksum crossbeam-epoch 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9b4e2817eb773f770dcb294127c011e22771899c21d18fce7dd739c0b9832e81"
 "checksum crossbeam-utils 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2760899e32a1d58d5abb31129f8fae5de75220bc2176e77ff7c627ae45c918d9"
+"checksum crossbeam-utils 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d636a8b3bcc1b409d7ffd3facef8f21dcb4009626adbd0c5e6c4305c07253c7b"
+"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
 "checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0"
+"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
 "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
 "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
+"checksum futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "1a70b146671de62ec8c8ed572219ca5d594d9b06c0b364d5e67b722fc559b48c"
 "checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb"
+"checksum httparse 1.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c2f407128745b78abc95c0ffbe4e5d37427fdc0d45470710cfef8c44522a2e37"
+"checksum hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)" = "368cb56b2740ebf4230520e2b90ebb0461e69034d85d1945febd9b3971426db2"
+"checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d"
+"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08"
+"checksum itoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c069bbec61e1ca5a596166e55dfe4773ff745c3d16b700013bcaff9a6df2c682"
+"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
+"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
+"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73"
 "checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d"
+"checksum lazycell 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a6f08839bc70ef4a3fe1d566d5350f519c5912ea86be0df1740a7d247c7fc0ef"
 "checksum libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "6fd41f331ac7c5b8ac259b8bf82c75c0fb2e469bbf37d2becbba9a6a2221965b"
 "checksum linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "70fb39025bc7cdd76305867c4eccf2f2dcf6e9a57f5b21a93e1c2d86cd03ec9e"
+"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
+"checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2"
+"checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376"
 "checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
 "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3"
+"checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0"
+"checksum mio 0.6.14 (registry+https://github.com/rust-lang/crates.io-index)" = "6d771e3ef92d58a8da8df7d6976bfca9371ed1de6619d9d5a5ce5b1f29b85bfe"
+"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
+"checksum native-tls 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f74dbadc8b43df7864539cedb7bc91345e532fdd913cfdc23ad94f4d2d40fbc0"
+"checksum net2 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)" = "9044faf1413a1057267be51b5afba8eb1090bd2231c693664aa1db716fe1eae0"
 "checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2"
+"checksum num-integer 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "6ac0ea58d64a89d9d6b7688031b3be9358d6c919badcf7fbb0527ccfd891ee45"
+"checksum num-traits 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "775393e285254d2f5004596d69bb8bc1149754570dcc08cf30cabeba67955e28"
 "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30"
+"checksum openssl 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)" = "a3605c298474a3aa69de92d21139fb5e2a81688d308262359d85cdd0d12a7985"
+"checksum openssl-sys 0.9.30 (registry+https://github.com/rust-lang/crates.io-index)" = "73ae718c3562989cd3a0a5c26610feca02f8116822f6f195e6cf4887481e57f5"
+"checksum pbr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "deb73390ab68d81992bd994d145f697451bb0b54fd39738e72eef32458ad6907"
+"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
+"checksum pkg-config 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "110d5ee3593dbb73f56294327fe5668bcc997897097cbc76b51e7aed3f52452f"
 "checksum proc-macro2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1b06e2f335f48d24442b35a19df506a835fb3547bc3c06ef27340da9acf5cae7"
 "checksum quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9949cfe66888ffe1d53e6ec9d9f3b70714083854be20fd5e271b232a017401e8"
 "checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1"
@@ -413,22 +1382,67 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum rayon 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80e811e76f1dbf68abf87a759083d34600017fc4e10b6bd5ad84a700f9dba4b1"
 "checksum rayon-core 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9d24ad214285a7729b174ed6d3bcfcb80177807f959d95fafd5bfc5c4f201ac8"
 "checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd"
+"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
 "checksum regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384"
 "checksum regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7"
+"checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5"
 "checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a"
 "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
 "checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f"
+"checksum schannel 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "85fd9df495640643ad2d00443b3d78aae69802ad488debab4f1dd52fc1806ade"
+"checksum scoped-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28"
 "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27"
+"checksum security-framework 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "dfa44ee9c54ce5eecc9de7d5acbad112ee58755239381f687e564004ba4a2332"
+"checksum security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "5421621e836278a0b139268f36eee0dc7e389b784dc3f79d8f11aabadf41bead"
 "checksum serde 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "428d3d818cb94ee037a17bf4f2200db2552e19b1825d33df2196624290716f92"
 "checksum serde_derive 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "ee76093b16868c4c9c8e5329c3d30745833e35390624019738472bd13e996e79"
+"checksum serde_json 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)" = "f3ad6d546e765177cf3dded3c2e424a8040f870083a0e64064746b958ece9cb1"
+"checksum sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cc30b1e1e8c40c121ca33b86c23308a090d19974ef001b4bf6e61fd1a0fb095c"
+"checksum simplelog 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce595117de34b75e057b41e99079e43e9fcc4e5ec9c7ba5f2fea55321f0c624e"
+"checksum slab 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fdeff4cd9ecff59ec7e3744cbca73dfe5ac35c2aedb2cfba8a1c715a18912e9d"
+"checksum sqlite 0.23.9 (registry+https://github.com/rust-lang/crates.io-index)" = "d18d7b10278336e7fd9dc259399a0f9ff419616738b6a841b136c362e16db626"
+"checksum sqlite3-src 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "46e0bc115b563b1ee6c665ef895b56bf488522f57d1c6571887547c57c8f5a88"
+"checksum sqlite3-sys 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "71fec807a1534bd13eeaaec396175d67c79bdc68df55e18a452726ec62a8fb08"
+"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
 "checksum syn 0.13.7 (registry+https://github.com/rust-lang/crates.io-index)" = "61b8f1b737f929c6516ba46a3133fd6d5215ad8a62f66760f851f7048aebedfb"
+"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
+"checksum term 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5e6b677dd1e8214ea1ef4297f85dbcbed8e8cdddb561040cc998ca2551c37561"
+"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
+"checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693"
 "checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963"
+"checksum threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865"
 "checksum time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "a15375f1df02096fb3317256ce2cee6a1f42fc84ea5ad5fc8c421cfe40c73098"
+"checksum tokio 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d00555353b013e170ed8bc4e13f648a317d1fd12157dbcae13f7013f6cf29f5"
+"checksum tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "aeeffbbb94209023feaef3c196a41cbcdafa06b4a6f893f68779bb5e53796f71"
+"checksum tokio-executor 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8cac2a7883ff3567e9d66bb09100d09b33d90311feca0206c7ca034bc0c55113"
+"checksum tokio-fs 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "76766830bbf9a2d5bfb50c95350d56a2e79e2c80f675967fff448bc615899708"
+"checksum tokio-io 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "6af9eb326f64b2d6b68438e1953341e00ab3cf54de7e35d92bfc73af8555313a"
+"checksum tokio-reactor 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3cedc8e5af5131dc3423ffa4f877cce78ad25259a9a62de0613735a13ebc64b"
+"checksum tokio-tcp 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ec9b094851aadd2caf83ba3ad8e8c4ce65a42104f7b94d9e6550023f0407853f"
+"checksum tokio-threadpool 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5783254b10c7c84a56f62c74766ef7e5b83d1f13053218c7cab8d3f2c826fa0e"
+"checksum tokio-timer 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535fed0ccee189f3d48447587697ba3fd234b3dbbb091f0ec4613ddfec0a7c4c"
+"checksum tokio-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "772f4b04e560117fe3b0a53e490c16ddc8ba6ec437015d91fa385564996ed913"
+"checksum tokio-udp 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "137bda266504893ac4774e0ec4c2108f7ccdbcb7ac8dced6305fe9e4e0b5041a"
+"checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079"
+"checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887"
 "checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d"
+"checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33"
+"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
+"checksum unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6a0180bc61fc5a987082bfa111f4cc95c4caff7f9799f3e46df09163a937aa25"
+"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f"
 "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
 "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
+"checksum url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f808aadd8cfec6ef90e4a14eb46f24511824d1ac596b9682703c87056c8678b7"
 "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
+"checksum vcpkg 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7ed0f6789c8a85ca41bbc1c9d175422116a9869bd1cf31bb08e1493ecce60380"
+"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
+"checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d"
 "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
+"checksum websocket 0.20.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eb277e7f4c23dc49176f74ae200e77651764efb2c25f56ad2d22623b63826369"
+"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
 "checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3"
+"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
 "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
 "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
+"checksum yaml-rust 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e66366e18dc58b46801afbf2ca7661a9f59cc8c5962c29892b6039b4f86fa992"
diff --git a/Cargo.toml b/Cargo.toml
index cb5535375711974cca62b64deaef2e95f5f5b793..4f26b8dcec354f666bee4ad07f156ae913af3714 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,35 @@
+[package]
+name = "durs"
+version = "0.1.0"
+authors = ["librelois <elois@duniter.org>","nanocryk <nanocryk@duniter.org>"]
+description = "DUniter-RS (durs) is a new implementation of Duniter protocol and software in Rust, a safe, concurrent, practical language"
+license = "AGPL-3.0"
+
+[dependencies]
+clap = "2.31.2"
+duniter-core = { path = "./core" }
+duniter-tui = { path = "./tui" }
+duniter-ws2p = { path = "./ws2p" }
+lazy_static = "1.0.0"
+serde_json = "1.0.9"
+websocket = "0.20.2"
+
+[features]
+# Treat warnings as a build error.
+strict = []
+
 [workspace]
 members = [
-    "wotb",
+    "blockchain",
+    "conf",
+    "core",
     "crypto",
+    "dal",
     "documents",
+    "message",
+    "module",
+    "network",
+    "tui",
+    "wotb",
+    "ws2p",
 ]
diff --git a/blockchain/Cargo.toml b/blockchain/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..1f4e9e0ecee27ee39cf63b179879bf05376b458a
--- /dev/null
+++ b/blockchain/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+name = "duniter-blockchain"
+version = "0.1.0"
+authors = ["librelois <elois@ifee.fr>"]
+description = "Blockchain module for the Duniter project."
+license = "AGPL-3.0"
+
+[lib]
+path = "lib.rs"
+
+[dependencies]
+duniter-conf = { path = "../conf" }
+duniter-crypto = { path = "../crypto" }
+duniter-dal = { path = "../dal" }
+duniter-documents = { path = "../documents" }
+duniter-message =  { path = "../message" }
+duniter-module = { path = "../module" }
+duniter-network = { path = "../network" }
+duniter-wotb = { path = "../wotb" }
+log = "0.4.1"
+pbr = "1.0.0"
+rand = "0.4.2"
+serde = "1.0.24"
+serde_derive = "1.0.24"
+serde_json = "1.0.9"
+sqlite = "0.23.9"
\ No newline at end of file
diff --git a/blockchain/lib.rs b/blockchain/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..8710a5214a4064bf7d46218666a320b285cb2113
--- /dev/null
+++ b/blockchain/lib.rs
@@ -0,0 +1,1006 @@
+//  Copyright (C) 2018  The Duniter Project Developers.
+//
+// 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/>.
+
+//! Module managing the Duniter blockchain.
+
+#![cfg_attr(feature = "strict", deny(warnings))]
+#![deny(
+    missing_docs, missing_debug_implementations, missing_copy_implementations, trivial_casts,
+    trivial_numeric_casts, unsafe_code, unstable_features, unused_import_braces,
+    unused_qualifications
+)]
+
+#[macro_use]
+extern crate log;
+
+extern crate duniter_conf;
+extern crate duniter_crypto;
+extern crate duniter_dal;
+extern crate duniter_documents;
+extern crate duniter_message;
+extern crate duniter_module;
+extern crate duniter_network;
+extern crate duniter_wotb;
+extern crate serde;
+extern crate serde_json;
+extern crate sqlite;
+
+mod stack_up_block;
+mod sync;
+
+use std::collections::HashMap;
+use std::env;
+use std::ops::Deref;
+use std::path::PathBuf;
+use std::str;
+use std::sync::mpsc;
+use std::time::{Duration, SystemTime, UNIX_EPOCH};
+
+use self::stack_up_block::try_stack_up_completed_block;
+use duniter_crypto::keys::ed25519;
+use duniter_dal::block::{DALBlock, WotEvent};
+use duniter_dal::constants::MAX_FORKS;
+use duniter_dal::dal_event::DALEvent;
+use duniter_dal::dal_requests::{DALReqBlockchain, DALRequest, DALResBlockchain, DALResponse};
+use duniter_dal::identity::DALIdentity;
+use duniter_dal::parsers::memberships::MembershipParseError;
+use duniter_dal::writers::requests::DBWriteRequest;
+use duniter_dal::{DuniterDB, ForkState};
+use duniter_documents::blockchain::v10::documents::{BlockDocument, V10Document};
+use duniter_documents::blockchain::{BlockchainProtocol, Document, VerificationResult};
+use duniter_documents::{BlockHash, BlockId, Blockstamp};
+use duniter_message::DuniterMessage;
+use duniter_module::*;
+use duniter_network::{
+    NetworkBlock, NetworkDocument, NetworkEvent, NetworkRequest, NetworkResponse, NodeFullId,
+};
+use duniter_wotb::data::rusty::RustyWebOfTrust;
+use duniter_wotb::operations::file::{BinaryFileFormater, FileFormater};
+use duniter_wotb::{NodeId, WebOfTrust};
+
+/// The blocks are requested by packet groups. This constant sets the block packet size.
+pub static CHUNK_SIZE: &'static u32 = &50;
+/// The blocks are requested by packet groups. This constant sets the number of packets per group.
+pub static MAX_BLOCKS_REQUEST: &'static u32 = &500;
+/// There can be several implementations of the wot file backup, this constant fixes the implementation used by the blockchain module.
+pub static WOT_FILE_FORMATER: BinaryFileFormater = BinaryFileFormater {};
+
+/// Blockchain Module
+#[derive(Debug)]
+pub struct BlockchainModule {
+    /// Subscribers
+    pub followers: Vec<mpsc::Sender<DuniterMessage>>,
+    /// Name of the user datas profile
+    pub conf_profile: String,
+    /// Currency
+    pub currency: Currency,
+    /// Database containing the blockchain
+    pub db: DuniterDB,
+    /// The block under construction
+    pub pending_block: Option<Box<BlockDocument>>,
+}
+
+#[derive(Debug, Clone)]
+/// Block
+enum Block<'a> {
+    /// Block coming from Network
+    NetworkBlock(&'a NetworkBlock),
+    /// Block coming from local database
+    LocalBlock(&'a BlockDocument),
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+/// When synchronizing the blockchain, checking all rules at each block really takes a long time.
+/// The user is therefore offered a fast synchronization that checks only what is strictly necessary for indexing the data.
+pub enum SyncVerificationLevel {
+    /// Fast sync, checks only what is strictly necessary for indexing the data.
+    FastSync(),
+    /// Cautious sync, checking all protocol rules (really takes a long time).
+    Cautious(),
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+/// Error returned by function complete_network_block()
+pub enum CompletedBlockError {
+    /// MembershipParseError
+    MembershipParseError(MembershipParseError),
+    /// Invalid block inner hash
+    InvalidInnerHash(),
+    /// Invalid block signature
+    InvalidSig(),
+    /// Invalid block hash
+    InvalidHash(),
+    /// Invalid block version
+    InvalidVersion(),
+}
+
+impl From<MembershipParseError> for CompletedBlockError {
+    fn from(e: MembershipParseError) -> CompletedBlockError {
+        CompletedBlockError::MembershipParseError(e)
+    }
+}
+
+impl BlockchainModule {
+    /// Return module identifier
+    pub fn id() -> ModuleId {
+        ModuleId::Str("blockchain")
+    }
+    /// Loading blockchain configuration
+    pub fn load_blockchain_conf(
+        conf: &DuniterConf,
+        _keys: RequiredKeysContent<ed25519::KeyPair>,
+        sync: bool,
+    ) -> BlockchainModule {
+        // Get db path
+        let db_path = duniter_conf::get_db_path(conf.profile().as_str(), &conf.currency(), sync);
+
+        // Open duniter database
+        let db = duniter_dal::open_db(&db_path, false).unwrap();
+
+        // Instanciate BlockchainModule
+        BlockchainModule {
+            followers: Vec::new(),
+            conf_profile: conf.profile(),
+            currency: conf.currency(),
+            db,
+            pending_block: None,
+        }
+    }
+    /// Synchronize blockchain from a duniter-ts database
+    pub fn sync_ts(conf: &DuniterConf, ts_profile: &str, cautious: bool) {
+        // Open local blockchain db
+        let db_path = duniter_conf::get_db_path(&conf.profile(), &conf.currency(), false);
+        let db = duniter_dal::open_db(&db_path, false).expect(&format!(
+            "Fatal error : fail to open blockchain database as path : {} !",
+            db_path.as_path().to_str().unwrap()
+        ));
+        // Get local current blockstamp
+        debug!("Get local current blockstamp...");
+        let current_block: Option<BlockDocument> = duniter_dal::new_get_current_block(&db);
+        let current_blockstamp = match current_block.clone() {
+            Some(block) => block.blockstamp(),
+            None => Blockstamp::default(),
+        };
+        debug!("Success to get local current blockstamp.");
+        // get db_ts_path
+        let mut db_ts_path = match env::home_dir() {
+            Some(path) => path,
+            None => panic!("Impossible to get your home dir!"),
+        };
+        db_ts_path.push(".config/duniter/");
+        db_ts_path.push(ts_profile);
+        db_ts_path.push("duniter.db");
+        if !db_ts_path.as_path().exists() {
+            panic!("Fatal error : duniter-ts database don't exist !");
+        }
+        sync::sync_ts(conf, &current_blockstamp, db_ts_path, cautious);
+    }
+    /// Request chunk from network (chunk = group of blocks)
+    fn request_chunk(&self, req_id: &ModuleReqId, from: u32) -> (ModuleReqId, NetworkRequest) {
+        let req = NetworkRequest::GetBlocks(
+            ModuleReqFullId(BlockchainModule::id(), req_id.clone()),
+            NodeFullId::default(),
+            *CHUNK_SIZE,
+            from,
+        );
+        (self.request_network(req.clone()), req)
+    }
+    /// Requests blocks from current to `to`
+    fn request_blocks_to(
+        &self,
+        pending_network_requests: &HashMap<ModuleReqId, NetworkRequest>,
+        current_blockstamp: &Blockstamp,
+        to: BlockId,
+    ) -> HashMap<ModuleReqId, NetworkRequest> {
+        let mut from = if *current_blockstamp == Blockstamp::default() {
+            0
+        } else {
+            current_blockstamp.id.0 + 1
+        };
+        info!(
+            "BlockchainModule : request_blocks_to({}-{})",
+            current_blockstamp.id.0 + 1,
+            to
+        );
+        let mut requests_ids = HashMap::new();
+        if current_blockstamp.id < to {
+            let mut real_to = to.0;
+            if (to.0 - current_blockstamp.id.0) > *MAX_BLOCKS_REQUEST {
+                real_to = current_blockstamp.id.0 + *MAX_BLOCKS_REQUEST;
+            }
+            while from <= real_to {
+                let mut req_id = ModuleReqId(0);
+                while pending_network_requests.contains_key(&req_id)
+                    || requests_ids.contains_key(&req_id)
+                {
+                    req_id = ModuleReqId(req_id.0 + 1);
+                }
+                let (req_id, req) = self.request_chunk(&req_id, from);
+                requests_ids.insert(req_id, req);
+                from += *CHUNK_SIZE;
+            }
+        }
+        requests_ids
+    }
+    /// Send network request
+    fn request_network(&self, request: NetworkRequest) -> ModuleReqId {
+        for follower in &self.followers {
+            if follower
+                .send(DuniterMessage::NetworkRequest(request.clone()))
+                .is_err()
+            {
+                debug!("BlockchainModule : one follower is unreachable !");
+            }
+        }
+        request.get_req_id()
+    }
+    /// Send blockchain event
+    fn send_event(&self, event: DALEvent) {
+        for follower in &self.followers {
+            match follower.send(DuniterMessage::DALEvent(event.clone())) {
+                Ok(_) => {}
+                Err(_) => {}
+            }
+        }
+    }
+    fn send_req_response(&self, response: DALResponse) {
+        for follower in &self.followers {
+            match follower.send(DuniterMessage::DALResponse(response.clone())) {
+                Ok(_) => {}
+                Err(_) => {}
+            }
+        }
+    }
+    fn receive_network_documents<W: WebOfTrust + Sync>(
+        &self,
+        network_documents: &Vec<NetworkDocument>,
+        current_blockstamp: &Blockstamp,
+        forks: &mut Vec<ForkState>,
+        wotb_index: &HashMap<ed25519::PublicKey, NodeId>,
+        wot: &W,
+    ) -> (Blockstamp, Vec<WotEvent>) {
+        let mut blockchain_documents = Vec::new();
+        let mut current_blockstamp = current_blockstamp.clone();
+        let mut wot_events = Vec::new();
+        for network_document in network_documents {
+            match network_document {
+                &NetworkDocument::Block(ref network_block) => {
+                    let (success, _new_forks, mut new_wot_events) = self.apply_block(
+                        &Block::NetworkBlock(network_block),
+                        &current_blockstamp,
+                        forks,
+                        wotb_index,
+                        wot,
+                    );
+                    if success {
+                        current_blockstamp = network_block.blockstamp();
+                        wot_events.append(&mut new_wot_events);
+                        // Update isolates forks
+                        let stackables_forks =
+                            DALBlock::get_stackables_forks(&self.db, &current_blockstamp);
+                        for fork in stackables_forks {
+                            debug!("unisolate fork {}", fork);
+                            if forks.len() > fork {
+                                forks[fork] = ForkState::Full();
+                                DALBlock::unisolate_fork(&self.db, fork);
+                            }
+                        }
+                    } /*else if !new_forks.is_empty() {
+                        forks = new_forks;
+                    }*/
+                }
+                &NetworkDocument::Identity(ref doc) => blockchain_documents.push(
+                    BlockchainProtocol::V10(Box::new(V10Document::Identity(doc.deref().clone()))),
+                ),
+                &NetworkDocument::Membership(ref doc) => blockchain_documents.push(
+                    BlockchainProtocol::V10(Box::new(V10Document::Membership(doc.deref().clone()))),
+                ),
+                &NetworkDocument::Certification(ref doc) => {
+                    blockchain_documents.push(BlockchainProtocol::V10(Box::new(
+                        V10Document::Certification(Box::new(doc.deref().clone())),
+                    )))
+                }
+                &NetworkDocument::Revocation(ref doc) => {
+                    blockchain_documents.push(BlockchainProtocol::V10(Box::new(
+                        V10Document::Revocation(Box::new(doc.deref().clone())),
+                    )))
+                }
+                &NetworkDocument::Transaction(ref doc) => {
+                    blockchain_documents.push(BlockchainProtocol::V10(Box::new(
+                        V10Document::Transaction(Box::new(doc.deref().clone())),
+                    )))
+                }
+            }
+        }
+        if !blockchain_documents.is_empty() {
+            self.receive_documents(&blockchain_documents);
+        }
+        (current_blockstamp, wot_events)
+    }
+    fn receive_documents(&self, documents: &Vec<BlockchainProtocol>) {
+        debug!("BlockchainModule : receive_documents()");
+        for document in documents {
+            trace!("BlockchainModule : Treat one document.");
+            match document {
+                &BlockchainProtocol::V10(ref doc_v10) => match doc_v10.deref() {
+                    _ => {}
+                },
+                _ => self.send_event(DALEvent::RefusedPendingDoc(document.clone())),
+            }
+        }
+    }
+    fn receive_blocks<W: WebOfTrust + Sync>(
+        &self,
+        blocks_in_box: &Vec<Box<NetworkBlock>>,
+        current_blockstamp: &Blockstamp,
+        forks: &Vec<ForkState>,
+        wotb_index: &HashMap<ed25519::PublicKey, NodeId>,
+        wot: &W,
+    ) -> (Blockstamp, Vec<ForkState>, Vec<WotEvent>) {
+        debug!("BlockchainModule : receive_blocks()");
+        let blocks: Vec<&NetworkBlock> = blocks_in_box.into_iter().map(|b| b.deref()).collect();
+        let mut current_blockstamp = current_blockstamp.clone();
+        let mut all_wot_events = Vec::new();
+        let mut forks = forks.clone();
+        let mut wot_copy: W = wot.clone();
+        let mut wotb_index_copy = wotb_index.clone();
+        for block in blocks {
+            let (success, _new_forks, mut wot_events) = self.apply_block::<W>(
+                &Block::NetworkBlock(block),
+                &current_blockstamp,
+                &mut forks,
+                &wotb_index_copy,
+                &wot_copy,
+            );
+            all_wot_events.append(&mut wot_events);
+            if success {
+                current_blockstamp = block.blockstamp();
+            } /*else if !new_forks.is_empty() {
+                forks = new_forks;
+            }*/
+            if !wot_events.is_empty() {
+                for wot_event in wot_events {
+                    match wot_event {
+                        WotEvent::AddNode(pubkey, wotb_id) => {
+                            wot_copy.add_node();
+                            wotb_index_copy.insert(pubkey, wotb_id);
+                        }
+                        WotEvent::RemNode(pubkey) => {
+                            wot_copy.rem_node();
+                            wotb_index_copy.remove(&pubkey);
+                        }
+                        WotEvent::AddLink(source, target) => {
+                            wot_copy.add_link(source, target);
+                        }
+                        WotEvent::RemLink(source, target) => {
+                            wot_copy.rem_link(source, target);
+                        }
+                        WotEvent::EnableNode(wotb_id) => {
+                            wot_copy.set_enabled(wotb_id, true);
+                        }
+                        WotEvent::DisableNode(wotb_id) => {
+                            wot_copy.set_enabled(wotb_id, false);
+                        }
+                    }
+                }
+            }
+        }
+        (current_blockstamp, forks, all_wot_events)
+    }
+    /*fn apply_local_block<W: WebOfTrust>(
+        db: &sqlite::connexion,
+        current_blockstamp: &Blockstamp,
+        wotb_index: &HashMap<ed25519::PublicKey, NodeId>,
+        wot: &W,
+    ) {
+        for f in 1..10 {
+            let potential_next_block = get_block(db, );
+        }
+    }*/
+    fn apply_block<W: WebOfTrust + Sync>(
+        &self,
+        block: &Block,
+        current_blockstamp: &Blockstamp,
+        forks: &mut Vec<ForkState>,
+        wotb_index: &HashMap<ed25519::PublicKey, NodeId>,
+        wot: &W,
+    ) -> (bool, Vec<ForkState>, Vec<WotEvent>) {
+        let mut already_have_block = false;
+        let block_doc = match block {
+            &Block::NetworkBlock(network_block) => match network_block {
+                &NetworkBlock::V10(ref network_block_v10) => {
+                    let (hashs, _) = DALBlock::get_blocks_hashs_all_forks(
+                        &self.db,
+                        &network_block_v10.uncompleted_block_doc.number,
+                    );
+                    for hash in hashs {
+                        if hash == network_block_v10.uncompleted_block_doc.hash.unwrap() {
+                            already_have_block = true;
+                        }
+                    }
+                    &network_block_v10.uncompleted_block_doc
+                }
+                _ => return (false, Vec::with_capacity(0), Vec::with_capacity(0)),
+            },
+            &Block::LocalBlock(block_doc) => {
+                already_have_block = true;
+                block_doc
+            }
+        };
+        if (block_doc.number.0 == current_blockstamp.id.0 + 1
+            && block_doc.previous_hash.to_string() == current_blockstamp.hash.0.to_string())
+            || (block_doc.number.0 == 0 && current_blockstamp.clone() == Blockstamp::default())
+        {
+            debug!(
+                "stackable_block : block {} chainable !",
+                block_doc.blockstamp()
+            );
+            let (success, db_requests, wot_events) = match block {
+                &Block::NetworkBlock(network_block) => self.try_stack_up_block(
+                    &network_block,
+                    wotb_index,
+                    wot,
+                    SyncVerificationLevel::Cautious(),
+                ),
+                &Block::LocalBlock(block_doc) => {
+                    try_stack_up_completed_block(&block_doc, wotb_index, wot)
+                }
+            };
+            debug!(
+                "stackable_block_ : block {} chainable !",
+                block_doc.blockstamp()
+            );
+            if success {
+                // Apply db requests
+                db_requests
+                    .iter()
+                    .map(|req| req.apply(&block_doc.currency, &self.db))
+                    .collect::<()>();
+                info!("StackUpValidBlock({})", block_doc.number.0);
+                self.send_event(DALEvent::StackUpValidBlock(Box::new(block_doc.clone())));
+                return (true, Vec::with_capacity(0), wot_events);
+            } else {
+                warn!("RefusedBlock({})", block_doc.number.0);
+                self.send_event(DALEvent::RefusedPendingDoc(BlockchainProtocol::V10(
+                    Box::new(V10Document::Block(Box::new(block_doc.clone()))),
+                )));
+            }
+        } else if !already_have_block
+            && (block_doc.number.0 >= current_blockstamp.id.0
+                || (current_blockstamp.id.0 - block_doc.number.0) < 100)
+        {
+            debug!(
+                "stackable_block : block {} not chainable, store this for future !",
+                block_doc.blockstamp()
+            );
+            //let mut forks = forks.clone();
+            let (fork, fork_state) = match DALBlock::get_block_fork(
+                &self.db,
+                &Blockstamp {
+                    id: BlockId(block_doc.number.0 - 1),
+                    hash: BlockHash(block_doc.previous_hash),
+                },
+            ) {
+                Some(fork) => if forks.len() > fork {
+                    (fork, forks[fork])
+                } else {
+                    panic!(format!("Error: fork n° {} is indicated as non-existent whereas it exists in database !", fork));
+                },
+                None => {
+                    let mut free_fork = 0;
+                    while forks.len() > free_fork && forks[free_fork] != ForkState::Free() {
+                        free_fork += 1;
+                    }
+                    if free_fork >= *MAX_FORKS {
+                        return (false, Vec::with_capacity(0), Vec::with_capacity(0));
+                    }
+                    info!("BlockchainModule : New Isolate fork : {}", free_fork);
+                    if free_fork == forks.len() {
+                        forks.push(ForkState::Isolate());
+                        (forks.len() - 1, ForkState::Isolate())
+                    } else {
+                        forks[free_fork] = ForkState::Isolate();
+                        (free_fork, ForkState::Isolate())
+                    }
+                }
+            };
+            let mut isolate = true;
+            match fork_state {
+                ForkState::Full() => isolate = false,
+                ForkState::Isolate() => {}
+                ForkState::Free() => {
+                    warn!("fork n° {} is indicated as free when it is not !", fork);
+                    forks[fork] = ForkState::Isolate();
+                }
+            }
+            match block {
+                &Block::NetworkBlock(network_block) => match network_block {
+                    &NetworkBlock::V10(ref network_block_v10) => {
+                        duniter_dal::writers::block::write_network_block(
+                            &self.db,
+                            &network_block_v10.uncompleted_block_doc,
+                            fork,
+                            isolate,
+                            &network_block_v10.revoked,
+                            &network_block_v10.certifications,
+                        )
+                    }
+                    _ => return (false, Vec::with_capacity(0), Vec::with_capacity(0)),
+                },
+                &Block::LocalBlock(block_doc) => {
+                    duniter_dal::writers::block::write(&self.db, &block_doc, fork, isolate)
+                }
+            };
+            return (false, forks.to_vec(), Vec::with_capacity(0));
+        } else {
+            debug!(
+                "stackable_block : block {} not chainable and already stored !",
+                block_doc.blockstamp()
+            );
+        }
+        (false, Vec::with_capacity(0), Vec::with_capacity(0))
+    }
+    /// Try stack up block
+    pub fn try_stack_up_block<W: WebOfTrust + Sync>(
+        &self,
+        network_block: &NetworkBlock,
+        wotb_index: &HashMap<ed25519::PublicKey, NodeId>,
+        wot: &W,
+        verif_level: SyncVerificationLevel,
+    ) -> (bool, Vec<DBWriteRequest>, Vec<WotEvent>) {
+        let block_doc = match complete_network_block(
+            &self.currency.to_string(),
+            Some(&self.db),
+            network_block,
+            verif_level.clone(),
+        ) {
+            Ok(block_doc) => block_doc,
+            Err(_) => return (false, Vec::with_capacity(0), Vec::with_capacity(0)),
+        };
+        try_stack_up_completed_block::<W>(&block_doc, wotb_index, wot)
+    }
+    /// Start blockchain module.
+    pub fn start_blockchain(&mut self, blockchain_receiver: mpsc::Receiver<DuniterMessage>) -> () {
+        info!("BlockchainModule::start_blockchain()");
+
+        // Get wot path
+        let wot_path = duniter_conf::get_wot_path(self.conf_profile.clone(), &self.currency);
+
+        // Get wotb index
+        let mut wotb_index: HashMap<ed25519::PublicKey, NodeId> =
+            DALIdentity::get_wotb_index(&self.db);
+
+        // Open wot file
+        let (mut wot, mut _wot_blockstamp): (RustyWebOfTrust, Blockstamp) = if wot_path
+            .as_path()
+            .exists()
+        {
+            match WOT_FILE_FORMATER.from_file(
+                wot_path.as_path().to_str().unwrap(),
+                duniter_dal::constants::G1_PARAMS.sig_stock as usize,
+            ) {
+                Ok((wot, binary_blockstamp)) => match str::from_utf8(&binary_blockstamp) {
+                    Ok(str_blockstamp) => (wot, Blockstamp::from_string(str_blockstamp).unwrap()),
+                    Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
+                },
+                Err(e) => panic!("Fatal Error : fail to read wot file : {:?}", e),
+            }
+        } else {
+            (
+                RustyWebOfTrust::new(duniter_dal::constants::G1_PARAMS.sig_stock as usize),
+                Blockstamp::default(),
+            )
+        };
+
+        // Get forks
+        let mut forks: Vec<ForkState> = duniter_dal::block::get_forks(&self.db);
+        let mut last_get_stackables_blocks = UNIX_EPOCH;
+        let mut last_request_blocks = UNIX_EPOCH;
+
+        // Get current block
+        let current_block: Option<BlockDocument> = duniter_dal::new_get_current_block(&self.db);
+        let mut current_blockstamp = match current_block.clone() {
+            Some(block) => block.blockstamp(),
+            None => Blockstamp::default(),
+        };
+
+        // Init datas
+        let mut pending_network_requests: HashMap<ModuleReqId, NetworkRequest> = HashMap::new();
+        let mut consensus = Blockstamp::default();
+
+        loop {
+            let mut wot_events = Vec::new();
+            // Request Consensus
+            let req = NetworkRequest::GetConsensus(ModuleReqFullId(
+                BlockchainModule::id(),
+                ModuleReqId(pending_network_requests.len() as u32),
+            ));
+            let req_id = self.request_network(req.clone());
+            pending_network_requests.insert(req_id, req);
+            // Request Blocks
+            let now = SystemTime::now();
+            if now.duration_since(last_request_blocks).unwrap() > Duration::new(20, 0) {
+                last_request_blocks = now;
+                // Request begin blocks
+                let to = match consensus.id.0 {
+                    0 => (current_blockstamp.id.0 + *MAX_BLOCKS_REQUEST),
+                    _ => consensus.id.0,
+                };
+                let new_pending_network_requests = self.request_blocks_to(
+                    &pending_network_requests,
+                    &current_blockstamp,
+                    BlockId(to),
+                );
+                for (new_req_id, new_req) in new_pending_network_requests {
+                    pending_network_requests.insert(new_req_id, new_req);
+                }
+                // Request end blocks
+                if consensus.id.0 > (current_blockstamp.id.0 + 100) {
+                    let mut req_id = ModuleReqId(0);
+                    while pending_network_requests.contains_key(&req_id) {
+                        req_id = ModuleReqId(req_id.0 + 1);
+                    }
+                    let from = consensus.id.0 - *CHUNK_SIZE - 1;
+                    let (new_req_id, new_req) = self.request_chunk(&req_id, from);
+                    pending_network_requests.insert(new_req_id, new_req);
+                }
+            }
+            match blockchain_receiver.recv_timeout(Duration::from_millis(1000)) {
+                Ok(ref message) => match message {
+                    &DuniterMessage::Followers(ref new_followers) => {
+                        info!("Blockchain module receive followers !");
+                        for new_follower in new_followers {
+                            self.followers.push(new_follower.clone());
+                        }
+                    }
+                    &DuniterMessage::DALRequest(ref dal_request) => match dal_request {
+                        &DALRequest::BlockchainRequest(ref blockchain_req) => {
+                            match blockchain_req {
+                                &DALReqBlockchain::CurrentBlock(ref requester_full_id) => {
+                                    debug!("BlockchainModule : receive DALReqBc::CurrentBlock()");
+
+                                    if let Some(current_block) = DALBlock::get_block(
+                                        &self.currency.to_string(),
+                                        &self.db,
+                                        &current_blockstamp,
+                                    ) {
+                                        debug!("BlockchainModule : send_req_response(CurrentBlock({}))", current_block.block.blockstamp());
+                                        self.send_req_response(DALResponse::Blockchain(
+                                            DALResBlockchain::CurrentBlock(
+                                                requester_full_id.clone(),
+                                                current_block.block,
+                                            ),
+                                        ));
+                                    } else {
+                                        warn!("BlockchainModule : Req : fail to get current_block in bdd !");
+                                    }
+                                }
+                                &DALReqBlockchain::UIDs(ref pubkeys) => {
+                                    self.send_req_response(DALResponse::Blockchain(
+                                        DALResBlockchain::UIDs(
+                                            pubkeys
+                                                .iter()
+                                                .map(|p| {
+                                                    if let Some(wotb_id) = wotb_index.get(p) {
+                                                        (
+                                                            p.clone(),
+                                                            duniter_dal::get_uid(
+                                                                &self.db, *wotb_id,
+                                                            ),
+                                                        )
+                                                    } else {
+                                                        (p.clone(), None)
+                                                    }
+                                                })
+                                                .collect(),
+                                        ),
+                                    ));
+                                }
+                                _ => {}
+                            }
+                        }
+                        _ => {}
+                    },
+                    &DuniterMessage::NetworkEvent(ref network_event) => match network_event {
+                        &NetworkEvent::ReceiveDocuments(ref network_docs) => {
+                            let (new_current_blockstamp, mut new_wot_events) = self
+                                .receive_network_documents(
+                                    network_docs,
+                                    &current_blockstamp,
+                                    &mut forks,
+                                    &wotb_index,
+                                    &wot,
+                                );
+                            current_blockstamp = new_current_blockstamp;
+                            wot_events.append(&mut new_wot_events);
+                        }
+                        &NetworkEvent::ReqResponse(ref network_response) => {
+                            debug!("BlockchainModule : receive NetworkEvent::ReqResponse() !");
+                            if let Some(request) =
+                                pending_network_requests.remove(&network_response.get_req_id())
+                            {
+                                match request {
+                                    NetworkRequest::GetConsensus(_) => {
+                                        if let &NetworkResponse::Consensus(_, response) =
+                                            network_response.deref()
+                                        {
+                                            if let Ok(blockstamp) = response {
+                                                consensus = blockstamp.clone();
+                                            }
+                                        }
+                                    }
+                                    NetworkRequest::GetBlocks(_, _, _, _) => {
+                                        if let &NetworkResponse::Chunk(_, _, ref blocks) =
+                                            network_response.deref()
+                                        {
+                                            let (
+                                                new_current_blockstamp,
+                                                new_forks,
+                                                mut new_wot_events,
+                                            ) = self.receive_blocks(
+                                                blocks,
+                                                &current_blockstamp,
+                                                &forks,
+                                                &wotb_index,
+                                                &wot,
+                                            );
+                                            current_blockstamp = new_current_blockstamp;
+                                            wot_events.append(&mut new_wot_events);
+                                            if !new_forks.is_empty() {
+                                                forks = new_forks;
+                                            }
+                                        }
+                                    }
+                                    _ => {}
+                                }
+                            }
+                        }
+                        _ => {}
+                    },
+                    &DuniterMessage::ReceiveDocsFromClient(ref docs) => {
+                        self.receive_documents(docs);
+                    }
+                    &DuniterMessage::Stop() => break,
+                    _ => {}
+                },
+                Err(e) => match e {
+                    mpsc::RecvTimeoutError::Disconnected => {
+                        panic!("Disconnected blockchain module !");
+                    }
+                    mpsc::RecvTimeoutError::Timeout => {}
+                },
+            }
+            // Write wot
+            BlockchainModule::apply_wot_events(
+                &wot_events,
+                &wot_path,
+                &current_blockstamp,
+                &mut wot,
+                &mut wotb_index,
+            );
+            // Try to apply local stackable blocks
+            let mut wot_events = Vec::new();
+            let now = SystemTime::now();
+            if now.duration_since(last_get_stackables_blocks).unwrap() > Duration::new(20, 0) {
+                last_get_stackables_blocks = now;
+                loop {
+                    let stackable_blocks = duniter_dal::block::DALBlock::get_stackables_blocks(
+                        &self.currency.to_string(),
+                        &self.db,
+                        &current_blockstamp,
+                    );
+                    if stackable_blocks.is_empty() {
+                        break;
+                    } else {
+                        let mut find_valid_block = false;
+                        for stackable_block in stackable_blocks {
+                            debug!("stackable_block({})", stackable_block.block.number);
+                            let (success, _new_forks, mut new_wot_events) = self.apply_block(
+                                &Block::LocalBlock(&stackable_block.block),
+                                &current_blockstamp,
+                                &mut forks,
+                                &wotb_index,
+                                &wot,
+                            );
+                            if success {
+                                debug!(
+                                    "success to stackable_block({})",
+                                    stackable_block.block.number
+                                );
+                                current_blockstamp = stackable_block.block.blockstamp();
+                                wot_events.append(&mut new_wot_events);
+                                find_valid_block = true;
+                                /*if !new_forks.is_empty() {
+                                    forks = new_forks;
+                                }*/
+                                break;
+                            } else {
+                                warn!(
+                                    "DEBUG: fail to stackable_block({})",
+                                    stackable_block.block.number
+                                );
+                                // Delete this fork
+                                DALBlock::delete_fork(&self.db, stackable_block.fork);
+                                forks[stackable_block.fork] = ForkState::Free();
+                            }
+                        }
+                        if !find_valid_block {
+                            break;
+                        }
+                    }
+                }
+                // Print current_blockstamp
+                info!(
+                    "BlockchainModule : current_blockstamp() = {:?}",
+                    current_blockstamp
+                );
+            }
+            // Write wot
+            BlockchainModule::apply_wot_events(
+                &wot_events,
+                &wot_path,
+                &current_blockstamp,
+                &mut wot,
+                &mut wotb_index,
+            );
+        }
+    }
+    fn apply_wot_events<W: WebOfTrust + Sync>(
+        wot_events: &Vec<WotEvent>,
+        wot_path: &PathBuf,
+        current_blockstamp: &Blockstamp,
+        wot: &mut W,
+        wotb_index: &mut HashMap<ed25519::PublicKey, NodeId>,
+    ) {
+        if !wot_events.is_empty() {
+            for wot_event in wot_events {
+                match wot_event {
+                    &WotEvent::AddNode(pubkey, wotb_id) => {
+                        wot.add_node();
+                        wotb_index.insert(pubkey.clone(), wotb_id.clone());
+                    }
+                    &WotEvent::RemNode(pubkey) => {
+                        wot.rem_node();
+                        wotb_index.remove(&pubkey);
+                    }
+                    &WotEvent::AddLink(source, target) => {
+                        wot.add_link(source.clone(), target.clone());
+                    }
+                    &WotEvent::RemLink(source, target) => {
+                        wot.rem_link(source.clone(), target.clone());
+                    }
+                    &WotEvent::EnableNode(wotb_id) => {
+                        wot.set_enabled(wotb_id.clone(), true);
+                    }
+                    &WotEvent::DisableNode(wotb_id) => {
+                        wot.set_enabled(wotb_id.clone(), false);
+                    }
+                }
+            }
+            // Save wot
+            WOT_FILE_FORMATER
+                .to_file(
+                    wot,
+                    current_blockstamp.to_string().as_bytes(),
+                    wot_path.as_path().to_str().unwrap(),
+                )
+                .expect("Fatal Error: Fail to write wotb in file !");
+        }
+    }
+}
+
+/// Complete Network Block
+pub fn complete_network_block(
+    currency: &str,
+    db: Option<&DuniterDB>,
+    network_block: &NetworkBlock,
+    verif_level: SyncVerificationLevel,
+) -> Result<BlockDocument, CompletedBlockError> {
+    if let &NetworkBlock::V10(ref network_block_v10) = network_block {
+        let mut block_doc = network_block_v10.uncompleted_block_doc.clone();
+        trace!("complete_network_block #{}...", block_doc.number);
+        if verif_level == SyncVerificationLevel::Cautious() {
+            // Indexing block_identities
+            let mut block_identities = HashMap::new();
+            block_doc
+                .identities
+                .iter()
+                .map(|idty| {
+                    if idty.issuers().is_empty() {
+                        panic!("idty without issuer !")
+                    }
+                    block_identities.insert(idty.issuers()[0], idty.clone());
+                })
+                .collect::<()>();
+            block_doc.certifications =
+                    duniter_dal::parsers::certifications::parse_certifications_from_json_value(
+                        currency,
+                        db.expect("complete_network_block() : Cautious mode need access to blockchain database !"),
+                        &block_identities,
+                        &network_block_v10.certifications,
+                    );
+            trace!("Success to complete certs.");
+            block_doc.revoked = duniter_dal::parsers::revoked::parse_revocations_from_json_value(
+                currency,
+                db.expect(
+                    "complete_network_block() : Cautious mode need access to blockchain database !",
+                ),
+                &block_identities,
+                &network_block_v10.revoked,
+            );
+        } else {
+            block_doc.certifications =
+                duniter_dal::parsers::certifications::parse_certifications_into_compact(
+                    &network_block_v10.certifications,
+                );
+            trace!("Success to complete certs.");
+            block_doc.revoked = duniter_dal::parsers::revoked::parse_revocations_into_compact(
+                &network_block_v10.revoked,
+            );
+        }
+        trace!("Success to complete certs & revocations.");
+        // In cautions mode, verify all signatures !
+        if verif_level == SyncVerificationLevel::Cautious() {
+            for idty in block_doc.clone().identities {
+                if idty.verify_signatures() != VerificationResult::Valid() {
+                    error!(
+                        "Fail to sync block #{} : Idty with invalid singature !",
+                        block_doc.number
+                    );
+                    panic!("Idty with invalid singature !");
+                }
+            }
+        }
+        let inner_hash = block_doc.inner_hash.expect(
+            "BlockchainModule : complete_network_block() : fatal error : block.inner_hash = None",
+        );
+        if block_doc.number.0 > 0 {
+            block_doc.compute_inner_hash();
+        }
+        let hash = block_doc.hash;
+        block_doc.compute_hash();
+        if block_doc.inner_hash.expect(
+            "BlockchainModule : complete_network_block() : fatal error : block.inner_hash = None",
+        ) == inner_hash
+        {
+            let nonce = block_doc.nonce;
+            block_doc.change_nonce(nonce);
+            if verif_level == SyncVerificationLevel::FastSync()
+                || block_doc.verify_signatures() == VerificationResult::Valid()
+                || block_doc.number.0 <= 1
+            {
+                if block_doc.hash == hash {
+                    trace!("Succes to complete_network_block #{}", block_doc.number.0);
+                    Ok(block_doc)
+                } else {
+                    warn!("BlockchainModule : Refuse Bloc : invalid hash !");
+                    Err(CompletedBlockError::InvalidHash())
+                }
+            } else {
+                warn!("BlockchainModule : Refuse Bloc : invalid signature !");
+                Err(CompletedBlockError::InvalidSig())
+            }
+        } else {
+            warn!("BlockchainModule : Refuse Bloc : invalid inner hash !");
+            debug!(
+                "BlockInnerFormat={}",
+                block_doc.generate_compact_inner_text()
+            );
+            Err(CompletedBlockError::InvalidInnerHash())
+        }
+    } else {
+        Err(CompletedBlockError::InvalidVersion())
+    }
+}
diff --git a/blockchain/stack_up_block.rs b/blockchain/stack_up_block.rs
new file mode 100644
index 0000000000000000000000000000000000000000..bcc7b15d316dc45d6afde7600e9d00a4fa84d934
--- /dev/null
+++ b/blockchain/stack_up_block.rs
@@ -0,0 +1,178 @@
+//  Copyright (C) 2018  The Duniter Project Developers.
+//
+// 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/>.
+
+extern crate duniter_crypto;
+extern crate duniter_dal;
+extern crate duniter_documents;
+extern crate duniter_wotb;
+
+use duniter_crypto::keys::ed25519;
+use duniter_dal::block::{DALBlock, WotEvent};
+use duniter_dal::writers::requests::DBWriteRequest;
+use duniter_documents::blockchain::v10::documents::BlockDocument;
+use duniter_documents::blockchain::Document;
+use duniter_wotb::{NodeId, WebOfTrust};
+
+use std::collections::HashMap;
+
+pub fn try_stack_up_completed_block<W: WebOfTrust + Sync>(
+    block: &BlockDocument,
+    wotb_index: &HashMap<ed25519::PublicKey, NodeId>,
+    wot: &W,
+) -> (bool, Vec<DBWriteRequest>, Vec<WotEvent>) {
+    debug!(
+        "BlockchainModule : try stack up complete block {}",
+        block.blockstamp()
+    );
+    let mut db_requests = Vec::new();
+    let mut wot_events = Vec::new();
+    let mut wot_copy: W = wot.clone();
+    let mut wotb_index_copy: HashMap<ed25519::PublicKey, NodeId> = wotb_index.clone();
+    let current_blockstamp = block.blockstamp();
+    let mut identities = HashMap::with_capacity(block.identities.len());
+    for identity in block.identities.clone() {
+        identities.insert(identity.issuers()[0], identity);
+    }
+    for joiner in block.joiners.clone() {
+        let pubkey = joiner.clone().issuers()[0];
+        if let Some(idty_doc) = identities.get(&pubkey) {
+            // Newcomer
+            let wotb_id = NodeId(wot_copy.size());
+            wot_events.push(WotEvent::AddNode(pubkey, wotb_id));
+            wot_copy.add_node();
+            wotb_index_copy.insert(pubkey, wotb_id);
+            db_requests.push(DBWriteRequest::CreateIdentity(
+                wotb_id,
+                current_blockstamp.clone(),
+                block.median_time,
+                idty_doc.clone(),
+            ));
+        } else {
+            // Renewer
+            let wotb_id = wotb_index_copy[&joiner.issuers()[0]];
+            wot_events.push(WotEvent::EnableNode(wotb_id));
+            wot_copy.set_enabled(wotb_id, true);
+            db_requests.push(DBWriteRequest::RenewalIdentity(
+                joiner.issuers()[0],
+                block.blockstamp(),
+                block.median_time,
+            ));
+        }
+    }
+    for active in block.actives.clone() {
+        let pubkey = active.issuers()[0];
+        if !identities.contains_key(&pubkey) {
+            let wotb_id = wotb_index_copy[&pubkey];
+            wot_events.push(WotEvent::EnableNode(wotb_id));
+            wot_copy.set_enabled(wotb_id, true);
+            db_requests.push(DBWriteRequest::RenewalIdentity(
+                pubkey,
+                block.blockstamp(),
+                block.median_time,
+            ));
+        }
+    }
+    for exclusion in block.excluded.clone() {
+        let wotb_id = wotb_index_copy[&exclusion];
+        wot_events.push(WotEvent::DisableNode(wotb_id));
+        wot_copy.set_enabled(wotb_id, false);
+        db_requests.push(DBWriteRequest::ExcludeIdentity(
+            wotb_id,
+            block.blockstamp(),
+            block.median_time,
+        ));
+    }
+    for revocation in block.revoked.clone() {
+        let compact_revoc = revocation.to_compact_document();
+        let wotb_id = wotb_index_copy[&compact_revoc.issuer];
+        wot_events.push(WotEvent::DisableNode(wotb_id));
+        wot_copy.set_enabled(wotb_id, false);
+        db_requests.push(DBWriteRequest::RevokeIdentity(
+            wotb_id,
+            block.blockstamp(),
+            block.median_time,
+        ));
+    }
+    for certification in block.certifications.clone() {
+        trace!("try_stack_up_completed_block: apply cert...");
+        let compact_cert = certification.to_compact_document();
+        let wotb_node_from = wotb_index_copy[&compact_cert.issuer];
+        let wotb_node_to = wotb_index_copy[&compact_cert.target];
+        wot_events.push(WotEvent::AddLink(wotb_node_from, wotb_node_to));
+        wot_copy.add_link(wotb_node_from, wotb_node_to);
+        db_requests.push(DBWriteRequest::CreateCert(
+            block.blockstamp(),
+            block.median_time,
+            compact_cert,
+        ));
+        trace!("try_stack_up_completed_block: apply cert...success.");
+    }
+
+    /*// Calculate the state of the wot
+        if !wot_events.is_empty() && verif_level != SyncVerificationLevel::FastSync() {
+            // Calculate sentries_count
+            let sentries_count = wot_copy.get_sentries(3).len();
+            // Calculate average_density
+            let average_density = calculate_average_density::<W>(&wot_copy);
+            let sentry_requirement =
+                get_sentry_requirement(block.members_count, G1_PARAMS.step_max);
+            // Calculate distances and connectivities
+            let (average_distance, distances, average_connectivity, connectivities) =
+                compute_distances::<W>(
+                    &wot_copy,
+                    sentry_requirement,
+                    G1_PARAMS.step_max,
+                    G1_PARAMS.x_percent,
+                );
+            // Calculate centralities and average_centrality
+            let centralities =
+                calculate_distance_stress_centralities::<W>(&wot_copy, G1_PARAMS.step_max);
+            let average_centrality =
+                (centralities.iter().sum::<u64>() as f64 / centralities.len() as f64) as usize;
+            // Register the state of the wot
+            duniter_dal::register_wot_state(
+                db,
+                &WotState {
+                    block_number: block.number.0,
+                    block_hash: block.hash.unwrap().to_string(),
+                    sentries_count,
+                    average_density,
+                    average_distance,
+                    distances,
+                    average_connectivity,
+                    connectivities: connectivities
+                        .iter()
+                        .map(|c| {
+                            if *c > *G1_CONNECTIVITY_MAX {
+                                *G1_CONNECTIVITY_MAX
+                            } else {
+                                *c
+                            }
+                        })
+                        .collect(),
+                    average_centrality,
+                    centralities,
+                },
+            );
+        }*/
+    // Write block in bdd
+    db_requests.push(DBWriteRequest::WriteBlock(DALBlock {
+        block: block.clone(),
+        fork: 0,
+        isolate: false,
+    }));
+
+    (true, db_requests, wot_events)
+}
diff --git a/blockchain/sync.rs b/blockchain/sync.rs
new file mode 100644
index 0000000000000000000000000000000000000000..3fc1000d64d2fbd60bafc8d5b36d3d13fc09b724
--- /dev/null
+++ b/blockchain/sync.rs
@@ -0,0 +1,504 @@
+//  Copyright (C) 2018  The Duniter Project Developers.
+//
+// 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/>.
+
+extern crate duniter_conf;
+extern crate duniter_crypto;
+extern crate duniter_dal;
+extern crate duniter_documents;
+extern crate duniter_message;
+extern crate duniter_module;
+extern crate duniter_network;
+extern crate pbr;
+extern crate serde;
+extern crate serde_json;
+extern crate sqlite;
+
+use self::pbr::ProgressBar;
+use duniter_crypto::keys::{ed25519, PublicKey, Signature};
+use duniter_dal::parsers::identities::parse_compact_identity;
+use duniter_dal::parsers::transactions::parse_transaction;
+//use duniter_dal::writers::requests::DBWriteRequest;
+use duniter_documents::blockchain::v10::documents::membership::MembershipType;
+use duniter_documents::blockchain::v10::documents::BlockDocument;
+use duniter_documents::{BlockHash, BlockId, Hash};
+use duniter_network::{NetworkBlock, NetworkBlockV10};
+use duniter_wotb::{NodeId, WebOfTrust};
+use std::collections::HashMap;
+use std::fs;
+use std::sync::mpsc;
+use std::thread;
+use std::time::SystemTime;
+
+use super::*;
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct BlockHeader {
+    pub number: BlockId,
+    pub hash: BlockHash,
+    pub issuer: ed25519::PublicKey,
+}
+
+enum ParserWorkMess {
+    TargetBlockstamp(Blockstamp),
+    NetworkBlock(NetworkBlock),
+    //DBWriteRequest(DBWriteRequest),
+    End(),
+}
+
+pub fn sync_ts(
+    conf: &DuniterConf,
+    current_blockstamp: &Blockstamp,
+    db_ts_path: PathBuf,
+    cautious: bool,
+) {
+    // get profile and currency and current_blockstamp
+    let profile = &conf.profile();
+    let currency = &conf.currency();
+    let mut current_blockstamp = current_blockstamp.clone();
+
+    // Copy blockchain db in ramfs
+    let db_path = duniter_conf::get_db_path(profile, currency, false);
+    if db_path.as_path().exists() {
+        info!("Copy blockchain DB in ramfs...");
+        fs::copy(db_path, format!("/dev/shm/{}_durs.db", profile))
+            .expect("Fatal error : fail to copy DB in ramfs !");
+    }
+
+    // Get wot path
+    let wot_path = duniter_conf::get_wot_path(profile.clone().to_string(), currency);
+
+    // Open wot file
+    let (mut wot, mut _wot_blockstamp): (RustyWebOfTrust, Blockstamp) =
+        if wot_path.as_path().exists() {
+            match WOT_FILE_FORMATER.from_file(
+                wot_path.as_path().to_str().unwrap(),
+                duniter_dal::constants::G1_PARAMS.sig_stock as usize,
+            ) {
+                Ok((wot, binary_blockstamp)) => match str::from_utf8(&binary_blockstamp) {
+                    Ok(str_blockstamp) => (wot, Blockstamp::from_string(str_blockstamp).unwrap()),
+                    Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
+                },
+                Err(e) => panic!("Fatal Error : fail te read wot file : {:?}", e),
+            }
+        } else {
+            (
+                RustyWebOfTrust::new(duniter_dal::constants::G1_PARAMS.sig_stock as usize),
+                Blockstamp::default(),
+            )
+        };
+
+    // Get verification level
+    let verif_level = if cautious {
+        println!("Start cautious sync...");
+        info!("Start cautious sync...");
+        SyncVerificationLevel::Cautious()
+    } else {
+        println!("Start fast sync...");
+        info!("Start fast sync...");
+        SyncVerificationLevel::FastSync()
+    };
+
+    // Create sync_thread channel
+    let (sender_sync_thread, recv_sync_thread) = mpsc::channel();
+
+    // Lauch ts thread
+    thread::spawn(move || {
+        // open db_ts
+        let ts_db = sqlite::open(db_ts_path.as_path())
+            .expect("Fatal error : fail to open duniter-ts database !");
+        info!("sync_ts : Success to open duniter-ts database.");
+
+        // Get ts current blockstamp
+        debug!("Get ts-db current blockstamp...");
+        let mut cursor: sqlite::Cursor = ts_db
+            .prepare("SELECT hash, number FROM block WHERE fork=? ORDER BY number DESC LIMIT 1;")
+            .expect("Request SQL get_ts_current_block is wrong !")
+            .cursor();
+        cursor
+            .bind(&[sqlite::Value::Integer(0)])
+            .expect("Fail to get ts current block !");
+        let current_ts_blockstamp = if let Some(row) = cursor.next().expect("cursor error") {
+            let block_id = BlockId(row[1]
+                .as_integer()
+                .expect("Fail to parse current ts blockstamp !")
+                as u32);
+            let block_hash = BlockHash(
+                Hash::from_hex(
+                    row[0]
+                        .as_string()
+                        .expect("Fail to parse current ts blockstamp !"),
+                ).expect("Fail to parse current ts blockstamp !"),
+            );
+            Blockstamp {
+                id: block_id,
+                hash: block_hash,
+            }
+        } else {
+            panic!("Fail to get current ts blockstamp !");
+        };
+        debug!("Success to ts-db current blockstamp.");
+
+        // Send ts current blockstamp
+        sender_sync_thread
+            .send(ParserWorkMess::TargetBlockstamp(current_ts_blockstamp))
+            .expect("Fatal error : sync_thread unrechable !");
+
+        // Get genesis block
+        if current_blockstamp == Blockstamp::default() {
+            let mut cursor: sqlite::Cursor = ts_db
+                    .prepare(
+                        "SELECT hash, inner_hash, signature, currency, issuer, parameters, previousHash,
+                            previousIssuer, version, membersCount, monetaryMass, medianTime, dividend, unitbase,
+                            time, powMin, number, nonce, transactions, certifications, identities, joiners,
+                            actives, leavers, revoked, excluded, issuersFrame, issuersFrameVar, issuersCount
+                            FROM block WHERE fork=0 AND number=? LIMIT 1;",
+                    )
+                    .expect("Request SQL get_ts_blocks is wrong !")
+                    .cursor();
+            cursor
+                .bind(&[sqlite::Value::Integer(0)])
+                .expect("Fail to get genesis block !");
+            if let Some(row) = cursor.next().expect("cursor error") {
+                sender_sync_thread
+                    .send(ParserWorkMess::NetworkBlock(parse_ts_block(row)))
+                    .expect("Fatal error : sync_thread unrechable !");
+            }
+        }
+
+        // Request ts blocks
+        let mut cursor: sqlite::Cursor = ts_db
+                .prepare(
+                    "SELECT hash, inner_hash, signature, currency, issuer, parameters, previousHash,
+                        previousIssuer, version, membersCount, monetaryMass, medianTime, dividend, unitbase,
+                        time, powMin, number, nonce, transactions, certifications, identities, joiners,
+                        actives, leavers, revoked, excluded, issuersFrame, issuersFrameVar, issuersCount
+                        FROM block WHERE fork=? AND number > ? ORDER BY number ASC;",
+                )
+                .expect("Request SQL get_ts_blocks is wrong !")
+                .cursor();
+        cursor
+            .bind(&[
+                sqlite::Value::Integer(0),
+                sqlite::Value::Integer(current_blockstamp.id.0 as i64),
+            ])
+            .expect("0");
+
+        // Parse ts blocks
+        //let mut ts_blocks = Vec::with_capacity(current_ts_blockstamp.id.0 + 1);
+        //let pool = ThreadPool::new(4);
+        while let Some(row) = cursor.next().expect("cursor error") {
+            //let sender_sync_thread_clone = sender_sync_thread.clone();
+            //pool.execute(move || {
+            sender_sync_thread
+                .send(ParserWorkMess::NetworkBlock(parse_ts_block(row)))
+                .expect("Fatal error : sync_thread unrechable !");
+            //});
+        }
+        sender_sync_thread
+            .send(ParserWorkMess::End())
+            .expect("Fatal error : sync_thread unrechable !");
+    });
+
+    // Get target blockstamp
+    let target_blockstamp =
+        if let Ok(ParserWorkMess::TargetBlockstamp(target_blockstamp)) = recv_sync_thread.recv() {
+            target_blockstamp
+        } else {
+            panic!("Fatal error : no TargetBlockstamp !")
+        };
+
+    // Instanciate blockchain module
+    let blockchain_module =
+        BlockchainModule::load_blockchain_conf(conf, RequiredKeysContent::None(), true);
+
+    // Node is already synchronized ?
+    if target_blockstamp.id.0 < current_blockstamp.id.0 {
+        println!("Your duniter-rs node is already synchronized.");
+        return;
+    }
+
+    // Get wotb index
+    let mut wotb_index: HashMap<ed25519::PublicKey, NodeId> =
+        DALIdentity::get_wotb_index(&blockchain_module.db);
+
+    // Start sync
+    let sync_start_time = SystemTime::now();
+    println!(
+        "Sync from #{} to #{} :",
+        current_blockstamp.id.0, target_blockstamp.id.0
+    );
+    info!(
+        "Sync from #{} to #{}...",
+        current_blockstamp.id.0, target_blockstamp.id.0
+    );
+    let mut pb = ProgressBar::new((target_blockstamp.id.0 + 1 - current_blockstamp.id.0).into());
+
+    // Apply blocks
+    while let Ok(ParserWorkMess::NetworkBlock(network_block)) = recv_sync_thread.recv() {
+        // Complete block
+        let block_doc = match complete_network_block(
+            &blockchain_module.currency.to_string(),
+            None,
+            &network_block,
+            SyncVerificationLevel::FastSync(),
+        ) {
+            Ok(block_doc) => block_doc,
+            Err(_) => panic!("Receive wrong block, please reset data and resync !"),
+        };
+        // Apply block
+        let (success, db_requests, new_wot_events) =
+            try_stack_up_completed_block::<RustyWebOfTrust>(&block_doc, &wotb_index, &wot);
+
+        blockchain_module.try_stack_up_block::<RustyWebOfTrust>(
+            &network_block,
+            &wotb_index,
+            &wot,
+            verif_level,
+        );
+        if success {
+            current_blockstamp = network_block.blockstamp();
+            debug!("Apply db requests...");
+            // Apply db requests
+            db_requests
+                .iter()
+                .map(|req| req.apply(&conf.currency().to_string(), &blockchain_module.db))
+                .collect::<()>();
+            debug!("Finish to apply db requests.");
+            // Apply WotEvents
+            if !new_wot_events.is_empty() {
+                for wot_event in new_wot_events {
+                    match wot_event {
+                        WotEvent::AddNode(pubkey, wotb_id) => {
+                            wot.add_node();
+                            wotb_index.insert(pubkey, wotb_id);
+                        }
+                        WotEvent::RemNode(pubkey) => {
+                            wot.rem_node();
+                            wotb_index.remove(&pubkey);
+                        }
+                        WotEvent::AddLink(source, target) => {
+                            wot.add_link(source, target);
+                        }
+                        WotEvent::RemLink(source, target) => {
+                            wot.rem_link(source, target);
+                        }
+                        WotEvent::EnableNode(wotb_id) => {
+                            wot.set_enabled(wotb_id, true);
+                        }
+                        WotEvent::DisableNode(wotb_id) => {
+                            wot.set_enabled(wotb_id, false);
+                        }
+                    }
+                }
+                if current_blockstamp.id.0 > target_blockstamp.id.0 - 100 {
+                    // Save wot file
+                    WOT_FILE_FORMATER
+                        .to_file(
+                            &wot,
+                            current_blockstamp.to_string().as_bytes(),
+                            wot_path.as_path().to_str().unwrap(),
+                        )
+                        .expect("Fatal Error: Fail to write wotb in file !");
+                }
+            }
+            pb.inc();
+            debug!("Success to apply block #{}", current_blockstamp.id.0);
+            if current_blockstamp.id.0 >= target_blockstamp.id.0 {
+                if current_blockstamp == target_blockstamp {
+                    // Sync completed
+                    break;
+                } else {
+                    panic!("Fatal Error : we get a fork, please reset data and sync again !");
+                }
+            }
+        } else {
+            panic!(
+                "Fatal error : fail to stack up block #{}",
+                current_blockstamp.id.0 + 1
+            )
+        }
+    }
+
+    // Copy memory db to real db
+    info!("Save blockchain DB in profile folder...");
+    fs::copy(
+        format!("/dev/shm/{}_durs.db", profile),
+        duniter_conf::get_db_path(profile, currency, false).as_path(),
+    ).expect("Fatal error : fail to copy DB in profile folder !");
+
+    // Remove memory db
+    fs::remove_file(format!("/dev/shm/{}_durs.db", profile))
+        .expect("Fatal error : fail to remove memory DB !");
+
+    // Print sync duration
+    let sync_duration = SystemTime::now().duration_since(sync_start_time).unwrap();
+    println!(
+        "Sync {} blocks in {}m {}s.",
+        current_blockstamp.id.0,
+        sync_duration.as_secs() / 60,
+        sync_duration.as_secs() % 60,
+    );
+}
+
+pub fn parse_ts_block(row: &[sqlite::Value]) -> NetworkBlock {
+    // Parse block
+    let current_header = BlockHeader {
+        number: BlockId(row[16].as_integer().expect("Fail to parse block number") as u32),
+        hash: BlockHash(
+            Hash::from_hex(row[0].as_string().expect("Fail to parse block hash"))
+                .expect("Fail to parse block hash (2)"),
+        ),
+        issuer: PublicKey::from_base58(row[4].as_string().expect("Fail to parse block issuer"))
+            .expect("Failt to parse block issuer (2)"),
+    };
+    let previous_header = if current_header.number.0 > 0 {
+        Some(BlockHeader {
+            number: BlockId(current_header.number.0 - 1),
+            hash: BlockHash(
+                Hash::from_hex(
+                    row[6]
+                        .as_string()
+                        .expect("Fail to parse block previous hash"),
+                ).expect("Fail to parse block previous hash (2)"),
+            ),
+            issuer: PublicKey::from_base58(
+                row[7]
+                    .as_string()
+                    .expect("Fail to parse previous block issuer"),
+            ).expect("Fail to parse previous block issuer (2)"),
+        })
+    } else {
+        None
+    };
+    let currency = row[3].as_string().expect("Fail to parse currency");
+    let dividend = match row[12].as_integer() {
+        Some(dividend) => Some(dividend as usize),
+        None => None,
+    };
+    let json_identities: serde_json::Value = serde_json::from_str(
+        row[20].as_string().expect("Fail to parse block identities"),
+    ).expect("Fail to parse block identities (2)");
+    let mut identities = Vec::new();
+    for raw_idty in json_identities
+        .as_array()
+        .expect("Fail to parse block identities (3)")
+    {
+        identities
+            .push(parse_compact_identity(&currency, &raw_idty).expect("Fail to parse block idty"));
+    }
+    let json_txs: serde_json::Value = serde_json::from_str(
+        row[18].as_string().expect("Fail to parse block txs"),
+    ).expect("Fail to parse block txs (2)");
+    let mut transactions = Vec::new();
+    for json_tx in json_txs.as_array().expect("Fail to parse block txs (3)") {
+        transactions.push(parse_transaction(currency, &json_tx).expect("Fail to parse block tx"));
+    }
+    let previous_hash = match previous_header.clone() {
+        Some(previous_header_) => previous_header_.hash.0,
+        None => Hash::default(),
+    };
+    let previous_issuer = match previous_header {
+        Some(previous_header_) => Some(previous_header_.issuer),
+        None => None,
+    };
+    let excluded: serde_json::Value = serde_json::from_str(
+        row[25].as_string().expect("Fail to parse excluded"),
+    ).expect("Fail to parse excluded (2)");
+    let uncompleted_block_doc = BlockDocument {
+        nonce: row[17].as_integer().expect("Fail to parse nonce") as u64,
+        number: current_header.number,
+        pow_min: row[15].as_integer().expect("Fail to parse pow_min") as usize,
+        time: row[14].as_integer().expect("Fail to parse time") as u64,
+        median_time: row[11].as_integer().expect("Fail to parse median_time") as u64,
+        members_count: row[9].as_integer().expect("Fail to parse members_count") as usize,
+        monetary_mass: row[10]
+            .as_string()
+            .expect("Fail to parse monetary_mass")
+            .parse()
+            .expect("Fail to parse monetary_mass (2)"),
+        unit_base: row[13].as_integer().expect("Fail to parse unit_base") as usize,
+        issuers_count: row[28].as_integer().expect("Fail to parse issuers_count") as usize,
+        issuers_frame: row[26].as_integer().expect("Fail to parse issuers_frame") as isize,
+        issuers_frame_var: row[27]
+            .as_integer()
+            .expect("Fail to parse issuers_frame_var") as isize,
+        currency: String::from(currency),
+        issuers: vec![
+            PublicKey::from_base58(row[4].as_string().expect("Fail to parse issuer"))
+                .expect("Fail to parse issuer '2)"),
+        ],
+        signatures: vec![
+            Signature::from_base64(row[2].as_string().expect("Fail to parse signature"))
+                .expect("Fail to parse signature (2)"),
+        ],
+        hash: Some(current_header.hash),
+        parameters: None,
+        previous_hash,
+        previous_issuer,
+        inner_hash: Some(
+            Hash::from_hex(row[1].as_string().expect("Fail to parse block inner_hash"))
+                .expect("Fail to parse block inner_hash (2)"),
+        ),
+        dividend: dividend,
+        identities,
+        joiners: duniter_dal::parsers::memberships::parse_memberships(
+            currency,
+            MembershipType::In(),
+            row[21].as_string().expect("Fail to parse joiners"),
+        ).expect("Fail to parse joiners (2)"),
+        actives: duniter_dal::parsers::memberships::parse_memberships(
+            currency,
+            MembershipType::In(),
+            row[22].as_string().expect("Fail to parse actives"),
+        ).expect("Fail to parse actives (2)"),
+        leavers: duniter_dal::parsers::memberships::parse_memberships(
+            currency,
+            MembershipType::In(),
+            row[23].as_string().expect("Fail to parse leavers"),
+        ).expect("Fail to parse leavers (2)"),
+        revoked: Vec::new(),
+        excluded: excluded
+            .as_array()
+            .expect("Fail to parse excluded (3)")
+            .to_vec()
+            .into_iter()
+            .map(|e| {
+                PublicKey::from_base58(e.as_str().expect("Fail to parse excluded (4)"))
+                    .expect("Fail to parse excluded (5)")
+            })
+            .collect(),
+        certifications: Vec::new(),
+        transactions,
+        inner_hash_and_nonce_str: String::new(),
+    };
+    let revoked: serde_json::Value = serde_json::from_str(
+        row[24].as_string().expect("Fail to parse revoked"),
+    ).expect("Fail to parse revoked (2)");
+    let certifications: serde_json::Value = serde_json::from_str(
+        row[19].as_string().expect("Fail to parse certifications"),
+    ).expect("Fail to parse certifications (2)");
+    // return NetworkBlock
+    NetworkBlock::V10(Box::new(NetworkBlockV10 {
+        uncompleted_block_doc,
+        revoked: revoked
+            .as_array()
+            .expect("Fail to parse revoked (3)")
+            .to_vec(),
+        certifications: certifications
+            .as_array()
+            .expect("Fail to parse certifications (3)")
+            .to_vec(),
+    }))
+}
diff --git a/conf/Cargo.toml b/conf/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..433ccf6dd0c3e14b17ba95a83574c6a45fb612de
--- /dev/null
+++ b/conf/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "duniter-conf"
+version = "0.1.0"
+authors = ["librelois <elois@ifee.fr>"]
+description = "Configuration module for the Duniter project."
+license = "AGPL-3.0"
+
+[lib]
+path = "lib.rs"
+
+[dependencies]
+rand = "0.4.2"
+serde = "1.0.24"
+serde_derive = "1.0.24"
+serde_json = "1.0.9"
+duniter-crypto = { path = "../crypto" }
+duniter-module = { path = "../module" }
+
+[features]
+# Treat warnings as a build error.
+strict = []
\ No newline at end of file
diff --git a/conf/lib.rs b/conf/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..5994cc80fa3148feeca9ad002ccf991752e534c4
--- /dev/null
+++ b/conf/lib.rs
@@ -0,0 +1,380 @@
+//  Copyright (C) 2018  The Duniter Project Developers.
+//
+// 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/>.
+
+//! Defined the few global types used by all modules,
+//! as well as the DuniterModule trait that all modules must implement.
+
+#![cfg_attr(feature = "strict", deny(warnings))]
+#![deny(
+    missing_docs, missing_debug_implementations, missing_copy_implementations, trivial_casts,
+    trivial_numeric_casts, unsafe_code, unstable_features, unused_import_braces,
+    unused_qualifications
+)]
+
+#[macro_use]
+extern crate serde_json;
+
+extern crate duniter_crypto;
+extern crate duniter_module;
+extern crate rand;
+extern crate serde;
+use duniter_crypto::keys::{ed25519, KeyPair, PrivateKey, PublicKey};
+use duniter_module::{Currency, DuniterConf, DuniterConfV1, RequiredKeys, RequiredKeysContent};
+use rand::Rng;
+use serde::ser::{Serialize, SerializeStruct, Serializer};
+use std::env;
+use std::fs;
+use std::fs::File;
+use std::io::prelude::*;
+use std::path::PathBuf;
+
+static USER_DATAS_FOLDER: &'static str = "durs-dev";
+
+/// If no currency is specified by the user, is the currency will be chosen by default
+pub static DEFAULT_CURRRENCY: &'static str = "g1";
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+/// Keypairs filled in by the user (via a file or by direct entry in the terminal).
+pub struct DuniterKeyPairs {
+    /// Keypair used by the node to sign its communications with other nodes. This keypair is mandatory, if it's not filled in, a random keypair is generated.
+    pub network_keypair: ed25519::KeyPair,
+    /// Keypair used to sign the blocks forged by this node. If this keypair is'nt filled in, the node will not calculate blocks.
+    pub member_keypair: Option<ed25519::KeyPair>,
+}
+
+impl Serialize for DuniterKeyPairs {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let member_sec = if let Some(member_keypair) = self.member_keypair {
+            member_keypair.private_key().to_string()
+        } else {
+            String::from("")
+        };
+        let member_pub = if let Some(member_keypair) = self.member_keypair {
+            member_keypair.pubkey.to_string()
+        } else {
+            String::from("")
+        };
+        let mut state = serializer.serialize_struct("DuniterKeyPairs", 4)?;
+        state.serialize_field(
+            "network_sec",
+            &self.network_keypair.private_key().to_string().as_str(),
+        )?;
+        state.serialize_field(
+            "network_pub",
+            &self.network_keypair.pubkey.to_string().as_str(),
+        )?;
+        state.serialize_field("member_sec", member_sec.as_str())?;
+        state.serialize_field("member_pub", member_pub.as_str())?;
+        state.end()
+    }
+}
+
+impl DuniterKeyPairs {
+    /// Returns only the keys indicated as required
+    pub fn get_required_keys_content(
+        required_keys: RequiredKeys,
+        keypairs: DuniterKeyPairs,
+    ) -> RequiredKeysContent<ed25519::KeyPair> {
+        match required_keys {
+            RequiredKeys::MemberKeyPair() => {
+                RequiredKeysContent::MemberKeyPair(keypairs.member_keypair)
+            }
+            RequiredKeys::MemberPublicKey() => {
+                RequiredKeysContent::MemberPublicKey(if let Some(keys) = keypairs.member_keypair {
+                    Some(keys.pubkey)
+                } else {
+                    None
+                })
+            }
+            RequiredKeys::NetworkKeyPair() => {
+                RequiredKeysContent::NetworkKeyPair(keypairs.network_keypair)
+            }
+            RequiredKeys::NetworkPublicKey() => {
+                RequiredKeysContent::NetworkPublicKey(keypairs.network_keypair.pubkey)
+            }
+            RequiredKeys::None() => RequiredKeysContent::None(),
+        }
+    }
+}
+
+fn _use_json_macro() -> serde_json::Value {
+    json!({})
+}
+
+fn generate_random_keypair() -> ed25519::KeyPair {
+    let mut rng = rand::thread_rng();
+    let generator = ed25519::KeyPairFromSaltedPasswordGenerator::with_default_parameters();
+    generator.generate(&[rng.gen::<u8>(); 8], &[rng.gen::<u8>(); 8])
+}
+
+fn generate_random_node_id() -> u32 {
+    let mut rng = rand::thread_rng();
+    rng.gen::<u32>()
+}
+
+/// Return the user datas folder name
+pub fn get_user_datas_folder() -> &'static str {
+    USER_DATAS_FOLDER
+}
+
+/// Returns the path to the folder containing the user data of the running profile
+pub fn datas_path(profile: &str, currency: &Currency) -> PathBuf {
+    let mut datas_path = match env::home_dir() {
+        Some(path) => path,
+        None => panic!("Impossible to get your home dir!"),
+    };
+    datas_path.push(".config/");
+    datas_path.push(USER_DATAS_FOLDER);
+    datas_path.push(profile);
+    datas_path.push(currency.to_string());
+    datas_path
+}
+
+/// Load configuration.
+pub fn load_conf(profile: &str) -> (DuniterConf, DuniterKeyPairs) {
+    // Define and create datas directory if not exist
+    let mut profile_path = match env::home_dir() {
+        Some(path) => path,
+        None => panic!("Impossible to get your home dir !"),
+    };
+    profile_path.push(".config/");
+    if !profile_path.as_path().exists() {
+        fs::create_dir(profile_path.as_path()).expect("Impossible to create ~/.config dir !");
+    }
+    profile_path.push(USER_DATAS_FOLDER);
+    if !profile_path.as_path().exists() {
+        fs::create_dir(profile_path.as_path()).expect(&format!(
+            "Impossible to create ~/.config/{} dir !",
+            USER_DATAS_FOLDER
+        ));
+    }
+    profile_path.push(profile);
+    if !profile_path.as_path().exists() {
+        fs::create_dir(profile_path.as_path()).expect("Impossible to create your profile dir !");
+    }
+
+    // Load conf
+    let (conf, keypairs) = load_conf_at_path(profile, &profile_path);
+
+    // Create currency dir
+    profile_path.push(conf.currency().to_string());
+    if !profile_path.as_path().exists() {
+        fs::create_dir(profile_path.as_path()).expect("Impossible to create currency dir !");
+    }
+
+    // Return conf and keypairs
+    (conf, keypairs)
+}
+
+/// Load configuration. at specified path
+pub fn load_conf_at_path(profile: &str, profile_path: &PathBuf) -> (DuniterConf, DuniterKeyPairs) {
+    // Default conf
+    let mut conf = DuniterConfV1 {
+        profile: String::from(profile),
+        currency: Currency::Str(DEFAULT_CURRRENCY.to_string()),
+        my_node_id: generate_random_node_id(),
+        modules: serde_json::Value::Null,
+    };
+
+    // Get KeyPairs
+    let mut keypairs_path = profile_path.clone();
+    keypairs_path.push("keypairs.json");
+    let keypairs = if keypairs_path.as_path().exists() {
+        if let Ok(mut f) = File::open(keypairs_path.as_path()) {
+            let mut contents = String::new();
+            if f.read_to_string(&mut contents).is_ok() {
+                let json_conf: serde_json::Value =
+                    serde_json::from_str(&contents).expect("Conf: Fail to parse keypairs file !");
+                if let Some(network_sec) = json_conf.get("network_sec") {
+                    if let Some(network_pub) = json_conf.get("network_pub") {
+                        let network_sec = network_sec
+                            .as_str()
+                            .expect("Conf: Fail to parse keypairs file !");
+                        let network_pub = network_pub
+                            .as_str()
+                            .expect("Conf: Fail to parse keypairs file !");
+                        DuniterKeyPairs {
+                            network_keypair: ed25519::KeyPair {
+                                privkey: PrivateKey::from_base58(network_sec)
+                                    .expect("conf : keypairs file : fail to parse network_sec !"),
+                                pubkey: PublicKey::from_base58(network_pub)
+                                    .expect("conf : keypairs file : fail to parse network_pub !"),
+                            },
+                            member_keypair: None,
+                        }
+                    } else {
+                        panic!("Fatal error : keypairs file wrong format : no field salt !")
+                    }
+                } else {
+                    panic!("Fatal error : keypairs file wrong format : no field password !")
+                }
+            } else {
+                panic!("Fail to read keypairs file !");
+            }
+        } else {
+            panic!("Fail to open keypairs file !");
+        }
+    } else {
+        // Create keypairs file with random keypair
+        let keypairs = DuniterKeyPairs {
+            network_keypair: generate_random_keypair(),
+            member_keypair: None,
+        };
+        write_keypairs_file(&keypairs_path, &keypairs)
+            .expect("Fatal error : fail to write default keypairs file !");
+        keypairs
+    };
+
+    // Open conf file
+    let mut conf_path = profile_path.clone();
+    conf_path.push("conf.json");
+    if conf_path.as_path().exists() {
+        if let Ok(mut f) = File::open(conf_path.as_path()) {
+            let mut contents = String::new();
+            if f.read_to_string(&mut contents).is_ok() {
+                let json_conf: serde_json::Value =
+                    serde_json::from_str(&contents).expect("Conf: Fail to parse conf file !");
+                if let Some(currency) = json_conf.get("currency") {
+                    conf.currency = Currency::Str(
+                        currency
+                            .as_str()
+                            .expect("Conf: fail to parse currency field !")
+                            .to_string(),
+                    );
+                };
+                if let Some(node_id) = json_conf.get("node_id") {
+                    conf.my_node_id =
+                        u32::from_str_radix(
+                            node_id
+                                .as_str()
+                                .expect("Conf : fail to parse node_id field !"),
+                            16,
+                        ).expect("Fail to load conf: node_id must be an hexadecimal number !");
+                };
+                if let Some(modules_conf) = json_conf.get("modules") {
+                    conf.modules = modules_conf.clone();
+                };
+            }
+        } else {
+            panic!("Fail to open conf file !");
+        }
+    } else {
+        // Create conf file with default conf
+        write_conf_file(&conf_path, &DuniterConf::V1(conf.clone()))
+            .expect("Fatal error : fail to write default conf file !");
+    }
+
+    // Return conf and keypairs
+    (DuniterConf::V1(conf), keypairs)
+}
+
+/// Save keypairs in profile folder
+pub fn write_keypairs_file(
+    file_path: &PathBuf,
+    keypairs: &DuniterKeyPairs,
+) -> Result<(), std::io::Error> {
+    let mut f = try!(File::create(file_path.as_path()));
+    try!(
+        f.write_all(
+            serde_json::to_string_pretty(keypairs)
+                .expect("Fatal error : fail to write default keypairs file !")
+                .as_bytes()
+        )
+    );
+    try!(f.sync_all());
+    Ok(())
+}
+
+/// Save configuration in profile folder
+pub fn write_conf_file(file_path: &PathBuf, conf: &DuniterConf) -> Result<(), std::io::Error> {
+    match *conf {
+        DuniterConf::V1(ref conf_v1) => {
+            let mut f = try!(File::create(file_path.as_path()));
+            try!(
+                f.write_all(
+                    serde_json::to_string_pretty(conf_v1)
+                        .expect("Fatal error : fail to write default conf file !")
+                        .as_bytes()
+                )
+            );
+            try!(f.sync_all());
+        }
+        _ => {
+            panic!("Fatal error : Conf version is not supported !");
+        }
+    }
+    Ok(())
+}
+
+/// Returns the path to the database containing the blockchain
+pub fn get_db_path(profile: &str, currency: &Currency, sync: bool) -> PathBuf {
+    if sync {
+        let mut db_path = PathBuf::new();
+        let mut db_name = String::from(profile);
+        db_name.push_str("_durs.db");
+        db_path.push("/dev/shm");
+        db_path.push(db_name);
+        db_path
+    } else {
+        let mut db_path = datas_path(profile, &currency);
+        db_path.push("blockchain.db");
+        db_path
+    }
+}
+
+/// Returns the path to the binary file containing the state of the web of trust
+pub fn get_wot_path(profile: String, currency: &Currency) -> PathBuf {
+    let mut wot_path = match env::home_dir() {
+        Some(path) => path,
+        None => panic!("Impossible to get your home dir!"),
+    };
+    wot_path.push(".config/");
+    wot_path.push(USER_DATAS_FOLDER);
+    wot_path.push(profile);
+    wot_path.push(currency.to_string());
+    wot_path.push("wot.bin");
+    wot_path
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn load_conf_file() {
+        let (conf, _keys) = load_conf_at_path("test", &PathBuf::from("./test/"));
+        assert_eq!(
+            conf.modules()
+                .get("ws2p")
+                .expect("Not found ws2p conf")
+                .clone(),
+            json!({
+                "sync_peers": [{
+                    "pubkey": "D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx",
+                    "ws2p_endpoints": ["WS2P c1c39a0a i3.ifee.fr 80 /ws2p"]
+                },{
+                    "pubkey": "BoZP6aqtErHjiKLosLrQxBafi4ATciyDZQ6XRQkNefqG",
+                    "ws2p_endpoints": ["WS2P 15af24db g1.ifee.fr 80 /ws2p"]
+                },{
+                    "pubkey": "7v2J4badvfWQ6qwRdCwhhJfAsmKwoxRUNpJHiJHj7zef",
+                    "ws2p_endpoints": ["WS2P b48824f0 g1.monnaielibreoccitanie.org 80 /ws2p"]
+                }]
+            })
+        );
+    }
+}
diff --git a/conf/test/conf.json b/conf/test/conf.json
new file mode 100644
index 0000000000000000000000000000000000000000..bf8541562facac5e99e634e0267272b2b254e6a4
--- /dev/null
+++ b/conf/test/conf.json
@@ -0,0 +1,19 @@
+{
+    "currency": "g1",
+    "node_id": "357fb4b",
+    "modules": {
+      "tui": null,
+      "ws2p": {
+        "sync_peers": [{
+            "pubkey": "D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx",
+            "ws2p_endpoints": ["WS2P c1c39a0a i3.ifee.fr 80 /ws2p"]
+        },{
+            "pubkey": "BoZP6aqtErHjiKLosLrQxBafi4ATciyDZQ6XRQkNefqG",
+            "ws2p_endpoints": ["WS2P 15af24db g1.ifee.fr 80 /ws2p"]
+        },{
+            "pubkey": "7v2J4badvfWQ6qwRdCwhhJfAsmKwoxRUNpJHiJHj7zef",
+            "ws2p_endpoints": ["WS2P b48824f0 g1.monnaielibreoccitanie.org 80 /ws2p"]
+        }]
+      }
+    }
+}
diff --git a/conf/test/keypairs.json b/conf/test/keypairs.json
new file mode 100644
index 0000000000000000000000000000000000000000..e52b2647843d9ad42b1b4ab4b11e39f39680d460
--- /dev/null
+++ b/conf/test/keypairs.json
@@ -0,0 +1,6 @@
+{
+	"network_sec": "RcrnPyfgEaet2VqQvLeBsgKvfaoUqinQnCooP9bFoDr1W1Focos351r4xBee6Hj2RaV6RRV8PxpdMR6hVVhNNDb",
+	"network_pub": "39SvsGzGZjdCh1wFS1QKdCR1ZB6rTxRbTZRW8sY5fhq7",
+	"member_sec": "",
+	"member_pub": ""
+  }
\ No newline at end of file
diff --git a/core/Cargo.toml b/core/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..d2a02d380ed74b4606c89ade69283da5c741b9b4
--- /dev/null
+++ b/core/Cargo.toml
@@ -0,0 +1,32 @@
+[package]
+name = "duniter-core"
+version = "0.1.0"
+authors = ["librelois <elois@ifee.fr>"]
+description = "Duniter-rs core."
+license = "AGPL-3.0"
+
+[lib]
+path = "lib.rs"
+
+[dependencies]
+clap = {version = "2.31.2", features = ["yaml"]}
+duniter-blockchain = { path = "../blockchain" }
+duniter-conf = { path = "../conf" }
+duniter-crypto = { path = "../crypto" }
+duniter-message =  { path = "../message" }
+duniter-module = { path = "../module" }
+lazy_static = "1.0.0"
+log = "0.4.1"
+rand = "0.4.2"
+regex = "0.2.6"
+rust-crypto = "0.2.36"
+serde = "1.0.24"
+serde_derive = "1.0.24"
+serde_json = "1.0.9"
+simplelog = "0.5.1"
+sqlite = "0.23.9"
+threadpool = "1.7.1"
+
+[features]
+# Treat warnings as a build error.
+strict = []
\ No newline at end of file
diff --git a/core/cli/en.yml b/core/cli/en.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6c0c983b81520e9ba1a7287c7566371db856a989
--- /dev/null
+++ b/core/cli/en.yml
@@ -0,0 +1,64 @@
+name: durs
+version: "0.1.0"
+author: Elois L. <elois@duniter.org>
+about: Rust implementation of Duniter
+args:
+    - profile:
+        short: p
+        long: profile
+        value_name: CUSTOM_PROFILE
+        help: Set a custom datas folder
+        takes_value: true
+    - logs:
+        short: l
+        long : logs
+        value_name: LOGS_LEVEL
+        takes_value: true
+        possible_values: ["e", "w", "i", "d", "t", "error", "warn", "info", "debug", "trace"]
+        help: Set the level of logs verbosity
+        long_help: "Set the level of logs verbosity :\n
+          error : print serious errors\n
+          warn : print hazardous situations\n
+          info : default level\n
+          debug : print a lot of debug informations\n
+          trace : print all traces (highly verbose)"
+subcommands:
+    - start:
+        about: start duniter server
+        version: "0.1.0"
+        author: Elois L. <elois@duniter.org>
+    - sync_ts:
+        about: synchronization via a duniter-ts database
+        version: "0.1.0"
+        author: Elois L. <elois@duniter.org>
+        args:
+            - TS_PROFILE:
+                help: Set the ts profile to use
+                index: 1
+            - cautious:
+                short: c
+                long: cautious
+                help: cautious mode (check all protocol rules, very slow)
+    - msync_ts:
+        about: synchronization in memory mode via a duniter-ts database
+        version: "0.1.0"
+        author: Elois L. <elois@duniter.org>
+        args:
+            - TS_PROFILE:
+                help: Set the ts profile to use
+                index: 1
+            - cautious:
+                short: c
+                long: cautious
+                help: cautious mode (check all protocol rules, very slow)
+    - reset:
+        about: reset data or conf or all
+        version: "0.1.0"
+        author: Elois L. <elois@duniter.org>
+        args:
+            - DATAS_TYPE:
+                help : choose type datas to reset
+                index: 1
+                possible_values: ["data","conf","all"]
+                required: true
+            
\ No newline at end of file
diff --git a/core/lib.rs b/core/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ac340eb402005f04855a7925dfec52f2f7f37e7f
--- /dev/null
+++ b/core/lib.rs
@@ -0,0 +1,440 @@
+//  Copyright (C) 2018  The Duniter Project Developers.
+//
+// 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/>.
+
+//! Crate containing Duniter-rust core.
+
+#![cfg_attr(feature = "strict", deny(warnings))]
+#![deny(
+    missing_docs, missing_debug_implementations, missing_copy_implementations, trivial_casts,
+    trivial_numeric_casts, unsafe_code, unstable_features, unused_import_braces,
+    unused_qualifications
+)]
+
+#[macro_use]
+extern crate clap;
+
+#[macro_use]
+extern crate log;
+
+extern crate duniter_blockchain;
+extern crate duniter_conf;
+extern crate duniter_crypto;
+extern crate duniter_message;
+extern crate duniter_module;
+extern crate serde_json;
+extern crate simplelog;
+extern crate sqlite;
+extern crate threadpool;
+
+use self::threadpool::ThreadPool;
+use clap::{App, ArgMatches};
+use duniter_blockchain::BlockchainModule;
+use duniter_conf::DuniterKeyPairs;
+use duniter_crypto::keys::ed25519;
+use duniter_message::DuniterMessage;
+use duniter_module::*;
+use log::Level;
+use simplelog::*;
+use std::env;
+use std::fs;
+use std::fs::{File, OpenOptions};
+use std::sync::mpsc;
+use std::thread;
+use std::time::Duration;
+
+#[derive(Debug)]
+/// Duniter Core Datas
+pub struct DuniterCore {
+    /// Does the entered command require to launch server ?
+    pub start: bool,
+    /// Software name
+    pub soft_name: &'static str,
+    /// Soft version
+    pub soft_version: &'static str,
+    /// Keypairs
+    pub keypairs: DuniterKeyPairs,
+    /// Duniter configuration
+    pub conf: DuniterConf,
+    /// Run duration. Zero = infinite duration.
+    pub run_duration_in_secs: u64,
+    /// Sender channel of rooter thread
+    pub rooter_sender: mpsc::Sender<RooterThreadMessage<DuniterMessage>>,
+    ///  Count the number of plugged modules
+    pub modules_count: usize,
+    /// ThreadPool that execute plugged modules
+    pub thread_pool: ThreadPool,
+}
+
+impl DuniterCore {
+    /// Instantiate Duniter classic node
+    pub fn new(soft_name: &'static str, soft_version: &'static str) -> Option<DuniterCore> {
+        DuniterCore::new_specialized_node(soft_name, soft_version, 0, vec![], vec![], None)
+    }
+    /// Instantiate Duniter specialize node
+    pub fn new_specialized_node<'a, 'b>(
+        soft_name: &'static str,
+        soft_version: &'static str,
+        run_duration_in_secs: u64,
+        external_followers: Vec<mpsc::Sender<DuniterMessage>>,
+        sup_apps: Vec<App<'a, 'b>>,
+        sup_apps_fn: Option<&Fn(&str, &ArgMatches) -> ()>,
+    ) -> Option<DuniterCore> {
+        // Get cli conf
+        let yaml = load_yaml!("./cli/en.yml");
+        let cli_conf = App::from_yaml(yaml);
+
+        // Math command line arguments
+        let cli_args = if !sup_apps.is_empty() {
+            cli_conf.subcommands(sup_apps).get_matches()
+        } else {
+            cli_conf.get_matches()
+        };
+
+        // Get datas profile name
+        let profile = match_profile(&cli_args);
+
+        // Init logger
+        init_logger(profile.as_str(), soft_name, &cli_args);
+
+        // Load global conf
+        let (conf, keypairs) = duniter_conf::load_conf(profile.as_str());
+        info!("Success to load global conf.");
+
+        if let Some(_matches) = cli_args.subcommand_matches("start") {
+            Some(start(
+                soft_name,
+                soft_version,
+                keypairs,
+                conf,
+                run_duration_in_secs,
+                external_followers,
+            ))
+        } else if let Some(matches) = cli_args.subcommand_matches("sync_ts") {
+            let ts_profile = matches.value_of("TS_PROFILE").unwrap_or("duniter_default");
+            sync_ts(conf, ts_profile, matches.is_present("cautious"));
+            None
+        } else if let Some(matches) = cli_args.subcommand_matches("reset") {
+            let mut profile_path = match env::home_dir() {
+                Some(path) => path,
+                None => panic!("Impossible to get your home dir !"),
+            };
+            profile_path.push(".config");
+            profile_path.push(duniter_conf::get_user_datas_folder());
+            profile_path.push(profile.clone());
+            if !profile_path.as_path().exists() {
+                panic!(format!("Error : {} profile don't exist !", profile));
+            }
+            match matches.value_of("DATAS_TYPE").unwrap() {
+                "data" => {
+                    let mut currency_datas_path = profile_path.clone();
+                    currency_datas_path.push("g1");
+                    fs::remove_dir_all(currency_datas_path.as_path())
+                        .expect("Fail to remove all currency datas !");
+                }
+                "conf" => {
+                    let mut conf_file_path = profile_path.clone();
+                    conf_file_path.push("conf.json");
+                    fs::remove_file(conf_file_path.as_path()).expect("Fail to remove conf file !");
+                    let mut conf_keys_path = profile_path.clone();
+                    conf_keys_path.push("keypairs.json");
+                    fs::remove_file(conf_keys_path.as_path())
+                        .expect("Fail to remove keypairs file !");
+                }
+                "all" => {
+                    fs::remove_dir_all(profile_path.as_path())
+                        .expect("Fail to remove all profile datas !");
+                }
+                _ => {}
+            }
+            None
+        } else if let Some(sup_apps_fn) = sup_apps_fn {
+            sup_apps_fn(profile.as_str(), &cli_args);
+            None
+        } else {
+            panic!("unknow sub-command !")
+        }
+    }
+    /// Start blockchain module
+    pub fn start_blockchain(&self) {
+        if self.start {
+            thread::sleep(Duration::from_secs(2));
+            // Create blockchain module channel
+            let (blockchain_sender, blockchain_receiver): (
+                mpsc::Sender<DuniterMessage>,
+                mpsc::Receiver<DuniterMessage>,
+            ) = mpsc::channel();
+
+            // Send blockchain sender to rooter thread
+            self.rooter_sender
+                .send(RooterThreadMessage::ModuleSender(blockchain_sender))
+                .expect("Fatal error: fail to send blockchain sender to rooter thread !");
+
+            // Send modules_count to rooter thread
+            self.rooter_sender
+                .send(RooterThreadMessage::ModulesCount(self.modules_count + 1))
+                .expect("Fatal error: fail to send modules count to rooter thread !");
+
+            // Instantiate blockchain module and load is conf
+            let mut blockchain_module = BlockchainModule::load_blockchain_conf(
+                &self.conf,
+                RequiredKeysContent::MemberKeyPair(None),
+                false,
+            );
+            info!("Success to load Blockchain module.");
+
+            // Start blockchain module in main thread
+            blockchain_module.start_blockchain(blockchain_receiver);
+        }
+    }
+    /// Plug a module
+    pub fn plug<M: DuniterModule<ed25519::KeyPair, DuniterMessage>>(&mut self) {
+        if self.start {
+            // Start module in a new thread
+            let soft_name_clone = self.soft_name.clone();
+            let soft_version_clone = self.soft_version.clone();
+            let required_keys = DuniterKeyPairs::get_required_keys_content(
+                M::ask_required_keys(),
+                self.keypairs.clone(),
+            );
+            let module_conf = if let Some(module_conf_) = self
+                .conf
+                .clone()
+                .modules()
+                .get(&M::id().to_string().as_str())
+            {
+                module_conf_.clone()
+            } else {
+                M::default_conf()
+            };
+            let rooter_sender_clone = self.rooter_sender.clone();
+            let conf_clone = self.conf.clone();
+            self.thread_pool.execute(move || {
+                M::start(
+                    soft_name_clone,
+                    soft_version_clone,
+                    required_keys,
+                    &conf_clone,
+                    &module_conf,
+                    rooter_sender_clone,
+                    false,
+                ).expect(&format!(
+                    "Fatal error : fail to load {} Module !",
+                    M::id().to_string()
+                ));
+            });
+            self.modules_count += 1;
+            info!("Success to load {} module.", M::id().to_string());
+        }
+    }
+}
+
+/// Match cli option --profile
+pub fn match_profile(cli_args: &ArgMatches) -> String {
+    String::from(cli_args.value_of("profile").unwrap_or("default"))
+}
+
+/// Launch duniter server
+pub fn start(
+    soft_name: &'static str,
+    soft_version: &'static str,
+    keypairs: DuniterKeyPairs,
+    conf: DuniterConf,
+    run_duration_in_secs: u64,
+    external_followers: Vec<mpsc::Sender<DuniterMessage>>,
+) -> DuniterCore {
+    info!("Starting Duniter-rs...");
+
+    // Create senders channel
+    let (rooter_sender, main_receiver): (
+        mpsc::Sender<RooterThreadMessage<DuniterMessage>>,
+        mpsc::Receiver<RooterThreadMessage<DuniterMessage>>,
+    ) = mpsc::channel();
+
+    // Create rooter thread
+    thread::spawn(move || {
+        // Wait to receiver modules senders
+        let mut modules_senders: Vec<mpsc::Sender<DuniterMessage>> = Vec::new();
+        let mut modules_count_expected = None;
+        while modules_count_expected.is_none()
+            || modules_senders.len() < modules_count_expected.unwrap() + 1
+        {
+            match main_receiver.recv_timeout(Duration::from_secs(20)) {
+                Ok(mess) => {
+                    match mess {
+                        RooterThreadMessage::ModuleSender(module_sender) => {
+                            // Subscribe this module to all others modules
+                            for other_module in modules_senders.clone() {
+                                if other_module
+                                    .send(DuniterMessage::Followers(vec![module_sender.clone()]))
+                                    .is_err()
+                                {
+                                    panic!("Fatal error : fail to send all modules senders to all modules !");
+                                }
+                            }
+                            // Subcribe this module to all external_followers
+                            for external_follower in external_followers.clone() {
+                                if external_follower
+                                    .send(DuniterMessage::Followers(vec![module_sender.clone()]))
+                                    .is_err()
+                                {
+                                    panic!("Fatal error : fail to send all modules senders to all external_followers !");
+                                }
+                            }
+                            // Subscribe all other modules to this module
+                            if module_sender
+                                .send(DuniterMessage::Followers(modules_senders.clone()))
+                                .is_err()
+                            {
+                                panic!("Fatal error : fail to send all modules senders to all modules !");
+                            }
+                            // Subcribe all external_followers to this module
+                            if module_sender
+                                .send(DuniterMessage::Followers(external_followers.clone()))
+                                .is_err()
+                            {
+                                panic!("Fatal error : fail to send all external_followers to all modules !");
+                            }
+                            // Push this module to modules_senders list
+                            modules_senders.push(module_sender);
+                            // Log the number of modules_senders received
+                            info!(
+                                "Rooter thread receive {} module senders",
+                                modules_senders.len()
+                            );
+                        }
+                        RooterThreadMessage::ModulesCount(modules_count) => {
+                            info!("Rooter thread receive ModulesCount({})", modules_count);
+                            if modules_senders.len() == modules_count {
+                                break;
+                            } else if modules_senders.len() < modules_count {
+                                modules_count_expected = Some(modules_count);
+                            } else {
+                                panic!("Fatal error : Receive more modules_sender than expected !")
+                            }
+                        }
+                    }
+                }
+                Err(e) => match e {
+                    mpsc::RecvTimeoutError::Timeout => {
+                        panic!("Fatal error : not receive all modules_senders after 20 secs !")
+                    }
+                    mpsc::RecvTimeoutError::Disconnected => {
+                        panic!("Fatal error : rooter thread disconnnected !")
+                    }
+                },
+            }
+        }
+        info!("Receive all modules senders.");
+        if run_duration_in_secs > 0 {
+            thread::sleep(Duration::from_secs(run_duration_in_secs));
+            // Send DuniterMessage::Stop() to all modules
+            for sender in modules_senders {
+                if sender.send(DuniterMessage::Stop()).is_err() {
+                    panic!("Fail to send Stop() message to one module !")
+                }
+            }
+            thread::sleep(Duration::from_secs(2));
+        }
+    });
+
+    // Instanciate DuniterCore
+    DuniterCore {
+        start: true,
+        soft_name,
+        soft_version,
+        keypairs,
+        conf,
+        run_duration_in_secs,
+        rooter_sender,
+        modules_count: 0,
+        thread_pool: ThreadPool::new(2),
+    }
+}
+
+/// Launch synchronisation from a duniter-ts database
+pub fn sync_ts(conf: DuniterConf, ts_profile: &str, cautious: bool) {
+    // Launch sync-ts
+    BlockchainModule::sync_ts(&conf, ts_profile, cautious);
+}
+
+/// Initialize logger
+pub fn init_logger(profile: &str, soft_name: &'static str, cli_args: &ArgMatches) {
+    // Get datas folder path
+    let mut log_file_path = match env::home_dir() {
+        Some(path) => path,
+        None => panic!("Fatal error : Impossible to get your home dir!"),
+    };
+    log_file_path.push(".config");
+    if !log_file_path.as_path().exists() {
+        fs::create_dir(log_file_path.as_path()).expect("Impossible to create ~/.config dir !");
+    }
+    log_file_path.push(duniter_conf::get_user_datas_folder());
+    if !log_file_path.as_path().exists() {
+        fs::create_dir(log_file_path.as_path()).expect("Impossible to create ~/.config/durs dir !");
+    }
+    log_file_path.push(profile);
+    // Create datas folder if not exist
+    if !log_file_path.as_path().exists() {
+        fs::create_dir(log_file_path.as_path()).expect("Impossible to create your profile dir !");
+    }
+
+    // Get log_file_path
+    log_file_path.push(format!("{}.log", soft_name));
+
+    // Get log level
+    let log_level = match cli_args.value_of("logs").unwrap_or("i") {
+        "e" | "error" => Level::Error,
+        "w" | "warn" => Level::Warn,
+        "i" | "info" => Level::Info,
+        "d" | "debug" => Level::Debug,
+        "t" | "trace" => Level::Trace,
+        _ => panic!("Fatal error : unknow log level !"),
+    };
+
+    // Config logger
+    let logger_config = Config {
+        time: Some(Level::Error),
+        level: Some(Level::Error),
+        target: Some(Level::Debug),
+        location: Some(Level::Debug),
+        time_format: Some("%Y-%m-%d %H:%M:%S%:z"),
+    };
+
+    // Create log file if not exist
+    if !log_file_path.as_path().exists() {
+        File::create(
+            log_file_path
+                .to_str()
+                .expect("Fatal error : fail to get log file path !"),
+        ).expect("Fatal error : fail to create log file path !");
+    }
+
+    CombinedLogger::init(vec![
+        TermLogger::new(LevelFilter::Error, logger_config).unwrap(),
+        WriteLogger::new(
+            log_level.to_level_filter(),
+            logger_config,
+            OpenOptions::new()
+                .write(true)
+                .append(true)
+                .open(
+                    log_file_path
+                        .to_str()
+                        .expect("Fatal error : fail to get log file path !"),
+                )
+                .expect("Fatal error : fail to open log file !"),
+        ),
+    ]).expect("Fatal error : fail to init logger !");
+}
diff --git a/dal/Cargo.toml b/dal/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..a7a36783342acec4de9056629244e6a5712599bb
--- /dev/null
+++ b/dal/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+name = "duniter-dal"
+version = "0.1.0"
+authors = ["librelois <elois@ifee.fr>"]
+description = "Data Access Layer for the Duniter project."
+license = "AGPL-3.0"
+
+[lib]
+path = "lib.rs"
+
+[dependencies]
+duniter-crypto = { path = "../crypto" }
+duniter-documents = { path = "../documents" }
+duniter-module = { path = "../module" }
+duniter-network = { path = "../network" }
+duniter-wotb = { path = "../wotb" }
+lazy_static = "1.0.0"
+log = "0.4.1"
+rand = "0.4.2"
+rust-crypto = "0.2.36"
+regex = "0.2.6"
+sqlite = "0.23.9"
+serde = "1.0.24"
+serde_derive = "1.0.24"
+serde_json = "1.0.9"
+websocket = "0.20.2"
+
+[features]
+exp = []
+# Treat warnings as a build error.
+strict = []
\ No newline at end of file
diff --git a/dal/block.rs b/dal/block.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ae29b95d44f867a822ff2dd6adb294a181fbe656
--- /dev/null
+++ b/dal/block.rs
@@ -0,0 +1,510 @@
+extern crate duniter_crypto;
+extern crate duniter_documents;
+extern crate duniter_wotb;
+extern crate serde;
+extern crate serde_json;
+extern crate sqlite;
+
+use self::duniter_crypto::keys;
+use self::duniter_crypto::keys::{ed25519, PublicKey, Signature};
+use self::duniter_documents::blockchain::v10::documents::identity::IdentityDocument;
+use self::duniter_documents::blockchain::v10::documents::membership::MembershipType;
+use self::duniter_documents::blockchain::v10::documents::BlockDocument;
+use self::duniter_documents::blockchain::Document;
+use self::duniter_documents::{BlockHash, BlockId, Blockstamp, Hash};
+use self::duniter_wotb::NodeId;
+use super::constants::MAX_FORKS;
+use super::parsers::certifications::parse_certifications;
+use super::parsers::excluded::parse_exclusions;
+use super::parsers::identities::parse_identities;
+use super::parsers::memberships::parse_memberships;
+use super::parsers::revoked::parse_revocations;
+use super::parsers::transactions::parse_compact_transactions;
+use super::{DuniterDB, ForkState};
+use std::collections::HashMap;
+
+pub fn blockstamp_to_timestamp(blockstamp: &Blockstamp, db: &DuniterDB) -> Option<u64> {
+    if blockstamp.id.0 == 0 {
+        return Some(1_488_987_127);
+    }
+    let mut cursor = db
+        .0
+        .prepare("SELECT median_time FROM blocks WHERE number=? AND hash=? LIMIT 1;")
+        .expect("convert blockstamp to timestamp failure at step 0 !")
+        .cursor();
+
+    cursor
+        .bind(&[
+            sqlite::Value::Integer(blockstamp.id.0 as i64),
+            sqlite::Value::String(blockstamp.hash.0.to_hex()),
+        ])
+        .expect("convert blockstamp to timestamp failure at step 1 !");
+
+    if let Some(row) = cursor
+        .next()
+        .expect("convert blockstamp to timestamp failure at step 2 !")
+    {
+        return Some(row[0]
+            .as_integer()
+            .expect("convert blockstamp to timestamp failure at step 3 !")
+            as u64);
+    }
+    None
+}
+
+#[derive(Debug, Copy, Clone)]
+pub enum WotEvent {
+    AddNode(ed25519::PublicKey, NodeId),
+    RemNode(ed25519::PublicKey),
+    AddLink(NodeId, NodeId),
+    RemLink(NodeId, NodeId),
+    EnableNode(NodeId),
+    DisableNode(NodeId),
+}
+
+#[derive(Debug, Clone)]
+pub struct BlockContext {
+    pub blockstamp: Blockstamp,
+    pub wot_events: Vec<WotEvent>,
+}
+
+#[derive(Debug, Clone)]
+pub struct BlockContextV2 {
+    pub blockstamp: Blockstamp,
+    pub wot_events: Vec<WotEvent>,
+}
+
+#[derive(Debug, Clone)]
+pub struct DALBlock {
+    pub fork: usize,
+    pub isolate: bool,
+    pub block: BlockDocument,
+}
+
+impl DALBlock {
+    pub fn blockstamp(&self) -> Blockstamp {
+        self.block.blockstamp()
+    }
+}
+
+pub fn get_forks(db: &DuniterDB) -> Vec<ForkState> {
+    let mut forks = Vec::new();
+    forks.push(ForkState::Full());
+    for fork in 1..*MAX_FORKS {
+        let mut cursor = db
+            .0
+            .prepare("SELECT isolate FROM blocks WHERE fork=? ORDER BY median_time DESC LIMIT 1;")
+            .expect("Fail to get block !")
+            .cursor();
+
+        cursor
+            .bind(&[sqlite::Value::Integer(fork as i64)])
+            .expect("Fail to get block !");
+
+        if let Some(row) = cursor.next().unwrap() {
+            if row[0].as_integer().unwrap() == 0 {
+                forks.push(ForkState::Full())
+            } else {
+                forks.push(ForkState::Isolate())
+            }
+        } else {
+            forks.push(ForkState::Free());
+        }
+    }
+    forks
+}
+
+impl DALBlock {
+    pub fn unisolate_fork(db: &DuniterDB, fork: usize) {
+        db.0
+            .execute(format!("UPDATE blocks SET isolate=0 WHERE fork={};", fork))
+            .unwrap();
+    }
+    pub fn delete_fork(db: &DuniterDB, fork: usize) {
+        db.0
+            .execute(format!("DELETE FROM blocks WHERE fork={};", fork))
+            .unwrap();
+    }
+    pub fn get_block_fork(db: &DuniterDB, blockstamp: &Blockstamp) -> Option<usize> {
+        let mut cursor = db
+            .0
+            .prepare("SELECT fork FROM blocks WHERE number=? AND hash=?;")
+            .expect("Fail to get block !")
+            .cursor();
+
+        cursor
+            .bind(&[
+                sqlite::Value::Integer(blockstamp.id.0 as i64),
+                sqlite::Value::String(blockstamp.hash.0.to_string()),
+            ])
+            .expect("Fail to get block !");
+
+        if let Some(row) = cursor.next().unwrap() {
+            Some(row[0].as_integer().unwrap() as usize)
+        } else {
+            None
+        }
+    }
+    pub fn get_block_hash(db: &DuniterDB, block_number: &BlockId) -> Option<BlockHash> {
+        let mut cursor = db
+            .0
+            .prepare("SELECT hash FROM blocks WHERE number=? AND fork=0;")
+            .expect("Fail to get block !")
+            .cursor();
+
+        cursor
+            .bind(&[sqlite::Value::Integer(block_number.0 as i64)])
+            .expect("Fail to get block !");
+
+        if let Some(row) = cursor.next().unwrap() {
+            Some(BlockHash(
+                Hash::from_hex(row[0].as_string().unwrap()).unwrap(),
+            ))
+        } else {
+            None
+        }
+    }
+
+    pub fn get_blocks_hashs_all_forks(
+        db: &DuniterDB,
+        block_number: &BlockId,
+    ) -> (Vec<BlockHash>, Vec<Hash>) {
+        let mut cursor = db
+            .0
+            .prepare("SELECT hash, previous_hash FROM blocks WHERE number=?;")
+            .expect("Fail to get block !")
+            .cursor();
+
+        cursor
+            .bind(&[sqlite::Value::Integer(block_number.0 as i64)])
+            .expect("Fail to get block !");
+
+        let mut hashs = Vec::new();
+        let mut previous_hashs = Vec::new();
+        while let Some(row) = cursor.next().unwrap() {
+            hashs.push(BlockHash(
+                Hash::from_hex(row[0].as_string().unwrap()).unwrap(),
+            ));
+            previous_hashs.push(Hash::from_hex(row[1].as_string().unwrap()).unwrap());
+        }
+        (hashs, previous_hashs)
+    }
+
+    pub fn get_stackables_blocks(
+        currency: &str,
+        db: &DuniterDB,
+        current_blockstamp: &Blockstamp,
+    ) -> Vec<DALBlock> {
+        debug!("get_stackables_blocks() after {}", current_blockstamp);
+        let mut stackables_blocks = Vec::new();
+        let block_id = BlockId(current_blockstamp.id.0 + 1);
+        let (hashs, previous_hashs) = DALBlock::get_blocks_hashs_all_forks(db, &block_id);
+        for (hash, previous_hash) in hashs.into_iter().zip(previous_hashs) {
+            if previous_hash == current_blockstamp.hash.0 {
+                if let Some(dal_block) =
+                    DALBlock::get_block(currency, db, &Blockstamp { id: block_id, hash })
+                {
+                    stackables_blocks.push(dal_block);
+                } else {
+                    panic!(format!(
+                        "Fail to get stackable block {} !",
+                        Blockstamp { id: block_id, hash }
+                    ));
+                }
+            }
+        }
+        stackables_blocks
+    }
+    pub fn get_stackables_forks(db: &DuniterDB, current_blockstamp: &Blockstamp) -> Vec<usize> {
+        let mut stackables_forks = Vec::new();
+        let block_id = BlockId(current_blockstamp.id.0 + 1);
+        let (hashs, previous_hashs) = DALBlock::get_blocks_hashs_all_forks(db, &block_id);
+        for (hash, previous_hash) in hashs.into_iter().zip(previous_hashs) {
+            if previous_hash == current_blockstamp.hash.0 {
+                if let Some(fork) = DALBlock::get_block_fork(db, &Blockstamp { id: block_id, hash })
+                {
+                    if fork > 0 {
+                        stackables_forks.push(fork);
+                    }
+                }
+            }
+        }
+        stackables_forks
+    }
+    pub fn get_block(currency: &str, db: &DuniterDB, blockstamp: &Blockstamp) -> Option<DALBlock> {
+        let mut cursor = db
+            .0
+            .prepare(
+                "SELECT fork, isolate, nonce, number,
+            pow_min, time, median_time, members_count,
+            monetary_mass, unit_base, issuers_count, issuers_frame,
+            issuers_frame_var, median_frame, second_tiercile_frame,
+            currency, issuer, signature, hash, previous_hash, dividend, identities, joiners,
+            actives, leavers, revoked, excluded, certifications,
+            transactions FROM blocks WHERE number=? AND hash=?;",
+            )
+            .expect("Fail to get block !")
+            .cursor();
+
+        cursor
+            .bind(&[
+                sqlite::Value::Integer(blockstamp.id.0 as i64),
+                sqlite::Value::String(blockstamp.hash.0.to_string()),
+            ])
+            .expect("Fail to get block !");
+
+        if let Some(row) = cursor.next().expect("block not found in bdd !") {
+            let dividend_amount = row[20]
+                .as_integer()
+                .expect("dal::get_block() : fail to parse dividend !");
+            let dividend = if dividend_amount > 0 {
+                Some(dividend_amount as usize)
+            } else if dividend_amount == 0 {
+                None
+            } else {
+                return None;
+            };
+            let nonce = row[2]
+                .as_integer()
+                .expect("dal::get_block() : fail to parse nonce !") as u64;
+            let inner_hash = Hash::from_hex(
+                row[18]
+                    .as_string()
+                    .expect("dal::get_block() : fail to parse inner_hash !"),
+            ).expect("dal::get_block() : fail to parse inner_hash (2) !");
+            let identities = parse_identities(
+                currency,
+                row[21]
+                    .as_string()
+                    .expect("dal::get_block() : fail to parse identities !"),
+            ).expect("dal::get_block() : fail to parse identities (2) !");
+            let hashmap_identities = identities
+                .iter()
+                .map(|i| (i.issuers()[0], i.clone()))
+                .collect::<HashMap<ed25519::PublicKey, IdentityDocument>>();
+            Some(DALBlock {
+                fork: row[0]
+                    .as_integer()
+                    .expect("dal::get_block() : fail to parse fork !")
+                    as usize,
+                isolate: if row[1]
+                    .as_integer()
+                    .expect("dal::get_block() : fail to parse isolate !")
+                    == 0
+                {
+                    false
+                } else {
+                    true
+                },
+                block: BlockDocument {
+                    nonce,
+                    number: BlockId(row[3]
+                        .as_integer()
+                        .expect("dal::get_block() : fail to parse number !")
+                        as u32),
+                    pow_min: row[4]
+                        .as_integer()
+                        .expect("dal::get_block() : fail to parse pow min !")
+                        as usize,
+                    time: row[5]
+                        .as_integer()
+                        .expect("dal::get_block() : fail to parse time !")
+                        as u64,
+                    median_time: row[6]
+                        .as_integer()
+                        .expect("dal::get_block() : fail to parse median_time !")
+                        as u64,
+                    members_count: row[7]
+                        .as_integer()
+                        .expect("dal::get_block() : fail to parse members_count !")
+                        as usize,
+                    monetary_mass: row[8]
+                        .as_integer()
+                        .expect("dal::get_block() : fail to parse monetary_mass !")
+                        as usize,
+                    unit_base: row[9]
+                        .as_integer()
+                        .expect("dal::get_block() : fail to parse unit_base !")
+                        as usize,
+                    issuers_count: row[10]
+                        .as_integer()
+                        .expect("dal::get_block() : fail to parse issuers_count !")
+                        as usize,
+                    issuers_frame: row[11]
+                        .as_integer()
+                        .expect("dal::get_block() : fail to parse issuers_frame !")
+                        as isize,
+                    issuers_frame_var: row[12]
+                        .as_integer()
+                        .expect("dal::get_block() : fail to parse issuers_frame_var !")
+                        as isize,
+                    currency: row[15]
+                        .as_string()
+                        .expect("dal::get_block() : fail to parse currency !")
+                        .to_string(),
+                    issuers: vec![
+                        PublicKey::from_base58(
+                            row[16]
+                                .as_string()
+                                .expect("dal::get_block() : fail to parse issuer !"),
+                        ).expect("dal::get_block() : fail to parse pubkey !"),
+                    ],
+                    signatures: vec![
+                        Signature::from_base64(
+                            row[17]
+                                .as_string()
+                                .expect("dal::get_block() : fail to parse signature !"),
+                        ).expect("dal::get_block() : fail to parse signature (2) !"),
+                    ],
+                    hash: Some(BlockHash(
+                        Hash::from_hex(
+                            row[18]
+                                .as_string()
+                                .expect("dal::get_block() : fail to parse hash !"),
+                        ).expect("dal::get_block() : fail to parse hash (2) !"),
+                    )),
+                    parameters: None,
+                    previous_hash: Hash::from_hex(
+                        row[19]
+                            .as_string()
+                            .expect("dal::get_block() : fail to parse previous_hash !"),
+                    ).expect(
+                        "dal::get_block() : fail to parse previous_hash (2) !",
+                    ),
+                    previous_issuer: None,
+                    inner_hash: Some(inner_hash),
+                    dividend,
+                    identities: identities.clone(),
+                    joiners: parse_memberships(
+                        currency,
+                        MembershipType::In(),
+                        row[22]
+                            .as_string()
+                            .expect("dal::get_block() : fail to parse joiners !"),
+                    ).expect("dal::get_block() : fail to parse joiners (2) !"),
+                    actives: parse_memberships(
+                        currency,
+                        MembershipType::In(),
+                        row[23]
+                            .as_string()
+                            .expect("dal::get_block() : fail to parse actives !"),
+                    ).expect("dal::get_block() : fail to parse actives (2) !"),
+                    leavers: parse_memberships(
+                        currency,
+                        MembershipType::Out(),
+                        row[24]
+                            .as_string()
+                            .expect("dal::get_block() : fail to parse leavers !"),
+                    ).expect("dal::get_block() : fail to parse leavers (2) !"),
+                    revoked: parse_revocations(
+                        currency,
+                        db,
+                        &hashmap_identities,
+                        row[25]
+                            .as_string()
+                            .expect("dal::get_block() : fail to parse revoked !"),
+                    ).expect("dal::get_block() : fail to parse revoked (2) !"),
+                    excluded: parse_exclusions(
+                        row[26]
+                            .as_string()
+                            .expect("dal::get_block() : fail to parse excluded !"),
+                    ).expect("dal::get_block() : fail to parse excluded (2) !"),
+                    certifications: parse_certifications(
+                        currency,
+                        db,
+                        &hashmap_identities,
+                        row[27]
+                            .as_string()
+                            .expect("dal::get_block() : fail to parse certifications !"),
+                    ).expect(
+                        "dal::get_block() : fail to parse certifications (2) !",
+                    ),
+                    transactions: parse_compact_transactions(
+                        currency,
+                        row[28]
+                            .as_string()
+                            .expect("dal::get_block() : fail to parse transactions !"),
+                    ).expect("dal::get_block() : fail to parse transactions (2) !"),
+                    inner_hash_and_nonce_str: format!(
+                        "InnerHash: {}\nNonce: {}\n",
+                        inner_hash.to_hex(),
+                        nonce
+                    ),
+                },
+                //median_frame: row[13].as_integer().unwrap_or(0) as usize,
+                //second_tiercile_frame: row[14].as_integer().unwrap_or(0) as usize,
+            })
+        } else {
+            None
+        }
+    }
+
+    pub fn get_current_frame(&self, db: &DuniterDB) -> HashMap<keys::ed25519::PublicKey, usize> {
+        let frame_begin = self.block.number.0 as i64 - (self.block.issuers_frame as i64);
+        let mut current_frame: HashMap<keys::ed25519::PublicKey, usize> = HashMap::new();
+        let mut cursor = db
+            .0
+            .prepare("SELECT issuer FROM blocks WHERE fork=0 AND number>=? LIMIT ?;")
+            .expect("get current frame blocks failure at step 1 !")
+            .cursor();
+        cursor
+            .bind(&[
+                sqlite::Value::Integer(frame_begin),
+                sqlite::Value::Integer(self.block.issuers_frame as i64),
+            ])
+            .expect("get current frame blocks failure at step 2 !");
+
+        while let Some(row) = cursor
+            .next()
+            .expect("get current frame blocks failure at step 3 !")
+        {
+            let current_frame_copy = current_frame.clone();
+            match current_frame_copy
+                .get(&PublicKey::from_base58(row[0].as_string().unwrap()).unwrap())
+            {
+                Some(blocks_count) => {
+                    if let Some(new_blocks_count) = current_frame
+                        .get_mut(&PublicKey::from_base58(row[0].as_string().unwrap()).unwrap())
+                    {
+                        *new_blocks_count = *blocks_count + 1;
+                    }
+                }
+                None => {
+                    current_frame.insert(
+                        PublicKey::from_base58(row[0].as_string().unwrap()).unwrap(),
+                        0,
+                    );
+                }
+            }
+        }
+        current_frame
+    }
+
+    pub fn compute_median_issuers_frame(&mut self, db: &DuniterDB) -> () {
+        let current_frame = self.get_current_frame(db);
+        if !current_frame.is_empty() {
+            let mut current_frame_vec: Vec<_> = current_frame.values().cloned().collect();
+            current_frame_vec.sort_unstable();
+
+            /*// Calculate median
+            let mut median_index = match self.block.issuers_count % 2 {
+                1 => (self.block.issuers_count / 2) + 1,
+                _ => self.block.issuers_count / 2,
+            };
+            if median_index >= self.block.issuers_count {
+                median_index = self.block.issuers_count - 1;
+            }
+            self.median_frame = current_frame_vec[median_index];
+
+            // Calculate second tiercile index
+            let mut second_tiercile_index = match self.block.issuers_count % 3 {
+                1 | 2 => (self.block.issuers_count as f64 * (2.0 / 3.0)) as usize + 1,
+                _ => (self.block.issuers_count as f64 * (2.0 / 3.0)) as usize,
+            };
+            if second_tiercile_index >= self.block.issuers_count {
+                second_tiercile_index = self.block.issuers_count - 1;
+            }
+            self.second_tiercile_frame = current_frame_vec[second_tiercile_index];*/
+        }
+    }
+}
diff --git a/dal/constants.rs b/dal/constants.rs
new file mode 100644
index 0000000000000000000000000000000000000000..a35986fcd9853f13f592c16643c3b7aae14f3b9b
--- /dev/null
+++ b/dal/constants.rs
@@ -0,0 +1,48 @@
+#[derive(Debug, Copy, Clone)]
+pub struct CurrencyParametersV10 {
+    pub c: f64,
+    pub dt: i64,
+    pub ud0: i64,
+    pub sig_period: u64,
+    pub sig_stock: i64,
+    pub sig_window: i64,
+    pub sig_validity: i64,
+    pub sig_qty: i64,
+    pub idty_window: i64,
+    pub ms_window: i64,
+    pub x_percent: f64,
+    pub ms_validity: u64,
+    pub step_max: u32,
+    pub median_time_blocks: i64,
+    pub avg_gen_time: i64,
+    pub dt_diff_eval: i64,
+    pub percent_rot: f64,
+    pub ud_time0: i64,
+    pub ud_reeval_time0: i64,
+    pub dt_reeval: i64,
+}
+
+pub static G1_PARAMS: &'static CurrencyParametersV10 = &CurrencyParametersV10 {
+    c: 0.0488,
+    dt: 86_400,
+    ud0: 1_000,
+    sig_period: 432_000,
+    sig_stock: 100,
+    sig_window: 5_259_600,
+    sig_validity: 63_115_200,
+    sig_qty: 5,
+    idty_window: 5_259_600,
+    ms_window: 5_259_600,
+    x_percent: 0.8,
+    ms_validity: 31_557_600,
+    step_max: 5,
+    median_time_blocks: 24,
+    avg_gen_time: 300,
+    dt_diff_eval: 12,
+    percent_rot: 0.67,
+    ud_time0: 1_488_970_800,
+    ud_reeval_time0: 1_490_094_000,
+    dt_reeval: 15_778_800,
+};
+pub static G1_CONNECTIVITY_MAX: &'static usize = &125;
+pub static MAX_FORKS: &'static usize = &50;
diff --git a/dal/dal_event.rs b/dal/dal_event.rs
new file mode 100644
index 0000000000000000000000000000000000000000..99e41058ea4b33366263c6b7cfef62717866f303
--- /dev/null
+++ b/dal/dal_event.rs
@@ -0,0 +1,13 @@
+extern crate duniter_documents;
+extern crate serde;
+
+use self::duniter_documents::blockchain::v10::documents::BlockDocument;
+use self::duniter_documents::blockchain::BlockchainProtocol;
+
+#[derive(Debug, Clone)]
+pub enum DALEvent {
+    StackUpValidBlock(Box<BlockDocument>),
+    RevertBlocks(Vec<Box<BlockDocument>>),
+    NewValidPendingDoc(BlockchainProtocol),
+    RefusedPendingDoc(BlockchainProtocol),
+}
diff --git a/dal/dal_requests.rs b/dal/dal_requests.rs
new file mode 100644
index 0000000000000000000000000000000000000000..efd50dc2b7b821c160ccfb44552396f63b86d4b5
--- /dev/null
+++ b/dal/dal_requests.rs
@@ -0,0 +1,63 @@
+extern crate duniter_crypto;
+extern crate duniter_documents;
+extern crate duniter_module;
+extern crate serde;
+
+use self::duniter_crypto::keys::ed25519;
+use self::duniter_documents::blockchain::v10::documents::{
+    BlockDocument, CertificationDocument, IdentityDocument, MembershipDocument, RevocationDocument,
+};
+use self::duniter_documents::Hash;
+use self::duniter_module::ModuleReqFullId;
+use std::collections::HashMap;
+
+#[derive(Debug, Copy, Clone)]
+pub enum DALReqPendings {
+    AllPendingIdentyties(ModuleReqFullId, usize),
+    AllPendingIdentytiesWithoutCerts(ModuleReqFullId, usize),
+    PendingWotDatasForPubkey(ModuleReqFullId, ed25519::PublicKey),
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub enum DALReqBlockchain {
+    CurrentBlock(ModuleReqFullId),
+    BlockByNumber(ModuleReqFullId, u64),
+    Chunk(ModuleReqFullId, u64, usize),
+    UIDs(Vec<ed25519::PublicKey>),
+}
+
+#[derive(Debug, Clone)]
+pub enum DALRequest {
+    BlockchainRequest(DALReqBlockchain),
+    PendingsRequest(DALReqPendings),
+}
+
+#[derive(Debug, Clone)]
+pub struct PendingIdtyDatas {
+    pub idty: IdentityDocument,
+    pub memberships: Vec<MembershipDocument>,
+    pub certs_count: usize,
+    pub certs: Vec<CertificationDocument>,
+    pub revocation: Option<RevocationDocument>,
+}
+
+#[derive(Debug, Clone)]
+pub enum DALResPendings {
+    AllPendingIdentyties(HashMap<Hash, PendingIdtyDatas>),
+    AllPendingIdentytiesWithoutCerts(HashMap<Hash, PendingIdtyDatas>),
+    PendingWotDatasForPubkey(Vec<PendingIdtyDatas>),
+}
+
+#[derive(Debug, Clone)]
+pub enum DALResBlockchain {
+    CurrentBlock(ModuleReqFullId, BlockDocument),
+    BlockByNumber(ModuleReqFullId, BlockDocument),
+    Chunk(ModuleReqFullId, Vec<BlockDocument>),
+    UIDs(HashMap<ed25519::PublicKey, Option<String>>),
+}
+
+#[derive(Debug, Clone)]
+pub enum DALResponse {
+    Blockchain(DALResBlockchain),
+    Pendings(ModuleReqFullId, DALResPendings),
+}
diff --git a/dal/endpoint.rs b/dal/endpoint.rs
new file mode 100644
index 0000000000000000000000000000000000000000..2bd5567ca2fa58578fdf66d4fe0b619aa41807b5
--- /dev/null
+++ b/dal/endpoint.rs
@@ -0,0 +1,166 @@
+extern crate crypto;
+extern crate duniter_crypto;
+extern crate sqlite;
+
+use std::time::Duration;
+
+use self::crypto::digest::Digest;
+use self::crypto::sha2::Sha256;
+use self::duniter_crypto::keys::PublicKey;
+use self::duniter_crypto::keys::ed25519::PublicKey as ed25519PublicKey;
+use super::DuniterDB;
+use super::WriteToDuniterDB;
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum DALEndpointApi {
+    WS2P,
+    //WS2PS,
+    //WS2PTOR,
+    //DASA,
+    //BMA,
+    //BMAS,
+}
+
+impl From<u32> for DALEndpointApi {
+    fn from(integer: u32) -> Self {
+        match integer {
+            _ => DALEndpointApi::WS2P,
+        }
+    }
+}
+
+pub fn string_to_api(api: &str) -> Option<DALEndpointApi> {
+    match api {
+        "WS2P" => Some(DALEndpointApi::WS2P),
+        //"WS2PS" => Some(DALEndpointApi::WS2PS),
+        //"WS2PTOR" => Some(DALEndpointApi::WS2PTOR),
+        //"DASA" => Some(DALEndpointApi::DASA),
+        //"BASIC_MERKLED_API" => Some(DALEndpointApi::BMA),
+        //"BMAS" => Some(DALEndpointApi::BMAS),
+        &_ => None,
+    }
+}
+
+pub fn api_to_integer(api: &DALEndpointApi) -> i64 {
+    match *api {
+        DALEndpointApi::WS2P => 0,
+        //DALEndpointApi::WS2PS => 1,
+        //DALEndpointApi::WS2PTOR => 2,
+        //DALEndpointApi::DASA => 3,
+        //DALEndpointApi::BMA => 4,
+        //DALEndpointApi::BMAS => 5,
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct DALEndpoint {
+    pub hash_full_id: String,
+    pub status: u32,
+    pub node_id: u32,
+    pub pubkey: ed25519PublicKey,
+    pub api: DALEndpointApi,
+    pub version: usize,
+    pub endpoint: String,
+    pub last_check: u64,
+}
+
+impl DALEndpoint {
+    pub fn new(
+        status: u32,
+        node_id: u32,
+        pubkey: ed25519PublicKey,
+        api: DALEndpointApi,
+        version: usize,
+        endpoint: String,
+        last_check: Duration,
+    ) -> DALEndpoint {
+        let mut sha = Sha256::new();
+        sha.input_str(&format!(
+            "{}{}{}{}",
+            node_id,
+            pubkey,
+            api_to_integer(&api),
+            version
+        ));
+        DALEndpoint {
+            hash_full_id: sha.result_str(),
+            status,
+            node_id,
+            pubkey,
+            api,
+            version,
+            endpoint,
+            last_check: last_check.as_secs(),
+        }
+    }
+    pub fn get_endpoints_for_api(db: &DuniterDB, api: DALEndpointApi) -> Vec<DALEndpoint> {
+        let mut cursor:sqlite::Cursor = db.0
+        .prepare("SELECT hash_full_id, status, node_id, pubkey, api, version, endpoint, last_check FROM endpoints WHERE api=? ORDER BY status DESC;")
+        .expect("get_endpoints_for_api() : Error in SQL request !")
+        .cursor();
+
+        cursor
+            .bind(&[sqlite::Value::Integer(api_to_integer(&api))])
+            .expect("get_endpoints_for_api() : Error in cursor binding !");
+        let mut endpoints = Vec::new();
+        while let Some(row) = cursor
+            .next()
+            .expect("get_endpoints_for_api() : Error in cursor.next()")
+        {
+            endpoints.push(DALEndpoint {
+                hash_full_id: row[0].as_string().unwrap().to_string(),
+                status: row[1].as_integer().unwrap() as u32,
+                node_id: row[2].as_integer().unwrap() as u32,
+                pubkey: ed25519PublicKey::from_base58(row[3].as_string().unwrap()).unwrap(),
+                api: DALEndpointApi::from(row[4].as_integer().unwrap() as u32),
+                version: row[5].as_integer().unwrap() as usize,
+                endpoint: row[6].as_string().unwrap().to_string(),
+                last_check: row[7].as_integer().unwrap() as u64,
+            });
+        }
+        endpoints
+    }
+}
+
+impl WriteToDuniterDB for DALEndpoint {
+    fn write(
+        &self,
+        db: &DuniterDB,
+        _written_blockstamp: super::block_v10::BlockStampV10,
+        _written_timestamp: u64,
+    ) {
+        // Check if endpoint it's already written
+        let mut cursor: sqlite::Cursor = db.0
+            .prepare("SELECT status FROM endpoints WHERE hash_full_id=? ORDER BY status DESC;")
+            .expect("get_endpoints_for_api() : Error in SQL request !")
+            .cursor();
+        cursor
+            .bind(&[sqlite::Value::String(self.hash_full_id.clone())])
+            .expect("get_endpoints_for_api() : Error in cursor binding !");
+
+        // If endpoint it's already written, update status
+        if let Some(row) = cursor
+            .next()
+            .expect("get_endpoints_for_api() : Error in cursor.next()")
+        {
+            if row[0].as_integer().unwrap() as u32 != self.status {
+                db.0
+                    .execute(format!(
+                        "UPDATE endpoints SET status={} WHERE hash_full_id='{}'",
+                        self.status, self.hash_full_id
+                    ))
+                    .unwrap();
+            }
+        } else {
+            db.0
+            .execute(
+                format!(
+                    "INSERT INTO endpoints (hash_full_id, status, node_id, pubkey, api, version, endpoint, last_check) VALUES ('{}', {}, {}, '{}', {}, {}, '{}', {});",
+                    self.hash_full_id, self.status, self.node_id, self.pubkey.to_string(),
+                    api_to_integer(&self.api), self.version, self.endpoint, self.last_check
+                )
+            )
+            .unwrap();
+        }
+    }
+}
diff --git a/dal/identity.rs b/dal/identity.rs
new file mode 100644
index 0000000000000000000000000000000000000000..2486c628b3e9ff9ceeeb17fb42b06b52c6fbae7b
--- /dev/null
+++ b/dal/identity.rs
@@ -0,0 +1,280 @@
+extern crate sqlite;
+
+use super::block::{blockstamp_to_timestamp, DALBlock};
+use super::DuniterDB;
+use duniter_crypto::keys::{ed25519, PublicKey, Signature};
+use duniter_documents::blockchain::v10::documents::identity::IdentityDocumentBuilder;
+use duniter_documents::blockchain::v10::documents::IdentityDocument;
+use duniter_documents::blockchain::{Document, DocumentBuilder};
+use duniter_documents::Blockstamp;
+use duniter_wotb::NodeId;
+use std::collections::HashMap;
+
+#[derive(Debug, Clone)]
+pub struct DALIdentity {
+    pub hash: String,
+    pub state: isize,
+    pub joined_on: Blockstamp,
+    pub penultimate_renewed_on: Blockstamp,
+    pub last_renewed_on: Blockstamp,
+    pub expires_on: u64,
+    pub revokes_on: u64,
+    pub expired_on: Option<Blockstamp>,
+    pub revoked_on: Option<Blockstamp>,
+    pub idty_doc: IdentityDocument,
+}
+
+impl DALIdentity {
+    pub fn exclude_identity(
+        db: &DuniterDB,
+        wotb_id: NodeId,
+        renewal_blockstamp: Blockstamp,
+        revert: bool,
+    ) {
+        let state = if revert { 0 } else { 1 };
+        let expired_on = if revert {
+            None
+        } else {
+            Some(renewal_blockstamp)
+        };
+        let mut cursor = db
+            .0
+            .prepare("UPDATE identities SET state=?, expired_on=?  WHERE wotb_id=?;")
+            .expect("Fail to exclude idty !")
+            .cursor();
+
+        cursor
+            .bind(&[
+                sqlite::Value::Integer(i64::from(state)),
+                sqlite::Value::String(
+                    expired_on
+                        .clone()
+                        .unwrap_or_else(Blockstamp::default)
+                        .to_string(),
+                ),
+                sqlite::Value::Integer(wotb_id.0 as i64),
+            ])
+            .expect("Fail to exclude idty !");
+    }
+
+    pub fn get_wotb_index(db: &DuniterDB) -> HashMap<ed25519::PublicKey, NodeId> {
+        let mut wotb_index: HashMap<ed25519::PublicKey, NodeId> = HashMap::new();
+
+        let mut cursor = db
+            .0
+            .prepare("SELECT wotb_id, pubkey FROM identities ORDER BY wotb_id ASC;")
+            .unwrap()
+            .cursor();
+
+        while let Some(row) = cursor.next().unwrap() {
+            wotb_index.insert(
+                PublicKey::from_base58(row[1].as_string().unwrap()).unwrap(),
+                NodeId(row[0].as_integer().unwrap() as usize),
+            );
+        }
+        wotb_index
+    }
+
+    pub fn create_identity(
+        db: &DuniterDB,
+        idty_doc: &IdentityDocument,
+        current_blockstamp: Blockstamp,
+    ) -> DALIdentity {
+        let created_on = idty_doc.blockstamp();
+        let created_time = blockstamp_to_timestamp(&created_on, &db)
+            .expect("convert blockstamp to timestamp failure !");
+
+        DALIdentity {
+            hash: "0".to_string(),
+            state: 0,
+            joined_on: current_blockstamp,
+            penultimate_renewed_on: created_on.clone(),
+            last_renewed_on: created_on,
+            expires_on: created_time + super::constants::G1_PARAMS.ms_validity,
+            revokes_on: created_time + super::constants::G1_PARAMS.ms_validity,
+            expired_on: None,
+            revoked_on: None,
+            idty_doc: idty_doc.clone(),
+        }
+    }
+
+    pub fn revoke_identity(
+        db: &DuniterDB,
+        wotb_id: NodeId,
+        renewal_blockstamp: &Blockstamp,
+        revert: bool,
+    ) {
+        let state = if revert { 2 } else { 1 };
+        let revoked_on = if revert {
+            String::from("")
+        } else {
+            renewal_blockstamp.to_string()
+        };
+        let mut cursor = db
+            .0
+            .prepare("UPDATE identities SET state=?, revoked_on=?  WHERE wotb_id=?;")
+            .expect("Fail to exclude idty !")
+            .cursor();
+
+        cursor
+            .bind(&[
+                sqlite::Value::Integer(state),
+                sqlite::Value::String(revoked_on),
+                sqlite::Value::Integer(wotb_id.0 as i64),
+            ])
+            .expect("Fail to exclude idty !");
+    }
+
+    pub fn renewal_identity(
+        &mut self,
+        db: &DuniterDB,
+        pubkey: &ed25519::PublicKey,
+        renewal_blockstamp: &Blockstamp,
+        renawal_timestamp: u64,
+        revert: bool,
+    ) {
+        let mut penultimate_renewed_block: Option<DALBlock> = None;
+        let revert_excluding = if revert {
+            penultimate_renewed_block = Some(
+                DALBlock::get_block(
+                    self.idty_doc.currency(),
+                    db,
+                    &self.penultimate_renewed_on.clone(),
+                ).expect("renewal_identity: Fail to get penultimate_renewed_block"),
+            );
+            penultimate_renewed_block.clone().unwrap().block.median_time
+                + super::constants::G1_PARAMS.ms_validity < renawal_timestamp
+        } else {
+            false
+        };
+        self.state = if revert && revert_excluding { 1 } else { 0 };
+        self.expires_on = if revert {
+            penultimate_renewed_block.unwrap().block.median_time
+                + super::constants::G1_PARAMS.ms_validity
+        } else {
+            renawal_timestamp + super::constants::G1_PARAMS.ms_validity
+        };
+        let mut cursor = db.0
+            .prepare(
+                "UPDATE identities SET state=?, last_renewed_on=?, expires_on=?, revokes_on=?  WHERE pubkey=?;",
+            )
+            .expect("Fail to renewal idty !")
+            .cursor();
+
+        cursor
+            .bind(&[
+                sqlite::Value::Integer(self.state as i64),
+                sqlite::Value::String(renewal_blockstamp.to_string()),
+                sqlite::Value::Integer(self.expires_on as i64),
+                sqlite::Value::Integer(
+                    (renawal_timestamp + (super::constants::G1_PARAMS.ms_validity * 2)) as i64,
+                ),
+                sqlite::Value::String(pubkey.to_string()),
+            ])
+            .expect("Fail to renewal idty !");
+    }
+
+    pub fn remove_identity(db: &DuniterDB, wotb_id: NodeId) -> () {
+        db.0
+            .execute(format!(
+                "DELETE FROM identities WHERE wotb_id={}",
+                wotb_id.0
+            ))
+            .unwrap();
+    }
+
+    pub fn get_identity(
+        currency: &str,
+        db: &DuniterDB,
+        pubkey: &ed25519::PublicKey,
+    ) -> Option<DALIdentity> {
+        let mut cursor = db
+            .0
+            .prepare(
+                "SELECT uid, hash, sig,
+                state, created_on, joined_on, penultimate_renewed_on, last_renewed_on,
+                expires_on, revokes_on, expired_on, revoked_on FROM identities WHERE pubkey=?;",
+            )
+            .expect("Fail to get idty !")
+            .cursor();
+
+        cursor
+            .bind(&[sqlite::Value::String(pubkey.to_string())])
+            .expect("Fail to get idty !");
+
+        if let Some(row) = cursor.next().expect("get_identity: cursor error") {
+            let idty_doc_builder = IdentityDocumentBuilder {
+                currency,
+                username: row[0]
+                    .as_string()
+                    .expect("get_identity: fail to parse username"),
+                blockstamp: &Blockstamp::from_string(
+                    row[4]
+                        .as_string()
+                        .expect("DB Error : idty created_on invalid !"),
+                ).expect("DB Error : idty created_on invalid (2) !"),
+                issuer: &pubkey,
+            };
+            let idty_sig = Signature::from_base64(
+                row[2].as_string().expect("get_identity: fail to parse sig"),
+            ).expect("get_identity: fail to parse sig (2)");
+            let idty_doc = idty_doc_builder.build_with_signature(vec![idty_sig]);
+
+            let expired_on = match Blockstamp::from_string(
+                row[10]
+                    .as_string()
+                    .expect("get_identity: fail to parse expire on"),
+            ) {
+                Ok(blockstamp) => Some(blockstamp),
+                Err(_) => None,
+            };
+            let revoked_on = match Blockstamp::from_string(
+                row[11]
+                    .as_string()
+                    .expect("get_identity: fail to parse revoked on"),
+            ) {
+                Ok(blockstamp) => Some(blockstamp),
+                Err(_) => None,
+            };
+            Some(DALIdentity {
+                hash: row[2]
+                    .as_string()
+                    .expect("get_identity: fail to parse hash")
+                    .to_string(),
+                state: row[3]
+                    .as_integer()
+                    .expect("get_identity: fail to parse state") as isize,
+                joined_on: Blockstamp::from_string(
+                    row[5]
+                        .as_string()
+                        .expect("DB Error : idty joined_on invalid !"),
+                ).expect("DB Error : idty joined_on invalid !"),
+                penultimate_renewed_on: Blockstamp::from_string(
+                    row[6]
+                        .as_string()
+                        .expect("DB Error : idty penultimate_renewed_on invalid !"),
+                ).expect(
+                    "DB Error : idty penultimate_renewed_on invalid (2) !",
+                ),
+                last_renewed_on: Blockstamp::from_string(
+                    row[7]
+                        .as_string()
+                        .expect("get_identity: fail to parse last_renewed_on"),
+                ).expect("get_identity: fail to parse last_renewed_on (2)"),
+                expires_on: row[8]
+                    .as_integer()
+                    .expect("get_identity: fail to parse expires_on")
+                    as u64,
+                revokes_on: row[9]
+                    .as_integer()
+                    .expect("get_identity: fail to parse revokes_on")
+                    as u64,
+                expired_on,
+                revoked_on,
+                idty_doc,
+            })
+        } else {
+            None
+        }
+    }
+}
diff --git a/dal/lib.rs b/dal/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..7c1541dcc352dbe86ff8262f27ae691df90a9b81
--- /dev/null
+++ b/dal/lib.rs
@@ -0,0 +1,266 @@
+//  Copyright (C) 2018  The Duniter Project Developers.
+//
+// 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/>.
+
+//! Defined the few global types used by all modules,
+//! as well as the DuniterModule trait that all modules must implement.
+
+#![cfg_attr(feature = "strict", deny(warnings))]
+#![cfg_attr(feature = "cargo-clippy", allow(implicit_hasher))]
+#![cfg_attr(feature = "exp", allow(warnings))]
+#![deny(
+    missing_debug_implementations, missing_copy_implementations, trivial_casts,
+    trivial_numeric_casts, unsafe_code, unstable_features, unused_import_braces
+)]
+
+#[macro_use]
+extern crate log;
+#[macro_use]
+extern crate serde_json;
+
+extern crate duniter_crypto;
+extern crate duniter_documents;
+extern crate duniter_wotb;
+extern crate serde;
+extern crate sqlite;
+
+pub mod block;
+pub mod constants;
+pub mod dal_event;
+pub mod dal_requests;
+pub mod identity;
+pub mod parsers;
+pub mod tools;
+pub mod writers;
+
+use duniter_crypto::keys::{PublicKey, Signature};
+use duniter_documents::blockchain::v10::documents::BlockDocument;
+use duniter_documents::{BlockHash, BlockId, Blockstamp, Hash};
+use duniter_wotb::NodeId;
+use std::fmt::Debug;
+use std::marker;
+use std::path::PathBuf;
+
+use self::block::DALBlock;
+
+pub struct DuniterDB(pub sqlite::Connection);
+
+impl Debug for DuniterDB {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+        write!(f, "DuniterDB {{ }}")
+    }
+}
+
+pub trait FromJsonValue
+where
+    Self: marker::Sized,
+{
+    fn from_json_value(value: &serde_json::Value) -> Option<Self>;
+}
+
+pub trait WriteToDuniterDB {
+    fn write(&self, db: &DuniterDB, written_blockstamp: Blockstamp, written_timestamp: u64);
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum ForkState {
+    Free(),
+    Full(),
+    Isolate(),
+}
+
+#[derive(Debug, Clone)]
+pub struct WotState {
+    pub block_number: u32,
+    pub block_hash: String,
+    pub sentries_count: usize,
+    pub average_density: usize,
+    pub average_distance: usize,
+    pub distances: Vec<usize>,
+    pub average_connectivity: usize,
+    pub connectivities: Vec<usize>,
+    pub average_centrality: usize,
+    pub centralities: Vec<u64>,
+}
+
+fn _use_json_macro() -> serde_json::Value {
+    json!({})
+}
+
+pub fn open_db(db_path: &PathBuf, memory_mode: bool) -> Result<DuniterDB, sqlite::Error> {
+    let conn: sqlite::Connection;
+    if memory_mode || !db_path.as_path().exists() {
+        if memory_mode {
+            conn = sqlite::open(":memory:")?;
+        } else {
+            conn = sqlite::open(db_path.as_path())?;
+        }
+        //conn.execute("PRAGMA synchronous = 0;")
+        //.expect("Fail to configure SQLite DB (PRAGMA) !");
+        conn.execute(
+            "
+        CREATE TABLE wot_history (block_number INTEGER, block_hash TEXT, sentries_count INTEGER,
+            average_density INTEGER, average_distance INTEGER,
+            distances TEXT, average_connectivity INTEGER, connectivities TEXT,
+            average_centrality INTEGER, centralities TEXT);
+        CREATE TABLE blocks (fork INTEGER, isolate INTEGER, version INTEGER, nonce INTEGER, number INTEGER,
+            pow_min INTEGER, time INTEGER, median_time INTEGER, members_count INTEGER,
+            monetary_mass INTEGER, unit_base INTEGER, issuers_count INTEGER, issuers_frame INTEGER,
+            issuers_frame_var INTEGER, median_frame INTEGER, second_tiercile_frame INTEGER,
+            currency TEXT, issuer TEXT, signature TEXT, hash TEXT, previous_hash TEXT, inner_hash TEXT, dividend INTEGER, identities TEXT, joiners TEXT,
+            actives TEXT, leavers TEXT, revoked TEXT, excluded TEXT, certifications TEXT,
+            transactions TEXT);
+        CREATE TABLE identities (wotb_id INTEGER, uid TEXT, pubkey TEXT, hash TEXT, sig TEXT,
+            state INTEGER, created_on TEXT, joined_on TEXT, penultimate_renewed_on TEXT, last_renewed_on TEXT,
+            expires_on INTEGER, revokes_on INTEGER, expired_on TEXT, revoked_on TEXT);
+        CREATE TABLE certifications (pubkey_from TEXT, pubkey_to TEXT, created_on TEXT,
+            signature TEXT, written_on TEXT, expires_on INTEGER, chainable_on INTEGER);
+        ",
+        )?;
+    } else {
+        conn = sqlite::open(db_path.as_path())?;
+    }
+    Ok(DuniterDB(conn))
+}
+
+pub fn close_db(db: &DuniterDB) {
+    db.0
+        .execute("PRAGMA optimize;")
+        .expect("Fail to optimize SQLite DB !");
+}
+
+pub fn get_uid(db: &DuniterDB, wotb_id: NodeId) -> Option<String> {
+    let mut cursor: sqlite::Cursor = db
+        .0
+        .prepare("SELECT uid FROM identities WHERE wotb_id=? AND state=0 LIMIT 1;")
+        .expect("Request SQL get_current_block is wrong !")
+        .cursor();
+    cursor
+        .bind(&[sqlite::Value::Integer(wotb_id.0 as i64)])
+        .expect("0");
+    if let Some(row) = cursor.next().expect("fait to get_uid() : cursor error") {
+        Some(String::from(
+            row[0]
+                .as_string()
+                .expect("get_uid: Fail to parse uid field in str !"),
+        ))
+    } else {
+        None
+    }
+}
+
+pub fn new_get_current_block(db: &DuniterDB) -> Option<BlockDocument> {
+    let mut cursor: sqlite::Cursor = db.0
+        .prepare(
+            "SELECT version, nonce, number, pow_min, time, median_time, members_count, monetary_mass, unit_base, issuers_count, issuers_frame, issuers_frame_var, median_frame, second_tiercile_frame, currency, issuer, signature, hash, dividend, joiners, actives, leavers, revoked, excluded, certifications, transactions FROM blocks
+            WHERE fork=0 ORDER BY median_time DESC LIMIT ?;",
+        )
+        .expect("Request SQL get_current_block is wrong !")
+        .cursor();
+
+    cursor.bind(&[sqlite::Value::Integer(1)]).expect("0");
+    if let Some(row) = cursor.next().expect("1") {
+        let dividend = row[18].as_integer().expect("dividend");
+        let dividend = if dividend > 0 {
+            Some(dividend as usize)
+        } else {
+            None
+        };
+        return Some(BlockDocument {
+            nonce: row[1].as_integer().expect("nonce") as u64,
+            number: BlockId(row[2].as_integer().expect("2") as u32),
+            pow_min: row[3].as_integer().expect("version") as usize,
+            time: row[4].as_integer().expect("time") as u64,
+            median_time: row[5].as_integer().expect("median_time") as u64,
+            members_count: row[6].as_integer().expect("7") as usize,
+            monetary_mass: row[7].as_integer().expect("8") as usize,
+            unit_base: row[8].as_integer().expect("unit_base") as usize,
+            issuers_count: row[9].as_integer().expect("issuers_count") as usize,
+            issuers_frame: row[10].as_integer().expect("issuers_frame") as isize,
+            issuers_frame_var: row[11].as_integer().expect("issuers_frame_var") as isize,
+            currency: row[14].as_string().expect("currency").to_string(),
+            issuers: vec![PublicKey::from_base58(row[15].as_string().expect("issuer")).unwrap()],
+            signatures: vec![
+                Signature::from_base64(row[16].as_string().expect("signature")).unwrap(),
+            ],
+            hash: Some(BlockHash(
+                Hash::from_hex(row[17].as_string().expect("hash")).unwrap(),
+            )),
+            parameters: None,
+            previous_hash: Hash::default(),
+            previous_issuer: None,
+            inner_hash: None,
+            dividend,
+            identities: Vec::with_capacity(0),
+            joiners: Vec::with_capacity(0),
+            actives: Vec::with_capacity(0),
+            leavers: Vec::with_capacity(0),
+            revoked: Vec::with_capacity(0),
+            excluded: Vec::with_capacity(0),
+            certifications: Vec::with_capacity(0),
+            transactions: Vec::with_capacity(0),
+            inner_hash_and_nonce_str: String::new(),
+        });
+    }
+    None
+}
+
+pub fn get_current_block(currency: &str, db: &DuniterDB) -> Option<DALBlock> {
+    let mut cursor: sqlite::Cursor = db
+        .0
+        .prepare("SELECT number, hash FROM blocks WHERE fork=0 ORDER BY median_time DESC LIMIT ?;")
+        .expect("Request SQL get_current_block is wrong !")
+        .cursor();
+
+    cursor.bind(&[sqlite::Value::Integer(1)]).expect("0");
+
+    if let Some(row) = cursor.next().unwrap() {
+        let blockstamp = Blockstamp {
+            id: BlockId(row[0].as_integer().unwrap() as u32),
+            hash: BlockHash(Hash::from_hex(row[1].as_string().unwrap()).unwrap()),
+        };
+        DALBlock::get_block(currency, db, &blockstamp)
+    } else {
+        None
+    }
+}
+
+pub fn register_wot_state(db: &DuniterDB, wot_state: &WotState) {
+    if wot_state.block_number != 1 {
+        db.0
+            .execute(format!(
+                "INSERT INTO wot_history (block_number, block_hash, sentries_count,
+                average_density, average_distance, distances,
+                average_connectivity, connectivities, average_centrality, centralities)
+                VALUES ({}, '{}', {}, {}, {}, '{}', {}, '{}', {}, '{}');",
+                wot_state.block_number,
+                wot_state.block_hash,
+                wot_state.sentries_count,
+                wot_state.average_density,
+                wot_state.average_distance,
+                serde_json::to_string(&wot_state.distances).unwrap(),
+                wot_state.average_connectivity,
+                serde_json::to_string(&wot_state.connectivities).unwrap(),
+                wot_state.average_centrality,
+                serde_json::to_string(&wot_state.centralities).unwrap(),
+            ))
+            .unwrap();
+    }
+}
+
+#[derive(Debug, Copy, Clone)]
+pub enum BlockchainError {
+    UnexpectedBlockNumber(),
+    UnknowError(),
+}
diff --git a/dal/parsers/blocks.rs b/dal/parsers/blocks.rs
new file mode 100644
index 0000000000000000000000000000000000000000..780ff728f3cc4057a26ff2123f7f3f85eef6ff0b
--- /dev/null
+++ b/dal/parsers/blocks.rs
@@ -0,0 +1,144 @@
+extern crate duniter_crypto;
+extern crate duniter_documents;
+extern crate duniter_network;
+extern crate serde_json;
+
+use self::duniter_network::{NetworkBlock, NetworkBlockV10};
+use super::excluded::parse_exclusions_from_json_value;
+use super::identities::parse_compact_identity;
+use super::transactions::parse_transaction;
+use duniter_crypto::keys::{PublicKey, Signature};
+use duniter_documents::blockchain::v10::documents::membership::MembershipType;
+use duniter_documents::blockchain::v10::documents::BlockDocument;
+use duniter_documents::{BlockHash, BlockId, Hash};
+
+pub fn parse_json_block(source: &serde_json::Value) -> Option<NetworkBlock> {
+    let number = BlockId(source.get("number")?.as_u64()? as u32);
+    let currency = source.get("currency")?.as_str()?.to_string();
+    let issuer = match PublicKey::from_base58(source.get("issuer")?.as_str()?) {
+        Ok(pubkey) => pubkey,
+        Err(_) => return None,
+    };
+    let sig = match Signature::from_base64(source.get("signature")?.as_str()?) {
+        Ok(sig) => sig,
+        Err(_) => return None,
+    };
+    let hash = match Hash::from_hex(source.get("hash")?.as_str()?) {
+        Ok(hash) => hash,
+        Err(_) => return None,
+    };
+    let previous_hash = match source.get("previousHash")?.as_str() {
+        Some(hash_str) => match Hash::from_hex(hash_str) {
+            Ok(hash) => hash,
+            Err(_) => return None,
+        },
+        None => if number.0 > 0 {
+            return None;
+        } else {
+            Hash::default()
+        },
+    };
+    let previous_issuer = match source.get("previousIssuer")?.as_str() {
+        Some(pubkey_str) => match PublicKey::from_base58(pubkey_str) {
+            Ok(pubkey) => Some(pubkey),
+            Err(_) => return None,
+        },
+        None => if number.0 > 0 {
+            return None;
+        } else {
+            None
+        },
+    };
+    let inner_hash = match Hash::from_hex(source.get("inner_hash")?.as_str()?) {
+        Ok(hash) => Some(hash),
+        Err(_) => return None,
+    };
+    let dividend = match source.get("dividend")?.as_u64() {
+        Some(dividend) => Some(dividend as usize),
+        None => None,
+    };
+    let mut identities = Vec::new();
+    for raw_idty in source.get("identities")?.as_array()? {
+        identities.push(parse_compact_identity(&currency, &raw_idty)?);
+    }
+    let mut joiners = Vec::new();
+    for joiner in super::memberships::parse_memberships_from_json_value(
+        &currency,
+        MembershipType::In(),
+        &source.get("joiners")?.as_array()?,
+    ) {
+        if let Ok(joiner) = joiner {
+            joiners.push(joiner);
+        } else {
+            return None;
+        }
+    }
+    let mut actives = Vec::new();
+    for active in super::memberships::parse_memberships_from_json_value(
+        &currency,
+        MembershipType::In(),
+        &source.get("actives")?.as_array()?,
+    ) {
+        if let Ok(active) = active {
+            actives.push(active);
+        } else {
+            return None;
+        }
+    }
+    let mut leavers = Vec::new();
+    for leaver in super::memberships::parse_memberships_from_json_value(
+        &currency,
+        MembershipType::Out(),
+        &source.get("leavers")?.as_array()?,
+    ) {
+        if let Ok(leaver) = leaver {
+            leavers.push(leaver);
+        } else {
+            return None;
+        }
+    }
+    let mut transactions = Vec::new();
+    for json_tx in source.get("transactions")?.as_array()? {
+        transactions.push(parse_transaction("g1", &json_tx)?);
+    }
+    let block_doc = BlockDocument {
+        nonce: source.get("nonce")?.as_i64()? as u64,
+        number: BlockId(source.get("number")?.as_u64()? as u32),
+        pow_min: source.get("powMin")?.as_u64()? as usize,
+        time: source.get("time")?.as_u64()?,
+        median_time: source.get("medianTime")?.as_u64()?,
+        members_count: source.get("membersCount")?.as_u64()? as usize,
+        monetary_mass: source.get("monetaryMass")?.as_u64()? as usize,
+        unit_base: source.get("unitbase")?.as_u64()? as usize,
+        issuers_count: source.get("issuersCount")?.as_u64()? as usize,
+        issuers_frame: source.get("issuersFrame")?.as_i64()? as isize,
+        issuers_frame_var: source.get("issuersFrameVar")?.as_i64()? as isize,
+        currency,
+        issuers: vec![issuer],
+        signatures: vec![sig],
+        hash: Some(BlockHash(hash)),
+        parameters: None,
+        previous_hash,
+        previous_issuer,
+        inner_hash,
+        dividend,
+        identities,
+        joiners,
+        actives,
+        leavers,
+        revoked: Vec::with_capacity(0),
+        excluded: parse_exclusions_from_json_value(&source.get("excluded")?.as_array()?),
+        certifications: Vec::with_capacity(0),
+        transactions,
+        inner_hash_and_nonce_str: format!(
+            "InnerHash: {}\nNonce: {}\n",
+            inner_hash.unwrap().to_hex(),
+            source.get("nonce")?.as_u64()?
+        ),
+    };
+    Some(NetworkBlock::V10(Box::new(NetworkBlockV10 {
+        uncompleted_block_doc: block_doc,
+        revoked: source.get("revoked")?.as_array()?.clone(),
+        certifications: source.get("certifications")?.as_array()?.clone(),
+    })))
+}
diff --git a/dal/parsers/certifications.rs b/dal/parsers/certifications.rs
new file mode 100644
index 0000000000000000000000000000000000000000..a62b0a4ad73c69b00f27bff8ee42d97574b61048
--- /dev/null
+++ b/dal/parsers/certifications.rs
@@ -0,0 +1,131 @@
+extern crate serde;
+extern crate serde_json;
+extern crate sqlite;
+
+use super::super::block::DALBlock;
+use super::super::identity::DALIdentity;
+use super::super::DuniterDB;
+use duniter_crypto::keys::{ed25519, PublicKey, Signature};
+use duniter_documents::blockchain::v10::documents::certification::{
+    CertificationDocumentBuilder, CompactCertificationDocument,
+};
+use duniter_documents::blockchain::v10::documents::{
+    CertificationDocument, IdentityDocument, TextDocumentFormat,
+};
+use duniter_documents::blockchain::{Document, DocumentBuilder};
+use duniter_documents::{BlockHash, BlockId, Blockstamp, Hash};
+use std::collections::HashMap;
+
+pub fn parse_certifications_into_compact(
+    json_certs: &Vec<serde_json::Value>,
+) -> Vec<TextDocumentFormat<CertificationDocument>> {
+    let mut certifications: Vec<TextDocumentFormat<CertificationDocument>> = Vec::new();
+    for certification in json_certs.iter() {
+        let certifications_datas: Vec<&str> = certification
+            .as_str()
+            .expect("Receive block in wrong format : fail to split cert !")
+            .split(':')
+            .collect();
+        if certifications_datas.len() == 4 {
+            certifications.push(TextDocumentFormat::Compact(CompactCertificationDocument {
+                issuer: PublicKey::from_base58(certifications_datas[0])
+                    .expect("Receive block in wrong format : fail to parse issuer !"),
+                target: PublicKey::from_base58(certifications_datas[1])
+                    .expect("Receive block in wrong format : fail to parse target !"),
+                block_number: BlockId(
+                    certifications_datas[2]
+                        .parse()
+                        .expect("Receive block in wrong format : fail to parse block number !"),
+                ),
+                signature: Signature::from_base64(certifications_datas[3])
+                    .expect("Receive block in wrong format : fail to parse signature !"),
+            }));
+        }
+    }
+    certifications
+}
+
+pub fn parse_certifications_from_json_value(
+    currency: &str,
+    db: &DuniterDB,
+    block_identities: &HashMap<ed25519::PublicKey, IdentityDocument>,
+    array_certifications: &[serde_json::Value],
+) -> Vec<TextDocumentFormat<CertificationDocument>> {
+    let mut certifications: Vec<TextDocumentFormat<CertificationDocument>> = Vec::new();
+    for certification in array_certifications.iter() {
+        let certification_datas: Vec<&str> = certification
+            .as_str()
+            .expect("Fail to parse certs : json isn't str !")
+            .split(':')
+            .collect();
+        if certification_datas.len() == 4 {
+            let target = PublicKey::from_base58(certification_datas[1])
+                .expect("Fail to parse cert target !");
+            let target_idty_doc: IdentityDocument = match block_identities.get(&target) {
+                Some(idty_doc) => idty_doc.clone(),
+                None => {
+                    let dal_idty = DALIdentity::get_identity(currency, db, &target)
+                        .expect("target identity not found in bdd !");
+                    dal_idty.idty_doc
+                }
+            };
+            let cert_blockstamp_id = BlockId(
+                certification_datas[2]
+                    .parse()
+                    .expect("Fail to parse cert blockstamp !"),
+            );
+            let cert_builder =
+                CertificationDocumentBuilder {
+                    currency,
+                    issuer: &PublicKey::from_base58(certification_datas[0])
+                        .expect("Fail to parse cert issuer !"),
+                    blockstamp: &Blockstamp {
+                        id: cert_blockstamp_id,
+                        hash: if cert_blockstamp_id == BlockId(0) {
+                            BlockHash(Hash::from_hex(
+                            "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855",
+                        ).expect("Fail to parse cert : invalid genesis hash"))
+                        } else {
+                            DALBlock::get_block_hash(db, &cert_blockstamp_id).expect(&format!(
+                                "Fatal Error : Block {} not found in bdd !",
+                                cert_blockstamp_id
+                            ))
+                        },
+                    },
+                    target: &target,
+                    identity_username: target_idty_doc.username(),
+                    identity_blockstamp: &target_idty_doc.blockstamp(),
+                    identity_sig: &target_idty_doc.signatures()[0],
+                };
+            let cert_sig =
+                Signature::from_base64(certification_datas[3]).expect("Fail to parse cert sig !");
+            certifications.push(TextDocumentFormat::Complete(
+                cert_builder.build_with_signature(vec![cert_sig]),
+            ));
+        }
+    }
+    certifications
+}
+
+pub fn parse_certifications(
+    currency: &str,
+    db: &DuniterDB,
+    block_identities: &HashMap<ed25519::PublicKey, IdentityDocument>,
+    json_datas: &str,
+) -> Option<Vec<TextDocumentFormat<CertificationDocument>>> {
+    let raw_certifications: serde_json::Value =
+        serde_json::from_str(json_datas).expect("Fail to parse certs: str isn't json !");
+
+    if raw_certifications.is_array() {
+        Some(parse_certifications_from_json_value(
+            currency,
+            db,
+            block_identities,
+            raw_certifications
+                .as_array()
+                .expect("Fail to parse certs: json datas must be an array !"),
+        ))
+    } else {
+        None
+    }
+}
diff --git a/dal/parsers/excluded.rs b/dal/parsers/excluded.rs
new file mode 100644
index 0000000000000000000000000000000000000000..329261da4fe5a876d6d751945b53ef1d3bd8d505
--- /dev/null
+++ b/dal/parsers/excluded.rs
@@ -0,0 +1,26 @@
+extern crate serde;
+extern crate serde_json;
+
+use duniter_crypto::keys::{ed25519, PublicKey};
+
+pub fn parse_exclusions(json_datas: &str) -> Option<Vec<ed25519::PublicKey>> {
+    let raw_exclusions: serde_json::Value = serde_json::from_str(json_datas).unwrap();
+
+    if raw_exclusions.is_array() {
+        Some(parse_exclusions_from_json_value(
+            raw_exclusions.as_array().unwrap(),
+        ))
+    } else {
+        None
+    }
+}
+
+pub fn parse_exclusions_from_json_value(
+    array_exclusions: &[serde_json::Value],
+) -> Vec<ed25519::PublicKey> {
+    let mut exclusions: Vec<ed25519::PublicKey> = Vec::new();
+    for exclusion in array_exclusions.iter() {
+        exclusions.push(PublicKey::from_base58(exclusion.as_str().unwrap()).unwrap());
+    }
+    exclusions
+}
diff --git a/dal/parsers/identities.rs b/dal/parsers/identities.rs
new file mode 100644
index 0000000000000000000000000000000000000000..9791d4c0f5045d08ce499709ee79857c1676f6c2
--- /dev/null
+++ b/dal/parsers/identities.rs
@@ -0,0 +1,102 @@
+extern crate serde_json;
+extern crate sqlite;
+
+use duniter_crypto::keys::{PublicKey, Signature};
+use duniter_documents::blockchain::v10::documents::identity::IdentityDocumentBuilder;
+use duniter_documents::blockchain::v10::documents::IdentityDocument;
+use duniter_documents::blockchain::DocumentBuilder;
+use duniter_documents::Blockstamp;
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum IdentityParseError {
+    WrongFormat(),
+}
+
+pub fn parse_identities(currency: &str, json_datas: &str) -> Option<Vec<IdentityDocument>> {
+    let raw_idties: serde_json::Value = serde_json::from_str(json_datas).unwrap();
+    if raw_idties.is_array() {
+        return Some(
+            parse_identities_from_json_value(currency, raw_idties.as_array().unwrap())
+                .iter()
+                .map(|i| {
+                    i.clone()
+                        .expect("Fatal error : Fail to parse identity from local DB !")
+                })
+                .collect(),
+        );
+    }
+    None
+}
+
+pub fn parse_identities_from_json_value(
+    currency: &str,
+    array_identities: &[serde_json::Value],
+) -> Vec<Result<IdentityDocument, IdentityParseError>> {
+    array_identities
+        .iter()
+        .map(|idty| {
+            let idty_datas: Vec<&str> = idty.as_str().unwrap().split(':').collect();
+            if idty_datas.len() == 4 {
+                let idty_doc_builder = IdentityDocumentBuilder {
+                    currency,
+                    issuer: &PublicKey::from_base58(idty_datas[0]).unwrap(),
+                    blockstamp: &Blockstamp::from_string(idty_datas[2]).unwrap(),
+                    username: idty_datas[3],
+                };
+                let idty_sig = Signature::from_base64(idty_datas[1]).unwrap();
+                //memberships.push(membership_doc_builder.build_with_signature(vec![membership_sig]));
+                Ok(idty_doc_builder.build_with_signature(vec![idty_sig]))
+            } else {
+                Err(IdentityParseError::WrongFormat())
+            }
+        })
+        .collect()
+
+    /*for membership in array_memberships.iter() {
+        let membership_datas: Vec<&str> = membership.as_str().unwrap().split(':').collect();
+        if membership_datas.len() == 5 {
+            let membership_doc_builder = IdentityDocumentBuilder {
+                currency,
+                issuer: &PublicKey::from_base58(membership_datas[0]).unwrap(),
+                blockstamp: &Blockstamp::from_string(membership_datas[2]).unwrap(),
+                membership: membership_type,
+                identity_username: membership_datas[4],
+                identity_blockstamp: &Blockstamp::from_string(membership_datas[3]).unwrap(),
+            };
+            let membership_sig = Signature::from_base64(membership_datas[1]).unwrap();
+            memberships.push(membership_doc_builder.build_with_signature(vec![membership_sig]));
+        }
+    }
+    memberships*/
+}
+
+pub fn parse_compact_identity(
+    currency: &str,
+    source: &serde_json::Value,
+) -> Option<IdentityDocument> {
+    if source.is_string() {
+        let idty_elements: Vec<&str> = source.as_str().unwrap().split(':').collect();
+        let issuer = match PublicKey::from_base58(idty_elements[0]) {
+            Ok(pubkey) => pubkey,
+            Err(_) => return None,
+        };
+        let signature = match Signature::from_base64(idty_elements[1]) {
+            Ok(sig) => sig,
+            Err(_) => return None,
+        };
+        let blockstamp = match Blockstamp::from_string(idty_elements[2]) {
+            Ok(blockstamp) => blockstamp,
+            Err(_) => return None,
+        };
+        let username = idty_elements[3];
+        let idty_doc_builder = IdentityDocumentBuilder {
+            currency,
+            username,
+            blockstamp: &blockstamp,
+            issuer: &issuer,
+        };
+        Some(idty_doc_builder.build_with_signature(vec![signature]))
+    } else {
+        None
+    }
+}
diff --git a/dal/parsers/memberships.rs b/dal/parsers/memberships.rs
new file mode 100644
index 0000000000000000000000000000000000000000..8db1a052ec09720219c62fefef64f77f6529444a
--- /dev/null
+++ b/dal/parsers/memberships.rs
@@ -0,0 +1,66 @@
+extern crate serde_json;
+extern crate sqlite;
+
+use duniter_crypto::keys::{PublicKey, Signature};
+use duniter_documents::blockchain::v10::documents::membership::{
+    MembershipDocumentBuilder, MembershipType,
+};
+use duniter_documents::blockchain::v10::documents::MembershipDocument;
+use duniter_documents::blockchain::DocumentBuilder;
+use duniter_documents::Blockstamp;
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum MembershipParseError {
+    WrongFormat(),
+}
+
+pub fn parse_memberships(
+    currency: &str,
+    membership_type: MembershipType,
+    json_datas: &str,
+) -> Option<Vec<MembershipDocument>> {
+    let raw_memberships: serde_json::Value = serde_json::from_str(json_datas).unwrap();
+    if raw_memberships.is_array() {
+        return Some(
+            parse_memberships_from_json_value(
+                currency,
+                membership_type,
+                raw_memberships.as_array().unwrap(),
+            ).iter()
+                .map(|m| {
+                    m.clone()
+                        .expect("Fatal error : Fail to parse membership from local DB !")
+                })
+                .collect(),
+        );
+    }
+    None
+}
+
+pub fn parse_memberships_from_json_value(
+    currency: &str,
+    membership_type: MembershipType,
+    array_memberships: &[serde_json::Value],
+) -> Vec<Result<MembershipDocument, MembershipParseError>> {
+    //let memberships: Vec<MembershipDocument> = Vec::new();
+    array_memberships
+        .iter()
+        .map(|membership| {
+            let membership_datas: Vec<&str> = membership.as_str().unwrap().split(':').collect();
+            if membership_datas.len() == 5 {
+                let membership_doc_builder = MembershipDocumentBuilder {
+                    currency,
+                    issuer: &PublicKey::from_base58(membership_datas[0]).unwrap(),
+                    blockstamp: &Blockstamp::from_string(membership_datas[2]).unwrap(),
+                    membership: membership_type,
+                    identity_username: membership_datas[4],
+                    identity_blockstamp: &Blockstamp::from_string(membership_datas[3]).unwrap(),
+                };
+                let membership_sig = Signature::from_base64(membership_datas[1]).unwrap();
+                Ok(membership_doc_builder.build_with_signature(vec![membership_sig]))
+            } else {
+                Err(MembershipParseError::WrongFormat())
+            }
+        })
+        .collect()
+}
diff --git a/dal/parsers/mod.rs b/dal/parsers/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..49a98a57d01502f01c3412b2cbd6e5643ef8e049
--- /dev/null
+++ b/dal/parsers/mod.rs
@@ -0,0 +1,132 @@
+pub mod blocks;
+pub mod certifications;
+pub mod excluded;
+pub mod identities;
+pub mod memberships;
+pub mod revoked;
+pub mod transactions;
+
+#[cfg(test)]
+mod tests {
+    use super::transactions::*;
+    use duniter_crypto::keys::{PublicKey, Signature};
+    use duniter_documents::blockchain::v10::documents::transaction::*;
+    use duniter_documents::blockchain::DocumentBuilder;
+    use duniter_documents::Blockstamp;
+
+    #[test]
+    fn parse_json_tx() {
+        let tx_json = json!({
+            "version": 10,
+            "currency": "g1",
+            "locktime": 0,
+            "hash": "3424206EF64C69E5F8C3906AAE571E378A498FCDAE0B85E9405A5205D7148EFE",
+            "blockstamp": "112533-000002150F2E805E604D9B31212D079570AAD8D3A4D8BB75F2C15A94A345B6B1",
+            "blockstampTime": 0,
+            "issuers": [
+                "51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2"
+            ],
+            "inputs": [
+                "1000:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:46496"
+            ],
+            "outputs": [
+                "1000:0:SIG(2yN8BRSkARcqE8NCxKMBiHfTpx1EvwULFn56Myf6qRmy)"
+            ],
+            "unlocks": [
+                "0:SIG(0)"
+            ],
+            "signatures": [
+                "5olrjFylTCsVq8I5Yr7FpXeviynICyvIwe1yG5N0RJF+VZb+bCFBnLAMpmMCU2qzUvK7z41UXOrMRybXiLa2Dw=="
+            ],
+            "comment": "Merci pour la calligraphie ;) de Liam"
+        });
+
+        let tx_builder = TransactionDocumentBuilder {
+            currency: "g1",
+            blockstamp: &Blockstamp::from_string(
+                "112533-000002150F2E805E604D9B31212D079570AAD8D3A4D8BB75F2C15A94A345B6B1",
+            ).unwrap(),
+            locktime: &0,
+            issuers: &vec![
+                PublicKey::from_base58("51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2").unwrap(),
+            ],
+            inputs: &vec![
+                TransactionInput::parse_from_str(
+                    "1000:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:46496",
+                ).unwrap(),
+            ],
+            outputs: &vec![
+                TransactionOutput::parse_from_str(
+                    "1000:0:SIG(2yN8BRSkARcqE8NCxKMBiHfTpx1EvwULFn56Myf6qRmy)",
+                ).unwrap(),
+            ],
+            unlocks: &vec![TransactionInputUnlocks::parse_from_str("0:SIG(0)").unwrap()],
+            comment: "Merci pour la calligraphie ;) de Liam",
+        };
+
+        assert_eq!(
+            parse_transaction("g1", &tx_json).expect("Fail to parse transaction !"),
+            tx_builder.build_with_signature(vec![Signature::from_base64("5olrjFylTCsVq8I5Yr7FpXeviynICyvIwe1yG5N0RJF+VZb+bCFBnLAMpmMCU2qzUvK7z41UXOrMRybXiLa2Dw==").unwrap()])
+        );
+    }
+
+    #[test]
+    fn parse_json_tx2() {
+        let tx_json = json!({
+            "version": 10,
+            "currency": "g1",
+            "locktime": 0,
+            "hash": "F98BF7A8BF82E76F5B69E70CEF0A07A08BFDB03561955EC57B254DB1E958529C",
+            "blockstamp": "58-00005B9167EBA1E32C6EAD42AE7F72D8F14B765D3C9E47D233B553D47C5AEE0C",
+            "blockstampTime": 1488990541,
+            "issuers": [
+                "FVUFRrk1K5TQGsY7PRLwqHgdHRoHrwb1hcucp4C2N5tD"
+            ],
+            "inputs": [
+                "1000:0:D:FVUFRrk1K5TQGsY7PRLwqHgdHRoHrwb1hcucp4C2N5tD:1"
+            ],
+            "outputs": [
+                "3:0:SIG(7vU9BMDhN6fBuRa2iK3JRbC6pqQKb4qDMGsFcQuT5cz)",
+                "997:0:SIG(FVUFRrk1K5TQGsY7PRLwqHgdHRoHrwb1hcucp4C2N5tD)"
+            ],
+            "unlocks": [
+                "0:SIG(0)"
+            ],
+            "signatures": [
+                "VWbvsiybM4L2X5+o+6lIiuKNw5KrD1yGZqmV+lHtA28XoRUFzochSIgfoUqBsTAaYEHY45vSX917LDXudTEzBg=="
+            ],
+            "comment": "Un petit cafe ;-)"
+        });
+
+        let tx_builder = TransactionDocumentBuilder {
+            currency: "g1",
+            blockstamp: &Blockstamp::from_string(
+                "58-00005B9167EBA1E32C6EAD42AE7F72D8F14B765D3C9E47D233B553D47C5AEE0C",
+            ).unwrap(),
+            locktime: &0,
+            issuers: &vec![
+                PublicKey::from_base58("FVUFRrk1K5TQGsY7PRLwqHgdHRoHrwb1hcucp4C2N5tD").unwrap(),
+            ],
+            inputs: &vec![
+                TransactionInput::parse_from_str(
+                    "1000:0:D:FVUFRrk1K5TQGsY7PRLwqHgdHRoHrwb1hcucp4C2N5tD:1",
+                ).unwrap(),
+            ],
+            outputs: &vec![
+                TransactionOutput::parse_from_str(
+                    "3:0:SIG(7vU9BMDhN6fBuRa2iK3JRbC6pqQKb4qDMGsFcQuT5cz)",
+                ).unwrap(),
+                TransactionOutput::parse_from_str(
+                    "997:0:SIG(FVUFRrk1K5TQGsY7PRLwqHgdHRoHrwb1hcucp4C2N5tD)",
+                ).unwrap(),
+            ],
+            unlocks: &vec![TransactionInputUnlocks::parse_from_str("0:SIG(0)").unwrap()],
+            comment: "Un petit cafe ;-)",
+        };
+
+        assert_eq!(
+            parse_transaction("g1", &tx_json).expect("Fail to parse transaction !"),
+            tx_builder.build_with_signature(vec![Signature::from_base64("VWbvsiybM4L2X5+o+6lIiuKNw5KrD1yGZqmV+lHtA28XoRUFzochSIgfoUqBsTAaYEHY45vSX917LDXudTEzBg==").unwrap()])
+        );
+    }
+}
diff --git a/dal/parsers/revoked.rs b/dal/parsers/revoked.rs
new file mode 100644
index 0000000000000000000000000000000000000000..28f12568284da1ef35e2e2583101000737babde5
--- /dev/null
+++ b/dal/parsers/revoked.rs
@@ -0,0 +1,92 @@
+extern crate serde_json;
+
+use duniter_crypto::keys::{ed25519, PublicKey, Signature};
+use duniter_documents::blockchain::v10::documents::revocation::{
+    CompactRevocationDocument, RevocationDocumentBuilder,
+};
+use duniter_documents::blockchain::v10::documents::{
+    IdentityDocument, RevocationDocument, TextDocumentFormat,
+};
+use duniter_documents::blockchain::{Document, DocumentBuilder};
+
+use super::super::identity::DALIdentity;
+use super::super::DuniterDB;
+
+use std::collections::HashMap;
+
+pub fn parse_revocations_into_compact(
+    json_recocations: &Vec<serde_json::Value>,
+) -> Vec<TextDocumentFormat<RevocationDocument>> {
+    let mut revocations: Vec<TextDocumentFormat<RevocationDocument>> = Vec::new();
+    for revocation in json_recocations.iter() {
+        let revocations_datas: Vec<&str> = revocation
+            .as_str()
+            .expect("Receive block in wrong format !")
+            .split(':')
+            .collect();
+        if revocations_datas.len() == 2 {
+            revocations.push(TextDocumentFormat::Compact(CompactRevocationDocument {
+                issuer: PublicKey::from_base58(revocations_datas[0])
+                    .expect("Receive block in wrong format !"),
+                signature: Signature::from_base64(revocations_datas[1])
+                    .expect("Receive block in wrong format !"),
+            }));
+        }
+    }
+    revocations
+}
+
+pub fn parse_revocations(
+    currency: &str,
+    db: &DuniterDB,
+    block_identities: &HashMap<ed25519::PublicKey, IdentityDocument>,
+    json_datas: &str,
+) -> Option<Vec<TextDocumentFormat<RevocationDocument>>> {
+    let raw_revocations: serde_json::Value = serde_json::from_str(json_datas).unwrap();
+
+    if raw_revocations.is_array() {
+        Some(parse_revocations_from_json_value(
+            currency,
+            db,
+            block_identities,
+            raw_revocations.as_array().unwrap(),
+        ))
+    } else {
+        None
+    }
+}
+
+pub fn parse_revocations_from_json_value(
+    currency: &str,
+    db: &DuniterDB,
+    block_identities: &HashMap<ed25519::PublicKey, IdentityDocument>,
+    array_revocations: &[serde_json::Value],
+) -> Vec<TextDocumentFormat<RevocationDocument>> {
+    let mut revocations: Vec<TextDocumentFormat<RevocationDocument>> = Vec::new();
+    for revocation in array_revocations.iter() {
+        let revocations_datas: Vec<&str> = revocation.as_str().unwrap().split(':').collect();
+        if revocations_datas.len() == 2 {
+            let idty_pubkey: ed25519::PublicKey =
+                PublicKey::from_base58(revocations_datas[0]).unwrap();
+            let idty_doc: IdentityDocument = match block_identities.get(&idty_pubkey) {
+                Some(idty_doc) => idty_doc.clone(),
+                None => {
+                    let dal_idty = DALIdentity::get_identity(currency, db, &idty_pubkey).unwrap();
+                    dal_idty.idty_doc
+                }
+            };
+            let revoc_doc_builder = RevocationDocumentBuilder {
+                currency,
+                issuer: &idty_pubkey,
+                identity_username: idty_doc.username(),
+                identity_blockstamp: &idty_doc.blockstamp(),
+                identity_sig: &idty_doc.signatures()[0],
+            };
+            let revoc_sig = Signature::from_base64(revocations_datas[1]).unwrap();
+            revocations.push(TextDocumentFormat::Complete(
+                revoc_doc_builder.build_with_signature(vec![revoc_sig]),
+            ));
+        }
+    }
+    revocations
+}
diff --git a/dal/parsers/transactions.rs b/dal/parsers/transactions.rs
new file mode 100644
index 0000000000000000000000000000000000000000..75146df4b1f32e6b842c6024616c1dd35dfbcb3d
--- /dev/null
+++ b/dal/parsers/transactions.rs
@@ -0,0 +1,233 @@
+extern crate serde;
+extern crate serde_json;
+
+use duniter_crypto::keys::{PublicKey, Signature};
+use duniter_documents::blockchain::v10::documents::transaction::{
+    TransactionDocument, TransactionDocumentBuilder, TransactionInput, TransactionInputUnlocks,
+    TransactionOutput,
+};
+use duniter_documents::blockchain::DocumentBuilder;
+use duniter_documents::Blockstamp;
+
+pub fn parse_compact_transactions(
+    currency: &str,
+    json_datas: &str,
+) -> Option<Vec<TransactionDocument>> {
+    let raw_transactions: serde_json::Value =
+        serde_json::from_str(json_datas).expect("Fatal error : fail to jsonifie tx from DB !");
+
+    if raw_transactions.is_array() {
+        let mut transactions = Vec::new();
+        for transaction in raw_transactions.as_array().unwrap() {
+            let transaction_lines: Vec<&str> = transaction
+                .as_str()
+                .expect("Fail to parse tx from DB !")
+                .split('$')
+                .collect();
+            let tx_headers: Vec<&str> = transaction_lines[0].split(':').collect();
+            let issuers_count = tx_headers[2]
+                .parse()
+                .expect("Fail to parse tx header NB_ISSUERS !");
+            let inputs_count = tx_headers[3]
+                .parse()
+                .expect("Fail to parse tx header NB_INPUTS !");
+            let unlocks_count = tx_headers[4]
+                .parse()
+                .expect("Fail to parse tx header NB_UNLOCKS !");
+            let outputs_count = tx_headers[5]
+                .parse()
+                .expect("Fail to parse tx header NB_OUTPUTS !");
+            let has_comment: usize = tx_headers[6]
+                .parse()
+                .expect("Fail to parse tx header HAS_COMMENT !");
+            let locktime = tx_headers[7]
+                .parse()
+                .expect("Fail to parse tx header LOCKTIME !");
+            let blockstamp = Blockstamp::from_string(transaction_lines[1])
+                .expect("Fail to parse tx BLOCKSTAMP !");
+            let mut line = 2;
+            let mut issuers = Vec::new();
+            for _ in 0..issuers_count {
+                issuers.push(
+                    PublicKey::from_base58(transaction_lines[line])
+                        .expect("Fail to parse tx issuer !"),
+                );
+                line += 1;
+            }
+            let mut inputs = Vec::new();
+            for _ in 0..inputs_count {
+                inputs.push(
+                    TransactionInput::parse_from_str(transaction_lines[line])
+                        .expect("Fail to parse tx issuer !"),
+                );
+                line += 1;
+            }
+            let mut unlocks = Vec::new();
+            for _ in 0..unlocks_count {
+                unlocks.push(
+                    TransactionInputUnlocks::parse_from_str(transaction_lines[line])
+                        .expect("Fail to parse tx issuer !"),
+                );
+                line += 1;
+            }
+            let mut outputs = Vec::new();
+            for _ in 0..outputs_count {
+                outputs.push(
+                    TransactionOutput::parse_from_str(transaction_lines[line])
+                        .expect("Fail to parse tx issuer !"),
+                );
+                line += 1;
+            }
+            let mut comment = "";
+            if has_comment == 1 {
+                comment = transaction_lines[line];
+                line += 1;
+            }
+            let mut signatures = Vec::new();
+            for _ in 0..issuers_count {
+                signatures.push(
+                    Signature::from_base64(transaction_lines[line])
+                        .expect("Fail to parse tx signature !"),
+                );
+                line += 1;
+            }
+            let tx_doc_builder = TransactionDocumentBuilder {
+                currency,
+                blockstamp: &blockstamp,
+                locktime: &locktime,
+                issuers: &issuers,
+                inputs: &inputs,
+                unlocks: &unlocks,
+                outputs: &outputs,
+                comment,
+            };
+            transactions.push(tx_doc_builder.build_with_signature(signatures));
+        }
+        Some(transactions)
+    } else {
+        None
+    }
+}
+
+pub fn parse_transaction(
+    currency: &str,
+    source: &serde_json::Value,
+) -> Option<TransactionDocument> {
+    //debug!("transaction={:#?}", source);
+    let blockstamp = match Blockstamp::from_string(source.get("blockstamp")?.as_str()?) {
+        Ok(blockstamp) => blockstamp,
+        Err(_) => {
+            return None;
+        }
+    };
+    let locktime = source.get("locktime")?.as_i64()? as u64;
+    let issuers_array = source.get("issuers")?.as_array()?;
+    let mut issuers = Vec::with_capacity(issuers_array.len());
+    for issuer in issuers_array {
+        match PublicKey::from_base58(issuer.as_str()?) {
+            Ok(pubkey) => issuers.push(pubkey),
+            Err(_) => {
+                return None;
+            }
+        }
+    }
+    let inputs_array = source.get("inputs")?.as_array()?;
+    let mut inputs = Vec::with_capacity(inputs_array.len());
+    for input in inputs_array {
+        let input_str = input.as_str()?;
+        match TransactionInput::parse_from_str(input_str) {
+            Ok(input) => inputs.push(input),
+            Err(_) => {
+                return None;
+            }
+        }
+    }
+    let unlocks_array = source.get("unlocks")?.as_array()?;
+    let mut unlocks = Vec::with_capacity(unlocks_array.len());
+    for unlock in unlocks_array {
+        match TransactionInputUnlocks::parse_from_str(unlock.as_str()?) {
+            Ok(unlock) => unlocks.push(unlock),
+            Err(_) => {
+                return None;
+            }
+        }
+    }
+    let outputs_array = source.get("outputs")?.as_array()?;
+    let mut outputs = Vec::with_capacity(outputs_array.len());
+    for output in outputs_array {
+        match TransactionOutput::parse_from_str(output.as_str()?) {
+            Ok(output) => outputs.push(output),
+            Err(_) => {
+                return None;
+            }
+        }
+    }
+    let signatures_array = source.get("signatures")?.as_array()?;
+    let mut signatures = Vec::with_capacity(signatures_array.len());
+    for signature in signatures_array {
+        match Signature::from_base64(signature.as_str()?) {
+            Ok(signature) => signatures.push(signature),
+            Err(_) => {
+                return None;
+            }
+        }
+    }
+    let comment = source.get("comment")?.as_str()?;
+
+    let tx_doc_builder = TransactionDocumentBuilder {
+        currency,
+        blockstamp: &blockstamp,
+        locktime: &locktime,
+        issuers: &issuers,
+        inputs: &inputs,
+        unlocks: &unlocks,
+        outputs: &outputs,
+        comment,
+    };
+    Some(tx_doc_builder.build_with_signature(signatures))
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn parse_compact_tx() {
+        let compact_txs = "[\"TX:10:1:1:1:1:1:0$\
+112533-000002150F2E805E604D9B31212D079570AAD8D3A4D8BB75F2C15A94A345B6B1$\
+51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2$\
+1000:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:46496$\
+0:SIG(0)$\
+1000:0:SIG(2yN8BRSkARcqE8NCxKMBiHfTpx1EvwULFn56Myf6qRmy)$\
+Merci pour la calligraphie ;) de Liam$\
+5olrjFylTCsVq8I5Yr7FpXeviynICyvIwe1yG5N0RJF+VZb+bCFBnLAMpmMCU2qzUvK7z41UXOrMRybXiLa2Dw==\"]";
+
+        let tx_builder = TransactionDocumentBuilder {
+            currency: "g1",
+            blockstamp: &Blockstamp::from_string(
+                "112533-000002150F2E805E604D9B31212D079570AAD8D3A4D8BB75F2C15A94A345B6B1",
+            ).unwrap(),
+            locktime: &0,
+            issuers: &vec![
+                PublicKey::from_base58("51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2").unwrap(),
+            ],
+            inputs: &vec![
+                TransactionInput::parse_from_str(
+                    "1000:0:D:51EFVNZwpfmTXU7BSLpeh3PZFgfdmm5hq5MzCDopdH2:46496",
+                ).unwrap(),
+            ],
+            outputs: &vec![
+                TransactionOutput::parse_from_str(
+                    "1000:0:SIG(2yN8BRSkARcqE8NCxKMBiHfTpx1EvwULFn56Myf6qRmy)",
+                ).unwrap(),
+            ],
+            unlocks: &vec![TransactionInputUnlocks::parse_from_str("0:SIG(0)").unwrap()],
+            comment: "Merci pour la calligraphie ;) de Liam",
+        };
+
+        assert_eq!(
+            parse_compact_transactions("g1", compact_txs).expect("Fail to parse compact transactions !"),
+            vec![tx_builder.build_with_signature(vec![Signature::from_base64("5olrjFylTCsVq8I5Yr7FpXeviynICyvIwe1yG5N0RJF+VZb+bCFBnLAMpmMCU2qzUvK7z41UXOrMRybXiLa2Dw==").unwrap()])]
+        );
+    }
+}
diff --git a/dal/test.db b/dal/test.db
new file mode 100644
index 0000000000000000000000000000000000000000..ab254abc64c4f280bdde96b863cdc6420fddff67
Binary files /dev/null and b/dal/test.db differ
diff --git a/dal/tools.rs b/dal/tools.rs
new file mode 100644
index 0000000000000000000000000000000000000000..9a4ecee0aa5f649b94ccd172c0eee2d326659533
--- /dev/null
+++ b/dal/tools.rs
@@ -0,0 +1,137 @@
+extern crate duniter_wotb;
+
+use duniter_wotb::operations::centrality::{
+    CentralitiesCalculator, UlrikBrandesCentralityCalculator,
+};
+use duniter_wotb::operations::distance::{
+    DistanceCalculator, RustyDistanceCalculator, WotDistance, WotDistanceParameters,
+};
+use duniter_wotb::operations::path::{PathFinder, RustyPathFinder};
+use duniter_wotb::{NodeId, WebOfTrust};
+
+pub static CENTRALITY_CALCULATOR: UlrikBrandesCentralityCalculator =
+    UlrikBrandesCentralityCalculator {};
+pub static DISTANCE_CALCULATOR: RustyDistanceCalculator = RustyDistanceCalculator {};
+pub static PATH_FINDER: RustyPathFinder = RustyPathFinder {};
+
+pub fn get_sentry_requirement(members_count: usize, step_max: u32) -> u32 {
+    match step_max {
+        5 => {
+            if members_count < 33 {
+                2
+            } else if members_count < 244 {
+                3
+            } else if members_count < 1025 {
+                4
+            } else if members_count < 3126 {
+                5
+            } else if members_count < 7777 {
+                6
+            } else {
+                panic!("get_sentry_requirement not define for members_count greater than 7777 !");
+            }
+        }
+        _ => panic!("get_sentry_requirement not define for step_max != 5 !"),
+    }
+}
+
+pub fn calculate_average_density<T: WebOfTrust>(wot: &T) -> usize {
+    let enabled_members = wot.get_enabled();
+    let enabled_members_count = enabled_members.len();
+    let mut count_actives_links: usize = 0;
+    for member in &enabled_members {
+        count_actives_links += wot.issued_count(*member).unwrap();
+    }
+    ((count_actives_links as f32 / enabled_members_count as f32) * 1_000.0) as usize
+}
+
+pub fn compute_distances<T: WebOfTrust + Sync>(
+    wot: &T,
+    sentry_requirement: u32,
+    step_max: u32,
+    x_percent: f64,
+) -> (usize, Vec<usize>, usize, Vec<usize>) {
+    let members_count = wot.get_enabled().len();
+    let mut distances = Vec::new();
+    let mut average_distance: usize = 0;
+    let mut connectivities = Vec::new();
+    let mut average_connectivity: usize = 0;
+    for i in 0..wot.size() {
+        let distance_datas: WotDistance = DISTANCE_CALCULATOR
+            .compute_distance(
+                wot,
+                WotDistanceParameters {
+                    node: NodeId(i),
+                    sentry_requirement,
+                    step_max,
+                    x_percent,
+                },
+            )
+            .expect("Fatal Error: compute_distance return None !");
+        let mut distance = ((f64::from(distance_datas.success)
+            / (x_percent * f64::from(distance_datas.sentries))) * 100.0)
+            as usize;
+        distances.push(distance);
+        average_distance += distance;
+        let mut connectivity =
+            ((f64::from(distance_datas.success - distance_datas.success_at_border)
+                / (x_percent * f64::from(distance_datas.sentries))) * 100.0) as usize;
+        connectivities.push(connectivity);
+        average_connectivity += connectivity;
+    }
+    average_distance /= members_count;
+    average_connectivity /= members_count;
+    (
+        average_distance,
+        distances,
+        average_connectivity,
+        connectivities,
+    )
+}
+
+pub fn calculate_distance_stress_centralities<T: WebOfTrust>(wot: &T, step_max: u32) -> Vec<u64> {
+    CENTRALITY_CALCULATOR.distance_stress_centralities(wot, step_max as usize)
+}
+
+pub fn calculate_centralities_degree<T: WebOfTrust>(wot: &T, step_max: u32) -> Vec<usize> {
+    let wot_size = wot.size();
+    let members_count = wot.get_enabled().len() as u64;
+    let oriented_couples_count: u64 = members_count * (members_count - 1);
+    let mut centralities: Vec<u64> = vec![0; wot_size];
+    for i in 0..wot_size {
+        for j in 0..wot_size {
+            let mut paths = PATH_FINDER.find_paths(wot, NodeId(i), NodeId(j), step_max);
+            if paths.is_empty() {
+                break;
+            }
+            //paths.sort_unstable_by(|a, b| a.len().cmp(&b.len()));
+            let shortest_path_len = paths[0].len();
+            let mut intermediate_members: Vec<NodeId> = Vec::new();
+            if shortest_path_len > 2 {
+                for path in paths {
+                    //if path.len() == shortest_path_len {
+                    for node_id in &path {
+                        if !intermediate_members.contains(node_id) {
+                            intermediate_members.push(*node_id);
+                        }
+                    }
+                    /*} else {
+                        break;
+                    }*/
+                }
+            }
+            let centralities_copy = centralities.clone();
+            for node_id in intermediate_members {
+                let centrality = &centralities_copy[node_id.0];
+                if let Some(tmp) = centralities.get_mut(node_id.0) {
+                    *tmp = *centrality + 1;
+                }
+            }
+        }
+    }
+    let mut relative_centralities = Vec::with_capacity(wot_size);
+    for centrality in centralities {
+        relative_centralities.push((centrality * 100_000 / oriented_couples_count) as usize);
+    }
+    relative_centralities
+}
diff --git a/dal/writers/block.rs b/dal/writers/block.rs
new file mode 100644
index 0000000000000000000000000000000000000000..aeb4d4d87415c38b6330c57d415032b79f03505b
--- /dev/null
+++ b/dal/writers/block.rs
@@ -0,0 +1,74 @@
+extern crate duniter_crypto;
+extern crate duniter_documents;
+extern crate duniter_wotb;
+extern crate serde;
+extern crate serde_json;
+extern crate sqlite;
+
+use self::duniter_documents::blockchain::v10::documents::BlockDocument;
+use self::duniter_documents::blockchain::Document;
+use super::super::block::DALBlock;
+use super::super::DuniterDB;
+
+pub fn write_network_block(
+    db: &DuniterDB,
+    block: &BlockDocument,
+    fork: usize,
+    isolate: bool,
+    revoked: &Vec<serde_json::Value>,
+    certifications: &Vec<serde_json::Value>,
+) {
+    db.0
+        .execute(
+            format!("INSERT INTO blocks (fork, isolate, version, nonce, number, pow_min, time, median_time, members_count, monetary_mass, unit_base, issuers_count, issuers_frame, issuers_frame_var, currency, issuer, signature, hash, previous_hash, inner_hash, dividend, identities, joiners, actives, leavers, revoked, excluded, certifications, transactions) VALUES ({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, '{}', '{}', '{}', '{}', '{}', '{}', {}, '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}');",
+                fork, if isolate { 1 } else { 0 }, 10,
+                block.nonce, block.number, block.pow_min, block.time, block.median_time,
+                block.members_count, block.monetary_mass, block.unit_base, block.issuers_count,
+                block.issuers_frame, block.issuers_frame_var, block.currency, block.issuers[0],
+                block.signatures[0].to_string(), block.hash.unwrap().0.to_string(),
+                block.previous_hash.to_string(), block.inner_hash.unwrap().to_string(),
+                block.dividend.unwrap_or(0),
+                serde_json::to_string(&block.identities).unwrap(),
+                serde_json::to_string(&block.joiners).unwrap(), serde_json::to_string(&block.actives).unwrap(),
+                serde_json::to_string(&block.leavers).unwrap(), serde_json::to_string(revoked).unwrap(),
+                serde_json::to_string(&block.excluded).unwrap(), serde_json::to_string(certifications).unwrap(),
+                serde_json::to_string(&block.transactions).unwrap()
+            ))
+        .unwrap();
+}
+
+pub fn write(db: &DuniterDB, block: &BlockDocument, fork: usize, isolate: bool) {
+    let mut insert = true;
+    if fork == 0 {
+        if let Some(_fork) = DALBlock::get_block_fork(db, &block.blockstamp()) {
+            insert = false;
+            db.0
+                .execute(format!(
+                    "UPDATE blocks SET fork=0 WHERE number={} AND hash='{}';",
+                    block.number,
+                    block.hash.unwrap().0.to_string()
+                ))
+                .unwrap();
+        }
+    }
+
+    if insert {
+        db.0
+            .execute(
+                format!("INSERT INTO blocks (fork, isolate, version, nonce, number, pow_min, time, median_time, members_count, monetary_mass, unit_base, issuers_count, issuers_frame, issuers_frame_var, currency, issuer, signature, hash, previous_hash, inner_hash, dividend, identities, joiners, actives, leavers, revoked, excluded, certifications, transactions) VALUES ({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, '{}', '{}', '{}', '{}', '{}', '{}', {}, '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}');",
+                    fork, if isolate { 1 } else { 0 }, 10,
+                    block.nonce, block.number, block.pow_min, block.time, block.median_time,
+                    block.members_count, block.monetary_mass, block.unit_base, block.issuers_count,
+                    block.issuers_frame, block.issuers_frame_var, block.currency, block.issuers[0],
+                    block.signatures[0].to_string(), block.hash.unwrap().0.to_string(),
+                    block.previous_hash.to_string(), block.inner_hash.unwrap().to_string(),
+                    block.dividend.unwrap_or(0), serde_json::to_string(&block.identities).unwrap(),
+                    serde_json::to_string(&block.joiners).unwrap(), serde_json::to_string(&block.actives).unwrap(),
+                    serde_json::to_string(&block.leavers).unwrap(), serde_json::to_string(&block.revoked).unwrap(),
+                    serde_json::to_string(&block.excluded).unwrap(), serde_json::to_string(&block.certifications).unwrap(),
+                    serde_json::to_string(&block.transactions).unwrap()
+                ),
+            )
+            .unwrap();
+    }
+}
diff --git a/dal/writers/certification.rs b/dal/writers/certification.rs
new file mode 100644
index 0000000000000000000000000000000000000000..03ec2db95ec3c77b0a9cdb7fb7acf4148b08342a
--- /dev/null
+++ b/dal/writers/certification.rs
@@ -0,0 +1,54 @@
+extern crate serde;
+extern crate serde_json;
+extern crate sqlite;
+
+use super::super::DuniterDB;
+use duniter_crypto::keys::ed25519;
+use duniter_documents::blockchain::v10::documents::certification::CompactCertificationDocument;
+use duniter_documents::Blockstamp;
+
+pub fn write_certification(
+    cert: &CompactCertificationDocument,
+    db: &DuniterDB,
+    written_blockstamp: Blockstamp,
+    written_timestamp: u64,
+) {
+    let mut cursor = db
+        .0
+        .prepare("SELECT median_time FROM blocks WHERE number=? AND fork=0 LIMIT 1;")
+        .expect("invalid write_certification sql request")
+        .cursor();
+
+    cursor
+        .bind(&[sqlite::Value::Integer(cert.block_number.0 as i64)])
+        .expect("convert blockstamp to timestamp failure at step 1 !");
+
+    let mut created_timestamp: i64 = 0;
+    if let Some(row) = cursor
+        .next()
+        .expect("convert blockstamp to timestamp failure at step 2 !")
+    {
+        created_timestamp = row[0]
+            .as_integer()
+            .expect("Fail to write cert, impossible to get created_timestamp !");
+    }
+
+    db.0
+        .execute(
+            format!("INSERT INTO certifications (pubkey_from, pubkey_to, created_on, signature, written_on, expires_on, chainable_on) VALUES ('{}', '{}', '{}', '{}', '{}', {}, {});",
+                cert.issuer, cert.target, cert.block_number.0, cert.signature,
+                written_blockstamp.to_string(),
+                created_timestamp+super::super::constants::G1_PARAMS.sig_validity,
+                written_timestamp+super::super::constants::G1_PARAMS.sig_period
+            ))
+        .expect("Fail to execute INSERT certification !");
+}
+
+pub fn remove_certification(from: ed25519::PublicKey, to: ed25519::PublicKey, db: &DuniterDB) {
+    db.0
+        .execute(format!(
+            "DELETE FROM certifications WHERE pubkey_from={} AND pubkey_to={}",
+            from, to
+        ))
+        .expect("Fail to execute DELETE certification !");
+}
diff --git a/dal/writers/identity.rs b/dal/writers/identity.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ea106fce344f2489d635bf26b09c49446fc67c4e
--- /dev/null
+++ b/dal/writers/identity.rs
@@ -0,0 +1,37 @@
+extern crate duniter_wotb;
+extern crate sqlite;
+
+use super::super::identity::DALIdentity;
+use super::super::DuniterDB;
+use duniter_documents::blockchain::Document;
+use duniter_documents::Blockstamp;
+use duniter_wotb::NodeId;
+
+pub fn write(
+    idty: &DALIdentity,
+    wotb_id: &NodeId,
+    db: &DuniterDB,
+    _written_blockstamp: Blockstamp,
+    _written_timestamp: u64,
+) {
+    let expired_on = match idty.expired_on {
+        Some(ref tmp) => tmp.to_string(),
+        None => String::from(""),
+    };
+    let revoked_on = match idty.revoked_on {
+        Some(ref tmp) => tmp.to_string(),
+        None => String::from(""),
+    };
+    db.0
+        .execute(
+            format!("INSERT INTO identities (wotb_id, uid, pubkey, hash, sig, state, created_on, joined_on, penultimate_renewed_on, last_renewed_on, expires_on, revokes_on, expired_on, revoked_on) VALUES ({}, '{}', '{}', '{}', '{}', {}, '{}', '{}', '{}', '{}', {}, {}, '{}', '{}');",
+                (*wotb_id).0, idty.idty_doc.username(), idty.idty_doc.issuers()[0], idty.hash,
+                idty.idty_doc.signatures()[0], idty.state,
+                idty.idty_doc.blockstamp().to_string(),
+                idty.joined_on.to_string(),
+                idty.penultimate_renewed_on.to_string(),
+                idty.last_renewed_on.to_string(),
+                idty.expires_on, idty.revokes_on, expired_on, revoked_on
+            ))
+        .unwrap();
+}
diff --git a/dal/writers/mod.rs b/dal/writers/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..aba7f80035e06d003fbf4387999ea70411845262
--- /dev/null
+++ b/dal/writers/mod.rs
@@ -0,0 +1,4 @@
+pub mod block;
+pub mod certification;
+pub mod identity;
+pub mod requests;
diff --git a/dal/writers/requests.rs b/dal/writers/requests.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c48b36a3e385cd6cc6929fb42bcb248c235ce492
--- /dev/null
+++ b/dal/writers/requests.rs
@@ -0,0 +1,83 @@
+extern crate duniter_crypto;
+extern crate duniter_documents;
+extern crate duniter_wotb;
+extern crate serde;
+extern crate serde_json;
+extern crate sqlite;
+
+use self::duniter_crypto::keys::ed25519;
+use self::duniter_documents::blockchain::v10::documents::certification::CompactCertificationDocument;
+use self::duniter_documents::blockchain::v10::documents::identity::IdentityDocument;
+use self::duniter_documents::Blockstamp;
+use self::duniter_wotb::NodeId;
+use super::super::block::DALBlock;
+use super::super::identity::DALIdentity;
+use super::super::DuniterDB;
+
+#[derive(Debug)]
+/// Contain a pending write request for blockchain database
+pub enum DBWriteRequest {
+    /// Newcomer
+    CreateIdentity(NodeId, Blockstamp, u64, IdentityDocument),
+    /// Active
+    RenewalIdentity(ed25519::PublicKey, Blockstamp, u64),
+    /// Excluded
+    ExcludeIdentity(NodeId, Blockstamp, u64),
+    /// Revoked
+    RevokeIdentity(NodeId, Blockstamp, u64),
+    /// Certification
+    CreateCert(Blockstamp, u64, CompactCertificationDocument),
+    /// Certification expiry
+    CertExpiry(NodeId, NodeId, Blockstamp, u64),
+    /// Write block
+    WriteBlock(DALBlock),
+    /// Revert block
+    RevertBlock(DALBlock),
+}
+
+impl DBWriteRequest {
+    pub fn apply(&self, currency: &str, db: &DuniterDB) {
+        match *self {
+            DBWriteRequest::CreateIdentity(
+                ref wotb_id,
+                ref blockstamp,
+                ref median_time,
+                ref idty_doc,
+            ) => {
+                trace!("DBWriteRequest::CreateIdentity...");
+                let idty = DALIdentity::create_identity(db, idty_doc, blockstamp.clone());
+                super::identity::write(&idty, wotb_id, db, blockstamp.clone(), *median_time);
+                trace!("DBWriteRequest::CreateIdentity...finish.");
+            }
+            DBWriteRequest::RenewalIdentity(ref pubkey, ref blockstamp, ref median_time) => {
+                trace!("DBWriteRequest::RenewalIdentity...");
+                let mut idty = DALIdentity::get_identity(currency, db, pubkey)
+                    .expect("Fatal error : impossible ton renewal an identidy that don't exist !");
+                idty.renewal_identity(db, pubkey, blockstamp, *median_time, false);
+                trace!("DBWriteRequest::RenewalIdentity...");
+            }
+            DBWriteRequest::ExcludeIdentity(ref wotb_id, ref blockstamp, ref _median_time) => {
+                DALIdentity::exclude_identity(db, *wotb_id, *blockstamp, false);
+            }
+            DBWriteRequest::RevokeIdentity(ref wotb_id, ref blockstamp, ref _median_time) => {
+                DALIdentity::revoke_identity(db, *wotb_id, blockstamp, false);
+            }
+            DBWriteRequest::CreateCert(ref blockstamp, ref median_time, ref compact_cert) => {
+                trace!("DBWriteRequest::CreateCert...");
+                super::certification::write_certification(
+                    compact_cert,
+                    db,
+                    blockstamp.clone(),
+                    *median_time,
+                );
+                trace!("DBWriteRequest::CreateCert...finish");
+            }
+            DBWriteRequest::WriteBlock(ref dal_block) => {
+                trace!("DBWriteRequest::WriteBlock...");
+                super::block::write(db, &dal_block.block, dal_block.fork, dal_block.isolate);
+                trace!("DBWriteRequest::WriteBlock...finish");
+            }
+            _ => {}
+        }
+    }
+}
diff --git a/documents/blockchain/v10/documents/block.rs b/documents/blockchain/v10/documents/block.rs
index 0305af99761abe685cafcc3d3c8d1f2c839398b5..1b51c4689331f4b1bbf849d348a3ef7428577222 100644
--- a/documents/blockchain/v10/documents/block.rs
+++ b/documents/blockchain/v10/documents/block.rs
@@ -24,7 +24,7 @@ use blockchain::v10::documents::identity::IdentityDocument;
 use blockchain::v10::documents::membership::MembershipDocument;
 use blockchain::v10::documents::revocation::RevocationDocument;
 use blockchain::v10::documents::transaction::TransactionDocument;
-use blockchain::v10::documents::{TextDocument, V10Document};
+use blockchain::v10::documents::*;
 use blockchain::{BlockchainProtocol, Document, IntoSpecializedDocument};
 use {BlockHash, BlockId, Blockstamp, Hash};
 
@@ -130,11 +130,11 @@ pub struct BlockDocument {
     /// Leavers
     pub leavers: Vec<MembershipDocument>,
     /// Revokeds
-    pub revoked: Vec<RevocationDocument>,
+    pub revoked: Vec<TextDocumentFormat<RevocationDocument>>,
     /// Excludeds
     pub excluded: Vec<ed25519::PublicKey>,
     /// Certifications
-    pub certifications: Vec<CertificationDocument>,
+    pub certifications: Vec<TextDocumentFormat<CertificationDocument>>,
     /// Transactions
     pub transactions: Vec<TransactionDocument>,
     /// Part to sign
@@ -146,9 +146,6 @@ impl BlockDocument {
     pub fn compute_inner_hash(&mut self) {
         let mut sha256 = Sha256::new();
         let inner_text = self.generate_compact_inner_text();
-        /*if self.number.0 == 46473 {
-            println!("#46473 raw_format=\"{}\"", inner_text);
-        }*/
         sha256.input_str(&inner_text);
         self.inner_hash = Some(Hash::from_hex(&sha256.result_str()).unwrap());
     }
@@ -176,7 +173,8 @@ impl BlockDocument {
         ));
         self.hash = Some(BlockHash(Hash::from_hex(&sha256.result_str()).unwrap()));
     }
-    fn generate_compact_inner_text(&self) -> String {
+    /// Generate compact inner text (for compute inner_hash)
+    pub fn generate_compact_inner_text(&self) -> String {
         let mut identities_str = String::from("");
         for identity in self.identities.clone() {
             identities_str.push_str("\n");
@@ -205,7 +203,7 @@ impl BlockDocument {
         let mut revokeds_str = String::from("");
         for revocation in self.revoked.clone() {
             revokeds_str.push_str("\n");
-            revokeds_str.push_str(&revocation.generate_compact_text());
+            revokeds_str.push_str(&revocation.as_compact_text());
         }
         let mut excludeds_str = String::from("");
         for exclusion in self.excluded.clone() {
@@ -215,7 +213,7 @@ impl BlockDocument {
         let mut certifications_str = String::from("");
         for certification in self.certifications.clone() {
             certifications_str.push_str("\n");
-            certifications_str.push_str(&certification.generate_compact_text());
+            certifications_str.push_str(&certification.as_compact_text());
         }
         let mut transactions_str = String::from("");
         for transaction in self.transactions.clone() {
@@ -313,12 +311,8 @@ impl Document for BlockDocument {
     }
 }
 
-impl TextDocument for BlockDocument {
-    fn as_text(&self) -> &str {
-        panic!();
-    }
-
-    fn generate_compact_text(&self) -> String {
+impl CompactTextDocument for BlockDocument {
+    fn as_compact_text(&self) -> String {
         let compact_inner_text = self.generate_compact_inner_text();
         format!(
             "{}InnerHash: {}\nNonce: ",
@@ -328,6 +322,18 @@ impl TextDocument for BlockDocument {
     }
 }
 
+impl TextDocument for BlockDocument {
+    type CompactTextDocument_ = BlockDocument;
+
+    fn as_text(&self) -> &str {
+        panic!();
+    }
+
+    fn to_compact_document(&self) -> Self::CompactTextDocument_ {
+        self.clone()
+    }
+}
+
 impl IntoSpecializedDocument<BlockchainProtocol> for BlockDocument {
     fn into_specialized(self) -> BlockchainProtocol {
         BlockchainProtocol::V10(Box::new(V10Document::Block(Box::new(self))))
@@ -506,7 +512,7 @@ a9PHPuSfw7jW8FRQHXFsGi/bnLjbtDnTYvEVgUC9u0WlR7GVofa+Xb+l5iy6NwuEXiwvueAkf08wPVY8
             leavers: Vec::new(),
             revoked: Vec::new(),
             excluded: Vec::new(),
-            certifications: vec![cert1],
+            certifications: vec![TextDocumentFormat::Complete(cert1)],
             transactions: vec![tx1, tx2],
             inner_hash_and_nonce_str: String::new(),
         };
diff --git a/documents/blockchain/v10/documents/certification.rs b/documents/blockchain/v10/documents/certification.rs
index b3e8857ccbe90c902b1e7d8bc307cb61e780aec3..842591838c7eb796bb28cfc5a76bd2e816fffffc 100644
--- a/documents/blockchain/v10/documents/certification.rs
+++ b/documents/blockchain/v10/documents/certification.rs
@@ -21,11 +21,9 @@ use self::serde::ser::{Serialize, Serializer};
 use duniter_crypto::keys::{ed25519, PublicKey, Signature};
 use regex::Regex;
 
-use blockchain::v10::documents::{
-    StandardTextDocumentParser, TextDocument, TextDocumentBuilder, V10Document,
-    V10DocumentParsingError,
-};
+use blockchain::v10::documents::*;
 use blockchain::{BlockchainProtocol, Document, DocumentBuilder, IntoSpecializedDocument};
+use BlockId;
 use Blockstamp;
 
 lazy_static! {
@@ -34,6 +32,31 @@ lazy_static! {
     ).unwrap();
 }
 
+#[derive(Debug, Copy, Clone)]
+/// Wrap an Compact Revocation document (in block content)
+pub struct CompactCertificationDocument {
+    /// Issuer
+    pub issuer: ed25519::PublicKey,
+    /// Target
+    pub target: ed25519::PublicKey,
+    /// Blockstamp
+    pub block_number: BlockId,
+    /// Signature
+    pub signature: ed25519::Signature,
+}
+
+impl CompactTextDocument for CompactCertificationDocument {
+    fn as_compact_text(&self) -> String {
+        format!(
+            "{issuer}:{target}:{block_number}:{signature}",
+            issuer = self.issuer,
+            target = self.target,
+            block_number = self.block_number.0,
+            signature = self.signature,
+        )
+    }
+}
+
 /// Wrap an Certification document.
 ///
 /// Must be created by parsing a text document or using a builder.
@@ -109,27 +132,28 @@ impl Document for CertificationDocument {
 }
 
 impl TextDocument for CertificationDocument {
+    type CompactTextDocument_ = CompactCertificationDocument;
+
     fn as_text(&self) -> &str {
         &self.text
     }
 
-    fn generate_compact_text(&self) -> String {
-        format!(
-            "{issuer}:{target}:{block_number}:{signature}",
-            issuer = self.issuers[0],
-            target = self.target,
-            block_number = self.blockstamp.id.0,
-            signature = self.signatures[0],
-        )
+    fn to_compact_document(&self) -> Self::CompactTextDocument_ {
+        CompactCertificationDocument {
+            issuer: self.issuers[0],
+            target: self.target,
+            block_number: self.blockstamp().id,
+            signature: self.signatures()[0],
+        }
     }
 }
 
-impl Serialize for CertificationDocument {
+impl Serialize for TextDocumentFormat<CertificationDocument> {
     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     where
         S: Serializer,
     {
-        serializer.serialize_str(&self.generate_compact_text())
+        serializer.serialize_str(&self.as_compact_text())
     }
 }
 
diff --git a/documents/blockchain/v10/documents/identity.rs b/documents/blockchain/v10/documents/identity.rs
index 6e986565b04dc04222ff93ed31b3509b7fb4b7be..40bd02bb3c7dc5c1c7d52938028b8f25e14998e0 100644
--- a/documents/blockchain/v10/documents/identity.rs
+++ b/documents/blockchain/v10/documents/identity.rs
@@ -21,10 +21,7 @@ use self::serde::ser::{Serialize, Serializer};
 use duniter_crypto::keys::{ed25519, PublicKey};
 use regex::Regex;
 
-use blockchain::v10::documents::{
-    StandardTextDocumentParser, TextDocument, TextDocumentBuilder, V10Document,
-    V10DocumentParsingError,
-};
+use blockchain::v10::documents::*;
 use blockchain::{BlockchainProtocol, Document, DocumentBuilder, IntoSpecializedDocument};
 use Blockstamp;
 
@@ -93,12 +90,8 @@ impl Document for IdentityDocument {
     }
 }
 
-impl TextDocument for IdentityDocument {
-    fn as_text(&self) -> &str {
-        &self.text
-    }
-
-    fn generate_compact_text(&self) -> String {
+impl CompactTextDocument for IdentityDocument {
+    fn as_compact_text(&self) -> String {
         format!(
             "{issuer}:{signature}:{blockstamp}:{username}",
             issuer = self.issuers[0],
@@ -109,6 +102,18 @@ impl TextDocument for IdentityDocument {
     }
 }
 
+impl TextDocument for IdentityDocument {
+    type CompactTextDocument_ = IdentityDocument;
+
+    fn as_text(&self) -> &str {
+        &self.text
+    }
+
+    fn to_compact_document(&self) -> Self::CompactTextDocument_ {
+        self.clone()
+    }
+}
+
 impl Serialize for IdentityDocument {
     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     where
diff --git a/documents/blockchain/v10/documents/membership.rs b/documents/blockchain/v10/documents/membership.rs
index 0cc53d51b5521471a4aa2719963e94a18704dbdf..2e313601e9f938d431ba33800582234a9ab15382 100644
--- a/documents/blockchain/v10/documents/membership.rs
+++ b/documents/blockchain/v10/documents/membership.rs
@@ -21,10 +21,7 @@ use self::serde::ser::{Serialize, Serializer};
 use duniter_crypto::keys::{ed25519, PublicKey};
 use regex::Regex;
 
-use blockchain::v10::documents::{
-    StandardTextDocumentParser, TextDocument, TextDocumentBuilder, V10Document,
-    V10DocumentParsingError,
-};
+use blockchain::v10::documents::*;
 use blockchain::{BlockchainProtocol, Document, DocumentBuilder, IntoSpecializedDocument};
 use Blockstamp;
 
@@ -114,12 +111,8 @@ impl Document for MembershipDocument {
     }
 }
 
-impl TextDocument for MembershipDocument {
-    fn as_text(&self) -> &str {
-        &self.text
-    }
-
-    fn generate_compact_text(&self) -> String {
+impl CompactTextDocument for MembershipDocument {
+    fn as_compact_text(&self) -> String {
         format!(
             "{issuer}:{signature}:{blockstamp}:{idty_blockstamp}:{username}",
             issuer = self.issuers[0],
@@ -131,6 +124,18 @@ impl TextDocument for MembershipDocument {
     }
 }
 
+impl TextDocument for MembershipDocument {
+    type CompactTextDocument_ = MembershipDocument;
+
+    fn as_text(&self) -> &str {
+        &self.text
+    }
+
+    fn to_compact_document(&self) -> Self::CompactTextDocument_ {
+        self.clone()
+    }
+}
+
 impl Serialize for MembershipDocument {
     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     where
diff --git a/documents/blockchain/v10/documents/mod.rs b/documents/blockchain/v10/documents/mod.rs
index 3557543aabb1be765eb61f36933a895a55c5eba1..495190dd55e0d22a5e671b0c16f1cc8e6e0f2405 100644
--- a/documents/blockchain/v10/documents/mod.rs
+++ b/documents/blockchain/v10/documents/mod.rs
@@ -42,6 +42,8 @@ pub use blockchain::v10::documents::transaction::{
     TransactionDocument, TransactionDocumentBuilder, TransactionDocumentParser,
 };
 
+use std::marker::Sized;
+
 // Use of lazy_static so the regex is only compiled at first use.
 lazy_static! {
     static ref DOCUMENT_REGEX: Regex = Regex::new(
@@ -54,6 +56,25 @@ lazy_static! {
     static ref SIGNATURES_REGEX: Regex = Regex::new("[[:alnum:]+/=]+\n?").unwrap();
 }
 
+#[derive(Debug, Clone)]
+/// Contains a document in full or compact format
+pub enum TextDocumentFormat<D: TextDocument> {
+    /// Complete format (Allows to check the validity of the signature)
+    Complete(D),
+    /// Format present in the blocks (does not always allow to verify the signature)
+    Compact(D::CompactTextDocument_),
+}
+
+impl<D: TextDocument> TextDocumentFormat<D> {
+    /// To compact document
+    pub fn to_compact_document(&self) -> D::CompactTextDocument_ {
+        match *self {
+            TextDocumentFormat::Complete(ref doc) => doc.to_compact_document(),
+            TextDocumentFormat::Compact(ref compact_doc) => (*compact_doc).clone(),
+        }
+    }
+}
+
 /// List of wrapped document types.
 ///
 /// > TODO Add wrapped types in enum variants.
@@ -78,8 +99,30 @@ pub enum V10Document {
     Revocation(Box<RevocationDocument>),
 }
 
+/// Trait for a compact V10 document.
+pub trait CompactTextDocument: Sized + Clone {
+    /// Generate document compact text.
+    /// the compact format is the one used in the blocks.
+    ///
+    /// - Don't contains leading signatures
+    /// - Contains line breaks on all line.
+    fn as_compact_text(&self) -> String;
+}
+
+impl<D: TextDocument> CompactTextDocument for TextDocumentFormat<D> {
+    fn as_compact_text(&self) -> String {
+        match *self {
+            TextDocumentFormat::Complete(ref doc) => doc.generate_compact_text(),
+            TextDocumentFormat::Compact(ref doc) => doc.as_compact_text(),
+        }
+    }
+}
+
 /// Trait for a V10 document.
 pub trait TextDocument: Document<PublicKey = ed25519::PublicKey, CurrencyType = str> {
+    /// Type of associated compact document.
+    type CompactTextDocument_: CompactTextDocument;
+
     /// Return document as text.
     fn as_text(&self) -> &str;
 
@@ -100,12 +143,19 @@ pub trait TextDocument: Document<PublicKey = ed25519::PublicKey, CurrencyType =
         text
     }
 
+    /// Generate compact document.
+    /// the compact format is the one used in the blocks.
+    /// - Don't contains leading signatures
+    fn to_compact_document(&self) -> Self::CompactTextDocument_;
+
     /// Generate document compact text.
     /// the compact format is the one used in the blocks.
     ///
     /// - Don't contains leading signatures
     /// - Contains line breaks on all line.
-    fn generate_compact_text(&self) -> String;
+    fn generate_compact_text(&self) -> String {
+        self.to_compact_document().as_compact_text()
+    }
 }
 
 /// Trait for a V10 document builder.
diff --git a/documents/blockchain/v10/documents/revocation.rs b/documents/blockchain/v10/documents/revocation.rs
index b954ae13b2ead8be53710732e66daadf3ae3eb26..d40d97046fe03fb391ae42598985aa8c143ffcde 100644
--- a/documents/blockchain/v10/documents/revocation.rs
+++ b/documents/blockchain/v10/documents/revocation.rs
@@ -21,10 +21,7 @@ use self::serde::ser::{Serialize, Serializer};
 use duniter_crypto::keys::{ed25519, PublicKey, Signature};
 use regex::Regex;
 
-use blockchain::v10::documents::{
-    StandardTextDocumentParser, TextDocument, TextDocumentBuilder, V10Document,
-    V10DocumentParsingError,
-};
+use blockchain::v10::documents::*;
 use blockchain::{BlockchainProtocol, Document, DocumentBuilder, IntoSpecializedDocument};
 use Blockstamp;
 
@@ -37,6 +34,25 @@ lazy_static! {
     ).unwrap();
 }
 
+#[derive(Debug, Copy, Clone)]
+/// Wrap an Compact Revocation document (in block content)
+pub struct CompactRevocationDocument {
+    /// Issuer
+    pub issuer: ed25519::PublicKey,
+    /// Signature
+    pub signature: ed25519::Signature,
+}
+
+impl CompactTextDocument for CompactRevocationDocument {
+    fn as_compact_text(&self) -> String {
+        format!(
+            "{issuer}:{signature}",
+            issuer = self.issuer,
+            signature = self.signature,
+        )
+    }
+}
+
 /// Wrap an Revocation document.
 ///
 /// Must be created by parsing a text document or using a builder.
@@ -98,25 +114,26 @@ impl Document for RevocationDocument {
 }
 
 impl TextDocument for RevocationDocument {
+    type CompactTextDocument_ = CompactRevocationDocument;
+
     fn as_text(&self) -> &str {
         &self.text
     }
 
-    fn generate_compact_text(&self) -> String {
-        format!(
-            "{issuer}:{signature}",
-            issuer = self.issuers[0],
-            signature = self.signatures[0],
-        )
+    fn to_compact_document(&self) -> Self::CompactTextDocument_ {
+        CompactRevocationDocument {
+            issuer: self.issuers[0],
+            signature: self.signatures[0],
+        }
     }
 }
 
-impl Serialize for RevocationDocument {
+impl Serialize for TextDocumentFormat<RevocationDocument> {
     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     where
         S: Serializer,
     {
-        serializer.serialize_str(&self.generate_compact_text())
+        serializer.serialize_str(&self.as_compact_text())
     }
 }
 
diff --git a/documents/blockchain/v10/documents/transaction.rs b/documents/blockchain/v10/documents/transaction.rs
index efcf04a7e1252862277bde9898b73b450bf415ee..992640c8a6ce937f77e9e703803fa1791f6a8835 100644
--- a/documents/blockchain/v10/documents/transaction.rs
+++ b/documents/blockchain/v10/documents/transaction.rs
@@ -25,10 +25,7 @@ use regex::RegexBuilder;
 
 use self::serde::ser::{Serialize, Serializer};
 
-use blockchain::v10::documents::{
-    StandardTextDocumentParser, TextDocument, TextDocumentBuilder, V10Document,
-    V10DocumentParsingError,
-};
+use blockchain::v10::documents::*;
 use blockchain::{BlockchainProtocol, Document, DocumentBuilder, IntoSpecializedDocument};
 use Blockstamp;
 
@@ -442,12 +439,8 @@ impl Document for TransactionDocument {
     }
 }
 
-impl TextDocument for TransactionDocument {
-    fn as_text(&self) -> &str {
-        &self.text
-    }
-
-    fn generate_compact_text(&self) -> String {
+impl CompactTextDocument for TransactionDocument {
+    fn as_compact_text(&self) -> String {
         let mut issuers_str = String::from("");
         for issuer in self.issuers.clone() {
             issuers_str.push_str("\n");
@@ -499,12 +492,24 @@ impl TextDocument for TransactionDocument {
     }
 }
 
+impl TextDocument for TransactionDocument {
+    type CompactTextDocument_ = TransactionDocument;
+
+    fn as_text(&self) -> &str {
+        &self.text
+    }
+
+    fn to_compact_document(&self) -> Self::CompactTextDocument_ {
+        self.clone()
+    }
+}
+
 impl Serialize for TransactionDocument {
     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     where
         S: Serializer,
     {
-        let compact_text = self.generate_compact_text();
+        let compact_text = self.to_compact_document().generate_compact_text();
         serializer.serialize_str(&compact_text.replace("\n", "$"))
     }
 }
diff --git a/message/Cargo.toml b/message/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..eb558403f94cc1389f09b552ba2ff9b04dfc36c8
--- /dev/null
+++ b/message/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "duniter-message"
+version = "0.1.0"
+authors = ["librelois <elois@duniter.org>"]
+description = "message model for the Duniter project."
+license = "AGPL-3.0"
+
+[lib]
+path = "lib.rs"
+
+[dependencies]
+duniter-crypto = { path = "../crypto" }
+duniter-dal = { path = "../dal" }
+duniter-documents = { path = "../documents" }
+duniter-module = { path = "../module" }
+duniter-network = { path = "../network" }
+serde = "1.0.24"
+serde_derive = "1.0.24"
+serde_json = "1.0.9"
+
+[features]
+# Treat warnings as a build error.
+strict = []
\ No newline at end of file
diff --git a/message/lib.rs b/message/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..f99560f56c18f75c029bdec43ac648b762d5fdd1
--- /dev/null
+++ b/message/lib.rs
@@ -0,0 +1,75 @@
+//  Copyright (C) 2018  The Duniter Project Developers.
+//
+// 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/>.
+
+//! Defined the few global types used by all modules,
+//! as well as the DuniterModule trait that all modules must implement.
+
+#![cfg_attr(feature = "strict", deny(warnings))]
+#![deny(
+    missing_docs, missing_debug_implementations, missing_copy_implementations, trivial_casts,
+    trivial_numeric_casts, unsafe_code, unstable_features, unused_import_braces,
+    unused_qualifications
+)]
+
+extern crate duniter_crypto;
+extern crate duniter_dal;
+extern crate duniter_documents;
+extern crate duniter_module;
+extern crate duniter_network;
+extern crate serde;
+extern crate serde_json;
+
+use std::sync::mpsc;
+
+use duniter_crypto::keys::ed25519;
+use duniter_dal::dal_event::DALEvent;
+use duniter_dal::dal_requests::{DALRequest, DALResponse};
+use duniter_documents::blockchain::BlockchainProtocol;
+use duniter_documents::{BlockId, Hash};
+use duniter_module::ModuleMessage;
+use duniter_network::{NetworkEvent, NetworkRequest};
+
+#[derive(Debug, Clone)]
+/// Message exchanged between Duniter-rs modules
+pub enum DuniterMessage {
+    /// Brut text message
+    Text(String),
+    /// Brut json message
+    Json(serde_json::Value),
+    /// Brut binary message
+    Binary(Vec<u8>),
+    /// Subscriptions to the module feed
+    Followers(Vec<mpsc::Sender<DuniterMessage>>),
+    /// Blockchain datas request
+    DALRequest(DALRequest),
+    /// Response of DALRequest
+    DALResponse(DALResponse),
+    /// Blockchain event
+    DALEvent(DALEvent),
+    /// Request to the network module
+    NetworkRequest(NetworkRequest),
+    /// Network event
+    NetworkEvent(NetworkEvent),
+    /// Request to the pow module
+    ProverRequest(BlockId, Hash),
+    /// Pow module response
+    ProverResponse(BlockId, ed25519::Signature, u64),
+    /// Client API event
+    ReceiveDocsFromClient(Vec<BlockchainProtocol>),
+    /// Stop signal
+    Stop(),
+}
+
+impl ModuleMessage for DuniterMessage {}
diff --git a/module/Cargo.toml b/module/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..bfc08e52a41fa92145972b86a5c87fddcc2a3151
--- /dev/null
+++ b/module/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "duniter-module"
+version = "0.1.0"
+authors = ["librelois <elois@duniter.org>"]
+description = "Modules model for the Duniter project."
+license = "AGPL-3.0"
+
+[lib]
+path = "lib.rs"
+
+[dependencies]
+duniter-crypto = { path = "../crypto" }
+duniter-documents = { path = "../documents" }
+serde = "1.0.24"
+serde_derive = "1.0.24"
+serde_json = "1.0.9"
+
+[features]
+# Treat warnings as a build error.
+strict = []
\ No newline at end of file
diff --git a/module/lib.rs b/module/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..75640b34d81fc9b912b0e5a2f77947954434cd9a
--- /dev/null
+++ b/module/lib.rs
@@ -0,0 +1,274 @@
+//  Copyright (C) 2018  The Duniter Project Developers.
+//
+// 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/>.
+
+//! Defined the few global types used by all modules,
+//! as well as the DuniterModule trait that all modules must implement.
+
+#![cfg_attr(feature = "strict", deny(warnings))]
+#![deny(
+    missing_docs, missing_debug_implementations, missing_copy_implementations, trivial_casts,
+    trivial_numeric_casts, unsafe_code, unstable_features, unused_import_braces,
+    unused_qualifications
+)]
+
+extern crate duniter_crypto;
+extern crate serde;
+extern crate serde_json;
+
+use duniter_crypto::keys::KeyPair;
+use serde::ser::{Serialize, SerializeStruct, Serializer};
+use std::fmt::Debug;
+use std::sync::mpsc;
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+/// Store Currency
+pub enum Currency {
+    /// Currency in string format
+    Str(String),
+    /// Currency in binary format
+    Bin([u8; 2]),
+}
+
+impl ToString for Currency {
+    fn to_string(&self) -> String {
+        match *self {
+            Currency::Str(ref currency_str) => currency_str.clone(),
+            Currency::Bin(_) => panic!("Currency binary format is not implemented !"),
+        }
+    }
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+/// Store module identifier
+pub enum ModuleId {
+    /// Module in static str format because module name must be know at compile time
+    Str(&'static str),
+    /// Module in binary format
+    Bin([u8; 2]),
+}
+
+impl ToString for ModuleId {
+    fn to_string(&self) -> String {
+        match *self {
+            ModuleId::Str(module_id_str) => String::from(module_id_str),
+            ModuleId::Bin(_) => panic!("ModuleId binary format is not implemented !"),
+        }
+    }
+}
+
+impl Serialize for ModuleId {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let module_id_string = match *self {
+            ModuleId::Str(module_id_str) => String::from(module_id_str),
+            ModuleId::Bin(_) => panic!("ModuleId binary format is not implemented !"),
+        };
+        serializer.serialize_str(module_id_string.as_str())
+    }
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+/// Identifier of an inter-module request
+pub struct ModuleReqId(pub u32);
+
+impl Serialize for ModuleReqId {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        serializer.serialize_str(&format!("{:x}", self.0))
+    }
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+/// Several modules can simultaneously send requests with the same identifier.
+/// To identify each request in a unique way, we must therefore also take into account the identifier of the module performing the request.
+pub struct ModuleReqFullId(pub ModuleId, pub ModuleReqId);
+
+impl ToString for ModuleReqFullId {
+    fn to_string(&self) -> String {
+        format!("{}-{}", self.0.to_string(), (self.1).0)
+    }
+}
+
+/*impl Serialize for ModuleReqFullId {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        serializer.serialize_str(&format!("{}-{}",  self.0.to_string(), (self.1).0))
+    }
+}*/
+
+#[derive(Debug, Clone, PartialEq)]
+/// Duniter configuration v1
+pub struct DuniterConfV1 {
+    /// Name of datas folder in ~/.config/durs/
+    pub profile: String,
+    /// Currency
+    pub currency: Currency,
+    /// Duniter node unique identifier
+    pub my_node_id: u32,
+    /// Configuration of modules in json format (obtained from the conf.json file)
+    pub modules: serde_json::Value,
+}
+
+impl Serialize for DuniterConfV1 {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let mut state = serializer.serialize_struct("DuniterConfV1", 3)?;
+
+        // Currency
+        state.serialize_field("currency", self.currency.to_string().as_str())?;
+
+        // Node id
+        state.serialize_field("node_id", &format!("{:x}", self.my_node_id))?;
+
+        // Modules
+        state.serialize_field("modules", &self.modules)?;
+
+        // End
+        state.end()
+    }
+}
+
+#[derive(Debug, Clone, PartialEq)]
+/// Duniter node configuration
+pub enum DuniterConf {
+    /// Duniter node configuration v1
+    V1(DuniterConfV1),
+    /// Duniter node configuration v2
+    V2(),
+}
+
+impl DuniterConf {
+    /// Get profile
+    pub fn profile(&self) -> String {
+        match *self {
+            DuniterConf::V1(ref conf_v1) => conf_v1.profile.clone(),
+            _ => panic!("Fail to load duniter conf : conf version not supported !"),
+        }
+    }
+    /// Get currency
+    pub fn currency(&self) -> Currency {
+        match *self {
+            DuniterConf::V1(ref conf_v1) => conf_v1.currency.clone(),
+            _ => panic!("Fail to load duniter conf : conf version not supported !"),
+        }
+    }
+    /// Get node id
+    pub fn my_node_id(&self) -> u32 {
+        match *self {
+            DuniterConf::V1(ref conf_v1) => conf_v1.my_node_id,
+            _ => panic!("Fail to load duniter conf : conf version not supported !"),
+        }
+    }
+    /// Get modules conf
+    pub fn modules(&self) -> serde_json::Value {
+        match *self {
+            DuniterConf::V1(ref conf_v1) => conf_v1.modules.clone(),
+            _ => panic!("Fail to load duniter conf : conf version not supported !"),
+        }
+    }
+}
+
+/// The different modules of Duniter-rs can exchange messages with the type of their choice,
+/// provided that this type implements the ModuleMessage trait.
+pub trait ModuleMessage: Debug + Clone {}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+/// Type returned by module initialization function
+pub enum ModuleInitError {
+    /// Fail to load configuration
+    FailToLoadConf(),
+    /// Unknow error
+    UnknowError(),
+}
+
+#[derive(Debug, Clone)]
+/// Type sent by each module to the rooter during initialization
+pub enum RooterThreadMessage<M: ModuleMessage> {
+    /// Channel on which the module listens
+    ModuleSender(mpsc::Sender<M>),
+    /// When the number of plugged modules is known, the rooter thread must be informed of the number of modules it must connect between them.
+    ModulesCount(usize),
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+/// Indicates which keys the module needs to operate
+pub enum RequiredKeys {
+    /// The module needs the member keypair (private key included).
+    MemberKeyPair(),
+    /// The module only needs the member public key.
+    MemberPublicKey(),
+    /// The module needs the network keypair (private key included).
+    NetworkKeyPair(),
+    /// The module only needs the network public key.
+    NetworkPublicKey(),
+    /// The module does not need any key
+    None(),
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+/// Contains the keys the module needs
+pub enum RequiredKeysContent<K: KeyPair> {
+    /// Contains the member keypair (private key included).
+    MemberKeyPair(Option<K>),
+    /// Contains the member public key.
+    MemberPublicKey(Option<K::PublicKey>),
+    /// Contains the network keypair (private key included).
+    NetworkKeyPair(K),
+    /// Contains the network public key.
+    NetworkPublicKey(K::PublicKey),
+    /// Does not contain any keys
+    None(),
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+/// Defined the priority level of the module
+pub enum ModulePriority {
+    /// This module is necessary for Duniter-Rs to work properly, impossible to disable it.
+    Essential(),
+    /// This module is recommended but it's not essential, it's enabled by default but can be disabled by the user.
+    Recommended(),
+    /// This module is disabled by default, it must be explicitly enabled by the user.
+    Optional(),
+}
+
+/// All Duniter-rs modules must implement this trait.
+pub trait DuniterModule<K: KeyPair, M: ModuleMessage> {
+    /// Returns the module identifier
+    fn id() -> ModuleId;
+    /// Returns the module priority
+    fn priority() -> ModulePriority;
+    /// Indicates which keys the module needs
+    fn ask_required_keys() -> RequiredKeys;
+    /// Provides the default module configuration
+    fn default_conf() -> serde_json::Value;
+    /// Launch the module
+    fn start(
+        soft_name: &str,
+        soft_version: &str,
+        keys: RequiredKeysContent<K>,
+        conf: &DuniterConf,
+        module_conf: &serde_json::Value,
+        main_sender: mpsc::Sender<RooterThreadMessage<M>>,
+        load_conf_only: bool,
+    ) -> Result<(), ModuleInitError>;
+}
diff --git a/network/Cargo.toml b/network/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..ab7bd3aca3cb068cc4c3c9de0682521334e86598
--- /dev/null
+++ b/network/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "duniter-network"
+version = "0.1.0"
+authors = ["librelois <elois@duniter.org>"]
+description = "network model for the Duniter project."
+license = "AGPL-3.0"
+
+[lib]
+path = "lib.rs"
+
+[dependencies]
+duniter-crypto = { path = "../crypto" }
+duniter-documents = { path = "../documents" }
+duniter-module = { path = "../module" }
+lazy_static = "1.0.0"
+regex = "0.2.6"
+rust-crypto = "0.2.36"
+serde = "1.0.24"
+serde_derive = "1.0.24"
+serde_json = "1.0.9"
+
+[features]
+# Treat warnings as a build error.
+strict = []
\ No newline at end of file
diff --git a/network/lib.rs b/network/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..9b477cb2a3549deb248f757ccf3bf7ea1d621eca
--- /dev/null
+++ b/network/lib.rs
@@ -0,0 +1,338 @@
+//  Copyright (C) 2018  The Duniter Project Developers.
+//
+// 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/>.
+
+//! Defined all aspects of the inter-node network that concern all modules and are therefore independent of one implementation or another of this network layer.
+
+#![cfg_attr(feature = "strict", deny(warnings))]
+#![deny(
+    missing_docs, missing_debug_implementations, missing_copy_implementations, trivial_casts,
+    trivial_numeric_casts, unsafe_code, unstable_features, unused_import_braces,
+    unused_qualifications
+)]
+
+#[macro_use]
+extern crate lazy_static;
+
+extern crate crypto;
+extern crate duniter_crypto;
+extern crate duniter_documents;
+extern crate duniter_module;
+extern crate serde;
+extern crate serde_json;
+
+pub mod network_endpoint;
+pub mod network_head;
+pub mod network_peer;
+
+use self::network_head::NetworkHead;
+use self::network_peer::NetworkPeer;
+use crypto::digest::Digest;
+use crypto::sha2::Sha256;
+use duniter_crypto::keys::{ed25519, PublicKey};
+use duniter_documents::blockchain::v10::documents::{
+    BlockDocument, CertificationDocument, IdentityDocument, MembershipDocument, RevocationDocument,
+    TransactionDocument,
+};
+use duniter_documents::blockchain::Document;
+use duniter_documents::{BlockHash, BlockId, Blockstamp, Hash};
+use duniter_module::{ModuleReqFullId, ModuleReqId};
+use std::fmt::{Debug, Display, Error, Formatter};
+use std::ops::Deref;
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+/// Random identifier with which several Duniter nodes with the same network keypair can be differentiated
+pub struct NodeUUID(pub u32);
+
+impl Default for NodeUUID {
+    fn default() -> NodeUUID {
+        NodeUUID(0)
+    }
+}
+
+impl Display for NodeUUID {
+    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
+        write!(f, "{:x}", self.0)
+    }
+}
+
+impl<'a> From<&'a str> for NodeUUID {
+    fn from(source: &'a str) -> NodeUUID {
+        NodeUUID(u32::from_str_radix(source, 16).expect("Fail to parse NodeUUID"))
+    }
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+/// Complete identifier of a duniter node.
+pub struct NodeFullId(pub NodeUUID, pub ed25519::PublicKey);
+
+impl Default for NodeFullId {
+    fn default() -> NodeFullId {
+        NodeFullId(
+            NodeUUID::default(),
+            PublicKey::from_base58("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA").unwrap(),
+        )
+    }
+}
+
+impl Display for NodeFullId {
+    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
+        write!(f, "{}-{}", self.0, self.1)
+    }
+}
+
+impl NodeFullId {
+    /// Compute sha256 hash
+    pub fn sha256(&self) -> Hash {
+        let mut sha256 = Sha256::new();
+        sha256.input_str(&format!("{}", self));
+        Hash::from_hex(&sha256.result_str()).unwrap()
+    }
+    /// To human string
+    pub fn to_human_string(&self) -> String {
+        let mut pubkey_string = self.1.to_string();
+        pubkey_string.truncate(8);
+        format!("{:8x}-{:8}", (self.0).0, pubkey_string)
+    }
+}
+
+/// Trait to be implemented by the configuration object of the module managing the inter-node network.
+pub trait NetworkConf: Debug + Copy + Clone + PartialEq {}
+
+#[derive(Debug, Clone)]
+/// Block v10 in network format (Some events require a blockchain access to reconstitute the corresponding document)
+pub struct NetworkBlockV10 {
+    /// Uncompleted block document
+    pub uncompleted_block_doc: BlockDocument,
+    /// revoked
+    pub revoked: Vec<serde_json::Value>,
+    /// certifications
+    pub certifications: Vec<serde_json::Value>,
+}
+
+#[derive(Debug, Clone)]
+/// Block in network format (Some events require a blockchain access to reconstitute the corresponding document)
+pub enum NetworkBlock {
+    /// Block V10
+    V10(Box<NetworkBlockV10>),
+    /// Block V11
+    V11(),
+}
+
+impl NetworkBlock {
+    /// Return blockstamp
+    pub fn blockstamp(&self) -> Blockstamp {
+        match *self {
+            NetworkBlock::V10(ref network_block_v10) => {
+                network_block_v10.deref().uncompleted_block_doc.blockstamp()
+            }
+            _ => panic!("Block version not supported !"),
+        }
+    }
+    /// Return previous blockstamp
+    pub fn previous_blockstamp(&self) -> Blockstamp {
+        match *self {
+            NetworkBlock::V10(ref network_block_v10) => Blockstamp {
+                id: BlockId(network_block_v10.deref().uncompleted_block_doc.number.0 - 1),
+                hash: BlockHash(
+                    network_block_v10
+                        .deref()
+                        .uncompleted_block_doc
+                        .previous_hash,
+                ),
+            },
+            _ => panic!("Block version not supported !"),
+        }
+    }
+}
+
+#[derive(Debug, Clone)]
+/// Network Document
+pub enum NetworkDocument {
+    /// Network Block
+    Block(NetworkBlock),
+    /// Identity Document
+    Identity(Box<IdentityDocument>),
+    /// Membership Document
+    Membership(Box<MembershipDocument>),
+    /// Certification Document
+    Certification(Box<CertificationDocument>),
+    /// Revocation Document
+    Revocation(Box<RevocationDocument>),
+    /// Transaction Document
+    Transaction(Box<TransactionDocument>),
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+/// Type returned when the network module fails to determine the current network consensus
+pub enum NetworkConsensusError {
+    /// The network module does not have enough data to determine consensus
+    InsufficientData(usize),
+    /// The network module does not determine consensus, there is most likely a fork
+    Fork(),
+}
+
+#[derive(Debug, Copy, Clone)]
+/// Type containing a request addressed to the network module
+pub enum NetworkRequest {
+    /// Get a current block of a specific node
+    GetCurrent(ModuleReqFullId, NodeFullId),
+    //GetBlock(ModuleReqFullId, NodeFullId, u64),
+    /// Get a blocks chunk from specified node
+    GetBlocks(ModuleReqFullId, NodeFullId, u32, u32),
+    /// Get pending wot documents from specified node
+    GetRequirementsPending(ModuleReqFullId, NodeFullId, u32),
+    /// Obtain the current network consensus
+    GetConsensus(ModuleReqFullId),
+    /// Getting the heads cache
+    GetHeadsCache(ModuleReqFullId),
+    /// Get a list of known endpoints
+    GetEndpoints(ModuleReqFullId),
+}
+
+impl NetworkRequest {
+    /// Get request full identitifier
+    pub fn get_req_full_id(&self) -> ModuleReqFullId {
+        match *self {
+            NetworkRequest::GetCurrent(ref req_id, _)
+            | NetworkRequest::GetBlocks(ref req_id, _, _, _)
+            | NetworkRequest::GetRequirementsPending(ref req_id, _, _)
+            | NetworkRequest::GetConsensus(ref req_id)
+            | NetworkRequest::GetHeadsCache(ref req_id)
+            | NetworkRequest::GetEndpoints(ref req_id) => *req_id,
+        }
+    }
+    /// Get request identitifier
+    pub fn get_req_id(&self) -> ModuleReqId {
+        self.get_req_full_id().1
+    }
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+/// Type returned when the network module does not get a satisfying answer to a request
+pub enum NetworkRequestError {
+    /// Receiving an invalid format response
+    WrongFormat(),
+    /// Unknow error
+    UnknowError(),
+    /// No response received
+    NoResponse(),
+    /// Unable to reach the target node
+    ReceiverUnreachable(),
+}
+
+#[derive(Debug, Clone)]
+/// Type containing the response to a network request
+pub enum NetworkResponse {
+    /// CurrentBlock
+    CurrentBlock(ModuleReqFullId, NodeFullId, Box<NetworkBlock>),
+    /// Block
+    Block(ModuleReqFullId, NodeFullId, Box<NetworkBlock>),
+    /// Chunk
+    Chunk(ModuleReqFullId, NodeFullId, Vec<Box<NetworkBlock>>),
+    /// PendingDocuments
+    PendingDocuments(ModuleReqFullId, Vec<NetworkDocument>),
+    /// Consensus
+    Consensus(ModuleReqFullId, Result<Blockstamp, NetworkConsensusError>),
+    /// HeadsCache
+    HeadsCache(ModuleReqFullId, Box<NetworkHead>),
+}
+
+impl NetworkResponse {
+    /// Get request full identifier
+    pub fn get_req_full_id(&self) -> ModuleReqFullId {
+        match *self {
+            NetworkResponse::CurrentBlock(ref req_id, _, _)
+            | NetworkResponse::Block(ref req_id, _, _)
+            | NetworkResponse::Chunk(ref req_id, _, _)
+            | NetworkResponse::PendingDocuments(ref req_id, _)
+            | NetworkResponse::Consensus(ref req_id, _)
+            | NetworkResponse::HeadsCache(ref req_id, _) => *req_id,
+        }
+    }
+    /// Get request identifier
+    pub fn get_req_id(&self) -> ModuleReqId {
+        self.get_req_full_id().1
+    }
+}
+
+#[derive(Debug, Clone)]
+/// Type containing a network event, each time a network event occurs it's relayed to all modules
+pub enum NetworkEvent {
+    /// Receiving a response to a network request
+    ReqResponse(Box<NetworkResponse>),
+    /// A connection has changed state(`u32` is the new state, `Option<String>` est l'uid du noeud)
+    ConnectionStateChange(NodeFullId, u32, Option<String>, String),
+    /// Receiving Pending Documents
+    ReceiveDocuments(Vec<NetworkDocument>),
+    /// Receipt of peer cards
+    ReceivePeers(Vec<NetworkPeer>),
+    /// Receiving heads
+    ReceiveHeads(Vec<NetworkHead>),
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::network_endpoint::*;
+    use super::*;
+
+    #[test]
+    fn parse_endpoint() {
+        let issuer =
+            PublicKey::from_base58("D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx").unwrap();
+        let node_id = NodeUUID(u32::from_str_radix("c1c39a0a", 16).unwrap());
+        let full_id = NodeFullId(node_id, issuer);
+        assert_eq!(
+            NetworkEndpoint::parse_from_raw("WS2P c1c39a0a i3.ifee.fr 80 /ws2p", issuer, 0, 0),
+            Some(NetworkEndpoint::V1(NetworkEndpointV1 {
+                version: 1,
+                issuer,
+                api: NetworkEndpointApi(String::from("WS2P")),
+                node_id: Some(node_id),
+                hash_full_id: Some(full_id.sha256()),
+                host: String::from("i3.ifee.fr"),
+                port: 80,
+                path: Some(String::from("ws2p")),
+                raw_endpoint: String::from("WS2P c1c39a0a i3.ifee.fr 80 /ws2p"),
+                last_check: 0,
+                status: 0,
+            },))
+        );
+    }
+
+    #[test]
+    fn parse_endpoint2() {
+        let issuer =
+            PublicKey::from_base58("5gJYnQp8v7bWwk7EWRoL8vCLof1r3y9c6VDdnGSM1GLv").unwrap();
+        let node_id = NodeUUID(u32::from_str_radix("cb06a19b", 16).unwrap());
+        let full_id = NodeFullId(node_id, issuer);
+        assert_eq!(
+            NetworkEndpoint::parse_from_raw("WS2P cb06a19b g1.imirhil.fr 53012 /", issuer, 0, 0),
+            Some(NetworkEndpoint::V1(NetworkEndpointV1 {
+                version: 1,
+                issuer,
+                api: NetworkEndpointApi(String::from("WS2P")),
+                node_id: Some(node_id),
+                hash_full_id: Some(full_id.sha256()),
+                host: String::from("g1.imirhil.fr"),
+                port: 53012,
+                path: None,
+                raw_endpoint: String::from("WS2P cb06a19b g1.imirhil.fr 53012 /"),
+                last_check: 0,
+                status: 0,
+            },))
+        );
+    }
+}
diff --git a/network/network_endpoint.rs b/network/network_endpoint.rs
new file mode 100644
index 0000000000000000000000000000000000000000..53e3ae82947ebecdfca1c757d1164e39096e9498
--- /dev/null
+++ b/network/network_endpoint.rs
@@ -0,0 +1,217 @@
+//  Copyright (C) 2017  The Duniter Project Developers.
+//
+// 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/>.
+
+//! Module defining the format of network endpoints and how to handle them.
+
+extern crate crypto;
+extern crate duniter_crypto;
+extern crate duniter_documents;
+extern crate duniter_module;
+extern crate regex;
+extern crate serde;
+extern crate serde_json;
+
+use self::regex::Regex;
+use super::{NodeFullId, NodeUUID};
+use duniter_crypto::keys::ed25519;
+use duniter_documents::Hash;
+
+lazy_static! {
+    #[derive(Debug)]
+    /// Regex match all endpoint in V1 format (works for all api)
+    pub static ref ENDPOINT_V1_REGEX: Regex = Regex::new(
+        r"^(?P<api>[A-Z0-9_]+) (?P<version>[1-9][0-9]*)? ?(?P<uuid>[a-f0-9]{6,8})? ?(?P<host>[a-z_][a-z0-9-_.]*|[0-9.]+|[0-9a-f:]+) (?P<port>[0-9]+)(?: /?(?P<path>.+)?)? *$"
+    ).unwrap();
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+/// Identifies the API of an endpoint
+pub struct NetworkEndpointApi(pub String);
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+/// Endpoint v1
+pub struct NetworkEndpointV1 {
+    /// API version
+    pub version: usize,
+    /// API Name
+    pub api: NetworkEndpointApi,
+    /// Node unique identifier
+    pub node_id: Option<NodeUUID>,
+    /// Public key of the node declaring this endpoint
+    pub issuer: ed25519::PublicKey,
+    /// NodeFullID hash
+    pub hash_full_id: Option<Hash>,
+    /// hostname
+    pub host: String,
+    /// port number
+    pub port: usize,
+    /// Optional path
+    pub path: Option<String>,
+    /// Endpoint in raw format (as it appears on the peer card)
+    pub raw_endpoint: String,
+    /// Accessibility status of this endpoint  (updated regularly)
+    pub status: u32,
+    /// Timestamp of the last connection attempt to this endpoint
+    pub last_check: u64,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+/// Endpoint
+pub enum NetworkEndpoint {
+    /// Endpoint v1
+    V1(NetworkEndpointV1),
+    /// Endpoint v2
+    V2(),
+}
+
+impl ToString for NetworkEndpoint {
+    fn to_string(&self) -> String {
+        match *self {
+            NetworkEndpoint::V1(ref ep) => ep.raw_endpoint.clone(),
+            _ => panic!("Endpoint version is not supported !"),
+        }
+    }
+}
+
+impl NetworkEndpoint {
+    /// Accessors providing API name
+    pub fn api(&self) -> NetworkEndpointApi {
+        match *self {
+            NetworkEndpoint::V1(ref ep) => ep.api.clone(),
+            _ => panic!("Endpoint version is not supported !"),
+        }
+    }
+    /// Accessors providing node unique identifier
+    pub fn node_uuid(&self) -> Option<NodeUUID> {
+        match *self {
+            NetworkEndpoint::V1(ref ep) => ep.node_id,
+            _ => panic!("Endpoint version is not supported !"),
+        }
+    }
+    /// Accessors providing node public key
+    pub fn pubkey(&self) -> ed25519::PublicKey {
+        match *self {
+            NetworkEndpoint::V1(ref ep) => ep.issuer,
+            _ => panic!("Endpoint version is not supported !"),
+        }
+    }
+    /// Accessors providing node full identifier
+    pub fn node_full_id(&self) -> Option<NodeFullId> {
+        match self.node_uuid() {
+            Some(node_id) => Some(NodeFullId(node_id, self.pubkey())),
+            None => None,
+        }
+    }
+    /// Accessors providing port number
+    pub fn port(&self) -> usize {
+        match *self {
+            NetworkEndpoint::V1(ref ep) => ep.port,
+            _ => panic!("Endpoint version is not supported !"),
+        }
+    }
+    /// Accessors providing raw format
+    pub fn raw(&self) -> String {
+        match *self {
+            NetworkEndpoint::V1(ref ep) => ep.raw_endpoint.clone(),
+            _ => panic!("Endpoint version is not supported !"),
+        }
+    }
+    /// Accessors providing endpoint accessibility status
+    pub fn status(&self) -> u32 {
+        match *self {
+            NetworkEndpoint::V1(ref ep) => ep.status,
+            _ => panic!("Endpoint version is not supported !"),
+        }
+    }
+    /// Set status
+    pub fn set_status(&mut self, new_status: u32) {
+        match *self {
+            NetworkEndpoint::V1(ref mut ep) => ep.status = new_status,
+            _ => panic!("Endpoint version is not supported !"),
+        }
+    }
+    /// Set last_check
+    pub fn set_last_check(&mut self, new_last_check: u64) {
+        match *self {
+            NetworkEndpoint::V1(ref mut ep) => ep.last_check = new_last_check,
+            _ => panic!("Endpoint version is not supported !"),
+        }
+    }
+    /// Generate endpoint url
+    pub fn get_url(&self, get_protocol: bool) -> String {
+        match *self {
+            NetworkEndpoint::V1(ref ep) => {
+                let protocol = match &ep.api.0[..] {
+                    "WS2P" | "WS2PTOR" => "ws",
+                    _ => "http",
+                };
+                let tls = match ep.port {
+                    443 => "s",
+                    _ => "",
+                };
+                let path = match ep.path {
+                    Some(ref path_string) => path_string.clone(),
+                    None => String::new(),
+                };
+                if get_protocol {
+                    format!("{}{}://{}:{}/{}", protocol, tls, ep.host, ep.port, path)
+                } else {
+                    format!("{}:{}/{}", ep.host, ep.port, path)
+                }
+            }
+            _ => panic!("Endpoint version is not supported !"),
+        }
+    }
+    /// Parse Endpoint from rax format
+    pub fn parse_from_raw(
+        raw_endpoint: &str,
+        issuer: ed25519::PublicKey,
+        status: u32,
+        last_check: u64,
+    ) -> Option<NetworkEndpoint> {
+        match ENDPOINT_V1_REGEX.captures(raw_endpoint) {
+            Some(caps) => {
+                let node_id = match caps.name("uuid") {
+                    Some(caps_node_id) => match u32::from_str_radix(caps_node_id.as_str(), 16) {
+                        Ok(node_id) => Some(NodeUUID(node_id)),
+                        Err(_) => None,
+                    },
+                    None => None,
+                };
+                let hash_full_id = match node_id {
+                    Some(node_id_) => Some(NodeFullId(node_id_, issuer).sha256()),
+                    None => None,
+                };
+                Some(NetworkEndpoint::V1(NetworkEndpointV1 {
+                    version: 1,
+                    issuer,
+                    api: NetworkEndpointApi(String::from(&caps["api"])),
+                    node_id,
+                    hash_full_id,
+                    host: String::from(&caps["host"]),
+                    port: caps["port"].parse().unwrap_or(80),
+                    path: match caps.name("path") {
+                        Some(m) => Some(m.as_str().to_string()),
+                        None => None,
+                    },
+                    raw_endpoint: String::from(raw_endpoint),
+                    status,
+                    last_check,
+                }))
+            }
+            None => None,
+        }
+    }
+}
diff --git a/network/network_head.rs b/network/network_head.rs
new file mode 100644
index 0000000000000000000000000000000000000000..3e248386dd0528e25d7aef123a4ac9c62eb3f220
--- /dev/null
+++ b/network/network_head.rs
@@ -0,0 +1,430 @@
+//  Copyright (C) 2017  The Duniter Project Developers.
+//
+// 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/>.
+
+//! Module defining the format of network heads and how to handle them.
+
+extern crate duniter_crypto;
+extern crate duniter_documents;
+extern crate serde_json;
+
+use super::{NodeFullId, NodeUUID};
+use duniter_crypto::keys::{ed25519, PublicKey, Signature};
+use duniter_documents::Blockstamp;
+use std::cmp::Ordering;
+use std::collections::HashMap;
+use std::ops::Deref;
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+/// Head Message V2
+pub struct NetworkHeadMessageV2 {
+    /// API details
+    pub api: String,
+    /// Head version
+    pub version: usize,
+    /// Head pubkey
+    pub pubkey: ed25519::PublicKey,
+    /// Head blockstamp
+    pub blockstamp: Blockstamp,
+    /// Head node id
+    pub node_uuid: NodeUUID,
+    /// Issuer node software
+    pub software: String,
+    /// Issuer node soft version
+    pub soft_version: String,
+    /// Issuer node prefix
+    pub prefix: usize,
+    /// Issuer node free member room
+    pub free_member_room: Option<usize>,
+    /// Issuer node free mirror room
+    pub free_mirror_room: Option<usize>,
+}
+
+impl PartialOrd for NetworkHeadMessageV2 {
+    fn partial_cmp(&self, other: &NetworkHeadMessageV2) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for NetworkHeadMessageV2 {
+    fn cmp(&self, other: &NetworkHeadMessageV2) -> Ordering {
+        self.blockstamp.cmp(&other.blockstamp)
+    }
+}
+
+impl NetworkHeadMessageV2 {
+    /// To human readable string
+    pub fn to_human_string(&self, max_len: usize, uid: Option<String>) -> String {
+        let short_api = &self.api[4..];
+
+        if max_len > 80 && uid.is_some() {
+            format!(
+                "{node_id:8}-{pubkey:.8} {blockstamp:.16} {soft:7}:{ver:7} {pre:3} [{api:5}]  {mer:02}:{mir:02} {uid}",
+                node_id = self.node_uuid.to_string(),
+                pubkey = self.pubkey.to_string(),
+                blockstamp = self.blockstamp.to_string(),
+                soft = self.software,
+                ver = self.soft_version,
+                pre = self.prefix,
+                api = short_api,
+                mer = self.free_member_room.unwrap_or(0),
+                mir = self.free_mirror_room.unwrap_or(0),
+                uid = uid.unwrap(),
+            )
+        } else if max_len > 67 {
+            format!(
+                "{node_id:8}-{pubkey:.8} {blockstamp:.16} {soft:7}:{ver:7} {pre:3} [{api:5}]  {mer:02}:{mir:02}",
+                node_id = self.node_uuid.to_string(),
+                pubkey = self.pubkey.to_string(),
+                blockstamp = self.blockstamp.to_string(),
+                soft = self.software,
+                ver = self.soft_version,
+                pre = self.prefix,
+                api = short_api,
+                mer = self.free_member_room.unwrap_or(0),
+                mir = self.free_mirror_room.unwrap_or(0),
+            )
+        } else if max_len > 63 {
+            format!(
+                "{node_id:8}-{pubkey:.8} {blockstamp:.16} {soft:7}:{ver:7} [{api:5}]  {mer:02}:{mir:02}",
+                node_id = self.node_uuid.to_string(),
+                pubkey = self.pubkey.to_string(),
+                blockstamp = self.blockstamp.to_string(),
+                soft = self.software,
+                ver = self.soft_version,
+                api = short_api,
+                mer = self.free_member_room.unwrap_or(0),
+                mir = self.free_mirror_room.unwrap_or(0),
+            )
+        } else if max_len > 47 {
+            format!(
+                "{node_id:8}-{pubkey:.8} {blockstamp:.16} [{api:5}]  {mer:02}:{mir:02}",
+                node_id = self.node_uuid.to_string(),
+                pubkey = self.pubkey.to_string(),
+                blockstamp = self.blockstamp.to_string(),
+                api = short_api,
+                mer = self.free_member_room.unwrap_or(0),
+                mir = self.free_mirror_room.unwrap_or(0),
+            )
+        } else if max_len > 41 {
+            format!(
+                "{node_id:8}-{pubkey:.8} {blockstamp:.16} [{api:5}]",
+                node_id = self.node_uuid.to_string(),
+                pubkey = self.pubkey.to_string(),
+                blockstamp = self.blockstamp.to_string(),
+                api = short_api,
+            )
+        } else {
+            String::from("Term width insufficient")
+        }
+    }
+}
+
+#[derive(Debug, Clone, Eq, Ord, PartialOrd, PartialEq, Hash)]
+/// Head Message
+pub enum NetworkHeadMessage {
+    /// Head Message V2
+    V2(NetworkHeadMessageV2),
+    /// Head Message V3
+    V3(),
+}
+
+impl NetworkHeadMessage {
+    /// To human readable string
+    pub fn to_human_string(&self, max_len: usize, uid: Option<String>) -> String {
+        match *self {
+            NetworkHeadMessage::V2(ref mess_v2) => mess_v2.deref().to_human_string(max_len, uid),
+            _ => panic!("NetworkHead version not supported !"),
+        }
+    }
+    /// Parse head from string
+    fn from_str(source: &str) -> Option<NetworkHeadMessage> {
+        let source_array: Vec<&str> = source.split(':').collect();
+        if let Ok(pubkey) = PublicKey::from_base58(&source_array[3].to_string()) {
+            Some(NetworkHeadMessage::V2(NetworkHeadMessageV2 {
+                api: source_array[0].to_string(),
+                version: source_array[2].parse().unwrap(),
+                pubkey,
+                blockstamp: Blockstamp::from_string(source_array[4]).unwrap(),
+                node_uuid: NodeUUID(u32::from_str_radix(source_array[5], 16).unwrap()),
+                software: source_array[6].to_string(),
+                soft_version: source_array[7].to_string(),
+                prefix: source_array[8].parse().unwrap(),
+                free_member_room: if let Some(field) = source_array.get(9) {
+                    Some(field.parse().unwrap())
+                } else {
+                    None
+                },
+                free_mirror_room: if let Some(field) = source_array.get(10) {
+                    Some(field.parse().unwrap())
+                } else {
+                    None
+                },
+            }))
+        } else {
+            None
+        }
+    }
+    /// Get head blockcstamp
+    fn blockstamp(&self) -> Blockstamp {
+        match *self {
+            NetworkHeadMessage::V2(ref head_message_v2) => head_message_v2.blockstamp,
+            _ => panic!("This HEAD version is not supported !"),
+        }
+    }
+    /// Get head node id
+    fn node_uuid(&self) -> NodeUUID {
+        match *self {
+            NetworkHeadMessage::V2(ref head_message_v2) => head_message_v2.node_uuid,
+            _ => panic!("This HEAD version is not supported !"),
+        }
+    }
+    /// Get head issuer public key
+    fn _pubkey(&self) -> ed25519::PublicKey {
+        match *self {
+            NetworkHeadMessage::V2(ref head_message_v2) => head_message_v2.pubkey,
+            _ => panic!("This HEAD version is not supported !"),
+        }
+    }
+}
+
+impl ToString for NetworkHeadMessageV2 {
+    fn to_string(&self) -> String {
+        match self.version {
+            1 => format!(
+                "{}:HEAD:1:{}:{}:{}:{}:{}:{}",
+                self.api,
+                self.pubkey,
+                self.blockstamp,
+                self.node_uuid,
+                self.software,
+                self.soft_version,
+                self.prefix
+            ),
+            2 => format!(
+                "{}:HEAD:2:{}:{}:{}:{}:{}:{}:{}:{}",
+                self.api,
+                self.pubkey,
+                self.blockstamp,
+                self.node_uuid,
+                self.software,
+                self.soft_version,
+                self.prefix,
+                self.free_member_room.unwrap(),
+                self.free_mirror_room.unwrap()
+            ),
+            _ => panic!("NetworkHeadMessage is wrongly parsed !"),
+        }
+    }
+}
+
+impl ToString for NetworkHeadMessage {
+    fn to_string(&self) -> String {
+        match *self {
+            NetworkHeadMessage::V2(ref head_message) => head_message.to_string(),
+            _ => panic!("This HEADMessage version is not supported !"),
+        }
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+/// Head V2
+pub struct NetworkHeadV2 {
+    /// Head V1 Message
+    pub message: NetworkHeadMessage,
+    /// signature of V1 Message
+    pub sig: ed25519::Signature,
+    /// Head V2 Message
+    pub message_v2: NetworkHeadMessage,
+    /// signature of V2 Message
+    pub sig_v2: ed25519::Signature,
+    /// Head step
+    pub step: u32,
+    /// Head issuer uid
+    pub uid: Option<String>,
+}
+
+impl ToString for NetworkHeadV2 {
+    fn to_string(&self) -> String {
+        self.message_v2.to_string()
+    }
+}
+
+impl PartialOrd for NetworkHeadV2 {
+    fn partial_cmp(&self, other: &NetworkHeadV2) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for NetworkHeadV2 {
+    fn cmp(&self, other: &NetworkHeadV2) -> Ordering {
+        self.message.cmp(&other.message)
+    }
+}
+
+impl NetworkHeadV2 {
+    /// To human readable string
+    pub fn to_human_string(&self, max_len: usize) -> String {
+        if max_len > 2 {
+            format!(
+                "{} {}",
+                self.step,
+                self.message_v2
+                    .to_human_string(max_len - 2, self.uid.clone())
+            )
+        } else {
+            String::from(".")
+        }
+    }
+    /// Get uid of head issuer
+    pub fn uid(&self) -> Option<String> {
+        self.uid.clone()
+    }
+}
+
+#[derive(Debug, Clone, Eq, Ord, PartialOrd, PartialEq)]
+/// Network Head : Set of information on the current state of a node, the central information being the blockstamp of its current block (= the head of its blockchain).
+pub enum NetworkHead {
+    /// Head V2
+    V2(Box<NetworkHeadV2>),
+    /// head V3
+    V3(),
+}
+
+impl ToString for NetworkHead {
+    fn to_string(&self) -> String {
+        match *self {
+            NetworkHead::V2(ref head_v2) => head_v2.deref().to_string(),
+            _ => panic!("NetworkHead version not supported !"),
+        }
+    }
+}
+
+impl NetworkHead {
+    /// Get HEAD version
+    pub fn version(&self) -> u32 {
+        match *self {
+            NetworkHead::V2(_) => 2,
+            _ => panic!("This HEAD version is not supported !"),
+        }
+    }
+    /// Get HEAD blockstamp
+    pub fn blockstamp(&self) -> Blockstamp {
+        match *self {
+            NetworkHead::V2(ref head_v2) => head_v2.message_v2.blockstamp(),
+            _ => panic!("This HEAD version is not supported !"),
+        }
+    }
+    /// Get pubkey of head issuer
+    pub fn pubkey(&self) -> ed25519::PublicKey {
+        match *self {
+            NetworkHead::V2(ref head_v2) => match head_v2.message_v2 {
+                NetworkHeadMessage::V2(ref head_message_v2) => head_message_v2.pubkey,
+                _ => panic!("This HEAD message version is not supported !"),
+            },
+            _ => panic!("This HEAD version is not supported !"),
+        }
+    }
+    /// Get uid of head issuer
+    pub fn uid(&self) -> Option<String> {
+        match *self {
+            NetworkHead::V2(ref head_v2) => head_v2.uid(),
+            _ => panic!("This HEAD version is not supported !"),
+        }
+    }
+    /// Change uid of head issuer
+    pub fn set_uid(&mut self, uid: &str) {
+        match *self {
+            NetworkHead::V2(ref mut head_v2) => head_v2.uid = Some(String::from(uid)),
+            _ => panic!("This HEAD version is not supported !"),
+        }
+    }
+    /// return the HEAD Step
+    pub fn step(&self) -> u32 {
+        match *self {
+            NetworkHead::V2(ref head_v2) => head_v2.step,
+            _ => panic!("This HEAD version is not supported !"),
+        }
+    }
+    /// Checks the validity of all head signatures
+    pub fn verify(&self) -> bool {
+        match *self {
+            NetworkHead::V2(ref head_v2) => {
+                let pubkey: ed25519::PublicKey =
+                    PublicKey::from_base58(&self.pubkey().to_string()).unwrap();
+                pubkey.verify(head_v2.message.to_string().as_bytes(), &head_v2.sig)
+                    && pubkey.verify(head_v2.message_v2.to_string().as_bytes(), &head_v2.sig_v2)
+            }
+            _ => panic!("This HEAD version is not supported !"),
+        }
+    }
+    /// Returns issuer node id
+    pub fn node_uuid(&self) -> NodeUUID {
+        match *self {
+            NetworkHead::V2(ref head_v2) => head_v2.message_v2.node_uuid(),
+            _ => panic!("This HEAD version is not supported !"),
+        }
+    }
+    /// Returns issuer node full identifier
+    pub fn node_full_id(&self) -> NodeFullId {
+        NodeFullId(self.node_uuid(), self.pubkey())
+    }
+    /// Returns true only if this head is to replace the old head of the same issuer in the head cache (or if it's the 1st head of this issuer)
+    pub fn apply(&self, heads_cache: &mut HashMap<NodeFullId, NetworkHead>) -> bool {
+        let heads_cache_copy = heads_cache.clone();
+        if let Some(head) = heads_cache_copy.get(&self.node_full_id()) {
+            if self.blockstamp().id.0 > head.blockstamp().id.0
+                || (self.blockstamp().id.0 == head.blockstamp().id.0
+                    && self.version() >= head.version()
+                    && self.step() < head.step())
+            {
+                if let Some(head_mut) = heads_cache.get_mut(&self.node_full_id()) {
+                    *head_mut = self.clone();
+                    true
+                } else {
+                    false
+                }
+            } else {
+                false
+            }
+        } else {
+            heads_cache.insert(self.node_full_id(), self.clone());
+            true
+        }
+    }
+    /// Parse Json Head
+    pub fn from_json_value(source: &serde_json::Value) -> Option<NetworkHead> {
+        let message = NetworkHeadMessage::from_str(source.get("message")?.as_str().unwrap())?;
+        match message {
+            NetworkHeadMessage::V2(_) => Some(NetworkHead::V2(Box::new(NetworkHeadV2 {
+                message,
+                sig: Signature::from_base64(source.get("sig")?.as_str().unwrap()).unwrap(),
+                message_v2: NetworkHeadMessage::from_str(
+                    source.get("messageV2")?.as_str().unwrap(),
+                )?,
+                sig_v2: Signature::from_base64(source.get("sigV2")?.as_str().unwrap()).unwrap(),
+                step: source.get("step")?.as_u64().unwrap() as u32,
+                uid: None,
+            }))),
+            _ => None,
+        }
+    }
+    /// To human readable string
+    pub fn to_human_string(&self, max_len: usize) -> String {
+        match *self {
+            NetworkHead::V2(ref head_v2) => head_v2.deref().to_human_string(max_len),
+            _ => panic!("NetworkHead version not supported !"),
+        }
+    }
+}
diff --git a/network/network_peer.rs b/network/network_peer.rs
new file mode 100644
index 0000000000000000000000000000000000000000..5efe5ed188f3e4e939ac08ffdc67eb681c3d97b7
--- /dev/null
+++ b/network/network_peer.rs
@@ -0,0 +1,79 @@
+//  Copyright (C) 2017  The Duniter Project Developers.
+//
+// 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/>.
+
+//! Module defining the format of network peer cards and how to handle them.
+
+extern crate crypto;
+extern crate duniter_crypto;
+extern crate duniter_documents;
+extern crate duniter_module;
+extern crate serde;
+extern crate serde_json;
+
+use super::network_endpoint::NetworkEndpoint;
+use duniter_crypto::keys::ed25519;
+use duniter_documents::Blockstamp;
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+/// Peer card V10
+pub struct NetworkPeerV10 {
+    /// Peer card Blockstamp
+    pub blockstamp: Blockstamp,
+    /// Peer card issuer
+    pub issuer: ed25519::PublicKey,
+    /// Peer card endpoints list
+    pub endpoints: Vec<NetworkEndpoint>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+/// Peer card
+pub enum NetworkPeer {
+    /// Peer card V10
+    V10(NetworkPeerV10),
+    /// Peer card V11
+    V11(),
+}
+
+impl NetworkPeer {
+    /// Get peer card version
+    pub fn version(&self) -> u32 {
+        match *self {
+            NetworkPeer::V10(ref _peer_v10) => 10,
+            _ => panic!("Peer version is not supported !"),
+        }
+    }
+    /// Get peer card blockstamp
+    pub fn blockstamp(&self) -> Blockstamp {
+        match *self {
+            NetworkPeer::V10(ref peer_v10) => peer_v10.blockstamp,
+            _ => panic!("Peer version is not supported !"),
+        }
+    }
+    /// Get peer card issuer
+    pub fn issuer(&self) -> ed25519::PublicKey {
+        match *self {
+            NetworkPeer::V10(ref peer_v10) => peer_v10.issuer,
+            _ => panic!("Peer version is not supported !"),
+        }
+    }
+    /// Verify validity of peer card signature
+    pub fn verify(&self) -> bool {
+        false
+    }
+    /// Get peer card endpoint
+    pub fn get_endpoints(&self) -> Vec<NetworkEndpoint> {
+        Vec::with_capacity(0)
+    }
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c9e127fb73aeea7c141dcba4ecfa7250f1f68531
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,50 @@
+//  Copyright (C) 2018  The Duniter Project Developers.
+//
+// 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/>.
+
+//! Main function for classic duniter-rust nodes (no specialization).
+
+#![cfg_attr(feature = "strict", deny(warnings))]
+#![deny(
+    missing_docs, missing_debug_implementations, missing_copy_implementations, trivial_casts,
+    trivial_numeric_casts, unsafe_code, unstable_features, unused_import_braces,
+    unused_qualifications
+)]
+
+extern crate duniter_core;
+extern crate duniter_tui;
+extern crate duniter_ws2p;
+
+pub use duniter_core::DuniterCore;
+pub use duniter_tui::TuiModule;
+pub use duniter_ws2p::WS2PModule;
+
+/// Main function
+fn main() {
+    // Get software name and version
+    let soft_name = env!("CARGO_PKG_NAME");
+    let soft_version = env!("CARGO_PKG_VERSION");
+
+    // Run duniter core
+    if let Some(mut duniter_core) = DuniterCore::new(soft_name, soft_version) {
+        duniter_core.plug::<WS2PModule>();
+        duniter_core.plug::<TuiModule>();
+        //duniter_core.plug::<PoolModule>();
+        //duniter_core.plug::<PowModule>();
+        //duniter_core.plug::<GvaModule>();
+        //duniter_core.plug::<DasaModule>();
+        //duniter_core.plug::<GuiModule>();
+        duniter_core.start_blockchain();
+    };
+}
diff --git a/tui/Cargo.toml b/tui/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..c3fcd4211ebc7963964a49113b379f98c80bed4a
--- /dev/null
+++ b/tui/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "duniter-tui"
+version = "0.1.0"
+authors = ["librelois <elois@ifee.fr>"]
+description = "Terminal user interface for Duniter-Rs."
+license = "AGPL-3.0"
+
+[lib]
+path = "lib.rs"
+
+[dependencies]
+duniter-conf = { path = "../conf" }
+duniter-crypto = { path = "../crypto" }
+duniter-dal = { path = "../dal" }
+duniter-documents = { path = "../documents" }
+duniter-message =  { path = "../message" }
+duniter-module = { path = "../module" }
+duniter-network = { path = "../network" }
+log = "0.4.1"
+serde_json = "1.0.9"
+termion = "1.5.1"
+
+[features]
+# Treat warnings as a build error.
+strict = []
\ No newline at end of file
diff --git a/tui/lib.rs b/tui/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..deaf579464f81fff9f404a9626f0a36c40d351fa
--- /dev/null
+++ b/tui/lib.rs
@@ -0,0 +1,624 @@
+//  Copyright (C) 2018  The Duniter Project Developers.
+//
+// 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/>.
+
+//! Defined the few global types used by all modules,
+//! as well as the DuniterModule trait that all modules must implement.
+
+#![cfg_attr(feature = "strict", deny(warnings))]
+#![deny(
+    missing_docs, missing_debug_implementations, missing_copy_implementations, trivial_casts,
+    trivial_numeric_casts, unsafe_code, unstable_features, unused_import_braces,
+    unused_qualifications
+)]
+
+#[macro_use]
+extern crate log;
+
+extern crate duniter_conf;
+extern crate duniter_crypto;
+extern crate duniter_dal;
+extern crate duniter_documents;
+extern crate duniter_message;
+extern crate duniter_module;
+extern crate duniter_network;
+extern crate serde_json;
+extern crate termion;
+
+use duniter_crypto::keys::ed25519;
+use duniter_dal::dal_event::DALEvent;
+use duniter_message::DuniterMessage;
+use duniter_module::*;
+use duniter_network::network_head::NetworkHead;
+use duniter_network::{NetworkEvent, NodeFullId};
+use std::collections::HashMap;
+use std::io::{stdout, Write};
+use std::sync::mpsc;
+use std::thread;
+use std::time::{Duration, SystemTime};
+use termion::event::*;
+use termion::input::{MouseTerminal, TermRead};
+use termion::raw::{IntoRawMode, RawTerminal};
+use termion::{clear, color, cursor, style};
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+/// Tui Module Configuration (For future use)
+pub struct TuiConf {}
+
+#[derive(Debug, Clone)]
+/// Format of messages received by the tui module
+pub enum TuiMess {
+    /// Message from another module
+    DuniterMessage(DuniterMessage),
+    /// Message from stdin (user event)
+    TermionEvent(Event),
+}
+
+#[derive(Debug, Copy, Clone)]
+/// Tui module
+pub struct TuiModule {}
+
+#[derive(Debug, Clone)]
+/// Network connexion (data to display)
+pub struct Connection {
+    /// Connexion status
+    status: u32,
+    /// Endpoint url
+    url: String,
+    /// Node uid at the other end of the connection (member nodes only)
+    uid: Option<String>,
+}
+
+#[derive(Debug, Clone)]
+/// Data that the Tui module needs to cache
+pub struct TuiModuleDatas {
+    /// Sender of all other modules
+    pub followers: Vec<mpsc::Sender<DuniterMessage>>,
+    /// HEADs cache content
+    pub heads_cache: HashMap<NodeFullId, NetworkHead>,
+    /// Position of the 1st head displayed on the screen
+    pub heads_index: usize,
+    /// Connections cache content
+    pub connections_status: HashMap<NodeFullId, Connection>,
+    /// Number of connections in `Established` status
+    pub established_conns_count: usize,
+}
+
+impl TuiModuleDatas {
+    /// Parse tui configuration
+    fn parse_tui_conf(_json_conf: &serde_json::Value) -> TuiConf {
+        TuiConf {}
+    }
+    /// Draw terminal
+    fn draw_term<W: Write>(
+        &self,
+        stdout: &mut RawTerminal<W>,
+        start_time: SystemTime,
+        heads_cache: &HashMap<NodeFullId, NetworkHead>,
+        heads_index: usize,
+        out_connections_status: &HashMap<NodeFullId, Connection>,
+        _in_connections_status: &HashMap<NodeFullId, Connection>,
+    ) {
+        // Get Terminal size
+        let (w, h) = termion::terminal_size().expect("Fail to get terminal size !");
+
+        // Prepare connections screen
+        let mut out_never_try_conns_count = 0;
+        let mut out_unreachable_conns_count = 0;
+        let mut out_trying_conns_count = 0;
+        let mut out_denial_conns_count = 0;
+        let mut out_disconnected_conns_count = 0;
+        let mut out_established_conns = Vec::new();
+        for (node_full_id, connection) in out_connections_status {
+            match connection.status {
+                0 => out_never_try_conns_count += 1,
+                2 | 4 => out_unreachable_conns_count += 1,
+                1 | 3 | 5 | 7 | 8 | 9 => out_trying_conns_count += 1,
+                10 => out_denial_conns_count += 1,
+                11 => out_disconnected_conns_count += 1,
+                12 => out_established_conns.push((
+                    node_full_id,
+                    connection.uid.clone(),
+                    connection.url.clone(),
+                )),
+                _ => {}
+            }
+        }
+
+        // Prepare HEADs screen
+        let mut heads = heads_cache.values().collect::<Vec<&NetworkHead>>();
+        heads.sort_unstable_by(|a, b| b.cmp(a));
+        let heads_index_max = if heads.len() > (h - 14) as usize {
+            heads.len() - (h - 14) as usize
+        } else {
+            0
+        };
+
+        // Clear term and reset background color
+        write!(
+            stdout,
+            "{}{}{}",
+            color::Bg(color::Black),
+            clear::All,
+            cursor::Goto(1, 1)
+        ).unwrap();
+
+        // Draw headers
+        let mut line = 1;
+        write!(
+            stdout,
+            "{}{}{} established connections : ",
+            cursor::Goto(1, line),
+            color::Fg(color::White),
+            out_established_conns.len()
+        ).unwrap();
+        line += 1;
+        write!(
+            stdout,
+            "{}{}{} NodeId-PubKey",
+            cursor::Goto(1, line),
+            color::Fg(color::White),
+            style::Italic,
+        ).unwrap();
+
+        // Draw inter-nodes established connections
+        if out_established_conns.is_empty() {
+            line += 1;
+            write!(
+                stdout,
+                "{}{}{}No established connections !",
+                cursor::Goto(2, line),
+                color::Fg(color::Red),
+                style::Bold,
+            ).unwrap();
+        } else {
+            for (ref node_full_id, ref uid, ref url) in out_established_conns {
+                line += 1;
+                let mut uid_string = uid.clone().unwrap_or(String::from("----------------"));
+                uid_string.truncate(16);
+                write!(
+                    stdout,
+                    "{}{} {} {:16} {}",
+                    cursor::Goto(2, line),
+                    color::Fg(color::Green),
+                    node_full_id.to_human_string(),
+                    uid_string,
+                    url,
+                ).unwrap();
+            }
+        }
+
+        // Draw number of conns per state
+        line += 1;
+        write!(
+            stdout,
+            "{}{}{} know endpoints : {} Never try, {} Unreach, {} on trial, {} Denial, {} Close.",
+            cursor::Goto(2, line),
+            color::Fg(color::Rgb(128, 128, 128)),
+            out_connections_status.len(),
+            out_never_try_conns_count,
+            out_unreachable_conns_count,
+            out_trying_conns_count,
+            out_denial_conns_count,
+            out_disconnected_conns_count,
+        ).unwrap();
+
+        // Draw separated line
+        line += 1;
+        let mut separated_line = String::with_capacity(w as usize);
+        for _ in 0..w as usize {
+            separated_line.push('-');
+        }
+        write!(
+            stdout,
+            "{}{}{}",
+            cursor::Goto(1, line),
+            color::Fg(color::White),
+            separated_line,
+        ).unwrap();
+
+        // Draw HEADs
+        line += 1;
+        write!(
+            stdout,
+            "{}{}{} HEADs :",
+            cursor::Goto(1, line),
+            color::Fg(color::White),
+            heads.len()
+        ).unwrap();
+        line += 1;
+        if heads_index > 0 {
+            write!(
+                stdout,
+                "{}{}/\\",
+                cursor::Goto(35, line),
+                color::Fg(color::Green),
+            ).unwrap();
+        } else {
+            write!(
+                stdout,
+                "{}{}/\\",
+                cursor::Goto(35, line),
+                color::Fg(color::Black),
+            ).unwrap();
+        }
+        line += 1;
+        write!(
+            stdout,
+            "{}{}Step NodeId-Pubkey BlockId-BlockHash    Soft:Ver     Pre [ Api ] MeR:MiR uid",
+            cursor::Goto(1, line),
+            color::Fg(color::White)
+        ).unwrap();
+        for head in &heads[heads_index..] {
+            if line < (h - 2) {
+                line += 1;
+                if head.step() == 0 {
+                    write!(
+                        stdout,
+                        "{}{}{}",
+                        cursor::Goto(1, line),
+                        color::Fg(color::Blue),
+                        head.to_human_string(w as usize),
+                    ).unwrap();
+                } else {
+                    write!(
+                        stdout,
+                        "{}{}{}",
+                        cursor::Goto(1, line),
+                        color::Fg(color::Green),
+                        head.to_human_string(w as usize),
+                    ).unwrap();
+                }
+            } else {
+                break;
+            }
+        }
+        line += 1;
+        if heads_index < heads_index_max {
+            write!(
+                stdout,
+                "{}{}\\/",
+                cursor::Goto(35, line),
+                color::Fg(color::Green),
+            ).unwrap();
+        } else {
+            write!(
+                stdout,
+                "{}{}\\/",
+                cursor::Goto(35, line),
+                color::Fg(color::Black),
+            ).unwrap();
+        }
+
+        // Draw footer
+        let mut runtime_in_secs = SystemTime::now()
+            .duration_since(start_time)
+            .expect("Fail to get runtime")
+            .as_secs();
+        let runtime_hours = runtime_in_secs / 3600;
+        runtime_in_secs -= runtime_hours * 3600;
+        let runtime_mins = runtime_in_secs / 60;
+        let runtime_secs = runtime_in_secs % 60;
+        let runtime_str = format!(
+            "{:02}:{:02}:{:02}",
+            runtime_hours, runtime_mins, runtime_secs
+        );
+        /*DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(runtime_in_secs, 0), Utc)
+                .format("%H:%M:%S")
+                .to_string();*/
+        write!(
+            stdout,
+            "{}{}{}runtime : {}",
+            cursor::Goto(1, h),
+            color::Bg(color::Blue),
+            color::Fg(color::White),
+            runtime_str,
+        ).unwrap();
+        write!(
+            stdout,
+            "{}{}{}q : quit{}",
+            cursor::Goto(w - 7, h),
+            color::Bg(color::Blue),
+            color::Fg(color::White),
+            cursor::Hide,
+        ).unwrap();
+
+        // Flush stdout (i.e. make the output appear).
+        stdout.flush().unwrap();
+    }
+}
+
+impl Default for TuiModule {
+    fn default() -> TuiModule {
+        TuiModule {}
+    }
+}
+
+impl DuniterModule<ed25519::KeyPair, DuniterMessage> for TuiModule {
+    fn id() -> ModuleId {
+        ModuleId::Str("tui")
+    }
+    fn priority() -> ModulePriority {
+        ModulePriority::Recommended()
+    }
+    fn ask_required_keys() -> RequiredKeys {
+        RequiredKeys::None()
+    }
+    fn default_conf() -> serde_json::Value {
+        serde_json::Value::default()
+    }
+    fn start(
+        _soft_name: &str,
+        _soft_version: &str,
+        _keys: RequiredKeysContent<ed25519::KeyPair>,
+        _conf: &DuniterConf,
+        module_conf: &serde_json::Value,
+        main_sender: mpsc::Sender<RooterThreadMessage<DuniterMessage>>,
+        load_conf_only: bool,
+    ) -> Result<(), ModuleInitError> {
+        let start_time = SystemTime::now(); //: DateTime<Utc> = Utc::now();
+
+        // load conf
+        let _conf = TuiModuleDatas::parse_tui_conf(module_conf);
+        if load_conf_only {
+            return Ok(());
+        }
+
+        // Instanciate Tui module datas
+        let mut tui = TuiModuleDatas {
+            followers: Vec::new(),
+            heads_cache: HashMap::new(),
+            heads_index: 0,
+            connections_status: HashMap::new(),
+            established_conns_count: 0,
+        };
+
+        // Create tui main thread channel
+        let (tui_sender, tui_receiver): (mpsc::Sender<TuiMess>, mpsc::Receiver<TuiMess>) =
+            mpsc::channel();
+
+        // Create proxy channel
+        let (proxy_sender, proxy_receiver): (
+            mpsc::Sender<DuniterMessage>,
+            mpsc::Receiver<DuniterMessage>,
+        ) = mpsc::channel();
+
+        // Launch a proxy thread that transform DuniterMessage() to TuiMess::DuniterMessage(DuniterMessage())
+        let tui_sender_clone = tui_sender.clone();
+        thread::spawn(move || {
+            // Send proxy sender to main
+            match main_sender.send(RooterThreadMessage::ModuleSender(proxy_sender)) {
+                Ok(_) => {
+                    debug!("Send tui sender to main thread.");
+                }
+                Err(_) => panic!("Fatal error : tui module fail to send is sender channel !"),
+            }
+            loop {
+                match proxy_receiver.recv() {
+                    Ok(message) => {
+                        match tui_sender_clone.send(TuiMess::DuniterMessage(message.clone())) {
+                            Ok(_) => {
+                                if let DuniterMessage::Stop() = message {
+                                    break;
+                                };
+                            }
+                            Err(_) => debug!(
+                                "tui proxy : fail to relay DuniterMessage to tui main thread !"
+                            ),
+                        }
+                    }
+                    Err(e) => {
+                        warn!("{}", e);
+                        break;
+                    }
+                }
+            }
+        });
+
+        // Enter raw mode.
+        //let mut stdout = stdout().into_raw_mode().unwrap();
+        let mut stdout = MouseTerminal::from(stdout().into_raw_mode().unwrap());
+
+        // Initial draw
+        let mut last_draw = SystemTime::now();
+        tui.draw_term(
+            &mut stdout,
+            start_time,
+            &tui.heads_cache,
+            tui.heads_index,
+            &tui.connections_status,
+            &HashMap::with_capacity(0),
+        );
+
+        // Launch stdin thread
+        let _stdin_thread = thread::spawn(move || {
+            // Get the standard input stream.
+            let stdin = std::io::stdin();
+            // Get stdin events
+            for c in stdin.events() {
+                match tui_sender.send(TuiMess::TermionEvent(
+                    c.expect("error to read stdin event !"),
+                )) {
+                    Ok(_) => {
+                        trace!("Send stdin event to tui main thread.");
+                    }
+                    Err(_) => {
+                        panic!("Fatal error : tui stdin thread module fail to send message !")
+                    }
+                }
+            }
+        });
+
+        // ui main loop
+        loop {
+            let mut user_event = false;
+            // Get messages
+            match tui_receiver.recv_timeout(Duration::from_millis(250)) {
+                Ok(ref message) => match message {
+                    &TuiMess::DuniterMessage(ref duniter_message) => match duniter_message {
+                        &DuniterMessage::Stop() => {
+                            writeln!(
+                                stdout,
+                                "{}{}{}{}{}",
+                                color::Fg(color::Reset),
+                                cursor::Goto(1, 1),
+                                color::Bg(color::Reset),
+                                cursor::Show,
+                                clear::All,
+                            ).unwrap();
+                            let _result_stop_propagation: Result<
+                                (),
+                                mpsc::SendError<DuniterMessage>,
+                            > = tui
+                                .followers
+                                .iter()
+                                .map(|f| f.send(DuniterMessage::Stop()))
+                                .collect();
+                            break;
+                        }
+                        &DuniterMessage::Followers(ref new_followers) => {
+                            info!("Tui module receive followers !");
+                            for new_follower in new_followers {
+                                debug!("TuiModule : push one follower.");
+                                tui.followers.push(new_follower.clone());
+                            }
+                        }
+                        &DuniterMessage::DALEvent(ref dal_event) => match dal_event {
+                            &DALEvent::StackUpValidBlock(ref _block) => {}
+                            &DALEvent::RevertBlocks(ref _blocks) => {}
+                            _ => {}
+                        },
+                        &DuniterMessage::NetworkEvent(ref network_event) => match network_event {
+                            &NetworkEvent::ConnectionStateChange(
+                                ref node_full_id,
+                                ref status,
+                                ref uid,
+                                ref url,
+                            ) => {
+                                if let Some(conn) = tui.connections_status.get(&node_full_id) {
+                                    if *status == 12 && (*conn).status != 12 {
+                                        tui.established_conns_count += 1;
+                                    } else if *status != 12
+                                        && (*conn).status == 12
+                                        && tui.established_conns_count > 0
+                                    {
+                                        tui.established_conns_count -= 1;
+                                    }
+                                };
+                                tui.connections_status.insert(
+                                    *node_full_id,
+                                    Connection {
+                                        status: *status,
+                                        url: url.clone(),
+                                        uid: uid.clone(),
+                                    },
+                                );
+                            }
+                            &NetworkEvent::ReceiveHeads(ref heads) => {
+                                heads
+                                    .iter()
+                                    .map(|h| tui.heads_cache.insert(h.node_full_id(), h.clone()))
+                                    .collect::<Vec<Option<NetworkHead>>>();
+                            }
+                            _ => {}
+                        },
+                        _ => {}
+                    },
+                    &TuiMess::TermionEvent(ref term_event) => match term_event {
+                        &Event::Key(Key::Char('q')) => {
+                            // Exit
+                            writeln!(
+                                stdout,
+                                "{}{}{}{}{}",
+                                color::Fg(color::Reset),
+                                cursor::Goto(1, 1),
+                                color::Bg(color::Reset),
+                                cursor::Show,
+                                clear::All,
+                            ).unwrap();
+                            let _result_stop_propagation: Result<
+                                (),
+                                mpsc::SendError<DuniterMessage>,
+                            > = tui
+                                .followers
+                                .iter()
+                                .map(|f| f.send(DuniterMessage::Stop()))
+                                .collect();
+                            break;
+                        }
+                        &Event::Mouse(ref me) => match me {
+                            &MouseEvent::Press(ref button, ref _a, ref _b) => match button {
+                                &MouseButton::WheelDown => {
+                                    // Get Terminal size
+                                    let (_w, h) = termion::terminal_size()
+                                        .expect("Fail to get terminal size !");
+                                    // heads_index
+                                    if h > 16 {
+                                        let heads_index_max =
+                                            if tui.heads_cache.len() > (h - 16) as usize {
+                                                tui.heads_cache.len() - (h - 16) as usize
+                                            } else {
+                                                0
+                                            };
+                                        if tui.heads_index < heads_index_max {
+                                            tui.heads_index += 1;
+                                            user_event = true;
+                                        } else {
+                                            tui.heads_index = heads_index_max;
+                                        }
+                                    }
+                                }
+                                &MouseButton::WheelUp => {
+                                    // heads_index
+                                    if tui.heads_index > 0 {
+                                        tui.heads_index -= 1;
+                                        user_event = true;
+                                    }
+                                }
+                                _ => {}
+                            },
+                            &MouseEvent::Release(ref _a, ref _b)
+                            | &MouseEvent::Hold(ref _a, ref _b) => {}
+                        },
+                        _ => {}
+                    },
+                },
+                Err(e) => match e {
+                    mpsc::RecvTimeoutError::Disconnected => {
+                        panic!("Disconnected tui module !");
+                    }
+                    mpsc::RecvTimeoutError::Timeout => {}
+                },
+            }
+            let now = SystemTime::now();
+            if user_event
+                || now
+                    .duration_since(last_draw)
+                    .expect("Tui : Fatal error : fail to get duration since last draw !")
+                    .subsec_nanos() > 250_000_000
+            {
+                last_draw = now;
+                tui.draw_term(
+                    &mut stdout,
+                    start_time,
+                    &tui.heads_cache,
+                    tui.heads_index,
+                    &tui.connections_status,
+                    &HashMap::with_capacity(0),
+                );
+            }
+        }
+        Ok(())
+    }
+}
diff --git a/wotb/data/legacy.rs b/wotb/data/legacy.rs
index 202807a26632302fe6f4cb7eda4c5d2a6d5521e7..6a38cf5d6d0b0edeb95c90366351f4d4bb4f3204 100644
--- a/wotb/data/legacy.rs
+++ b/wotb/data/legacy.rs
@@ -268,7 +268,8 @@ impl WebOfTrust for LegacyWebOfTrust {
         let node = &self.nodes[node.0];
 
         Some(
-            node.enabled && node.issued_count() >= sentry_requirement
+            node.enabled
+                && node.issued_count() >= sentry_requirement
                 && node.links_iter().count() >= sentry_requirement,
         )
     }
@@ -277,7 +278,8 @@ impl WebOfTrust for LegacyWebOfTrust {
         self.nodes
             .iter()
             .filter(|x| {
-                x.enabled && x.issued_count() >= sentry_requirement
+                x.enabled
+                    && x.issued_count() >= sentry_requirement
                     && x.links_iter().count() >= sentry_requirement
             })
             .map(|x| x.id())
diff --git a/wotb/data/rusty.rs b/wotb/data/rusty.rs
index e55fefaf88b5445c4e0fbdac482eefe0317a256e..85eefdd324172d7242085965eaa7dbd615c1a08d 100644
--- a/wotb/data/rusty.rs
+++ b/wotb/data/rusty.rs
@@ -176,7 +176,8 @@ impl WebOfTrust for RustyWebOfTrust {
         let node = &self.nodes[node.0];
 
         Some(
-            node.enabled && node.issued_count >= sentry_requirement
+            node.enabled
+                && node.issued_count >= sentry_requirement
                 && node.links_source.len() >= sentry_requirement,
         )
     }
@@ -186,7 +187,8 @@ impl WebOfTrust for RustyWebOfTrust {
             .par_iter()
             .enumerate()
             .filter(|&(_, n)| {
-                n.enabled && n.issued_count >= sentry_requirement
+                n.enabled
+                    && n.issued_count >= sentry_requirement
                     && n.links_source.len() >= sentry_requirement
             })
             .map(|(i, _)| NodeId(i))
diff --git a/ws2p/Cargo.toml b/ws2p/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..955f268c5a60cc526dfc1280b56fd190bb860d51
--- /dev/null
+++ b/ws2p/Cargo.toml
@@ -0,0 +1,33 @@
+[package]
+name = "duniter-ws2p"
+version = "0.1.0"
+authors = ["librelois <elois@ifee.fr>"]
+description = "WebSocketToPeer API for the Duniter project."
+license = "AGPL-3.0"
+
+[lib]
+path = "lib.rs"
+
+[dependencies]
+duniter-conf = { path = "../conf" }
+duniter-crypto = { path = "../crypto" }
+duniter-dal = { path = "../dal" }
+duniter-documents = { path = "../documents" }
+duniter-message =  { path = "../message" }
+duniter-module = { path = "../module" }
+duniter-network = { path = "../network" }
+duniter-wotb = { path = "../wotb" }
+lazy_static = "1.0.0"
+log = "0.4.1"
+rand = "0.4.2"
+regex = "0.2.6"
+rust-crypto = "0.2.36"
+sqlite = "0.23.9"
+serde = "1.0.24"
+serde_derive = "1.0.24"
+serde_json = "1.0.9"
+websocket = "0.20.2"
+
+[features]
+# Treat warnings as a build error.
+strict = []
\ No newline at end of file
diff --git a/ws2p/ack_message.rs b/ws2p/ack_message.rs
new file mode 100644
index 0000000000000000000000000000000000000000..07d4b4a2233e57f41cbf7a635194bb7846b54a7a
--- /dev/null
+++ b/ws2p/ack_message.rs
@@ -0,0 +1,67 @@
+extern crate duniter_crypto;
+extern crate serde;
+extern crate serde_json;
+
+use self::serde::ser::{Serialize, SerializeStruct, Serializer};
+use super::WS2PMessage;
+use duniter_crypto::keys::ed25519::PublicKey as ed25519PublicKey;
+use duniter_crypto::keys::PublicKey;
+
+#[derive(Debug, Clone)]
+pub struct WS2PAckMessageV1 {
+    pub currency: String,
+    pub pubkey: ed25519PublicKey,
+    pub challenge: String,
+    pub signature: Option<duniter_crypto::keys::ed25519::Signature>,
+}
+
+impl WS2PMessage for WS2PAckMessageV1 {
+    fn parse(v: &serde_json::Value, currency: String) -> Option<Self> {
+        let pubkey = match v.get("pub") {
+            Some(pubkey) => pubkey.as_str().unwrap().to_string(),
+            None => return None,
+        };
+        let signature = match v.get("sig") {
+            Some(signature) => signature.as_str().unwrap().to_string(),
+            None => return None,
+        };
+        let pubkey: ed25519PublicKey = ed25519PublicKey::from_base58(&pubkey).unwrap();
+        let signature: Option<duniter_crypto::keys::ed25519::Signature> =
+            Some(duniter_crypto::keys::Signature::from_base64(&signature).unwrap());
+        Some(WS2PAckMessageV1 {
+            currency,
+            pubkey,
+            challenge: "".to_string(),
+            signature,
+        })
+    }
+    fn to_raw(&self) -> String {
+        format!(
+            "WS2P:ACK:{}:{}:{}",
+            self.currency, self.pubkey, self.challenge
+        )
+    }
+    fn verify(&self) -> bool {
+        self.pubkey
+            .verify(self.to_raw().as_bytes(), &self.signature.unwrap())
+    }
+}
+
+impl Serialize for WS2PAckMessageV1 {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let mut connect_message_in_json = serializer.serialize_struct("message", 3)?;
+        connect_message_in_json.serialize_field("auth", "ACK")?;
+        connect_message_in_json.serialize_field("pub", &self.pubkey.to_string())?;
+        connect_message_in_json.serialize_field(
+            "sig",
+            &self
+                .signature
+                .expect("Fail to serialize ACK message : the signature field is set to None !")
+                .to_string(),
+        )?;
+        connect_message_in_json.end()
+    }
+}
diff --git a/ws2p/connect_message.rs b/ws2p/connect_message.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4b048143a38d8dd22d88dbcb95abb4989d893ea4
--- /dev/null
+++ b/ws2p/connect_message.rs
@@ -0,0 +1,87 @@
+extern crate duniter_crypto;
+extern crate serde;
+extern crate serde_json;
+
+use self::serde::ser::{Serialize, SerializeStruct, Serializer};
+use super::WS2PMessage;
+use duniter_crypto::keys::ed25519::PublicKey as ed25519PublicKey;
+use duniter_crypto::keys::PublicKey;
+
+#[derive(Debug, Clone)]
+pub struct WS2PConnectMessageV1 {
+    pub currency: String,
+    pub pubkey: ed25519PublicKey,
+    pub challenge: String,
+    pub signature: Option<duniter_crypto::keys::ed25519::Signature>,
+}
+
+impl WS2PMessage for WS2PConnectMessageV1 {
+    fn parse(v: &serde_json::Value, currency: String) -> Option<Self> {
+        let pubkey = match v.get("pub") {
+            Some(pubkey) => pubkey.as_str().unwrap().to_string(),
+            None => return None,
+        };
+        let challenge = match v.get("challenge") {
+            Some(challenge) => challenge.as_str().unwrap().to_string(),
+            None => return None,
+        };
+        let signature = match v.get("sig") {
+            Some(signature) => signature.as_str().unwrap().to_string(),
+            None => return None,
+        };
+        let pubkey: ed25519PublicKey = ed25519PublicKey::from_base58(&pubkey).unwrap();
+        let signature: Option<duniter_crypto::keys::ed25519::Signature> =
+            Some(duniter_crypto::keys::Signature::from_base64(&signature).unwrap());
+        Some(WS2PConnectMessageV1 {
+            currency,
+            pubkey,
+            challenge,
+            signature,
+        })
+    }
+    fn to_raw(&self) -> String {
+        format!(
+            "WS2P:CONNECT:{}:{}:{}",
+            self.currency, self.pubkey, self.challenge
+        )
+    }
+    fn verify(&self) -> bool {
+        self.pubkey
+            .verify(self.to_raw().as_bytes(), &self.signature.unwrap())
+    }
+    /*fn parse_and_verify(v: serde_json::Value, currency: String) -> bool {
+        let pubkey = match v.get("pub") {
+            Some(pubkey) => pubkey.as_str().unwrap().to_string(),
+            None => return false,
+        };
+        let challenge = match v.get("pub") {
+            Some(challenge) => challenge.as_str().unwrap().to_string(),
+            None => return false,
+        };
+        let signature  = match v.get("pub") {
+            Some(signature) => signature.as_str().unwrap().to_string(),
+            None => return false,
+        };
+        ed25519PublicKey::from_base58(&pubkey).unwrap().verify(format!("WS2P:CONNECT:{}:{}:{}", currency, pubkey, challenge),&duniter_keys::Signature::from_base64(&signature).unwrap())
+    }*/
+}
+
+impl Serialize for WS2PConnectMessageV1 {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let mut connect_message_in_json = serializer.serialize_struct("message", 4)?;
+        connect_message_in_json.serialize_field("auth", "CONNECT")?;
+        connect_message_in_json.serialize_field("pub", &self.pubkey.to_string())?;
+        connect_message_in_json.serialize_field("challenge", &self.challenge)?;
+        connect_message_in_json.serialize_field(
+            "sig",
+            &self
+                .signature
+                .expect("Fail to serialize CONNECT message : the signature field is set to None !")
+                .to_string(),
+        )?;
+        connect_message_in_json.end()
+    }
+}
diff --git a/ws2p/constants.rs b/ws2p/constants.rs
new file mode 100644
index 0000000000000000000000000000000000000000..7d33b80de435b96c4fc4d93a3c79637aa932b5c7
--- /dev/null
+++ b/ws2p/constants.rs
@@ -0,0 +1,22 @@
+extern crate regex;
+
+use self::regex::Regex;
+
+lazy_static! {
+    #[derive(Debug)]
+    pub static ref WS2P_V1_ENDPOINT_REGEX: Regex = Regex::new(
+        "^WS2P (?P<version>[1-9][0-9]* )?(?P<uuid>[a-f0-9]{6,8}) (?P<host>[a-z_][a-z0-9-_.]*|[0-9.]+|[0-9a-f:]+) (?P<port>[0-9]+)(?: /?(?P<path>.+)?)? *$"
+    ).unwrap();
+}
+pub static WS2P_OUTCOMING_INTERVAL_AT_STARTUP: &'static u64 = &75;
+pub static WS2P_OUTCOMING_INTERVAL: &'static u64 = &300;
+pub static WS2P_DEFAULT_OUTCOMING_QUOTA: &'static usize = &10;
+pub static WS2P_NEGOTIATION_TIMEOUT: &'static u64 = &15;
+//pub static WS2P_REQUEST_TIMEOUT : &'static u64 = &30;
+pub static WS2P_CONNECTION_TIMEOUT: &'static u64 = &120;
+pub static WS2P_SPAM_INTERVAL_IN_MILLI_SECS: &'static u64 = &80;
+pub static WS2P_SPAM_LIMIT: &'static u64 = &6;
+pub static WS2P_SPAM_SLEEP_TIME_IN_SEC: &'static u64 = &100;
+pub static DURATION_BEFORE_RECORDING_ENDPOINT: &'static u64 = &180;
+pub static BLOCKS_REQUEST_INTERVAL: &'static u64 = &60;
+pub static PENDING_IDENTITIES_REQUEST_INTERVAL: &'static u64 = &40;
diff --git a/ws2p/lib.rs b/ws2p/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..91f8bf76def013126dc172bf0a11dadaaf4ff15a
--- /dev/null
+++ b/ws2p/lib.rs
@@ -0,0 +1,1961 @@
+//  Copyright (C) 2018  The Duniter Project Developers.
+//
+// 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/>.
+
+//! Crate containing Duniter-rust core.
+
+#![cfg_attr(feature = "strict", deny(warnings))]
+#![deny(
+    missing_debug_implementations, missing_copy_implementations, trivial_casts, unsafe_code,
+    unstable_features, unused_import_braces, unused_qualifications
+)]
+#![recursion_limit = "256"]
+
+#[macro_use]
+extern crate lazy_static;
+
+#[macro_use]
+extern crate log;
+
+#[macro_use]
+extern crate serde_json;
+
+extern crate duniter_conf;
+extern crate duniter_crypto;
+extern crate duniter_dal;
+extern crate duniter_documents;
+extern crate duniter_message;
+extern crate duniter_module;
+extern crate duniter_network;
+extern crate rand;
+extern crate sqlite;
+extern crate websocket;
+
+use std::collections::HashMap;
+use std::ops::Deref;
+use std::path::PathBuf;
+use std::str::from_utf8;
+use std::sync::mpsc;
+use std::thread;
+use std::time::{Duration, SystemTime, UNIX_EPOCH};
+
+use duniter_crypto::keys::ed25519::Signature;
+use duniter_crypto::keys::{ed25519, KeyPair, PrivateKey, PublicKey};
+use duniter_dal::dal_event::DALEvent;
+use duniter_dal::dal_requests::{DALReqBlockchain, DALRequest, DALResBlockchain, DALResponse};
+use duniter_dal::parsers::blocks::parse_json_block;
+use duniter_documents::blockchain::Document;
+use duniter_documents::Blockstamp;
+use duniter_message::DuniterMessage;
+use duniter_module::*;
+use duniter_network::network_endpoint::*;
+use duniter_network::network_head::*;
+use duniter_network::*;
+
+use websocket::{ClientBuilder, Message};
+
+mod ack_message;
+mod connect_message;
+pub mod constants;
+mod ok_message;
+pub mod serializer;
+pub mod ws2p_connection;
+pub mod ws2p_db;
+pub mod ws2p_requests;
+
+use self::ack_message::WS2PAckMessageV1;
+use self::connect_message::WS2PConnectMessageV1;
+use self::constants::*;
+use self::ok_message::WS2POkMessageV1;
+use self::rand::Rng;
+use self::ws2p_connection::*;
+use self::ws2p_requests::network_request_to_json;
+
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct WS2PConf {
+    pub node_id: NodeUUID,
+    pub outcoming_quota: usize,
+    pub sync_endpoints: Vec<NetworkEndpoint>,
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub enum WS2PConfParseError {
+    UnknowError(),
+}
+
+#[derive(Debug)]
+pub enum WS2PSignal {
+    WSError(NodeFullId),
+    ConnectionEstablished(NodeFullId),
+    NegociationTimeout(NodeFullId),
+    Timeout(NodeFullId),
+    DalRequest(NodeFullId, ModuleReqId, serde_json::Value),
+    PeerCard(NodeFullId, serde_json::Value, Vec<NetworkEndpoint>),
+    Heads(NodeFullId, Vec<NetworkHead>),
+    Document(NodeFullId, NetworkDocument),
+    ReqResponse(ModuleReqId, NetworkRequest, NodeFullId, serde_json::Value),
+    Empty,
+    NoConnection,
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub enum NetworkConsensusError {
+    InsufficientData(usize),
+    Fork,
+}
+
+#[derive(Debug)]
+pub enum SendRequestError {
+    RequestTypeMustNotBeTransmitted(),
+    WSError(usize, Vec<websocket::WebSocketError>),
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub struct WS2PModule {}
+
+#[derive(Debug)]
+pub struct WS2PModuleDatas {
+    pub followers: Vec<mpsc::Sender<DuniterMessage>>,
+    pub currency: Option<String>,
+    pub key_pair: Option<ed25519::KeyPair>,
+    pub conf: Option<WS2PConf>,
+    pub main_thread_channel: (
+        mpsc::Sender<WS2PThreadSignal>,
+        mpsc::Receiver<WS2PThreadSignal>,
+    ),
+    pub ws2p_endpoints: HashMap<NodeFullId, (NetworkEndpoint, WS2PConnectionState)>,
+    pub connections_meta_datas: HashMap<NodeFullId, WS2PConnectionMetaData>,
+    pub websockets: HashMap<NodeFullId, WebsocketSender>,
+    pub threads_senders_channels: HashMap<NodeFullId, mpsc::Sender<WS2POrderForListeningThread>>,
+    pub requests_awaiting_response: HashMap<ModuleReqId, (NetworkRequest, NodeFullId, SystemTime)>,
+    pub heads_cache: HashMap<NodeFullId, NetworkHead>,
+    pub my_head: Option<NetworkHead>,
+    pub uids_cache: HashMap<ed25519::PublicKey, String>,
+}
+
+#[derive(Debug)]
+pub enum WS2PThreadSignal {
+    DuniterMessage(DuniterMessage),
+    WS2PConnectionMessage(WS2PConnectionMessage),
+}
+
+pub trait WS2PMessage: Sized {
+    fn parse(v: &serde_json::Value, currency: String) -> Option<Self>;
+    fn to_raw(&self) -> String;
+    fn sign(&self, key_pair: ed25519::KeyPair) -> Signature {
+        key_pair.sign(self.to_raw().as_bytes())
+    }
+    fn verify(&self) -> bool;
+    //fn parse_and_verify(v: serde_json::Value, currency: String) -> bool;
+}
+
+pub fn get_random_connection(
+    connections: &HashMap<NodeFullId, (NetworkEndpoint, WS2PConnectionState)>,
+) -> NodeFullId {
+    let mut rng = rand::thread_rng();
+    let mut loop_count = 0;
+    loop {
+        for (ws2p_full_id, (_ep, state)) in connections.clone() {
+            if loop_count > 10 {
+                return ws2p_full_id;
+            }
+            if let WS2PConnectionState::Established = state {
+                if rng.gen::<bool>() {
+                    return ws2p_full_id;
+                }
+            }
+        }
+        loop_count += 1;
+    }
+}
+
+impl Default for WS2PModule {
+    fn default() -> WS2PModule {
+        WS2PModule {}
+    }
+}
+
+impl DuniterModule<ed25519::KeyPair, DuniterMessage> for WS2PModule {
+    fn id() -> ModuleId {
+        ModuleId::Str("ws2p")
+    }
+    fn priority() -> ModulePriority {
+        ModulePriority::Essential()
+    }
+    fn ask_required_keys() -> RequiredKeys {
+        RequiredKeys::NetworkKeyPair()
+    }
+    fn default_conf() -> serde_json::Value {
+        json!({
+            "sync_peers": [{
+                "pubkey": "7v2J4badvfWQ6qwRdCwhhJfAsmKwoxRUNpJHiJHj7zef",
+                "ws2p_endpoints": ["WS2P b48824f0 g1.monnaielibreoccitanie.org 80 /ws2p"]
+            }]
+        })
+    }
+    fn start(
+        soft_name: &str,
+        soft_version: &str,
+        keys: RequiredKeysContent<ed25519::KeyPair>,
+        duniter_conf: &DuniterConf,
+        module_conf: &serde_json::Value,
+        rooter_sender: mpsc::Sender<RooterThreadMessage<DuniterMessage>>,
+        load_conf_only: bool,
+    ) -> Result<(), ModuleInitError> {
+        let start_time = SystemTime::now();
+        let mut ws2p_module = WS2PModuleDatas {
+            followers: Vec::new(),
+            key_pair: None,
+            currency: None,
+            conf: None,
+            main_thread_channel: mpsc::channel(),
+            ws2p_endpoints: HashMap::new(),
+            connections_meta_datas: HashMap::new(),
+            websockets: HashMap::new(),
+            threads_senders_channels: HashMap::new(),
+            requests_awaiting_response: HashMap::new(),
+            heads_cache: HashMap::new(),
+            my_head: None,
+            uids_cache: HashMap::new(),
+        };
+
+        // load conf
+        let key_pair = match keys {
+            RequiredKeysContent::NetworkKeyPair(key_pair) => key_pair,
+            _ => panic!("WS2PModule fatal error at load_conf() : keys != NetworkKeyPair"),
+        };
+        let conf = WS2PModuleDatas::parse_ws2p_conf(duniter_conf, module_conf);
+        let mut ws2p_endpoints = HashMap::new();
+        for ep in conf.sync_endpoints.clone() {
+            ws2p_endpoints.insert(
+                ep.node_full_id().unwrap(),
+                (ep.clone(), WS2PConnectionState::Close),
+            );
+            info!("Load sync endpoint {}", ep.raw());
+        }
+        ws2p_module.key_pair = Some(key_pair.clone());
+        ws2p_module.currency = Some(duniter_conf.currency().to_string());
+        ws2p_module.conf = Some(conf.clone());
+        ws2p_module.ws2p_endpoints = ws2p_endpoints;
+
+        // Create ws2p main thread channel
+        let ws2p_sender_clone = ws2p_module.main_thread_channel.0.clone();
+
+        // Create proxy channel
+        let (proxy_sender, proxy_receiver): (
+            mpsc::Sender<DuniterMessage>,
+            mpsc::Receiver<DuniterMessage>,
+        ) = mpsc::channel();
+        let proxy_sender_clone = proxy_sender.clone();
+
+        // Launch a proxy thread that transform DuniterMessage to WS2PThreadSignal(DuniterMessage)
+        thread::spawn(move || {
+            // Send proxy sender to main
+            match rooter_sender.send(RooterThreadMessage::ModuleSender(proxy_sender_clone)) {
+                Ok(_) => {
+                    debug!("Send ws2p sender to main thread.");
+                }
+                Err(_) => panic!("Fatal error : ws2p module fail to send is sender channel !"),
+            }
+            //drop(rooter_sender);
+            loop {
+                match proxy_receiver.recv() {
+                    Ok(message) => match ws2p_sender_clone
+                        .send(WS2PThreadSignal::DuniterMessage(message.clone()))
+                    {
+                        Ok(_) => {
+                            if let DuniterMessage::Stop() = message {
+                                break;
+                            };
+                        }
+                        Err(_) => panic!(
+                            "Fatal error : fail to relay DuniterMessage to ws2p main thread !"
+                        ),
+                    },
+                    Err(e) => panic!(format!("{}", e)),
+                }
+            }
+        });
+
+        // open ws2p bdd
+        let mut db_path =
+            duniter_conf::datas_path(duniter_conf.profile().as_str(), &duniter_conf.currency());
+        db_path.push("ws2p.db");
+        let db = WS2PModuleDatas::open_db(db_path).expect("Fatal error : fail to open WS2P DB !");
+
+        // Get ws2p endpoints in BDD
+        let mut count = 0;
+        let dal_enpoints =
+            ws2p_db::get_endpoints_for_api(&db, NetworkEndpointApi(String::from("WS2P")));
+        for ep in dal_enpoints {
+            if ep.api() == NetworkEndpointApi(String::from("WS2P")) && ep.port() != 443 {
+                count += 1;
+                ws2p_module.ws2p_endpoints.insert(
+                    ep.node_full_id().unwrap(),
+                    (ep.clone(), WS2PConnectionState::from(ep.status())),
+                );
+            }
+        }
+        info!("Load {} endpoints from bdd !", count);
+
+        // Stop here in load_conf_only mode
+        if load_conf_only {
+            return Ok(());
+        }
+
+        // Initialize variables
+        let mut last_ws2p_connecting_wave = SystemTime::now();
+        let mut last_ws2p_connections_print = SystemTime::now();
+        let mut endpoints_to_update_status: HashMap<NodeFullId, SystemTime> = HashMap::new();
+        let mut last_identities_request = UNIX_EPOCH;
+        let mut current_blockstamp = Blockstamp::default();
+        let mut next_receiver = 0;
+
+        // Start
+        ws2p_module.connect_to_know_endpoints();
+        loop {
+            match ws2p_module
+                .main_thread_channel
+                .1
+                .recv_timeout(Duration::from_millis(200))
+            {
+                Ok(message) => match message {
+                    WS2PThreadSignal::DuniterMessage(ref duniter_mesage) => {
+                        match duniter_mesage {
+                            &DuniterMessage::Stop() => break,
+                            &DuniterMessage::Followers(ref new_followers) => {
+                                info!("WS2P module receive followers !");
+                                for new_follower in new_followers {
+                                    debug!("WS2PModule : push one follower.");
+                                    ws2p_module.followers.push(new_follower.clone());
+                                    if current_blockstamp == Blockstamp::default() {
+                                        // Request local current blockstamp
+                                        ws2p_module.send_dal_request(
+                                            &DALRequest::BlockchainRequest(
+                                                DALReqBlockchain::CurrentBlock(ModuleReqFullId(
+                                                    WS2PModule::id(),
+                                                    ModuleReqId(0),
+                                                )),
+                                            ),
+                                        );
+                                    } else {
+                                        if ws2p_module.my_head.is_none() {
+                                            ws2p_module.my_head =
+                                                Some(WS2PModuleDatas::generate_my_head(
+                                                    &key_pair.clone(),
+                                                    &conf.clone(),
+                                                    soft_name,
+                                                    soft_version,
+                                                    &current_blockstamp,
+                                                    None,
+                                                ));
+                                        }
+                                        ws2p_module.send_network_event(
+                                            &NetworkEvent::ReceiveHeads(vec![
+                                                ws2p_module.my_head.clone().unwrap(),
+                                            ]),
+                                        );
+                                    }
+                                }
+                            }
+                            &DuniterMessage::NetworkRequest(ref request) => match request {
+                                &NetworkRequest::GetBlocks(
+                                    ref req_id,
+                                    ref receiver,
+                                    ref count,
+                                    ref from,
+                                ) => {
+                                    if *receiver == NodeFullId::default() {
+                                        let mut receiver_index = 0;
+                                        let mut real_receiver = NodeFullId::default();
+                                        for (ws2p_full_id, (_ep, state)) in
+                                            ws2p_module.ws2p_endpoints.clone()
+                                        {
+                                            if let WS2PConnectionState::Established = state {
+                                                if receiver_index == next_receiver {
+                                                    real_receiver = ws2p_full_id;
+                                                    break;
+                                                }
+                                                receiver_index += 1;
+                                            }
+                                        }
+                                        if real_receiver == NodeFullId::default() {
+                                            next_receiver = 0;
+                                            for (ws2p_full_id, (_ep, state)) in
+                                                ws2p_module.ws2p_endpoints.clone()
+                                            {
+                                                if let WS2PConnectionState::Established = state {
+                                                    real_receiver = ws2p_full_id;
+                                                    break;
+                                                }
+                                            }
+                                        } else {
+                                            next_receiver += 1;
+                                        }
+                                        if real_receiver != NodeFullId::default() {
+                                            let _blocks_request_result = ws2p_module
+                                                .send_request_to_specific_node(
+                                                    &real_receiver,
+                                                    &NetworkRequest::GetBlocks(
+                                                        req_id.clone(),
+                                                        receiver.clone(),
+                                                        count.clone(),
+                                                        from.clone(),
+                                                    ),
+                                                );
+                                        } else {
+                                            warn!("WS2PModule : No WS2P connections !");
+                                        }
+                                    } else {
+                                        let _blocks_request_result = ws2p_module
+                                            .send_request_to_specific_node(
+                                                &receiver,
+                                                &NetworkRequest::GetBlocks(
+                                                    req_id.clone(),
+                                                    receiver.clone(),
+                                                    count.clone(),
+                                                    from.clone(),
+                                                ),
+                                            );
+                                    }
+                                }
+                                _ => {}
+                            },
+                            &DuniterMessage::DALEvent(ref dal_event) => match dal_event {
+                                &DALEvent::StackUpValidBlock(ref block) => {
+                                    current_blockstamp = block.deref().blockstamp();
+                                    debug!(
+                                        "WS2PModule : current_blockstamp = {}",
+                                        current_blockstamp
+                                    );
+                                    ws2p_module.my_head = Some(WS2PModuleDatas::generate_my_head(
+                                        &key_pair.clone(),
+                                        &conf.clone(),
+                                        soft_name,
+                                        soft_version,
+                                        &current_blockstamp,
+                                        None,
+                                    ));
+                                    ws2p_module.send_network_event(&NetworkEvent::ReceiveHeads(
+                                        vec![ws2p_module.my_head.clone().unwrap()],
+                                    ));
+                                    // Send my head to all connections
+                                    let my_json_head = serializer::serialize_head(
+                                        ws2p_module.my_head.clone().unwrap(),
+                                    );
+                                    trace!("Send my HEAD: {:#?}", my_json_head);
+                                    let _results: Result<
+                                        (),
+                                        websocket::WebSocketError,
+                                    > = ws2p_module
+                                        .websockets
+                                        .iter_mut()
+                                        .map(|ws| {
+                                            (ws.1).0.send_message(&Message::text(
+                                                json!({
+                                                "name": "HEAD",
+                                                "body": {
+                                                    "heads": [my_json_head]
+                                                }
+                                            }).to_string(),
+                                            ))
+                                        })
+                                        .collect();
+                                }
+                                _ => {}
+                            },
+                            &DuniterMessage::DALResponse(ref dal_res) => match dal_res {
+                                &DALResponse::Blockchain(ref dal_res_bc) => match dal_res_bc {
+                                    &DALResBlockchain::CurrentBlock(
+                                        ref _requester_full_id,
+                                        ref current_block,
+                                    ) => {
+                                        debug!(
+                                            "WS2PModule : receive DALResBc::CurrentBlock({})",
+                                            current_block.blockstamp()
+                                        );
+                                        current_blockstamp = current_block.blockstamp();
+                                        if ws2p_module.my_head.is_none() {
+                                            ws2p_module.my_head =
+                                                Some(WS2PModuleDatas::generate_my_head(
+                                                    &key_pair.clone(),
+                                                    &conf.clone(),
+                                                    soft_name,
+                                                    soft_version,
+                                                    &current_blockstamp,
+                                                    None,
+                                                ));
+                                        }
+                                        ws2p_module.send_network_event(
+                                            &NetworkEvent::ReceiveHeads(vec![
+                                                ws2p_module.my_head.clone().unwrap(),
+                                            ]),
+                                        );
+                                    }
+                                    &DALResBlockchain::UIDs(ref uids) => {
+                                        // Add uids to heads
+                                        for (_, head) in ws2p_module.heads_cache.iter_mut() {
+                                            if let Some(uid_option) = uids.get(&head.pubkey()) {
+                                                if let &Some(ref uid) = uid_option {
+                                                    head.set_uid(uid);
+                                                    ws2p_module
+                                                        .uids_cache
+                                                        .insert(head.pubkey(), uid.to_string());
+                                                } else {
+                                                    ws2p_module.uids_cache.remove(&head.pubkey());
+                                                }
+                                            }
+                                        }
+                                        // Resent heads to other modules
+                                        ws2p_module.send_network_event(
+                                            &NetworkEvent::ReceiveHeads(
+                                                ws2p_module
+                                                    .heads_cache
+                                                    .values()
+                                                    .map(|h| h.clone())
+                                                    .collect(),
+                                            ),
+                                        );
+                                        // Resent to other modules connections that match receive uids
+                                        for (node_full_id, (ep, conn_state)) in
+                                            ws2p_module.ws2p_endpoints.clone()
+                                        {
+                                            if let Some(uid_option) = uids.get(&node_full_id.1) {
+                                                ws2p_module.send_network_event(
+                                                    &NetworkEvent::ConnectionStateChange(
+                                                        node_full_id,
+                                                        conn_state as u32,
+                                                        uid_option.clone(),
+                                                        ep.get_url(false),
+                                                    ),
+                                                );
+                                            }
+                                        }
+                                    }
+                                    _ => {}
+                                },
+                                _ => {}
+                            },
+                            _ => {}
+                        }
+                    }
+                    WS2PThreadSignal::WS2PConnectionMessage(ws2p_conn_message) => match ws2p_module
+                        .ws2p_conn_message_pretreatment(ws2p_conn_message)
+                    {
+                        WS2PSignal::NoConnection => {
+                            warn!("WS2PSignal::NoConnection");
+                            last_ws2p_connecting_wave = SystemTime::now();
+                            ws2p_module.connect_to_know_endpoints();
+                        }
+                        WS2PSignal::ConnectionEstablished(ws2p_full_id) => {
+                            let req_id =
+                                ModuleReqId(ws2p_module.requests_awaiting_response.len() as u32);
+                            let module_id = WS2PModule::id();
+                            let _current_request_result = ws2p_module
+                                .send_request_to_specific_node(
+                                    &ws2p_full_id,
+                                    &NetworkRequest::GetCurrent(
+                                        ModuleReqFullId(module_id, req_id),
+                                        ws2p_full_id,
+                                    ),
+                                );
+                            ws2p_module.send_network_event(&NetworkEvent::ConnectionStateChange(
+                                ws2p_full_id,
+                                WS2PConnectionState::Established as u32,
+                                ws2p_module.uids_cache.get(&ws2p_full_id.1).cloned(),
+                                ws2p_module
+                                    .ws2p_endpoints
+                                    .get(&ws2p_full_id)
+                                    .unwrap()
+                                    .0
+                                    .get_url(false),
+                            ));
+                        }
+                        WS2PSignal::WSError(ws2p_full_id) => {
+                            endpoints_to_update_status.insert(ws2p_full_id, SystemTime::now());
+                            ws2p_module.send_network_event(&NetworkEvent::ConnectionStateChange(
+                                ws2p_full_id,
+                                WS2PConnectionState::WSError as u32,
+                                ws2p_module.uids_cache.get(&ws2p_full_id.1).cloned(),
+                                ws2p_module
+                                    .ws2p_endpoints
+                                    .get(&ws2p_full_id)
+                                    .unwrap()
+                                    .0
+                                    .get_url(false),
+                            ));
+                        }
+                        WS2PSignal::NegociationTimeout(ws2p_full_id) => {
+                            endpoints_to_update_status.insert(ws2p_full_id, SystemTime::now());
+                            ws2p_module.send_network_event(&NetworkEvent::ConnectionStateChange(
+                                ws2p_full_id,
+                                WS2PConnectionState::Denial as u32,
+                                ws2p_module.uids_cache.get(&ws2p_full_id.1).cloned(),
+                                ws2p_module
+                                    .ws2p_endpoints
+                                    .get(&ws2p_full_id)
+                                    .unwrap()
+                                    .0
+                                    .get_url(false),
+                            ));
+                        }
+                        WS2PSignal::Timeout(ws2p_full_id) => {
+                            endpoints_to_update_status.insert(ws2p_full_id, SystemTime::now());
+                            ws2p_module.send_network_event(&NetworkEvent::ConnectionStateChange(
+                                ws2p_full_id,
+                                WS2PConnectionState::Close as u32,
+                                ws2p_module.uids_cache.get(&ws2p_full_id.1).cloned(),
+                                ws2p_module
+                                    .ws2p_endpoints
+                                    .get(&ws2p_full_id)
+                                    .unwrap()
+                                    .0
+                                    .get_url(false),
+                            ));
+                        }
+                        WS2PSignal::PeerCard(_ws2p_full_id, _peer_card, ws2p_endpoints) => {
+                            //trace!("WS2PSignal::PeerCard({})", ws2p_full_id);
+                            //ws2p_module.send_network_event(NetworkEvent::ReceivePeers(_));
+                            for ep in ws2p_endpoints {
+                                if ep.port() != 443 {
+                                    match ws2p_module
+                                        .ws2p_endpoints
+                                        .get(&ep.node_full_id().unwrap())
+                                    {
+                                        Some(_) => {}
+                                        None => {
+                                            if let Some(_api) =
+                                                ws2p_db::string_to_api(&ep.api().0.clone())
+                                            {
+                                                endpoints_to_update_status.insert(
+                                                    ep.node_full_id().unwrap(),
+                                                    SystemTime::now(),
+                                                );
+                                            }
+                                            ws2p_module.connect_to(ep);
+                                        }
+                                    };
+                                }
+                            }
+                        }
+                        WS2PSignal::Heads(ws2p_full_id, heads) => {
+                            trace!("WS2PSignal::Heads({}, {:?})", ws2p_full_id, heads.len());
+                            ws2p_module.send_dal_request(&DALRequest::BlockchainRequest(
+                                DALReqBlockchain::UIDs(heads.iter().map(|h| h.pubkey()).collect()),
+                            ));
+                            ws2p_module.send_network_event(&NetworkEvent::ReceiveHeads(
+                                heads
+                                    .iter()
+                                    .map(|head| {
+                                        let mut new_head = head.clone();
+                                        if let Some(uid) =
+                                            ws2p_module.uids_cache.get(&head.pubkey())
+                                        {
+                                            new_head.set_uid(uid);
+                                        }
+                                        new_head
+                                    })
+                                    .collect(),
+                            ));
+                        }
+                        WS2PSignal::Document(ws2p_full_id, network_doc) => {
+                            trace!("WS2PSignal::Document({})", ws2p_full_id);
+                            ws2p_module.send_network_event(&NetworkEvent::ReceiveDocuments(vec![
+                                network_doc,
+                            ]));
+                        }
+                        WS2PSignal::ReqResponse(req_id, req, recipient_full_id, response) => {
+                            match req {
+                                NetworkRequest::GetCurrent(ref _req_id, _receiver) => {
+                                    info!("WS2PSignal::ReceiveCurrent({}, {:?})", req_id.0, req);
+                                    if let Some(block) = parse_json_block(&response) {
+                                        ws2p_module.send_network_event(&NetworkEvent::ReqResponse(
+                                            Box::new(NetworkResponse::CurrentBlock(
+                                                ModuleReqFullId(WS2PModule::id(), req_id),
+                                                recipient_full_id,
+                                                Box::new(block),
+                                            )),
+                                        ));
+                                    }
+                                    /*if let Some(block) = BlockV10::from_json_value(&response) {
+                                        ws2p_module
+                                            .connections_meta_datas
+                                            .get_mut(&recipient_full_id)
+                                            .unwrap()
+                                            .current_blockstamp = Some((block.id, block.hash));
+                                    }*/
+                                }
+                                NetworkRequest::GetBlocks(ref _req_id, _receiver, _count, from) => {
+                                    info!("WS2PSignal::ReceiveChunk({}, {:?})", req_id.0, req);
+                                    if response.is_array() {
+                                        let mut chunk = Vec::new();
+                                        for json_block in response.as_array().unwrap() {
+                                            if let Some(block) = parse_json_block(json_block) {
+                                                chunk.push(NetworkDocument::Block(block));
+                                            } else {
+                                                warn!("WS2PModule: Error : fail to parse one json block !");
+                                            }
+                                        }
+                                        debug!("Send chunk to followers : {}", from);
+                                        ws2p_module.send_network_event(
+                                            &NetworkEvent::ReceiveDocuments(chunk),
+                                        );
+                                    }
+                                }
+                                NetworkRequest::GetRequirementsPending(
+                                    _req_id,
+                                    _receiver,
+                                    min_cert,
+                                ) => {
+                                    info!(
+                                        "WS2PSignal::ReceiveRequirementsPending({}, {})",
+                                        req_id.0, min_cert
+                                    );
+                                    debug!("----------------------------------------");
+                                    debug!("-      BEGIN IDENTITIES PENDING        -");
+                                    debug!("----------------------------------------");
+                                    debug!("{:#?}", response);
+                                    debug!("----------------------------------------");
+                                    debug!("-       END IDENTITIES PENDING         -");
+                                    debug!("----------------------------------------");
+                                }
+                                _ => {}
+                            }
+                        }
+                        WS2PSignal::Empty => {}
+                        _ => {}
+                    },
+                },
+                Err(e) => match e {
+                    mpsc::RecvTimeoutError::Disconnected => {
+                        panic!("Disconnected ws2p module !");
+                    }
+                    mpsc::RecvTimeoutError::Timeout => {}
+                },
+            }
+            if SystemTime::now()
+                .duration_since(last_ws2p_connections_print)
+                .unwrap() > Duration::new(5, 0)
+            {
+                last_ws2p_connections_print = SystemTime::now();
+                let mut connected_nodes = Vec::new();
+                let mut denial_nodes = Vec::new();
+                let mut disconnected_nodes = Vec::new();
+                let mut unreachable_nodes = Vec::new();
+                let mut ws_error_nodes = Vec::new();
+                for (k, (_ep, state)) in ws2p_module.ws2p_endpoints.clone() {
+                    match state {
+                        WS2PConnectionState::NeverTry => {
+                            //writeln!("Never try : {}", k);
+                        }
+                        WS2PConnectionState::TryToOpenWS => {}//writeln!("TryToOpenWS : {}", k),
+                        WS2PConnectionState::WSError => {
+                            ws_error_nodes.push(k);
+                        }
+                        WS2PConnectionState::TryToSendConnectMess => {
+                            //writeln!("TryToSendConnectMess : {}", k)
+                        }
+                        WS2PConnectionState::Unreachable => {
+                            unreachable_nodes.push(k);
+                        }
+                        WS2PConnectionState::WaitingConnectMess => {
+                            //writeln!("WaitingConnectMess : {}", k)
+                        }
+                        WS2PConnectionState::NoResponse => {}//writeln!("NoResponse : {}", k),
+                        WS2PConnectionState::AckMessOk
+                        | WS2PConnectionState::ConnectMessOk
+                        | WS2PConnectionState::OkMessOkWaitingAckMess => {
+                            //writeln!("Ongoing negotiations : {}", k)
+                        }
+                        WS2PConnectionState::Denial => {
+                            denial_nodes.push(k);
+                        }
+                        WS2PConnectionState::Established => {
+                            connected_nodes.push(k);
+                        }
+                        WS2PConnectionState::Close => {
+                            disconnected_nodes.push(k);
+                        }
+                    }
+                }
+                /*writeln!(
+                                    "Connected with {} nodes. (Denial : {}, Disconnected : {}, Unreachable: {}, WSError : {})",
+                                    connected_nodes.len(),
+                                    denial_nodes.len(),
+                                    disconnected_nodes.len(),
+                                    unreachable_nodes.len(),
+                                    ws_error_nodes.len()
+                                );*/
+                for _node in connected_nodes.clone() {
+                    //writeln!("Connection established : {}", node);
+                }
+                for _node in denial_nodes {
+                    //writeln!("Denial : {}", node);
+                }
+                for _node in disconnected_nodes {
+                    //writeln!("Disconnected : {}", node);
+                }
+                for _node in unreachable_nodes {
+                    //writeln!("Unreachable : {}", node);
+                }
+                // Print network consensus
+                match ws2p_module.get_network_consensus() {
+                    Ok(consensus_blockstamp) => {
+                        debug!(
+                            "WS2PModule : get_network_consensus() = {:?}",
+                            consensus_blockstamp
+                        );
+                        if current_blockstamp.id.0 < (consensus_blockstamp.id.0 + 2) {
+                            warn!("We probably are in a fork branch !");
+                        }
+                    }
+                    Err(e) => warn!("{:?}", e),
+                }
+                // Print current_blockstamp
+                info!(
+                    "WS2PModule : current_blockstamp() = {:?}",
+                    current_blockstamp
+                );
+                // New WS2P connection wave
+                if connected_nodes.len() < ws2p_module.conf.clone().unwrap().outcoming_quota
+                    && (SystemTime::now()
+                        .duration_since(last_ws2p_connecting_wave)
+                        .unwrap()
+                        > Duration::new(*WS2P_OUTCOMING_INTERVAL, 0)
+                        || (SystemTime::now()
+                            .duration_since(last_ws2p_connecting_wave)
+                            .unwrap()
+                            > Duration::new(*WS2P_OUTCOMING_INTERVAL_AT_STARTUP, 0)
+                            && SystemTime::now().duration_since(start_time).unwrap()
+                                < Duration::new(*WS2P_OUTCOMING_INTERVAL, 0)))
+                {
+                    last_ws2p_connecting_wave = SystemTime::now();
+                    info!("Connected to know endpoints...");
+                    ws2p_module.connect_to_know_endpoints();
+                }
+                /*// Request blocks from network
+                if SystemTime::now()
+                    .duration_since(last_blocks_request)
+                    .unwrap() > Duration::new(*BLOCKS_REQUEST_INTERVAL, 0)
+                    && SystemTime::now().duration_since(start_time).unwrap() > Duration::new(10, 0)
+                {
+                    let mut request_blocks_from = current_blockstamp.id.0;
+                    if request_blocks_from > 0 {
+                        request_blocks_from += 1;
+                    }
+                    info!("get chunks from all connections...");
+                    let module_id = WS2PModule::id();
+                    let _blocks_request_result =
+                        ws2p_module.send_request_to_all_connections(&NetworkRequest::GetBlocks(
+                            ModuleReqFullId(module_id, ModuleReqId(0 as u32)),
+                            NodeFullId::default(),
+                            50,
+                            request_blocks_from,
+                        ));
+                    last_blocks_request = SystemTime::now();
+                }*/
+                // Request pending_identities from network
+                if SystemTime::now()
+                    .duration_since(last_identities_request)
+                    .unwrap()
+                    > Duration::new(*PENDING_IDENTITIES_REQUEST_INTERVAL, 0)
+                    && SystemTime::now().duration_since(start_time).unwrap() > Duration::new(10, 0)
+                {
+                    /*info!("get pending_identities from all connections...");
+                                    let _blocks_request_result = ws2p_module.send_request_to_all_connections(
+                                        &NetworkRequest::GetRequirementsPending(ModuleReqId(0 as u32), 5),
+                                    );*/
+                    last_identities_request = SystemTime::now();
+                }
+                // Write pending endpoints
+                for (ep_full_id, received_time) in endpoints_to_update_status.clone() {
+                    if SystemTime::now().duration_since(received_time).unwrap()
+                        > Duration::new(*DURATION_BEFORE_RECORDING_ENDPOINT, 0)
+                    {
+                        if let Some(&(ref ep, ref state)) =
+                            ws2p_module.ws2p_endpoints.get(&ep_full_id)
+                        {
+                            /*let dal_endpoint = duniter_dal::endpoint::DALEndpoint::new(
+                                                state.clone() as u32,
+                                                ep.node_uuid().unwrap().0,
+                                                ep.pubkey(),
+                                                duniter_dal::endpoint::string_to_api(&ep.api().0).unwrap(),
+                                                1,
+                                                ep.to_string(),
+                                                received_time.duration_since(UNIX_EPOCH).unwrap(),
+                                            );*/
+                            ws2p_db::write_endpoint(
+                                &db,
+                                &ep,
+                                state.to_u32(),
+                                SystemTime::now()
+                                    .duration_since(UNIX_EPOCH)
+                                    .unwrap()
+                                    .as_secs(),
+                            );
+                        }
+                        endpoints_to_update_status.remove(&ep_full_id);
+                    } else {
+                        info!(
+                            "Write {} endpoint in {} secs.",
+                            ep_full_id,
+                            *DURATION_BEFORE_RECORDING_ENDPOINT
+                                - SystemTime::now()
+                                    .duration_since(received_time)
+                                    .unwrap()
+                                    .as_secs()
+                        );
+                    }
+                }
+                // ..
+            }
+        }
+        Ok(())
+    }
+}
+
+impl WS2PModuleDatas {
+    fn open_db(db_path: PathBuf) -> Result<sqlite::Connection, sqlite::Error> {
+        let conn: sqlite::Connection;
+        if !db_path.as_path().exists() {
+            conn = sqlite::open(db_path.as_path())?;
+            conn.execute(
+                "CREATE TABLE endpoints (hash_full_id TEXT, status INTEGER, node_id INTEGER, pubkey TEXT,
+                api INTEGER, version INTEGER, endpoint TEXT, last_check INTEGER);",
+            )?;
+        } else {
+            conn = sqlite::open(db_path.as_path())?;
+        }
+        Ok(conn)
+    }
+    pub fn parse_ws2p_conf(
+        duniter_conf: &DuniterConf,
+        ws2p_json_conf: &serde_json::Value,
+    ) -> WS2PConf {
+        let mut sync_endpoints = Vec::new();
+        match ws2p_json_conf.get("sync_peers") {
+            Some(peers) => {
+                let array_peers = peers.as_array().expect("Conf: Fail to parse conf file !");
+                for peer in array_peers {
+                    let pubkey = match peer.get("pubkey") {
+                        Some(pubkey) => {
+                            PublicKey::from_base58(
+                                pubkey
+                                    .as_str()
+                                    .expect("WS2PConf Error : fail to parse sync endpoint pubkey"),
+                            ).expect("WS2PConf Error : fail to parse sync endpoint pubkey")
+                        }
+                        None => panic!(
+                            "Fail to load ws2p conf : \
+                             WrongFormat : not found pubkey field !"
+                        ),
+                    };
+                    match peer.get("ws2p_endpoints") {
+                        Some(endpoints) => {
+                            let array_endpoints = endpoints
+                                .as_array()
+                                .expect("Conf: Fail to parse conf file !");
+                            for endpoint in array_endpoints {
+                                sync_endpoints.push(
+                                    NetworkEndpoint::parse_from_raw(
+                                        endpoint.as_str().unwrap(),
+                                        pubkey,
+                                        0,
+                                        0,
+                                    ).expect(&format!(
+                                        "WS2PConf Error : fail to parse sync Endpoint = {:?}",
+                                        endpoint.as_str().unwrap()
+                                    )),
+                                );
+                            }
+                        }
+                        None => panic!(
+                            "Fail to load conf : \
+                             WrongFormat : not found ws2p_endpoints field !"
+                        ),
+                    };
+                }
+            }
+            None => panic!(
+                "Configuration Error : \
+                 You must declare at least one node on which to synchronize !"
+            ),
+        };
+        WS2PConf {
+            outcoming_quota: *WS2P_DEFAULT_OUTCOMING_QUOTA,
+            node_id: NodeUUID(duniter_conf.my_node_id()),
+            sync_endpoints,
+        }
+    }
+    pub fn send_dal_request(&self, req: &DALRequest) {
+        for follower in &self.followers {
+            match follower.send(DuniterMessage::DALRequest(req.clone())) {
+                Ok(_) => {}
+                Err(_) => {}
+            }
+        }
+    }
+    pub fn send_network_event(&self, event: &NetworkEvent) {
+        for follower in &self.followers {
+            match follower.send(DuniterMessage::NetworkEvent(event.clone())) {
+                Ok(_) => {
+                    debug!("Send NetworkEvent to one follower.");
+                }
+                Err(_) => {
+                    warn!("Fail to send NetworkEvent to one follower !");
+                }
+            }
+        }
+    }
+    pub fn generate_my_head(
+        network_keypair: &ed25519::KeyPair,
+        conf: &WS2PConf,
+        soft_name: &str,
+        soft_version: &str,
+        my_current_blockstamp: &Blockstamp,
+        my_uid: Option<String>,
+    ) -> NetworkHead {
+        let message = NetworkHeadMessage::V2(NetworkHeadMessageV2 {
+            api: String::from("WS2POCA"),
+            version: 1,
+            pubkey: network_keypair.pubkey,
+            blockstamp: my_current_blockstamp.clone(),
+            node_uuid: conf.node_id,
+            software: String::from(soft_name),
+            soft_version: String::from(soft_version),
+            prefix: 1,
+            free_member_room: None,
+            free_mirror_room: None,
+        });
+        let message_v2 = NetworkHeadMessage::V2(NetworkHeadMessageV2 {
+            api: String::from("WS2POCA"),
+            version: 2,
+            pubkey: network_keypair.pubkey,
+            blockstamp: my_current_blockstamp.clone(),
+            node_uuid: conf.node_id,
+            software: String::from(soft_name),
+            soft_version: String::from(soft_version),
+            prefix: 1,
+            free_member_room: Some(0),
+            free_mirror_room: Some(0),
+        });
+        NetworkHead::V2(Box::new(NetworkHeadV2 {
+            message: message.clone(),
+            sig: network_keypair.privkey.sign(message.to_string().as_bytes()),
+            message_v2: message_v2.clone(),
+            sig_v2: network_keypair
+                .privkey
+                .sign(message_v2.to_string().as_bytes()),
+            step: 0,
+            uid: my_uid,
+        }))
+    }
+    pub fn get_network_consensus(&self) -> Result<Blockstamp, NetworkConsensusError> {
+        let mut count_known_blockstamps = 0;
+        let mut farthest_blockstamp = Blockstamp::default();
+        let mut blockstamps_occurences: HashMap<Blockstamp, usize> =
+            HashMap::with_capacity(*WS2P_DEFAULT_OUTCOMING_QUOTA);
+        let mut dominant_blockstamp = Blockstamp::default();
+        let mut dominant_blockstamp_occurences = 0;
+        for (_ws2p_full_id, head) in self.heads_cache.clone() {
+            count_known_blockstamps += 1;
+            let blockstamps_occurences_copy = blockstamps_occurences.clone();
+            match blockstamps_occurences_copy.get(&head.blockstamp()) {
+                Some(occurences) => {
+                    let mut occurences_mut =
+                        blockstamps_occurences.get_mut(&head.blockstamp()).unwrap();
+                    *occurences_mut += 1;
+                    if *occurences > dominant_blockstamp_occurences {
+                        dominant_blockstamp_occurences = *occurences;
+                        dominant_blockstamp = head.blockstamp().clone();
+                    }
+                }
+                None => {
+                    blockstamps_occurences.insert(head.blockstamp().clone(), 0);
+                }
+            }
+            if head.blockstamp().id.0 > farthest_blockstamp.id.0 {
+                farthest_blockstamp = head.blockstamp().clone();
+            }
+        }
+        if count_known_blockstamps < 5 {
+            return Err(NetworkConsensusError::InsufficientData(
+                count_known_blockstamps,
+            ));
+        } else if farthest_blockstamp == dominant_blockstamp {
+            return Ok(dominant_blockstamp);
+        }
+        Err(NetworkConsensusError::Fork)
+    }
+    fn count_established_connections(&self) -> usize {
+        let mut count_established_connections = 0;
+        for (_ws2p_full_id, (_ep, state)) in self.ws2p_endpoints.clone() {
+            if let WS2PConnectionState::Established = state {
+                count_established_connections += 1;
+            }
+        }
+        count_established_connections
+    }
+    pub fn connect_to_know_endpoints(&mut self) -> () {
+        let mut count_established_connections = 0;
+        let mut reachable_endpoints = Vec::new();
+        let mut unreachable_endpoints = Vec::new();
+        for (_ws2p_full_id, (ep, state)) in self.ws2p_endpoints.clone() {
+            match state {
+                WS2PConnectionState::Established => count_established_connections += 1,
+                WS2PConnectionState::NeverTry
+                | WS2PConnectionState::Close
+                | WS2PConnectionState::Denial => reachable_endpoints.push(ep),
+                _ => unreachable_endpoints.push(ep),
+            }
+        }
+        let mut free_outcoming_rooms =
+            self.conf.clone().unwrap().outcoming_quota - count_established_connections;
+        while free_outcoming_rooms > 0 {
+            let ep = if !reachable_endpoints.is_empty() {
+                reachable_endpoints.pop().unwrap()
+            } else if !unreachable_endpoints.is_empty() {
+                unreachable_endpoints.pop().unwrap()
+            } else {
+                break;
+            };
+            self.connect_to_without_checking_quotas(ep);
+            free_outcoming_rooms -= 1;
+        }
+    }
+    pub fn connect_to(&mut self, endpoint: NetworkEndpoint) -> () {
+        // Add endpoint to endpoints list (if there isn't already)
+        match self.ws2p_endpoints.get(&endpoint.node_full_id().unwrap()) {
+            Some(_) => {
+                self.ws2p_endpoints
+                    .get_mut(&endpoint.node_full_id().unwrap())
+                    .unwrap()
+                    .1 = WS2PConnectionState::NeverTry;
+            }
+            None => {
+                self.ws2p_endpoints.insert(
+                    endpoint.node_full_id().unwrap(),
+                    (endpoint.clone(), WS2PConnectionState::NeverTry),
+                );
+            }
+        };
+        if self.conf.clone().unwrap().outcoming_quota > self.count_established_connections() {
+            self.connect_to_without_checking_quotas(endpoint);
+        }
+    }
+    fn close_connection(&mut self, ws2p_full_id: &NodeFullId, reason: WS2PCloseConnectionReason) {
+        match reason {
+            WS2PCloseConnectionReason::NegociationTimeout => {}
+            WS2PCloseConnectionReason::AuthMessInvalidSig
+            | WS2PCloseConnectionReason::Timeout
+            | WS2PCloseConnectionReason::Unknow => {
+                self.ws2p_endpoints
+                    .get_mut(ws2p_full_id)
+                    .expect("Failure : attempt to delete a non-existent connection !")
+                    .1 = WS2PConnectionState::Close
+            }
+        }
+        self.connections_meta_datas.remove(ws2p_full_id);
+        self.websockets.remove(ws2p_full_id).expect(&format!(
+            "Fatal error : no websocket for {} !",
+            ws2p_full_id
+        ));
+        self.threads_senders_channels.remove(ws2p_full_id);
+    }
+    pub fn ws2p_conn_message_pretreatment(&mut self, message: WS2PConnectionMessage) -> WS2PSignal {
+        let connections_count = self.connections_meta_datas.len();
+        if connections_count == 0 {
+            return WS2PSignal::NoConnection;
+        }
+        let ws2p_full_id = message.0;
+        match message.1 {
+            WS2PConnectionMessagePayload::WrongUrl
+            | WS2PConnectionMessagePayload::FailOpenWS
+            | WS2PConnectionMessagePayload::FailToSplitWS => {
+                self.ws2p_endpoints.get_mut(&ws2p_full_id).unwrap().1 =
+                    WS2PConnectionState::WSError;
+                return WS2PSignal::WSError(ws2p_full_id);
+            }
+            WS2PConnectionMessagePayload::TryToSendConnectMess => {
+                self.ws2p_endpoints.get_mut(&ws2p_full_id).unwrap().1 =
+                    WS2PConnectionState::TryToSendConnectMess;
+            }
+            WS2PConnectionMessagePayload::FailSendConnectMess => {
+                self.ws2p_endpoints.get_mut(&ws2p_full_id).unwrap().1 =
+                    WS2PConnectionState::Unreachable;
+            }
+            WS2PConnectionMessagePayload::WebsocketOk(sender) => {
+                self.websockets.insert(ws2p_full_id, sender);
+            }
+            WS2PConnectionMessagePayload::ValidConnectMessage(response, new_con_state) => {
+                self.ws2p_endpoints.get_mut(&ws2p_full_id).unwrap().1 = new_con_state;
+                if let WS2PConnectionState::ConnectMessOk = self.ws2p_endpoints[&ws2p_full_id].1 {
+                    trace!("Send: {:#?}", response);
+                    self.websockets
+                        .get_mut(&ws2p_full_id)
+                        .expect(&format!(
+                            "Fatal error : no websocket for {} !",
+                            ws2p_full_id
+                        ))
+                        .0
+                        .send_message(&Message::text(response))
+                        .unwrap();
+                }
+            }
+            WS2PConnectionMessagePayload::ValidAckMessage(r, new_con_state) => {
+                self.ws2p_endpoints.get_mut(&ws2p_full_id).unwrap().1 = new_con_state;
+                if let WS2PConnectionState::AckMessOk = self.ws2p_endpoints[&ws2p_full_id].1 {
+                    trace!("DEBUG : Send: {:#?}", r);
+                    self.websockets
+                        .get_mut(&ws2p_full_id)
+                        .expect(&format!(
+                            "Fatal error : no websocket for {} !",
+                            ws2p_full_id
+                        ))
+                        .0
+                        .send_message(&Message::text(r))
+                        .unwrap();
+                }
+            }
+            WS2PConnectionMessagePayload::ValidOk(new_con_state) => {
+                self.ws2p_endpoints.get_mut(&ws2p_full_id).unwrap().1 = new_con_state;
+                match self.ws2p_endpoints[&ws2p_full_id].1 {
+                    WS2PConnectionState::OkMessOkWaitingAckMess => {}
+                    WS2PConnectionState::Established => {
+                        return WS2PSignal::ConnectionEstablished(ws2p_full_id)
+                    }
+                    _ => {
+                        self.threads_senders_channels[&ws2p_full_id]
+                            .send(WS2POrderForListeningThread::Close)
+                            .unwrap();
+                        self.close_connection(&ws2p_full_id, WS2PCloseConnectionReason::Unknow);
+                        return WS2PSignal::Empty;
+                    }
+                }
+            }
+            WS2PConnectionMessagePayload::DalRequest(req_id, req_body) => {
+                return WS2PSignal::DalRequest(ws2p_full_id, req_id, req_body);
+            }
+            WS2PConnectionMessagePayload::PeerCard(body, ws2p_endpoints) => {
+                return WS2PSignal::PeerCard(ws2p_full_id, body, ws2p_endpoints);
+            }
+            WS2PConnectionMessagePayload::Heads(heads) => {
+                let mut applied_heads = Vec::with_capacity(heads.len());
+                for head in heads {
+                    if let Some(head) = NetworkHead::from_json_value(&head) {
+                        if head.verify() {
+                            if (self.my_head.is_none()
+                                || head.node_full_id()
+                                    != self.my_head.clone().unwrap().node_full_id())
+                                && head.apply(&mut self.heads_cache)
+                            {
+                                applied_heads.push(head);
+                            }
+                        }
+                    }
+                }
+                return WS2PSignal::Heads(ws2p_full_id, applied_heads);
+            }
+            WS2PConnectionMessagePayload::Document(network_doc) => {
+                return WS2PSignal::Document(ws2p_full_id, network_doc);
+            }
+            WS2PConnectionMessagePayload::ReqResponse(req_id, response) => {
+                if self.requests_awaiting_response.len() > req_id.0 as usize {
+                    if let Some((ref ws2p_request, ref recipient_fulld_id, ref _timestamp)) =
+                        self.requests_awaiting_response.remove(&req_id)
+                    {
+                        return WS2PSignal::ReqResponse(
+                            req_id,
+                            ws2p_request.clone(),
+                            *recipient_fulld_id,
+                            response,
+                        );
+                    }
+                }
+            }
+            WS2PConnectionMessagePayload::NegociationTimeout => {
+                match self.ws2p_endpoints[&ws2p_full_id].1 {
+                    WS2PConnectionState::AckMessOk | WS2PConnectionState::ConnectMessOk => {
+                        self.ws2p_endpoints.get_mut(&ws2p_full_id).unwrap().1 =
+                            WS2PConnectionState::Denial
+                    }
+                    WS2PConnectionState::WaitingConnectMess => {
+                        self.ws2p_endpoints.get_mut(&ws2p_full_id).unwrap().1 =
+                            WS2PConnectionState::NoResponse
+                    }
+                    _ => {
+                        self.ws2p_endpoints.get_mut(&ws2p_full_id).unwrap().1 =
+                            WS2PConnectionState::Unreachable
+                    }
+                }
+                self.close_connection(&ws2p_full_id, WS2PCloseConnectionReason::NegociationTimeout);
+                return WS2PSignal::NegociationTimeout(ws2p_full_id);
+            }
+            WS2PConnectionMessagePayload::Timeout => {
+                self.close_connection(&ws2p_full_id, WS2PCloseConnectionReason::Timeout);
+                return WS2PSignal::Timeout(ws2p_full_id);
+            }
+            WS2PConnectionMessagePayload::UnknowMessage => warn!(
+                "WS2P : Receive Unknow Message from {}.",
+                &self.connections_meta_datas[&ws2p_full_id]
+                    .remote_pubkey
+                    .unwrap()
+            ),
+            WS2PConnectionMessagePayload::WrongFormatMessage => warn!(
+                "WS2P : Receive Wrong Format Message from {}.",
+                &self.connections_meta_datas[&ws2p_full_id]
+                    .remote_pubkey
+                    .unwrap()
+            ),
+            WS2PConnectionMessagePayload::InvalidMessage => return WS2PSignal::Empty,
+            WS2PConnectionMessagePayload::Close => {
+                self.close_connection(&ws2p_full_id, WS2PCloseConnectionReason::AuthMessInvalidSig)
+            }
+        }
+        // Detect timeout requests
+        let mut requests_timeout = Vec::new();
+        for &(ref req, ref _ws2p_full_id, ref timestamp) in
+            self.requests_awaiting_response.clone().values()
+        {
+            if SystemTime::now().duration_since(*timestamp).unwrap() > Duration::new(20, 0) {
+                requests_timeout.push(req.get_req_full_id());
+                warn!("request timeout : {:?}", req);
+            }
+        }
+        // Delete (and resend) timeout requests
+        for req_id in requests_timeout {
+            //let ws2p_endpoints = self.ws2p_endpoints.clone();
+            let _request_option = self.requests_awaiting_response.remove(&req_id.1);
+            /*if let Some((request, _, _)) = request_option {
+                let _request_result = self.send_request_to_specific_node(
+                    &get_random_connection(&ws2p_endpoints),
+                    &request,
+                );
+            }*/
+        }
+        WS2PSignal::Empty
+    }
+
+    pub fn send_request_to_all_connections(
+        &mut self,
+        ws2p_request: &NetworkRequest,
+    ) -> Result<(), SendRequestError> {
+        let mut count_successful_sending: usize = 0;
+        let mut errors: Vec<websocket::WebSocketError> = Vec::new();
+        match ws2p_request.clone() {
+            NetworkRequest::GetCurrent(req_full_id, _receiver) => {
+                for (ws2p_full_id, (_ep, state)) in self.ws2p_endpoints.clone() {
+                    if let WS2PConnectionState::Established = state {
+                        let ws2p_request = NetworkRequest::GetCurrent(
+                            ModuleReqFullId(
+                                req_full_id.clone().0,
+                                ModuleReqId(
+                                    (self.requests_awaiting_response.len()
+                                        + count_successful_sending)
+                                        as u32,
+                                ),
+                            ),
+                            ws2p_full_id,
+                        );
+                        match self.send_request_to_specific_node(&ws2p_full_id, &ws2p_request) {
+                            Ok(_) => count_successful_sending += 1,
+                            Err(e) => errors.push(e),
+                        };
+                    }
+                }
+            }
+            /* NetworkRequest::GetBlock(req_full_id, number) => {} */
+            NetworkRequest::GetBlocks(_req_full_id, _receiver, _count, _from_number) => {}
+            NetworkRequest::GetRequirementsPending(req_full_id, _receiver, min_cert) => {
+                for (ws2p_full_id, (_ep, state)) in self.ws2p_endpoints.clone() {
+                    if let WS2PConnectionState::Established = state {
+                        let ws2p_request = NetworkRequest::GetRequirementsPending(
+                            ModuleReqFullId(
+                                req_full_id.clone().0,
+                                ModuleReqId(self.requests_awaiting_response.len() as u32),
+                            ),
+                            ws2p_full_id,
+                            min_cert,
+                        );
+                        match self.send_request_to_specific_node(&ws2p_full_id, &ws2p_request) {
+                            Ok(_) => count_successful_sending += 1,
+                            Err(e) => errors.push(e),
+                        };
+                    }
+                }
+            }
+            _ => {
+                return Err(SendRequestError::RequestTypeMustNotBeTransmitted());
+            }
+        }
+        debug!("count_successful_sending = {}", count_successful_sending);
+        if !errors.is_empty() {
+            return Err(SendRequestError::WSError(count_successful_sending, errors));
+        }
+        Ok(())
+    }
+
+    pub fn send_request_to_specific_node(
+        &mut self,
+        receiver_ws2p_full_id: &NodeFullId,
+        ws2p_request: &NetworkRequest,
+    ) -> Result<(), websocket::WebSocketError> {
+        self.websockets
+            .get_mut(receiver_ws2p_full_id)
+            .unwrap()
+            .0
+            .send_message(&Message::text(
+                network_request_to_json(ws2p_request).to_string(),
+            ))?;
+        self.requests_awaiting_response.insert(
+            ws2p_request.get_req_id(),
+            (
+                ws2p_request.clone(),
+                *receiver_ws2p_full_id,
+                SystemTime::now(),
+            ),
+        );
+        debug!(
+            "send request {} to {}",
+            network_request_to_json(ws2p_request).to_string(),
+            receiver_ws2p_full_id
+        );
+        Ok(())
+    }
+
+    fn connect_to_without_checking_quotas(&mut self, endpoint: NetworkEndpoint) -> () {
+        // update connection state
+        self.ws2p_endpoints
+            .get_mut(&endpoint.node_full_id().unwrap())
+            .expect("Fatal error: try to connect to unlisted endpoint ! ")
+            .1 = WS2PConnectionState::TryToOpenWS;
+
+        // get endpoint url
+        let ws_url = endpoint.get_url(true);
+
+        // Create WS2PConnection
+        let mut conn_meta_datas = WS2PConnectionMetaData::new(
+            "b60a14fd-0826-4ae0-83eb-1a92cd59fd5308535fd3-78f2-4678-9315-cd6e3b7871b1".to_string(),
+        );
+        conn_meta_datas.remote_pubkey = Some(endpoint.pubkey());
+        conn_meta_datas.remote_uuid = Some(endpoint.node_uuid().unwrap());
+
+        // Prepare datas for listening thread
+        let mut datas_for_listening_thread = WS2PDatasForListeningThread {
+            conn_meta_datas: conn_meta_datas.clone(),
+            currency: self.currency.clone().unwrap(),
+            key_pair: self.key_pair.unwrap(),
+        };
+
+        // Create CONNECT Message
+        let mut connect_message = WS2PConnectMessageV1 {
+            currency: self.currency.clone().unwrap(),
+            pubkey: self.key_pair.unwrap().pubkey,
+            challenge: conn_meta_datas.challenge.clone(),
+            signature: None,
+        };
+        connect_message.signature = Some(connect_message.sign(self.key_pair.unwrap()));
+        let json_connect_message =
+            serde_json::to_string(&connect_message).expect("Fail to serialize CONNECT message !");
+
+        // Log
+        trace!("Try connection to {} ...", ws_url);
+
+        // Listen incoming messages into a thread
+        let sender_to_main_thread: mpsc::Sender<WS2PThreadSignal> =
+            mpsc::Sender::clone(&self.main_thread_channel.0);
+        let (tx2, rx2) = mpsc::channel();
+        self.connections_meta_datas
+            .insert(conn_meta_datas.node_full_id(), conn_meta_datas.clone());
+        self.threads_senders_channels
+            .insert(conn_meta_datas.node_full_id(), tx2);
+        thread::spawn(move || {
+            // Open websocket
+            let open_ws_time = SystemTime::now();
+            let client = match ClientBuilder::new(&ws_url) {
+                Ok(mut client_builder) => match client_builder.connect_insecure() {
+                    Ok(c) => c,
+                    Err(_) => {
+                        debug!("WS2PConnectResult::FailOpenWS");
+                        sender_to_main_thread
+                            .send(WS2PThreadSignal::WS2PConnectionMessage(
+                                WS2PConnectionMessage(
+                                    datas_for_listening_thread.conn_meta_datas.node_full_id(),
+                                    WS2PConnectionMessagePayload::FailOpenWS,
+                                ),
+                            ))
+                            .unwrap_or(());
+                        return ();
+                    }
+                },
+                Err(_) => {
+                    warn!("WS2PConnectResult::WrongUrl : {}", ws_url);
+                    sender_to_main_thread
+                        .send(WS2PThreadSignal::WS2PConnectionMessage(
+                            WS2PConnectionMessage(
+                                datas_for_listening_thread.conn_meta_datas.node_full_id(),
+                                WS2PConnectionMessagePayload::WrongUrl,
+                            ),
+                        ))
+                        .unwrap_or(());
+
+                    return ();
+                }
+            };
+            let (mut receiver, mut sender) = match client.split() {
+                Ok((mut r, mut s)) => (r, s),
+                Err(_) => {
+                    sender_to_main_thread
+                        .send(WS2PThreadSignal::WS2PConnectionMessage(
+                            WS2PConnectionMessage(
+                                datas_for_listening_thread.conn_meta_datas.node_full_id(),
+                                WS2PConnectionMessagePayload::FailToSplitWS,
+                            ),
+                        ))
+                        .unwrap_or(());
+                    return ();
+                }
+            };
+
+            // Send CONNECT Message
+            sender_to_main_thread
+                .send(WS2PThreadSignal::WS2PConnectionMessage(
+                    WS2PConnectionMessage(
+                        datas_for_listening_thread.conn_meta_datas.node_full_id(),
+                        WS2PConnectionMessagePayload::TryToSendConnectMess,
+                    ),
+                ))
+                .unwrap_or(());
+            match sender.send_message(&Message::text(json_connect_message)) {
+                Ok(_) => {
+                    sender_to_main_thread
+                        .send(WS2PThreadSignal::WS2PConnectionMessage(
+                            WS2PConnectionMessage(
+                                datas_for_listening_thread.conn_meta_datas.node_full_id(),
+                                WS2PConnectionMessagePayload::WebsocketOk(WebsocketSender(sender)),
+                            ),
+                        ))
+                        .unwrap_or(());
+                }
+                Err(_) => {
+                    receiver.shutdown_all().unwrap_or(());
+                    sender_to_main_thread
+                        .send(WS2PThreadSignal::WS2PConnectionMessage(
+                            WS2PConnectionMessage(
+                                datas_for_listening_thread.conn_meta_datas.node_full_id(),
+                                WS2PConnectionMessagePayload::FailSendConnectMess,
+                            ),
+                        ))
+                        .unwrap_or(());
+                    return ();
+                }
+            }
+
+            let mut last_mess_time = SystemTime::now();
+            let mut spam_interval = false;
+            let mut spam_counter = 0;
+            for incoming_message in receiver.incoming_messages() {
+                // Spam ?
+                if SystemTime::now().duration_since(last_mess_time).unwrap()
+                    > Duration::new(*WS2P_SPAM_INTERVAL_IN_MILLI_SECS, 0)
+                {
+                    if spam_interval {
+                        spam_counter += 1;
+                    } else {
+                        spam_interval = true;
+                        spam_counter = 2;
+                    }
+                } else {
+                    spam_interval = false;
+                    spam_counter = 0;
+                }
+                // Spam ?
+                if spam_counter >= *WS2P_SPAM_LIMIT {
+                    thread::sleep(Duration::from_millis(*WS2P_SPAM_SLEEP_TIME_IN_SEC));
+                    last_mess_time = SystemTime::now();
+                } else {
+                    // Negociation timeout ?
+                    if datas_for_listening_thread.conn_meta_datas.state
+                        != WS2PConnectionState::Established
+                        && SystemTime::now().duration_since(open_ws_time).unwrap()
+                            > Duration::new(*WS2P_NEGOTIATION_TIMEOUT, 0)
+                    {
+                        sender_to_main_thread
+                            .send(WS2PThreadSignal::WS2PConnectionMessage(
+                                WS2PConnectionMessage(
+                                    datas_for_listening_thread.conn_meta_datas.node_full_id(),
+                                    WS2PConnectionMessagePayload::NegociationTimeout,
+                                ),
+                            ))
+                            .unwrap_or(());
+                        break;
+                    }
+                    // Connection timeout ?
+                    else if SystemTime::now().duration_since(last_mess_time).unwrap()
+                        > Duration::new(*WS2P_CONNECTION_TIMEOUT, 0)
+                    {
+                        sender_to_main_thread
+                            .send(WS2PThreadSignal::WS2PConnectionMessage(
+                                WS2PConnectionMessage(
+                                    datas_for_listening_thread.conn_meta_datas.node_full_id(),
+                                    WS2PConnectionMessagePayload::Timeout,
+                                ),
+                            ))
+                            .unwrap_or(());
+                        break;
+                    }
+                    last_mess_time = SystemTime::now();
+                    match rx2.recv_timeout(Duration::from_millis(40)) {
+                        Ok(s) => match s {
+                            WS2POrderForListeningThread::Close => break,
+                        },
+                        Err(e) => {
+                            match e {
+                                mpsc::RecvTimeoutError::Timeout => {
+                                    match incoming_message {
+                                        Ok(message) => {
+                                            if message.is_close() {
+                                                if sender_to_main_thread
+                                                    .send(WS2PThreadSignal::WS2PConnectionMessage(
+                                                        WS2PConnectionMessage(
+                                                            datas_for_listening_thread
+                                                                .conn_meta_datas
+                                                                .node_full_id(),
+                                                            WS2PConnectionMessagePayload::Close,
+                                                        ),
+                                                    ))
+                                                    .is_ok()
+                                                {
+                                                    break;
+                                                }
+                                            } else if message.is_data() {
+                                                // Parse message
+                                                let m = Message::from(message);
+                                                let s: String = from_utf8(&m.payload)
+                                                    .unwrap()
+                                                    .to_string();
+                                                let message: serde_json::Value =
+                                                    serde_json::from_str(&s)
+                                                    .unwrap();
+                                                let result = sender_to_main_thread.send(
+                                                    WS2PThreadSignal::WS2PConnectionMessage(
+                                                        WS2PConnectionMessage(
+                                                            datas_for_listening_thread
+                                                                .conn_meta_datas
+                                                                .node_full_id(),
+                                                            datas_for_listening_thread
+                                                                .conn_meta_datas
+                                                                .parse_and_check_incoming_message(
+                                                                    &datas_for_listening_thread
+                                                                        .currency,
+                                                                    datas_for_listening_thread
+                                                                        .key_pair,
+                                                                    &message,
+                                                                ),
+                                                        ),
+                                                    ),
+                                                );
+                                                if result.is_err() {
+                                                    debug!("Close ws2p connection because ws2p main thread is unrechable !");
+                                                    break;
+                                                }
+                                            }
+                                        }
+                                        Err(e) => {
+                                            warn!("WebSocketError : {} ! Close ", e);
+                                            //receiver.shutdown_all().unwrap_or(());
+                                            break;
+                                        }
+                                    };
+                                }
+                                mpsc::RecvTimeoutError::Disconnected => {
+                                    break;
+                                }
+                            }
+                        }
+                    };
+                }
+            }
+        });
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    extern crate duniter_conf;
+    extern crate duniter_crypto;
+    extern crate duniter_dal;
+    extern crate duniter_documents;
+    extern crate duniter_message;
+    extern crate duniter_module;
+    extern crate duniter_network;
+
+    use self::duniter_crypto::keys::ed25519;
+    use self::duniter_crypto::keys::PublicKey;
+    use self::duniter_dal::parsers::blocks::parse_json_block;
+    use self::duniter_documents::blockchain::v10::documents::BlockDocument;
+    use self::duniter_module::DuniterModule;
+    use self::duniter_network::network_endpoint::{NetworkEndpoint, NetworkEndpointApi};
+    use self::duniter_network::NetworkBlock;
+    use super::*;
+    use std::fs;
+    use std::path::PathBuf;
+    use std::time::{SystemTime, UNIX_EPOCH};
+
+    #[test]
+    fn test_parse_json_block() {
+        let json_block = json!({
+            "fork": false,
+            "version": 10,
+            "nonce": 10500000059239 as u64,
+            "number": 109966,
+            "powMin": 88,
+            "time": 1523300656,
+            "medianTime": 1523295259,
+            "membersCount": 933,
+            "monetaryMass": 146881563,
+            "unitbase": 0,
+            "issuersCount": 44,
+            "issuersFrame": 221,
+            "issuersFrameVar": 0,
+            "currency": "g1",
+            "issuer": "GRBPV3Y7PQnB9LaZhSGuS3BqBJbSHyibzYq65kTh1nQ4",
+            "signature": "GCg2Lti3TdxWlhA8JF8pRI+dRQ0XZVtcC4BqO/COTpjTQFdWG6qmUNVvdeYCtR/lu1JQe3N/IhrbyV6L/6I+Cg==",
+            "hash": "000000EF5B2AA849F4C3AF3D35E1284EA1F34A9F617EA806CE8371619023DC74",
+            "parameters": "",
+            "previousHash": "000004C00602F8A27AE078DE6351C0DDA1EA0974A78D2BEFA7DFBE7B7C3146FD",
+            "previousIssuer": "5SwfQubSat5SunNafCsunEGTY93nVM4kLSsuprNqQb6S",
+            "inner_hash": "61F02B1A6AE2E4B9A1FD66CE673258B4B21C0076795571EE3C9DC440DD06C46C",
+            "dividend": null,
+            "identities": [],
+            "joiners": [],
+            "actives": [],
+            "leavers": [],
+            "revoked": [],
+            "excluded": [],
+            "certifications": [
+                "Hm5qjaNuHogNRdGZ4vgnLA9DMZVUu5YWzVup5mubuxCc:8AmdBsimcLziXaCS4AcVUfPx7rkjeic7482dLbBkuZw6:109964:yHKBGMeuxyIqFb295gVNK6neRC+U0tmsX1Zed3TLjS3ZZHYYycE1piLcYsTKll4ifNVp6rm+hd/CLdHYB+29CA==",
+                "BncjgJeFpGsMCCsUfzNLEexjsbuX3V2mg9P67ov2LkwK:DyBUBNpzpfvjtwYYSaVMM6ST6t2DNg3NCE9CU9bRQFhF:105864:cJEGW9WxJwlMA2+4LNAK4YieyseUy1WIkFh1YLYD+JJtJEoCSnIQRXzhiAoRpGaj0bRz8sTpwI6PRkuVoDJJDQ=="
+            ],
+            "transactions": [
+                {
+                "version": 10,
+                "currency": "g1",
+                "locktime": 0,
+                "hash": "80FE1E83DC4D0B722CA5F8363EFC6A3E29071032EBB71C1E0DF8D4FEA589C698",
+                "blockstamp": "109964-00000168105D4A8A8BC8C0DC70033F45ABE472782C75A7F2074D0F4D4A3B7B2B",
+                "blockstampTime": 0,
+                "issuers": [
+                    "6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT"
+                ],
+                "inputs": [
+                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:98284",
+                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:98519",
+                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:98779",
+                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:99054",
+                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:99326",
+                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:99599",
+                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:99884",
+                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:100174",
+                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:100469",
+                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:100746",
+                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:101036",
+                    "1001:0:D:6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT:101327"
+                ],
+                "outputs": [
+                    "12000:0:SIG(HmH5beJqKGMeotcQUrSW7Wo5tKvAksHmfYXfiSQ9EbWz)",
+                    "12:0:SIG(6PiqcuUWhyiBF3Lgcht8c1yfk6gMfQzcUc46CqrJfeLT)"
+                ],
+                "unlocks": [
+                    "0:SIG(0)",
+                    "1:SIG(0)",
+                    "2:SIG(0)",
+                    "3:SIG(0)",
+                    "4:SIG(0)",
+                    "5:SIG(0)",
+                    "6:SIG(0)",
+                    "7:SIG(0)",
+                    "8:SIG(0)",
+                    "9:SIG(0)",
+                    "10:SIG(0)",
+                    "11:SIG(0)"
+                ],
+                "signatures": [
+                    "MZxoKxYgwufh/s5mwLCsYEZXtIsP1hEKCyAzLipJsvCbR9xj7wXUw0C/ahwvZfBtR7+QVPIfLmwYEol1JcHjDw=="
+                ],
+                "comment": "Adhesion 2018"
+                },
+                {
+                "version": 10,
+                "currency": "g1",
+                "locktime": 0,
+                "hash": "B80507412B35BD5EB437AE0D3EB97E60E3A4974F5CDEA1AF7E2127C0E943481F",
+                "blockstamp": "109964-00000168105D4A8A8BC8C0DC70033F45ABE472782C75A7F2074D0F4D4A3B7B2B",
+                "blockstampTime": 0,
+                "issuers": [
+                    "8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU"
+                ],
+                "inputs": [
+                    "1001:0:D:8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU:91560",
+                    "1001:0:D:8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU:91850",
+                    "1001:0:D:8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU:92111",
+                    "1001:0:D:8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU:92385",
+                    "1001:0:D:8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU:92635"
+                ],
+                "outputs": [
+                    "5000:0:SIG(BzHnbec1Gov7dLSt1EzJS7vikoQCECeuvZs4wamZAcT1)",
+                    "5:0:SIG(8gundJEbfm73Kx3jjw8YivJyz8qD2igjf6baCBLFCxPU)"
+                ],
+                "unlocks": [
+                    "0:SIG(0)",
+                    "1:SIG(0)",
+                    "2:SIG(0)",
+                    "3:SIG(0)",
+                    "4:SIG(0)"
+                ],
+                "signatures": [
+                    "A+ukwRvLWs1gZQ0KAqAnknEgmRQHdrnOvNuBx/WZqje17BAPrVxSxKpqwU6MiajU+ppigsYp6Bu0FdPf/tGnCQ=="
+                ],
+                "comment": ""
+                },
+                {
+                "version": 10,
+                "currency": "g1",
+                "locktime": 0,
+                "hash": "D8970E6629C0381A78534EEDD86803E9215A7EC4C494BAEA79EB19425F9B4D31",
+                "blockstamp": "109964-00000168105D4A8A8BC8C0DC70033F45ABE472782C75A7F2074D0F4D4A3B7B2B",
+                "blockstampTime": 0,
+                "issuers": [
+                    "FnSXE7QyBfs4ozoYAt5NEewWhHEPorf38cNXu3kX9xsg"
+                ],
+                "inputs": [
+                    "1000:0:D:FnSXE7QyBfs4ozoYAt5NEewWhHEPorf38cNXu3kX9xsg:36597",
+                    "1000:0:D:FnSXE7QyBfs4ozoYAt5NEewWhHEPorf38cNXu3kX9xsg:36880",
+                    "1000:0:D:FnSXE7QyBfs4ozoYAt5NEewWhHEPorf38cNXu3kX9xsg:37082"
+                ],
+                "outputs": [
+                    "3000:0:SIG(BBC8Rnh4CWN1wBrPLevK7GRFFVDVw7Lu24YNMUmhqoHU)"
+                ],
+                "unlocks": [
+                    "0:SIG(0)",
+                    "1:SIG(0)",
+                    "2:SIG(0)"
+                ],
+                "signatures": [
+                    "OpiF/oQfIigOeAtsteukU0w9FPSELE+BVTxhmsQ8bEeYGlwovG2VF8ZFiJkLLPi6vFuKgwzULJfjNGd97twZCw=="
+                ],
+                "comment": "1 billet pour une seance.pour un chouette film"
+                }
+            ],
+        });
+        let mut block: BlockDocument =
+            match parse_json_block(&json_block).expect("Fail to parse test json block !") {
+                NetworkBlock::V10(network_block_v10) => network_block_v10.uncompleted_block_doc,
+                _ => {
+                    panic!("Test block must be a v10 block !");
+                }
+            };
+        assert_eq!(
+            block.inner_hash.unwrap().to_hex(),
+            "61F02B1A6AE2E4B9A1FD66CE673258B4B21C0076795571EE3C9DC440DD06C46C"
+        );
+        block.compute_hash();
+        assert_eq!(
+            block.hash.unwrap().0.to_hex(),
+            "000000EF5B2AA849F4C3AF3D35E1284EA1F34A9F617EA806CE8371619023DC74"
+        );
+    }
+
+    #[test]
+    fn endpoint_db_tests() {
+        let test_db_path = PathBuf::from("test.db");
+        if test_db_path.as_path().exists() {
+            fs::remove_file(&test_db_path).unwrap();
+        }
+        let db = WS2PModuleDatas::open_db(test_db_path).unwrap();
+
+        let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
+
+        let mut endpoint = NetworkEndpoint::parse_from_raw(
+            "WS2P cb06a19b g1.imirhil.fr 53012 /",
+            ed25519::PublicKey::from_base58("5gJYnQp8v7bWwk7EWRoL8vCLof1r3y9c6VDdnGSM1GLv")
+                .unwrap(),
+            1,
+            current_time.as_secs(),
+        ).expect("Failt to parse test endpoint !");
+
+        ws2p_db::write_endpoint(&db, &endpoint, 1, current_time.as_secs());
+        let mut written_endpoints =
+            ws2p_db::get_endpoints_for_api(&db, NetworkEndpointApi(String::from("WS2P")));
+        assert_eq!(endpoint, written_endpoints.pop().unwrap());
+
+        // Test status update
+        endpoint.set_status(3);
+        ws2p_db::write_endpoint(&db, &endpoint, 3, current_time.as_secs());
+        let mut written_endpoints =
+            ws2p_db::get_endpoints_for_api(&db, NetworkEndpointApi(String::from("WS2P")));
+        assert_eq!(endpoint, written_endpoints.pop().unwrap());
+    }
+
+    #[test]
+    fn ws2p_requests() {
+        let module_id = WS2PModule::id();
+        let request = NetworkRequest::GetBlocks(
+            ModuleReqFullId(module_id, ModuleReqId(58)),
+            NodeFullId::default(),
+            50,
+            0,
+        );
+        assert_eq!(
+            network_request_to_json(&request),
+            json!({
+            "reqId": format!("{:x}", 58),
+            "body": {
+                "name": "BLOCKS_CHUNK",
+                "params": {
+                    "count": 50,
+                    "fromNumber": 0
+                }
+            }
+        })
+        );
+        assert_eq!(
+            network_request_to_json(&request).to_string(),
+            "{\"body\":{\"name\":\"BLOCKS_CHUNK\",\"params\":{\"count\":50,\"fromNumber\":0}},\"reqId\":\"3a\"}"
+        );
+    }
+
+    #[test]
+    fn ws2p_parse_head() {
+        let head = json!({
+            "message": "WS2POTMIC:HEAD:1:D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx:104512-0000051B9CE9C1CA89F269375A6751FB88B9E88DE47A36506057E5BFBCFBB276:c1c39a0a:duniter:1.6.21:3",
+            "sig": "trtK9GXvTdfND995ohWEderpO3NkIqi1X6mBeVvMcaHckq+lIGqjWvJ9t9Vccz5t+VGaSmGUihDl4q6eldIYBw==",
+            "messageV2": "WS2POTMIC:HEAD:2:D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx:104512-0000051B9CE9C1CA89F269375A6751FB88B9E88DE47A36506057E5BFBCFBB276:c1c39a0a:duniter:1.6.21:3:25:22",
+            "sigV2": "x6ehPMuYjGY+z7wEGnJGyMBxMKUdu01RWaF0b0XCtoVjg67cCvT4H0V/Qcxn4bAGqzy5ux2fA7NiI+81bBnqDw==",
+            "step": 0
+        });
+        let mut heads_count = 0;
+        if let Some(head) = NetworkHead::from_json_value(&head) {
+            if let NetworkHead::V2(ref head_v2) = head {
+                heads_count += 1;
+                assert_eq!(
+                    head_v2.message.to_string(),
+                    String::from("WS2POTMIC:HEAD:1:D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx:104512-0000051B9CE9C1CA89F269375A6751FB88B9E88DE47A36506057E5BFBCFBB276:c1c39a0a:duniter:1.6.21:3")
+                );
+            }
+            assert_eq!(head.verify(), true);
+        }
+        assert_eq!(heads_count, 1);
+    }
+}
diff --git a/ws2p/ok_message.rs b/ws2p/ok_message.rs
new file mode 100644
index 0000000000000000000000000000000000000000..9572c822c9761930a622a9cf0eca9ceff631acc4
--- /dev/null
+++ b/ws2p/ok_message.rs
@@ -0,0 +1,69 @@
+extern crate duniter_crypto;
+extern crate serde;
+extern crate serde_json;
+
+use self::serde::ser::{Serialize, SerializeStruct, Serializer};
+use super::WS2PMessage;
+use duniter_crypto::keys::ed25519::PublicKey as ed25519PublicKey;
+use duniter_crypto::keys::PublicKey;
+
+#[derive(Debug, Clone)]
+pub struct WS2POkMessageV1 {
+    pub currency: String,
+    pub pubkey: ed25519PublicKey,
+    pub challenge: String,
+    pub signature: Option<duniter_crypto::keys::ed25519::Signature>,
+}
+
+impl WS2PMessage for WS2POkMessageV1 {
+    fn parse(v: &serde_json::Value, currency: String) -> Option<Self> {
+        let signature = match v.get("sig") {
+            Some(signature) => signature
+                .as_str()
+                .expect("Parsing of OK message : fail to convert sig to str")
+                .to_string(),
+            None => return None,
+        };
+        let pubkey: ed25519PublicKey = ed25519PublicKey::from_base58(
+            "969qRJs8KhsnkyzqarpL4RKZGMdVKNbZgu8fhsigM7Lj",
+        ).expect("fail to create default pubkey !");
+        let signature: Option<duniter_crypto::keys::ed25519::Signature> = Some(
+            duniter_crypto::keys::Signature::from_base64(&signature)
+                .expect("fail to parse signature of OK message !"),
+        );
+        Some(WS2POkMessageV1 {
+            currency,
+            pubkey,
+            challenge: "".to_string(),
+            signature,
+        })
+    }
+    fn to_raw(&self) -> String {
+        format!(
+            "WS2P:OK:{}:{}:{}",
+            self.currency, self.pubkey, self.challenge
+        )
+    }
+    fn verify(&self) -> bool {
+        self.pubkey
+            .verify(self.to_raw().as_bytes(), &self.signature.unwrap())
+    }
+}
+
+impl Serialize for WS2POkMessageV1 {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let mut connect_message_in_json = serializer.serialize_struct("message", 2)?;
+        connect_message_in_json.serialize_field("auth", "OK")?;
+        connect_message_in_json.serialize_field(
+            "sig",
+            &self
+                .signature
+                .expect("Fail to serialize OK message : the signature field is set to None !")
+                .to_string(),
+        )?;
+        connect_message_in_json.end()
+    }
+}
diff --git a/ws2p/serializer.rs b/ws2p/serializer.rs
new file mode 100644
index 0000000000000000000000000000000000000000..f780006811a746eb6e6c93426abd9ed65d7a0f1f
--- /dev/null
+++ b/ws2p/serializer.rs
@@ -0,0 +1,21 @@
+extern crate serde_json;
+
+use duniter_network::network_head::*;
+
+use std::ops::Deref;
+
+pub fn serialize_head(head: NetworkHead) -> serde_json::Value {
+    match head {
+        NetworkHead::V2(box_head_v2) => {
+            let head_v2 = box_head_v2.deref();
+            json!({
+                "message": head_v2.message.to_string(),
+                "sig": head_v2.sig.to_string(),
+                "messageV2": head_v2.message_v2.to_string(),
+                "sigV2": head_v2.sig_v2.to_string(),
+                "step": head_v2.step + 1
+            })
+        }
+        _ => panic!("HEAD version not supported !"),
+    }
+}
diff --git a/ws2p/test.db b/ws2p/test.db
new file mode 100644
index 0000000000000000000000000000000000000000..e14c9a19404e0e792e709cf7079a7e068f3cfb13
Binary files /dev/null and b/ws2p/test.db differ
diff --git a/ws2p/ws2p_connection.rs b/ws2p/ws2p_connection.rs
new file mode 100644
index 0000000000000000000000000000000000000000..a36f820971f15c46b5774c90dd338cc2bc570ad3
--- /dev/null
+++ b/ws2p/ws2p_connection.rs
@@ -0,0 +1,403 @@
+extern crate serde_json;
+extern crate websocket;
+
+use duniter_crypto::keys::ed25519;
+use duniter_crypto::keys::PublicKey;
+use duniter_dal::parsers::blocks::parse_json_block;
+use duniter_module::ModuleReqId;
+use duniter_network::network_endpoint::{NetworkEndpoint, NetworkEndpointApi};
+use duniter_network::{NetworkDocument, NodeUUID};
+use std::fmt::Debug;
+use std::net::TcpStream;
+
+use super::{NodeFullId, WS2PAckMessageV1, WS2PConnectMessageV1, WS2PMessage, WS2POkMessageV1};
+
+#[derive(Debug, Copy, Clone)]
+pub enum WS2POrderForListeningThread {
+    Close,
+}
+
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub enum WS2PConnectionState {
+    NeverTry = 0,
+    TryToOpenWS = 1,
+    WSError = 2,
+    TryToSendConnectMess = 3,
+    Unreachable = 4,
+    WaitingConnectMess = 5,
+    NoResponse = 6,
+    ConnectMessOk = 7,
+    OkMessOkWaitingAckMess = 8,
+    AckMessOk = 9,
+    Denial = 10,
+    Close = 11,
+    Established = 12,
+}
+
+impl From<u32> for WS2PConnectionState {
+    fn from(integer: u32) -> Self {
+        match integer {
+            1 | 2 => WS2PConnectionState::WSError,
+            3 | 4 => WS2PConnectionState::Unreachable,
+            5 | 6 => WS2PConnectionState::NoResponse,
+            7 | 8 | 9 | 10 => WS2PConnectionState::Denial,
+            11 | 12 => WS2PConnectionState::Close,
+            _ => WS2PConnectionState::NeverTry,
+        }
+    }
+}
+
+impl WS2PConnectionState {
+    pub fn from_u32(integer: u32, from_db: bool) -> Self {
+        if from_db {
+            WS2PConnectionState::from(integer)
+        } else {
+            match integer {
+                1 => WS2PConnectionState::TryToOpenWS,
+                2 => WS2PConnectionState::WSError,
+                3 | 4 => WS2PConnectionState::Unreachable,
+                5 | 6 => WS2PConnectionState::NoResponse,
+                7 => WS2PConnectionState::ConnectMessOk,
+                8 => WS2PConnectionState::OkMessOkWaitingAckMess,
+                9 => WS2PConnectionState::AckMessOk,
+                10 => WS2PConnectionState::Denial,
+                11 => WS2PConnectionState::Close,
+                12 => WS2PConnectionState::Established,
+                _ => WS2PConnectionState::NeverTry,
+            }
+        }
+    }
+    pub fn to_u32(&self) -> u32 {
+        match *self {
+            WS2PConnectionState::NeverTry => 0,
+            _ => 1,
+        }
+    }
+}
+
+pub struct WebsocketSender(pub websocket::sender::Writer<TcpStream>);
+
+impl Debug for WebsocketSender {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+        write!(f, "WebsocketSender {{ }}")
+    }
+}
+
+#[derive(Debug)]
+pub enum WS2PConnectionMessagePayload {
+    FailOpenWS,
+    WrongUrl,
+    FailToSplitWS,
+    TryToSendConnectMess,
+    FailSendConnectMess,
+    WebsocketOk(WebsocketSender),
+    NegociationTimeout,
+    ValidConnectMessage(String, WS2PConnectionState),
+    ValidAckMessage(String, WS2PConnectionState),
+    ValidOk(WS2PConnectionState),
+    DalRequest(ModuleReqId, serde_json::Value),
+    PeerCard(serde_json::Value, Vec<NetworkEndpoint>),
+    Heads(Vec<serde_json::Value>),
+    Document(NetworkDocument),
+    ReqResponse(ModuleReqId, serde_json::Value),
+    InvalidMessage,
+    WrongFormatMessage,
+    UnknowMessage,
+    Timeout,
+    Close,
+}
+
+#[derive(Debug)]
+pub struct WS2PConnectionMessage(pub NodeFullId, pub WS2PConnectionMessagePayload);
+
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub enum WS2PCloseConnectionReason {
+    AuthMessInvalidSig,
+    NegociationTimeout,
+    Timeout,
+    Unknow,
+}
+
+#[derive(Debug, Clone)]
+pub struct WS2PConnectionMetaData {
+    pub state: WS2PConnectionState,
+    pub remote_uuid: Option<NodeUUID>,
+    pub remote_pubkey: Option<ed25519::PublicKey>,
+    pub challenge: String,
+    pub remote_challenge: String,
+    pub current_blockstamp: Option<(u32, String)>,
+}
+
+#[derive(Debug, Clone)]
+pub struct WS2PDatasForListeningThread {
+    pub conn_meta_datas: WS2PConnectionMetaData,
+    pub currency: String,
+    pub key_pair: ed25519::KeyPair,
+}
+
+impl WS2PConnectionMetaData {
+    pub fn new(challenge: String) -> Self {
+        WS2PConnectionMetaData {
+            state: WS2PConnectionState::WaitingConnectMess,
+            remote_uuid: None,
+            remote_pubkey: None,
+            challenge,
+            remote_challenge: "".to_string(),
+            current_blockstamp: None,
+        }
+    }
+
+    pub fn node_full_id(&self) -> NodeFullId {
+        NodeFullId(
+            self.clone()
+                .remote_uuid
+                .expect("Fail to get NodeFullId : remote_uuid is None !"),
+            self.remote_pubkey
+                .expect("Fail to get NodeFullId : remote_pubkey is None !"),
+        )
+    }
+    pub fn parse_and_check_incoming_message(
+        &mut self,
+        currency: &str,
+        key_pair: ed25519::KeyPair,
+        m: &serde_json::Value,
+    ) -> WS2PConnectionMessagePayload {
+        if let Some(s) = m.get("auth") {
+            if s.is_string() {
+                match s.as_str().unwrap() {
+                    "CONNECT" => {
+                        let message = WS2PConnectMessageV1::parse(m, currency.to_string())
+                            .expect("Failed to parsing CONNECT Message !");
+                        if message.verify() && message.pubkey == self.remote_pubkey.unwrap() {
+                            match self.state {
+                                WS2PConnectionState::WaitingConnectMess => {
+                                    trace!("CONNECT sig is valid.");
+                                    self.state = WS2PConnectionState::ConnectMessOk;
+                                    self.remote_challenge = message.challenge.clone();
+                                    let mut response = WS2PAckMessageV1 {
+                                        currency: currency.to_string(),
+                                        pubkey: key_pair.pubkey,
+                                        challenge: self.remote_challenge.clone(),
+                                        signature: None,
+                                    };
+                                    response.signature = Some(response.sign(key_pair));
+                                    return WS2PConnectionMessagePayload::ValidConnectMessage(
+                                        serde_json::to_string(&response).unwrap(),
+                                        self.state.clone(),
+                                    );
+                                }
+                                _ => return WS2PConnectionMessagePayload::InvalidMessage,
+                            }
+                        } else {
+                            warn!("The signature of message CONNECT is invalid !")
+                        }
+                    }
+                    "ACK" => {
+                        let mut message = WS2PAckMessageV1::parse(m, currency.to_string())
+                            .expect("Failed to parsing ACK Message !");
+                        message.challenge = self.challenge.to_string();
+                        if message.verify() {
+                            trace!("ACK sig is valid.");
+                            self.state = match self.state {
+                                WS2PConnectionState::ConnectMessOk => {
+                                    WS2PConnectionState::AckMessOk
+                                }
+                                WS2PConnectionState::OkMessOkWaitingAckMess => {
+                                    WS2PConnectionState::Established
+                                }
+                                _ => return WS2PConnectionMessagePayload::InvalidMessage,
+                            };
+                            let mut response = WS2POkMessageV1 {
+                                currency: currency.to_string(),
+                                pubkey: key_pair.pubkey,
+                                challenge: self.challenge.to_string(),
+                                signature: None,
+                            };
+                            response.signature = Some(response.sign(key_pair));
+                            return WS2PConnectionMessagePayload::ValidAckMessage(
+                                serde_json::to_string(&response).unwrap(),
+                                self.state.clone(),
+                            );
+                        } else {
+                            warn!("The signature of message ACK is invalid !")
+                        }
+                    }
+                    "OK" => {
+                        let mut message = WS2POkMessageV1::parse(m, currency.to_string())
+                            .expect("Failed to parsing OK Message !");
+                        trace!("Received OK");
+                        message.challenge = self.remote_challenge.to_string();
+                        message.pubkey = self.remote_pubkey.expect("fail to get remote pubkey !");
+                        if message.verify() {
+                            trace!("OK sig is valid.");
+                            match self.state {
+                                WS2PConnectionState::ConnectMessOk => {
+                                    self.state = WS2PConnectionState::OkMessOkWaitingAckMess;
+                                    return WS2PConnectionMessagePayload::ValidOk(
+                                        self.state.clone(),
+                                    );
+                                }
+                                WS2PConnectionState::AckMessOk => {
+                                    info!(
+                                        "WS2P Connection established with the key {}",
+                                        self.remote_pubkey.expect("fail to get remote pubkey !")
+                                    );
+                                    self.state = WS2PConnectionState::Established;
+                                    return WS2PConnectionMessagePayload::ValidOk(
+                                        self.state.clone(),
+                                    );
+                                }
+                                _ => {
+                                    warn!("WS2P Error : OK message not expected !");
+                                    return WS2PConnectionMessagePayload::InvalidMessage;
+                                }
+                            }
+                        } else {
+                            warn!("The signature of message OK is invalid !");
+                            return WS2PConnectionMessagePayload::InvalidMessage;
+                        }
+                    }
+                    &_ => debug!("unknow message"),
+                };
+            }
+        };
+        if let Some(req_id) = m.get("reqId") {
+            match req_id.as_str() {
+                Some(req_id) => match m.get("body") {
+                    Some(body) => {
+                        trace!("WS2P : Receive DAL Request from {}.", self.node_full_id());
+                        match u32::from_str_radix(req_id, 16) {
+                            Ok(req_id) => {
+                                return WS2PConnectionMessagePayload::DalRequest(
+                                    ModuleReqId(req_id),
+                                    body.clone(),
+                                );
+                            }
+                            Err(_) => return WS2PConnectionMessagePayload::WrongFormatMessage,
+                        }
+                    }
+                    None => {
+                        warn!("WS2P Error : invalid format : Request must contain a field body !");
+                        return WS2PConnectionMessagePayload::WrongFormatMessage;
+                    }
+                },
+                None => {
+                    warn!("WS2P Error : invalid format : Request must contain a field body !");
+                    return WS2PConnectionMessagePayload::WrongFormatMessage;
+                }
+            }
+        }
+        if let Some(req_id) = m.get("resId") {
+            match req_id.as_str() {
+                Some(req_id_str) => match m.get("body") {
+                    Some(body) => match u32::from_str_radix(req_id_str, 16) {
+                        Ok(req_id) => {
+                            return WS2PConnectionMessagePayload::ReqResponse(
+                                ModuleReqId(req_id),
+                                body.clone(),
+                            )
+                        }
+                        Err(_) => return WS2PConnectionMessagePayload::WrongFormatMessage,
+                    },
+                    None => match m.get("err") {
+                        Some(err) => warn!("Error in req : {:?}", err),
+                        None => return WS2PConnectionMessagePayload::WrongFormatMessage,
+                    },
+                },
+                None => return WS2PConnectionMessagePayload::WrongFormatMessage,
+            }
+        }
+        if let Some(body) = m.get("body") {
+            match body.get("name") {
+                Some(s) => if s.is_string() {
+                    match s.as_str().unwrap() {
+                        "BLOCK" => match body.get("block") {
+                            Some(block) => {
+                                if let Some(network_block) = parse_json_block(&block) {
+                                    return WS2PConnectionMessagePayload::Document(
+                                        NetworkDocument::Block(network_block),
+                                    );
+                                } else {
+                                    info!("WS2PSignal: receive invalid block (wrong format).");
+                                };
+                            }
+                            None => return WS2PConnectionMessagePayload::WrongFormatMessage,
+                        },
+                        "HEAD" => match body.get("heads") {
+                            Some(heads) => match heads.as_array() {
+                                Some(heads_array) => {
+                                    return WS2PConnectionMessagePayload::Heads(heads_array.clone())
+                                }
+                                None => return WS2PConnectionMessagePayload::WrongFormatMessage,
+                            },
+                            None => return WS2PConnectionMessagePayload::WrongFormatMessage,
+                        },
+                        "PEER" => return self.parse_and_check_peer_message(body),
+                        "CERTIFICATION" => {
+                            trace!("WS2P : Receive CERTIFICATION from {}.", self.node_full_id());
+                            /*return WS2PConnectionMessagePayload::Document(
+                                NetworkDocument::Certification(_)
+                            );*/
+                        }
+                        _ => {
+                            /*trace!(
+                                "WS2P : Receive Unknow Message from {}.",
+                                self.node_full_id()
+                            );*/
+                            return WS2PConnectionMessagePayload::UnknowMessage;
+                        }
+                    };
+                },
+                None => {
+                    warn!("WS2P Error : invalid format : Body must contain a field name !");
+                    return WS2PConnectionMessagePayload::WrongFormatMessage;
+                }
+            }
+        };
+        WS2PConnectionMessagePayload::UnknowMessage
+    }
+
+    pub fn parse_and_check_peer_message(
+        &mut self,
+        body: &serde_json::Value,
+    ) -> WS2PConnectionMessagePayload {
+        match body.get("peer") {
+            Some(peer) => match peer.get("pubkey") {
+                Some(raw_pubkey) => match PublicKey::from_base58(raw_pubkey.as_str().unwrap_or(""))
+                {
+                    Ok(pubkey) => {
+                        let mut ws2p_endpoints: Vec<NetworkEndpoint> = Vec::new();
+                        match peer.get("endpoints") {
+                            Some(endpoints) => match endpoints.as_array() {
+                                Some(array_endpoints) => {
+                                    for endpoint in array_endpoints {
+                                        if let Some(ep) = NetworkEndpoint::parse_from_raw(
+                                            endpoint.as_str().unwrap_or(""),
+                                            pubkey,
+                                            0,
+                                            0,
+                                        ) {
+                                            if ep.api() == NetworkEndpointApi(String::from("WS2P"))
+                                            {
+                                                ws2p_endpoints.push(ep);
+                                            }
+                                        }
+                                    }
+                                    WS2PConnectionMessagePayload::PeerCard(
+                                        body.clone(),
+                                        ws2p_endpoints,
+                                    )
+                                }
+                                None => WS2PConnectionMessagePayload::WrongFormatMessage,
+                            },
+                            None => WS2PConnectionMessagePayload::WrongFormatMessage,
+                        }
+                    }
+                    Err(_) => WS2PConnectionMessagePayload::WrongFormatMessage,
+                },
+                None => WS2PConnectionMessagePayload::WrongFormatMessage,
+            },
+            None => WS2PConnectionMessagePayload::WrongFormatMessage,
+        }
+    }
+}
diff --git a/ws2p/ws2p_db.rs b/ws2p/ws2p_db.rs
new file mode 100644
index 0000000000000000000000000000000000000000..f487710d74802ccdec80588d990b7285d9fb9697
--- /dev/null
+++ b/ws2p/ws2p_db.rs
@@ -0,0 +1,138 @@
+extern crate duniter_crypto;
+extern crate duniter_documents;
+extern crate duniter_message;
+extern crate duniter_module;
+extern crate duniter_network;
+extern crate serde_json;
+extern crate sqlite;
+extern crate websocket;
+
+use duniter_crypto::keys::{ed25519, PublicKey};
+use duniter_network::network_endpoint::{NetworkEndpoint, NetworkEndpointApi};
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum EndpointApi {
+    WS2P,
+    //WS2PS,
+    //WS2PTOR,
+    //DASA,
+    //BMA,
+    //BMAS,
+}
+
+impl From<u32> for EndpointApi {
+    fn from(integer: u32) -> Self {
+        match integer {
+            _ => EndpointApi::WS2P,
+        }
+    }
+}
+
+pub fn string_to_api(api: &str) -> Option<EndpointApi> {
+    match api {
+        "WS2P" => Some(EndpointApi::WS2P),
+        //"WS2PS" => Some(EndpointApi::WS2PS),
+        //"WS2PTOR" => Some(EndpointApi::WS2PTOR),
+        //"DASA" => Some(EndpointApi::DASA),
+        //"BASIC_MERKLED_API" => Some(EndpointApi::BMA),
+        //"BMAS" => Some(EndpointApi::BMAS),
+        &_ => None,
+    }
+}
+
+pub fn api_to_integer(api: &NetworkEndpointApi) -> i64 {
+    match api.0.as_str() {
+        "WS2P" => 1,
+        //EndpointApi::WS2PS => 2,
+        //EndpointApi::WS2PTOR => 3,
+        //EndpointApi::DASA => 4,
+        //EndpointApi::BMA => 5,
+        //EndpointApi::BMAS => 6,
+        _ => 0,
+    }
+}
+
+pub fn get_endpoints_for_api(
+    db: &sqlite::Connection,
+    api: NetworkEndpointApi,
+) -> Vec<NetworkEndpoint> {
+    let mut cursor:sqlite::Cursor = db
+        .prepare("SELECT hash_full_id, status, node_id, pubkey, api, version, endpoint, last_check FROM endpoints WHERE api=? ORDER BY status DESC;")
+        .expect("get_endpoints_for_api() : Error in SQL request !")
+        .cursor();
+
+    cursor
+        .bind(&[sqlite::Value::Integer(api_to_integer(&api))])
+        .expect("get_endpoints_for_api() : Error in cursor binding !");
+    let mut endpoints = Vec::new();
+    while let Some(row) = cursor
+        .next()
+        .expect("get_endpoints_for_api() : Error in cursor.next()")
+    {
+        let raw_ep = row[6].as_string().unwrap().to_string();
+        let ep_issuer = ed25519::PublicKey::from_base58(row[3].as_string().unwrap()).unwrap();
+        let mut ep = match NetworkEndpoint::parse_from_raw(
+            &raw_ep,
+            ep_issuer,
+            row[1].as_integer().unwrap() as u32,
+            row[7].as_integer().unwrap() as u64,
+        ) {
+            Some(ep) => ep,
+            None => panic!(format!("Fail to parse endpoint : {}", raw_ep)),
+        };
+        ep.set_status(row[1].as_integer().unwrap() as u32);
+        ep.set_last_check(row[7].as_integer().unwrap() as u64);
+
+        endpoints.push(ep);
+    }
+    endpoints
+}
+
+pub fn write_endpoint(
+    db: &sqlite::Connection,
+    endpoint: &NetworkEndpoint,
+    new_status: u32,
+    new_last_check: u64,
+) {
+    let hash_full_id = endpoint
+        .node_full_id()
+        .expect("Fail to write endpoint : node_full_id() return None !")
+        .sha256();
+    // Check if endpoint it's already written
+    let mut cursor: sqlite::Cursor = db
+        .prepare("SELECT status FROM endpoints WHERE hash_full_id=? ORDER BY status DESC;")
+        .expect("write_endpoint() : Error in SQL request !")
+        .cursor();
+    cursor
+        .bind(&[sqlite::Value::String(hash_full_id.to_string())])
+        .expect("write_endpoint() : Error in cursor binding !");
+
+    // If endpoint it's already written, update status
+    if let Some(row) = cursor
+        .next()
+        .expect("write_endpoint() : Error in cursor.next()")
+    {
+        if row[0].as_integer().expect("fail to read ep status !") as u32 != endpoint.status() {
+            db.execute(format!(
+                "UPDATE endpoints SET status={} WHERE hash_full_id='{}'",
+                endpoint.status(),
+                hash_full_id
+            )).expect("Fail to parse SQL request update endpoint  status !");
+        }
+    } else {
+        if let &NetworkEndpoint::V1(ref ep_v1) = endpoint {
+            db
+                    .execute(
+                        format!(
+                            "INSERT INTO endpoints (hash_full_id, status, node_id, pubkey, api, version, endpoint, last_check) VALUES ('{}', {}, {}, '{}', {}, {}, '{}', {});",
+                            ep_v1.hash_full_id.expect("ep_v1.hash_full_id = None"), new_status, ep_v1.node_id.expect("ep_v1.node_id = None").0,
+                            ep_v1.issuer.to_string(), api_to_integer(&ep_v1.api),
+                            ep_v1.version, ep_v1.raw_endpoint, new_last_check
+                        )
+                    )
+                    .expect("Fail to parse SQL request INSERT endpoint !");
+        } else {
+            panic!("write_endpoint() : Endpoint version is not supported !")
+        }
+    }
+}
diff --git a/ws2p/ws2p_requests.rs b/ws2p/ws2p_requests.rs
new file mode 100644
index 0000000000000000000000000000000000000000..722b0154b8a156a119774775671c41ab1a6f7eb6
--- /dev/null
+++ b/ws2p/ws2p_requests.rs
@@ -0,0 +1,44 @@
+extern crate duniter_crypto;
+extern crate duniter_network;
+extern crate serde;
+extern crate serde_json;
+
+use duniter_network::NetworkRequest;
+
+pub fn network_request_to_json(request: &NetworkRequest) -> serde_json::Value {
+    let (request_id, request_type, request_params) = match *request {
+        NetworkRequest::GetCurrent(ref req_full_id, _receiver) => {
+            (req_full_id.1, "CURRENT", json!({}))
+        }
+        NetworkRequest::GetBlocks(ref req_full_id, _receiver, count, from_mumber) => (
+            req_full_id.1,
+            "BLOCKS_CHUNK",
+            json!({
+                    "count": count,
+                    "fromNumber": from_mumber
+                }),
+        ),
+        NetworkRequest::GetRequirementsPending(ref req_full_id, _receiver, min_cert) => (
+            req_full_id.1,
+            "WOT_REQUIREMENTS_OF_PENDING",
+            json!({ "minCert": min_cert }),
+        ),
+        NetworkRequest::GetConsensus(_) => {
+            panic!("GetConsensus() request must be not convert to json !");
+        }
+        NetworkRequest::GetHeadsCache(_) => {
+            panic!("GetHeadsCache() request must be not convert to json !");
+        }
+        NetworkRequest::GetEndpoints(_) => {
+            panic!("GetEndpoints() request must be not convert to json !");
+        }
+    };
+
+    json!({
+            "reqId": request_id,
+            "body" : {
+                "name": request_type,
+                "params": request_params
+            }
+        })
+}