diff --git a/app/lib/common-libs/constants.ts b/app/lib/common-libs/constants.ts
index 223c77795a627cd1db7962b504fc283d0676f260..9b15d71a6f548bdc0923e0046e837a55142902cc 100755
--- a/app/lib/common-libs/constants.ts
+++ b/app/lib/common-libs/constants.ts
@@ -309,6 +309,7 @@ export const CommonConstants = {
 
   ARCHIVES_BLOCKS_CHUNK: 250,
   SYNC_BLOCKS_CHUNK: 250,
+  MILESTONES_PER_PAGE: 50,
   CHUNK_PREFIX: 'chunk_',
   BLOCKS_IN_MEMORY_MAX: 288 * 60, // 288 = 1 day
 
diff --git a/app/lib/common-libs/errors.ts b/app/lib/common-libs/errors.ts
index 0db0e8a14078f5cf61504c8c27967ba065e547e5..412c07ccd94c8fb5d6b6117af5bffab20a711a61 100755
--- a/app/lib/common-libs/errors.ts
+++ b/app/lib/common-libs/errors.ts
@@ -1,4 +1,3 @@
-
 export enum DataErrors {
   INVALID_LEVELDB_IINDEX_DATA_WAS_KICKED,
   INVALID_LEVELDB_IINDEX_DATA_TO_BE_KICKED,
@@ -26,5 +25,6 @@ export enum DataErrors {
   CANNOT_REAPPLY_NO_CURRENT_BLOCK,
   CANNOT_REVERT_NO_CURRENT_BLOCK,
   BLOCK_TO_REVERT_NOT_FOUND,
-  MEMBER_NOT_FOUND
+  MEMBER_NOT_FOUND,
+  MILESTONE_BLOCK_NOT_FOUND
 }
diff --git a/app/modules/bma/index.ts b/app/modules/bma/index.ts
index 7bfd78702290cfb9590907750ebe22282a3b9420..b28c5025d860066d0f5d47fdde748a08a264e832 100644
--- a/app/modules/bma/index.ts
+++ b/app/modules/bma/index.ts
@@ -149,19 +149,6 @@ export const BmaDependency = {
         if (program.upnp === true) {
           conf.upnp = true;
         }
-
-        // Configuration errors
-        if (!conf.nobma) {
-          if(!conf.ipv4 && !conf.ipv6){
-            throw new Error("No interface to listen to.");
-          }
-          if(!conf.remoteipv4 && !conf.remoteipv6 && !conf.remotehost){
-            throw new Error('No interface for remote contact.');
-          }
-          if (!conf.remoteport) {
-            throw new Error('No port for remote contact.');
-          }
-        }
       },
 
       beforeSave: async (conf:NetworkConfDTO, program:any) => {
@@ -175,6 +162,18 @@ export const BmaDependency = {
 
     service: {
       input: (server:Server, conf:NetworkConfDTO, logger:any) => {
+        // Configuration errors
+        if (!conf.nobma) {
+          if(!conf.ipv4 && !conf.ipv6){
+            throw new Error("BMA: no interface to listen to.");
+          }
+          if(!conf.remoteipv4 && !conf.remoteipv6 && !conf.remotehost){
+            throw new Error('BMA: no interface for remote contact.');
+          }
+          if (!conf.remoteport) {
+            throw new Error('BMA: no port for remote contact.');
+          }
+        }
         if (!conf.nobma) {
           server.addEndpointsDefinitions(() => Promise.resolve(getEndpoint(conf)))
           server.addWrongEndpointFilter((endpoints:string[]) => getWrongEndpoints(endpoints, server.conf.pair.pub))
diff --git a/app/modules/bma/lib/bma.ts b/app/modules/bma/lib/bma.ts
index 129bcbc15fcfb5ed4af0ff47a360fd024776a73a..66c16dde7f785ff68f145fc2f37f9848ba75f860 100644
--- a/app/modules/bma/lib/bma.ts
+++ b/app/modules/bma/lib/bma.ts
@@ -65,6 +65,8 @@ export const bma = function(server:Server, interfaces:NetworkInterface[]|null, h
     httpMethods.httpPOST( '/blockchain/block',                      (req:any) => blockchain.parseBlock(req),                BMALimitation.limitAsHighUsage());
     httpMethods.httpGET(  '/blockchain/block/:number',              (req:any) => blockchain.promoted(req),                  BMALimitation.limitAsHighUsage());
     httpMethods.httpGET(  '/blockchain/blocks/:count/:from',        (req:any) => blockchain.blocks(req),                    BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/blockchain/milestones',                 (req:any) => blockchain.milestones(req),                BMALimitation.limitAsHighUsage());
+    httpMethods.httpGET(  '/blockchain/milestones/:page',           (req:any) => blockchain.milestones(req),                BMALimitation.limitAsHighUsage());
     httpMethods.httpGET(  '/blockchain/current',                    (req:any) => blockchain.current(),                      BMALimitation.limitAsHighUsage());
     httpMethods.httpGET(  '/blockchain/hardship/:search',           (req:any) => blockchain.hardship(req),                  BMALimitation.limitAsHighUsage());
     httpMethods.httpGET(  '/blockchain/difficulties',               (req:any) => blockchain.difficulties(),                 BMALimitation.limitAsHighUsage());
diff --git a/app/modules/bma/lib/constants.ts b/app/modules/bma/lib/constants.ts
index 2488dd573ac3579d7aa7dd097925e8029f6dd0ba..4ba022f2920eed2a23a4f16948bc6b2e7b907ee3 100644
--- a/app/modules/bma/lib/constants.ts
+++ b/app/modules/bma/lib/constants.ts
@@ -52,7 +52,8 @@ export const BMAConstants = {
     NO_CURRENT_BLOCK:                     { httpCode: 404, uerr: { ucode: 2010, message: "No current block" }},
     PEER_NOT_FOUND:                       { httpCode: 404, uerr: { ucode: 2012, message: "Peer not found" }},
     NO_IDTY_MATCHING_PUB_OR_UID:          { httpCode: 404, uerr: { ucode: 2021, message: "No identity matching this pubkey or uid" }},
-    TX_NOT_FOUND:                         { httpCode: 400, uerr: { ucode: 2034, message: 'Transaction not found' }}
+    TX_NOT_FOUND:                         { httpCode: 400, uerr: { ucode: 2034, message: 'Transaction not found' }},
+    INCORRECT_PAGE_NUMBER:                { httpCode: 400, uerr: { ucode: 2035, message: 'Incorrect page number' }}
 
     // New errors: range 3000-4000
   }
diff --git a/app/modules/bma/lib/controllers/blockchain.ts b/app/modules/bma/lib/controllers/blockchain.ts
index 2fc5fa748dce9a9c453a532cec79b083c30dbf88..d5afc6273bc4ca3c0998b57d01057ad5f2db24f4 100644
--- a/app/modules/bma/lib/controllers/blockchain.ts
+++ b/app/modules/bma/lib/controllers/blockchain.ts
@@ -24,15 +24,16 @@ import {
   HttpHardship,
   HttpMembership,
   HttpMemberships,
+  HttpMilestonePage,
   HttpParameters,
   HttpStat
 } from "../dtos"
 import {TransactionDTO} from "../../../../lib/dto/TransactionDTO"
 import {DataErrors} from "../../../../lib/common-libs/errors"
 import {Underscore} from "../../../../lib/common-libs/underscore"
+import * as toJson from "../tojson"
 
 const http2raw         = require('../http2raw');
-const toJson = require('../tojson');
 
 export class BlockchainBinding extends AbstractController {
 
@@ -168,6 +169,11 @@ export class BlockchainBinding extends AbstractController {
     }))
   }
 
+  async milestones(req: any): Promise<HttpMilestonePage> {
+    const page = ParametersService.getPage(req)
+    return this.server.milestones(page)
+  }
+
   async current(): Promise<HttpBlock> {
     const current = await this.server.dal.getCurrentBlockOrNull();
     if (!current) throw BMAConstants.ERRORS.NO_CURRENT_BLOCK;
diff --git a/app/modules/bma/lib/dtos.ts b/app/modules/bma/lib/dtos.ts
index d0b7f27d19e7f89c896a36799fc440b2013712cf..63805afe06753383b814dab4c12a4f1a29aedef9 100644
--- a/app/modules/bma/lib/dtos.ts
+++ b/app/modules/bma/lib/dtos.ts
@@ -969,3 +969,25 @@ export interface HttpSandboxes {
 export const LogLink = {
   link: String
 };
+
+export interface HttpMilestonePage {
+  totalPages: number
+  chunkSize: number
+  milestonesPerPage: number
+  currentPage?: number
+  blocks?: HttpBlock[]
+}
+
+export const Milestones = {
+  totalPages: Number,
+  chunkSize: Number,
+  milestonesPerPage: Number,
+  currentPage: Number,
+  "blocks": [Block]
+}
+
+export const MilestonesPage = {
+  totalPages: Number,
+  chunkSize: Number,
+  milestonesPerPage: Number,
+}
diff --git a/app/modules/bma/lib/parameters.ts b/app/modules/bma/lib/parameters.ts
index 3db765af14be26c83ebc4ce899d08953667618ba..9126088fad473585e7d20f97b4e7282afb9eeab8 100644
--- a/app/modules/bma/lib/parameters.ts
+++ b/app/modules/bma/lib/parameters.ts
@@ -73,6 +73,17 @@ export class ParametersService {
     return parseInt(req.params.minsig)
   }
 
+  static getPage(req:any): number|undefined {
+    if(!req.params.page){
+      return undefined
+    }
+    const matches = req.params.page.match(/\d+/)
+    if(!matches){
+      throw Error("`page` format is incorrect, must be an integer")
+    }
+    return parseInt(req.params.page)
+  }
+
   static getPubkey = function (req:any, callback:any){
     if(!req.params.pubkey){
       callback('Parameter `pubkey` is required');
diff --git a/app/modules/crawler/lib/contacter.ts b/app/modules/crawler/lib/contacter.ts
index 75f37f0e98ce38da4c152ec7033da4dbcecf2614..105a8c2df2734bfc8a376c7856862bdf941695cf 100644
--- a/app/modules/crawler/lib/contacter.ts
+++ b/app/modules/crawler/lib/contacter.ts
@@ -57,6 +57,14 @@ export class Contacter {
   getCurrent() {
     return this.get('/blockchain/current', dtos.Block)
   }
+
+  getMilestonesPage() {
+    return this.get('/blockchain/milestones', dtos.MilestonesPage)
+  }
+
+  getMilestones(page: number) {
+    return this.get('/blockchain/milestones/' + page, dtos.Milestones)
+  }
   
   getPeer() {
     return this.get('/network/peering', dtos.Peer)
diff --git a/app/modules/crawler/lib/sync/AbstractSynchronizer.ts b/app/modules/crawler/lib/sync/AbstractSynchronizer.ts
index 803088c44b03101c2178b43b2ca6927689124d41..61455b3812fe81fc0ff7d7e8d1c5536302ef28d7 100644
--- a/app/modules/crawler/lib/sync/AbstractSynchronizer.ts
+++ b/app/modules/crawler/lib/sync/AbstractSynchronizer.ts
@@ -28,6 +28,7 @@ export abstract class AbstractSynchronizer {
   abstract initWithKnownLocalAndToAndCurrency(to: number, localNumber: number, currency: string): Promise<void>
   abstract getCurrent(): Promise<BlockDTO|null>
   abstract getBlock(number: number): Promise<BlockDTO|null>
+  abstract getMilestone(number: number): Promise<BlockDTO|null>
   abstract p2pDownloader(): ISyncDownloader
   abstract fsDownloader(): ISyncDownloader
   abstract syncPeers(fullSync:boolean, to?:number): Promise<void>
diff --git a/app/modules/crawler/lib/sync/BMARemoteContacter.ts b/app/modules/crawler/lib/sync/BMARemoteContacter.ts
index 1a3f5be406d1605215fef387fcb33d5e1d546a7e..14fdbdebabae43ad000007a55207a70f44c76a72 100644
--- a/app/modules/crawler/lib/sync/BMARemoteContacter.ts
+++ b/app/modules/crawler/lib/sync/BMARemoteContacter.ts
@@ -37,6 +37,14 @@ export class BMARemoteContacter implements IRemoteContacter {
     return this.contacter.getBlocks(count, from)
   }
 
+  getMilestones(page: number): Promise<{ chunkSize: number; totalPages: number; currentPage: number; milestonesPerPage: number; blocks: BlockDTO[] }> {
+    return this.contacter.getMilestones(page)
+  }
+
+  getMilestonesPage(): Promise<{ chunkSize: number; totalPages: number; milestonesPerPage: number }> {
+    return this.contacter.getMilestonesPage()
+  }
+
   async getPeers(): Promise<(JSONDBPeer|null)[]> {
     return (await this.contacter.getPeersArray()).peers
   }
diff --git a/app/modules/crawler/lib/sync/IRemoteContacter.ts b/app/modules/crawler/lib/sync/IRemoteContacter.ts
index 42bbc491018fff7edca81cadcb286f4febfd4f50..ef0c2e706fbfcb758e99499f5fd06c0e8c83c534 100644
--- a/app/modules/crawler/lib/sync/IRemoteContacter.ts
+++ b/app/modules/crawler/lib/sync/IRemoteContacter.ts
@@ -25,6 +25,10 @@ export interface IRemoteContacter {
 
   getBlock(number: number): Promise<BlockDTO|null>
 
+  getMilestonesPage(): Promise<{ chunkSize: number, totalPages: number, milestonesPerPage: number }>
+
+  getMilestones(page: number): Promise<{ chunkSize: number, totalPages: number, currentPage: number, milestonesPerPage: number, blocks: BlockDTO[] }>
+
   getBlocks(count: number, from: number): Promise<BlockDTO[]>
 
   getRequirementsPending(number: number): Promise<HttpRequirements>
diff --git a/app/modules/crawler/lib/sync/LocalPathSynchronizer.ts b/app/modules/crawler/lib/sync/LocalPathSynchronizer.ts
index 3098f2ff180435fb259514308c84371333d1bbda..3bb3881c01038054e79f051ddd1bb9342b322a8e 100644
--- a/app/modules/crawler/lib/sync/LocalPathSynchronizer.ts
+++ b/app/modules/crawler/lib/sync/LocalPathSynchronizer.ts
@@ -105,6 +105,10 @@ export class LocalPathSynchronizer extends AbstractSynchronizer {
     return chunk[position]
   }
 
+  getMilestone(number: number) {
+    return this.getBlock(number)
+  }
+
   async syncPeers(fullSync: boolean, to?: number): Promise<void> {
     // Does nothing on LocalPathSynchronizer
   }
diff --git a/app/modules/crawler/lib/sync/P2PSyncDownloader.ts b/app/modules/crawler/lib/sync/P2PSyncDownloader.ts
index 489342dfc66b0d6464b3060765889fef831d9d48..96db98a2df962daab97956d4fd103351c8498f86 100644
--- a/app/modules/crawler/lib/sync/P2PSyncDownloader.ts
+++ b/app/modules/crawler/lib/sync/P2PSyncDownloader.ts
@@ -56,7 +56,7 @@ export class P2PSyncDownloader extends ASyncDownloader implements ISyncDownloade
     return this.p2pCandidates.filter(p => p.hasAvailableApi()).length
   }
 
-  private async waitForAvailableNodes(needed = 1): Promise<P2pCandidate[]> {
+  private async waitForAvailableNodesAndReserve(needed = 1): Promise<P2pCandidate[]> {
     let nodesToWaitFor = this.p2pCandidates.slice()
     let nodesAvailable: P2pCandidate[] = []
     let i = 0
@@ -67,7 +67,10 @@ export class P2PSyncDownloader extends ASyncDownloader implements ISyncDownloade
       nodesAvailable = nodesAvailable.concat(readyNodes)
       i++
     }
-    return nodesAvailable
+    return nodesAvailable.slice(0, needed).map(n => {
+      n.reserve()
+      return n
+    })
   }
 
   /**
@@ -78,7 +81,7 @@ export class P2PSyncDownloader extends ASyncDownloader implements ISyncDownloade
   private async getP2Pcandidates(chunkIndex: number): Promise<P2pCandidate[]> {
     return this.fifoPromise.pushFIFOPromise('getP2Pcandidates_' + getNanosecondsTime(), async () => {
       // We wait a bit to have some available nodes
-      const readyNodes = await this.waitForAvailableNodes()
+      const readyNodes = await this.waitForAvailableNodesAndReserve()
       // We remove the nodes impossible to reach (timeout)
       let byAvgAnswerTime = Underscore.sortBy(readyNodes, p => p.avgResponseTime())
       const parallelMax = Math.min(this.PARALLEL_PER_CHUNK, byAvgAnswerTime.length)
diff --git a/app/modules/crawler/lib/sync/RemoteSynchronizer.ts b/app/modules/crawler/lib/sync/RemoteSynchronizer.ts
index 0d33f7977ddced0baca9326c254a1184e0805ccf..7916d0c0157c813aa931883c49733bfc8b36ebc5 100644
--- a/app/modules/crawler/lib/sync/RemoteSynchronizer.ts
+++ b/app/modules/crawler/lib/sync/RemoteSynchronizer.ts
@@ -53,6 +53,10 @@ export class RemoteSynchronizer extends AbstractSynchronizer {
   private localNumber: number
   private watcher: Watcher
   private endpoint: string = ""
+  private hasMilestonesPages: boolean|undefined
+  private milestones: { [k: number]: BlockDTO } = {}
+  private milestonesPerPage = 1
+  private maxPage = 0
 
   constructor(
     private host: string,
@@ -247,6 +251,46 @@ export class RemoteSynchronizer extends AbstractSynchronizer {
     return this.node.getBlock(number)
   }
 
+  async getMilestone(number: number): Promise<BlockDTO|null> {
+    if (this.hasMilestonesPages === undefined) {
+      try {
+        const mlPage = await this.node.getMilestonesPage()
+        this.hasMilestonesPages = mlPage.chunkSize === this.chunkSize
+        this.milestonesPerPage = mlPage.milestonesPerPage
+        this.maxPage = mlPage.totalPages
+      } catch (e) {
+        this.hasMilestonesPages = false
+      }
+    }
+    if (!this.hasMilestonesPages) {
+      return this.getBlock(number)
+    }
+    if (this.milestones[number]) {
+      return this.milestones[number]
+    }
+
+    if ((number + 1) % this.chunkSize !== 0) {
+      // Something went wrong: we cannot rely on milestones method
+      this.hasMilestonesPages = false
+      return this.getBlock(number)
+    }
+    const chunkNumber = (number + 1) / this.chunkSize
+    const pageNumber = (chunkNumber - (chunkNumber % this.milestonesPerPage)) / this.milestonesPerPage + 1
+    if (pageNumber > this.maxPage) {
+      // The page is not available: we cannot rely on milestones method at this point
+      this.hasMilestonesPages = false
+      return this.getBlock(number)
+    }
+    const mlPage = await this.node.getMilestones(pageNumber)
+    mlPage.blocks.forEach(b => this.milestones[b.number] = b)
+    if (this.milestones[number]) {
+      return this.milestones[number]
+    }
+    // Even after the download, it seems we don't have our milestone. We will download normally.
+    this.hasMilestonesPages = false
+    return this.getBlock(number)
+  }
+
   static async test(host: string, port: number, keypair: Keypair): Promise<BlockDTO> {
     const syncApi = await RemoteSynchronizer.getSyncAPI([{ host, port }], keypair)
     const current = await syncApi.api.getCurrent()
diff --git a/app/modules/crawler/lib/sync/WS2PRemoteContacter.ts b/app/modules/crawler/lib/sync/WS2PRemoteContacter.ts
index a5ea8af5fa17d15051db0d2ed6751234932d23fd..eeb7226cbb581273ec52104a121ed8ebd376eb79 100644
--- a/app/modules/crawler/lib/sync/WS2PRemoteContacter.ts
+++ b/app/modules/crawler/lib/sync/WS2PRemoteContacter.ts
@@ -34,6 +34,14 @@ export class WS2PRemoteContacter implements IRemoteContacter {
     return this.requester.getBlock(number)
   }
 
+  getMilestones(page: number): Promise<{ chunkSize: number; totalPages: number; currentPage: number; milestonesPerPage: number; blocks: BlockDTO[] }> {
+    return this.requester.getMilestones(page) as any
+  }
+
+  getMilestonesPage(): Promise<{ chunkSize: number; totalPages: number; milestonesPerPage: number }> {
+    return this.requester.getMilestonesPage()
+  }
+
   getCurrent(): Promise<BlockDTO | null> {
     return this.requester.getCurrent()
   }
diff --git a/app/modules/crawler/lib/sync/p2p/p2p-candidate.ts b/app/modules/crawler/lib/sync/p2p/p2p-candidate.ts
index 4ece5e23313db13af861c935f6d609ece58c5915..7bdfdbe36974ff636865a70983d1d560469dbbf7 100644
--- a/app/modules/crawler/lib/sync/p2p/p2p-candidate.ts
+++ b/app/modules/crawler/lib/sync/p2p/p2p-candidate.ts
@@ -15,6 +15,7 @@ export class P2pCandidate {
   private nbSuccess = 0
   private isExcluded: boolean
   private failures = 0
+  private reserved = false
 
   constructor(
     private p: PeerDTO,
@@ -35,7 +36,7 @@ export class P2pCandidate {
   }
 
   isReady() {
-    return this.apiPromise.isResolved() && this.dlPromise.isResolved() && this.api && !this.isExcluded
+    return !this.reserved && this.apiPromise.isResolved() && this.dlPromise.isResolved() && this.api && !this.isExcluded
   }
 
   async waitAvailability(maxWait: number): Promise<boolean> {
@@ -64,6 +65,7 @@ export class P2pCandidate {
   async downloadBlocks(count: number, from: number) {
     const start = Date.now()
     let error: Error|undefined
+    this.reserved = false
     this.dlPromise = querablep((async () => {
       // We try to download the blocks
       let blocks: BlockDTO[]|null
@@ -125,6 +127,10 @@ export class P2pCandidate {
       }
     })())
   }
+
+  reserve() {
+    this.reserved = true
+  }
 }
 
 interface RemoteAPI {
diff --git a/app/modules/crawler/lib/sync/v2/ValidatorStream.ts b/app/modules/crawler/lib/sync/v2/ValidatorStream.ts
index f4641a8e4a67cbd02bbea47b8f0094baa0ef89c5..85c4c2d875580ffb1177405655dec08f4183e3d0 100644
--- a/app/modules/crawler/lib/sync/v2/ValidatorStream.ts
+++ b/app/modules/crawler/lib/sync/v2/ValidatorStream.ts
@@ -50,7 +50,7 @@ export class ValidatorStream extends Readable {
           try {
             const bNumber = Math.min(this.to, (i + 1) * this.syncStrategy.chunkSize - 1)
             if (bNumber > maximumCacheNumber) {
-              block = await this.syncStrategy.getBlock(bNumber)
+              block = await this.syncStrategy.getMilestone(bNumber)
             } else {
               block = await this.getBlockFromCache(bNumber)
             }
diff --git a/app/modules/ws2p/lib/WS2PDocpoolPuller.ts b/app/modules/ws2p/lib/WS2PDocpoolPuller.ts
index a782b9fec67895497070509af09f9bf7fae75ad4..4178a7a4946a4e52779a4db2ff7d5f1f74e4e268 100644
--- a/app/modules/ws2p/lib/WS2PDocpoolPuller.ts
+++ b/app/modules/ws2p/lib/WS2PDocpoolPuller.ts
@@ -35,6 +35,8 @@ export class WS2PDocpoolPuller {
       getCurrent: async () => null,
       getBlock: async () => null,
       getBlocks: async () => [],
+      getMilestonesPage: async () => ({ chunkSize: 0, totalPages: 0, milestonesPerPage: 0 }),
+      getMilestones: async () => ({ chunkSize: 0, totalPages: 0, currentPage: 0, milestonesPerPage: 0, blocks: [] }),
       hostName: ''
     }, this.server, this.server.logger)
   }
diff --git a/app/modules/ws2p/lib/WS2PRequester.ts b/app/modules/ws2p/lib/WS2PRequester.ts
index afaa6783209c5680ac4461261f5083a2b8de9bdd..051a07d8171ac789e167b361747c6b92d653c7d8 100644
--- a/app/modules/ws2p/lib/WS2PRequester.ts
+++ b/app/modules/ws2p/lib/WS2PRequester.ts
@@ -13,7 +13,8 @@
 
 import {WS2PConnection} from "./WS2PConnection"
 import {BlockDTO} from "../../../lib/dto/BlockDTO"
-import {PeerDTO} from "../../../lib/dto/PeerDTO";
+import {PeerDTO} from "../../../lib/dto/PeerDTO"
+import {HttpMilestonePage} from "../../bma/lib/dtos"
 
 export enum WS2P_REQ {
   KNOWN_PEERS,
@@ -21,7 +22,9 @@ export enum WS2P_REQ {
   WOT_REQUIREMENTS_OF_PENDING,
   BLOCKS_CHUNK,
   BLOCK_BY_NUMBER,
-  CURRENT
+  CURRENT,
+  MILESTONES_PAGE,
+  MILESTONES
 }
 
 export class WS2PRequester {
@@ -45,6 +48,14 @@ export class WS2PRequester {
     return this.query(WS2P_REQ.CURRENT)
   }
 
+  getMilestonesPage(): Promise<HttpMilestonePage> {
+    return this.query(WS2P_REQ.MILESTONES_PAGE)
+  }
+
+  getMilestones(page: number): Promise<HttpMilestonePage> {
+    return this.query(WS2P_REQ.MILESTONES, { page })
+  }
+
   getBlock(number:number): Promise<BlockDTO> {
     return this.query(WS2P_REQ.BLOCK_BY_NUMBER, { number })
   }
diff --git a/app/modules/ws2p/lib/impl/WS2PReqMapperByServer.ts b/app/modules/ws2p/lib/impl/WS2PReqMapperByServer.ts
index ce683320513e017e84cd456111c437222376e9c0..2bc2c58f0d1130e83a8227868299b74c3639d9b9 100644
--- a/app/modules/ws2p/lib/impl/WS2PReqMapperByServer.ts
+++ b/app/modules/ws2p/lib/impl/WS2PReqMapperByServer.ts
@@ -17,6 +17,7 @@ import {WS2PReqMapper} from "../interface/WS2PReqMapper"
 import {BlockDTO} from "../../../../lib/dto/BlockDTO"
 import {DBBlock} from "../../../../lib/db/DBBlock"
 import {PeerDTO} from "../../../../lib/dto/PeerDTO"
+import {HttpMilestonePage} from "../../../bma/lib/dtos"
 
 export class WS2PReqMapperByServer implements WS2PReqMapper {
 
@@ -45,6 +46,14 @@ export class WS2PReqMapperByServer implements WS2PReqMapper {
     return (await this.server.dal.getBlocksBetween(from, from + count - 1)).map((b:DBBlock) => BlockDTO.fromJSONObject(b))
   }
 
+  getMilestones(page: number): Promise<HttpMilestonePage> {
+    return this.server.milestones(page)
+  }
+
+  getMilestonesPage(): Promise<HttpMilestonePage> {
+    return this.server.milestones()
+  }
+
   async getRequirementsOfPending(minsig: number): Promise<any> {
     let identities:IdentityForRequirements[] = (await this.server.dal.idtyDAL.query(
       'SELECT i.*, count(c.sig) as nbSig ' +
diff --git a/app/modules/ws2p/lib/interface/WS2PReqMapper.ts b/app/modules/ws2p/lib/interface/WS2PReqMapper.ts
index 83b37d29b6bc8f49fe10bd0ebd649309297860b1..4a0ca36a75b3a6821389b574275de2635d9cb5f7 100644
--- a/app/modules/ws2p/lib/interface/WS2PReqMapper.ts
+++ b/app/modules/ws2p/lib/interface/WS2PReqMapper.ts
@@ -14,6 +14,7 @@
 import {BlockDTO} from "../../../../lib/dto/BlockDTO"
 import {DBBlock} from "../../../../lib/db/DBBlock"
 import {PeerDTO} from "../../../../lib/dto/PeerDTO"
+import {HttpMilestonePage} from "../../../bma/lib/dtos"
 
 export interface WS2PReqMapper {
 
@@ -23,4 +24,6 @@ export interface WS2PReqMapper {
   getRequirementsOfPending(minCert:number): Promise<any>
   getPeer(): Promise<PeerDTO>
   getKnownPeers(): Promise<PeerDTO[]>
+  getMilestones(page: number): Promise<HttpMilestonePage>
+  getMilestonesPage(): Promise<HttpMilestonePage>
 }
\ No newline at end of file
diff --git a/app/modules/ws2p/lib/interface/WS2PServerMessageHandler.ts b/app/modules/ws2p/lib/interface/WS2PServerMessageHandler.ts
index 27f43f308fbd25961d51defd74f0153a2c9f10de..06c99b901a37548e3c7feb6fe8035368ff4e39b2 100644
--- a/app/modules/ws2p/lib/interface/WS2PServerMessageHandler.ts
+++ b/app/modules/ws2p/lib/interface/WS2PServerMessageHandler.ts
@@ -210,6 +210,18 @@ export class WS2PServerMessageHandler implements WS2PMessageHandler {
           body = await this.mapper.getKnownPeers()
           break;
 
+        case WS2P_REQ[WS2P_REQ.MILESTONES_PAGE]:
+          body = await this.mapper.getMilestonesPage()
+          break;
+
+        case WS2P_REQ[WS2P_REQ.MILESTONES]:
+          if (isNaN(data.params.page)) {
+            throw "Wrong param `page`"
+          }
+          const page:number = data.params.page
+          body = await this.mapper.getMilestones(page)
+          break;
+
         default:
           throw Error(WS2P_REQERROR[WS2P_REQERROR.UNKNOWN_REQUEST])
       }
diff --git a/server.ts b/server.ts
index 60b4d30540c58f6269cea44c290e9ac4fa4ce874..163b4a8915c0ad8e75e1da02b5bcc98c6bd328fb 100644
--- a/server.ts
+++ b/server.ts
@@ -22,7 +22,7 @@ import * as stream from "stream"
 import {KeyGen, randomKey} from "./app/lib/common-libs/crypto/keyring"
 import {parsers} from "./app/lib/common-libs/parsers/index"
 import {Cloneable} from "./app/lib/dto/Cloneable"
-import {DuniterDocument, duniterDocument2str} from "./app/lib/common-libs/constants"
+import {CommonConstants, DuniterDocument, duniterDocument2str} from "./app/lib/common-libs/constants"
 import {GlobalFifoPromise} from "./app/service/GlobalFifoPromise"
 import {BlockchainContext} from "./app/lib/computation/BlockchainContext"
 import {BlockDTO} from "./app/lib/dto/BlockDTO"
@@ -42,6 +42,9 @@ import {DBPeer} from "./app/lib/db/DBPeer"
 import {Underscore} from "./app/lib/common-libs/underscore"
 import {SQLiteDriver} from "./app/lib/dal/drivers/SQLiteDriver"
 import {LevelUp} from "levelup";
+import {BMAConstants} from "./app/modules/bma/lib/constants"
+import {HttpMilestonePage} from "./app/modules/bma/lib/dtos"
+import * as toJson from "./app/modules/bma/lib/tojson"
 
 export interface HookableServer {
   generatorGetJoinData: (...args:any[]) => Promise<any>
@@ -88,6 +91,7 @@ export class Server extends stream.Duplex implements HookableServer {
   BlockchainService:BlockchainService
   TransactionsService:TransactionService
   private documentFIFO:GlobalFifoPromise
+  milestoneArray: DBBlock[] = []
 
   constructor(home:string, private memoryOnly:boolean, private overrideConf:any) {
     super({ objectMode: true })
@@ -698,4 +702,52 @@ export class Server extends stream.Duplex implements HookableServer {
   resetConfigHook(): Promise<any> {
     return Promise.resolve({})
   }
+
+  async milestones(page?: number): Promise<HttpMilestonePage> {
+    const chunkSize = CommonConstants.SYNC_BLOCKS_CHUNK
+    const milestonesPerPage = CommonConstants.MILESTONES_PER_PAGE
+    const current = await this.dal.getCurrentBlockOrNull();
+    if (!current) {
+      return {
+        totalPages: 0,
+        chunkSize,
+        milestonesPerPage
+      }
+    }
+    const topNumber = current.number - this.conf.forksize
+    const nbMilestones = (topNumber - (topNumber % chunkSize)) / chunkSize
+    const totalPages = (nbMilestones - (nbMilestones % milestonesPerPage)) / milestonesPerPage
+    if (page === undefined) {
+      return {
+        totalPages,
+        chunkSize,
+        milestonesPerPage
+      }
+    }
+    if (page > totalPages || page <= 0) throw BMAConstants.ERRORS.INCORRECT_PAGE_NUMBER
+    while (this.milestoneArray.length < page * milestonesPerPage) {
+      const lastMilestoneNumber = this.milestoneArray.length
+      // Feed the milestones
+      const newMilestones: DBBlock[] = []
+      for (let i = 1; i <= milestonesPerPage && this.milestoneArray.length < page * milestonesPerPage; i++) {
+        const b = await this.dal.getBlock((lastMilestoneNumber + i) * chunkSize - 1)
+        if (!b) {
+          throw Error(DataErrors[DataErrors.MILESTONE_BLOCK_NOT_FOUND])
+        }
+        newMilestones.push(b)
+      }
+      // As the process is async, another call to "milestones()" maybe have already filled in the milestones
+      if (this.milestoneArray.length < page * milestonesPerPage) {
+        this.milestoneArray = this.milestoneArray.concat(newMilestones)
+      }
+    }
+    const blocks = this.milestoneArray.slice((page - 1) * milestonesPerPage, page * milestonesPerPage)
+    return {
+      totalPages,
+      chunkSize,
+      milestonesPerPage,
+      currentPage: page,
+      blocks: blocks.map(b => toJson.block(b))
+    }
+  }
 }