From f911bbd429d265987da1c49f7748d8817c512db7 Mon Sep 17 00:00:00 2001
From: cgeek <cem.moreau@gmail.com>
Date: Thu, 3 Jan 2019 15:24:49 +0100
Subject: [PATCH] [fix] Add /blockchain/milestones/* URLs (cherry-picked from
 1.7 branch)

---
 app/lib/common-libs/constants.ts              |  4 +-
 app/modules/bma/index.ts                      | 25 +++++----
 app/modules/bma/lib/bma.ts                    |  2 +
 app/modules/bma/lib/constants.ts              |  3 +-
 app/modules/bma/lib/controllers/blockchain.ts |  9 +++-
 app/modules/bma/lib/dtos.ts                   | 22 ++++++++
 app/modules/bma/lib/parameters.ts             | 11 ++++
 server.ts                                     | 54 ++++++++++++++++++-
 8 files changed, 112 insertions(+), 18 deletions(-)

diff --git a/app/lib/common-libs/constants.ts b/app/lib/common-libs/constants.ts
index be669566c..bfe572eee 100644
--- a/app/lib/common-libs/constants.ts
+++ b/app/lib/common-libs/constants.ts
@@ -300,7 +300,9 @@ export const CommonConstants = {
     SPECIAL_BLOCK
   },
 
-  BLOCK_MAX_TX_CHAINING_DEPTH: 5
+  BLOCK_MAX_TX_CHAINING_DEPTH: 5,
+  SYNC_BLOCKS_CHUNK: 250,
+  MILESTONES_PER_PAGE: 50,
 }
 
 function exact (regexpContent:string) {
diff --git a/app/modules/bma/index.ts b/app/modules/bma/index.ts
index 462ee2c85..815f2ba72 100644
--- a/app/modules/bma/index.ts
+++ b/app/modules/bma/index.ts
@@ -153,19 +153,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) => {
@@ -179,6 +166,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("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.');
+          }
+        }
         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 183125820..cf8424ea0 100644
--- a/app/modules/bma/lib/bma.ts
+++ b/app/modules/bma/lib/bma.ts
@@ -66,6 +66,8 @@ export const bma = function(server:Server, interfaces:NetworkInterface[], httpLo
     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 2488dd573..4ba022f29 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 aef5eec9a..301076e86 100644
--- a/app/modules/bma/lib/controllers/blockchain.ts
+++ b/app/modules/bma/lib/controllers/blockchain.ts
@@ -24,14 +24,14 @@ import {
   HttpDifficulties,
   HttpHardship,
   HttpMembership,
-  HttpMemberships,
+  HttpMemberships, HttpMilestonePage,
   HttpParameters,
   HttpStat
 } from "../dtos"
 
 const _                = require('underscore');
 const http2raw         = require('../http2raw');
-const toJson = require('../tojson');
+import * as toJson from "../tojson"
 
 export class BlockchainBinding extends AbstractController {
 
@@ -125,6 +125,11 @@ export class BlockchainBinding extends AbstractController {
     return blocks;
   }
 
+  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 649f7c425..871a76926 100644
--- a/app/modules/bma/lib/dtos.ts
+++ b/app/modules/bma/lib/dtos.ts
@@ -967,3 +967,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 fed7b661e..492e4da4c 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/server.ts b/server.ts
index 026ddf671..097a09e20 100644
--- a/server.ts
+++ b/server.ts
@@ -24,7 +24,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"
@@ -38,6 +38,9 @@ import {OtherConstants} from "./app/lib/other_constants"
 import {WS2PCluster} from "./app/modules/ws2p/lib/WS2PCluster"
 import {DBBlock} from "./app/lib/db/DBBlock"
 import { ProxiesConf } from './app/lib/proxy';
+import {BMAConstants} from "./app/modules/bma/lib/constants"
+import * as toJson from "./app/modules/bma/lib/tojson"
+import {HttpMilestonePage} from "./app/modules/bma/lib/dtos"
 
 export interface HookableServer {
   generatorGetJoinData: (...args:any[]) => Promise<any>
@@ -85,6 +88,7 @@ export class Server extends stream.Duplex implements HookableServer {
   BlockchainService:BlockchainService
   TransactionsService:TransactionService
   private documentFIFO:GlobalFifoPromise
+  milestoneArray: DBBlock[] = []
 
   constructor(home:string, memoryOnly:boolean, private overrideConf:any) {
     super({ objectMode: true })
@@ -669,4 +673,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('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))
+    }
+  }
 }
\ No newline at end of file
-- 
GitLab