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)) + } + } }