diff --git a/app/lib/common-libs/constants.ts b/app/lib/common-libs/constants.ts index be669566c6875e5c55095b81780d84a99b67f5b6..bfe572eee15c2dbef702ac3c0742c359fb612fa6 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 462ee2c857bfbb0c2f7097931e341b99f1ac23f1..815f2ba72268c3d98c007fac809df9d48d8fe4e2 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 183125820a98dcc8ed1773bafed2a2273ae0f288..cf8424ea0a13bea9110a0e359911bf36ae6e3397 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 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 aef5eec9a24e41f1c7d98a575045a1f111d7101e..301076e86df4e01198d20f81b793cb1fa1a861e8 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 649f7c4254310e47dea4392411d30c32d705b8f8..871a769263f87e5fb231cb0b3390f10c4d08c1d5 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 fed7b661e6b9f33fda042444a1ada168a7764a52..492e4da4c11de93471d2f29b60ca22f62a239893 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 026ddf671f33b9ca98489bb4005e2257c2aad78b..097a09e204f2f184c231cb3668c2dc9a9e8741e7 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