Commit 8b374c7a authored by Cédric Moreau's avatar Cédric Moreau

[fix] #1037 Migrate back duniter-bma

parent 5f8c0f1a
...@@ -32,5 +32,9 @@ app/modules/prover/*.js ...@@ -32,5 +32,9 @@ app/modules/prover/*.js
app/modules/prover/lib/*.js app/modules/prover/lib/*.js
app/modules/keypair/*.js app/modules/keypair/*.js
app/modules/keypair/lib/*.js app/modules/keypair/lib/*.js
app/modules/bma/*.js
app/modules/bma/lib/*.js
app/modules/bma/lib/entity/*.js
app/modules/bma/lib/controllers/*.js
test/*.js test/*.js
test/**/*.js test/**/*.js
\ No newline at end of file
...@@ -328,7 +328,7 @@ export class FileDAL { ...@@ -328,7 +328,7 @@ export class FileDAL {
return this.txsDAL.removeTX(hash) return this.txsDAL.removeTX(hash)
} }
getTransactionsPending(versionMin:number) { getTransactionsPending(versionMin = 0) {
return this.txsDAL.getAllPending(versionMin) return this.txsDAL.getAllPending(versionMin)
} }
...@@ -457,7 +457,7 @@ export class FileDAL { ...@@ -457,7 +457,7 @@ export class FileDAL {
return _(pending).sortBy((ms:any) => -ms.number)[0]; return _(pending).sortBy((ms:any) => -ms.number)[0];
} }
async findNewcomers(blockMedianTime:number) { async findNewcomers(blockMedianTime = 0) {
const pending = await this.msDAL.getPendingIN() const pending = await this.msDAL.getPendingIN()
const mss = await Promise.all(pending.map(async (p:any) => { const mss = await Promise.all(pending.map(async (p:any) => {
const reduced = await this.mindexDAL.getReducedMS(p.issuer) const reduced = await this.mindexDAL.getReducedMS(p.issuer)
......
...@@ -107,8 +107,12 @@ export class BlockDTO { ...@@ -107,8 +107,12 @@ export class BlockDTO {
return found; return found;
} }
getRawUnSigned() {
return this.getRawInnerPart() + this.getSignedPart()
}
getRawSigned() { getRawSigned() {
return this.getRawInnerPart() + this.getSignedPart() + this.signature + "\n" return this.getRawUnSigned() + this.signature + "\n"
} }
getSignedPart() { getSignedPart() {
......
...@@ -32,12 +32,25 @@ export interface CurrencyConfDTO { ...@@ -32,12 +32,25 @@ export interface CurrencyConfDTO {
export interface KeypairConfDTO { export interface KeypairConfDTO {
pair: Keypair pair: Keypair
oldPair: Keypair oldPair: Keypair|null
salt: string salt: string
passwd: string passwd: string
} }
export class ConfDTO implements CurrencyConfDTO { export interface NetworkConfDTO {
remoteport: number
remotehost: string|null
remoteipv4: string|null
remoteipv6: string|null
port: number
ipv4: string
ipv6: string
dos:any
upnp:boolean
httplogs:boolean
}
export class ConfDTO implements CurrencyConfDTO, KeypairConfDTO, NetworkConfDTO {
constructor( constructor(
public loglevel: string, public loglevel: string,
...@@ -79,19 +92,24 @@ export class ConfDTO implements CurrencyConfDTO { ...@@ -79,19 +92,24 @@ export class ConfDTO implements CurrencyConfDTO {
public sigWindow: number, public sigWindow: number,
public swichOnTimeAheadBy: number, public swichOnTimeAheadBy: number,
public pair: Keypair, public pair: Keypair,
public oldPair: Keypair|null,
public salt: string,
public passwd: string,
public remoteport: number, public remoteport: number,
public remotehost: string, public remotehost: string|null,
public remoteipv4: string, public remoteipv4: string|null,
public remoteipv6: string, public remoteipv6: string|null,
public port: number, public port: number,
public ipv4: string, public ipv4: string,
public ipv6: string, public ipv6: string,
public dos: any,
public upnp: boolean,
public homename: string, public homename: string,
public memory: boolean, public memory: boolean,
) {} ) {}
static mock() { static mock() {
return new ConfDTO("", "", [], [], 0, 0, 0.6, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false, 0, false, 0, 0, 0, 0, 0, { pub:'', sec:'' }, 0, "", "", "", 0, "", "", "", true) return new ConfDTO("", "", [], [], 0, 0, 0.6, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false, 0, false, 0, 0, 0, 0, 0, { pub:'', sec:'' }, null, "", "", 0, "", "", "", 0, "", "", null, false, "", true)
} }
static defaultConf() { static defaultConf() {
......
This diff is collapsed.
This diff is collapsed.
export const BMAConstants = {
ENTITY_BLOCK: 'block',
ENTITY_IDENTITY: 'identity',
ENTITY_CERTIFICATION: 'certification',
ENTITY_MEMBERSHIP: 'membership',
ENTITY_REVOCATION: 'revocation',
ENTITY_TRANSACTION: 'transaction',
ENTITY_PEER: 'peer',
DEFAULT_PORT: 10901,
IPV4_REGEXP: /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/,
IPV6_REGEXP: /^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b).){3}(b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b))|(([0-9A-Fa-f]{1,4}:){0,5}:((b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b).){3}(b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b))|(::([0-9A-Fa-f]{1,4}:){0,5}((b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b).){3}(b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/,
PORT_START: 15000,
UPNP_INTERVAL: 300,
UPNP_TTL: 600,
PUBLIC_KEY: /^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{43,44}$/,
SHA256_HASH: /^[A-F0-9]{64}$/,
ERRORS: {
// Technical errors
UNKNOWN: { httpCode: 500, uerr: { ucode: 1001, message: "An unknown error occured" }},
UNHANDLED: { httpCode: 500, uerr: { ucode: 1002, message: "An unhandled error occured" }},
HTTP_LIMITATION: { httpCode: 503, uerr: { ucode: 1006, message: "This URI has reached its maximum usage quota. Please retry later." }},
HTTP_PARAM_PUBKEY_REQUIRED: { httpCode: 400, uerr: { ucode: 1101, message: "Parameter `pubkey` is required" }},
HTTP_PARAM_IDENTITY_REQUIRED: { httpCode: 400, uerr: { ucode: 1102, message: "Parameter `identity` is required" }},
HTTP_PARAM_PEER_REQUIRED: { httpCode: 400, uerr: { ucode: 1103, message: "Requires a peer" }},
HTTP_PARAM_BLOCK_REQUIRED: { httpCode: 400, uerr: { ucode: 1104, message: "Requires a block" }},
HTTP_PARAM_MEMBERSHIP_REQUIRED: { httpCode: 400, uerr: { ucode: 1105, message: "Requires a membership" }},
HTTP_PARAM_TX_REQUIRED: { httpCode: 400, uerr: { ucode: 1106, message: "Requires a transaction" }},
HTTP_PARAM_SIG_REQUIRED: { httpCode: 400, uerr: { ucode: 1107, message: "Parameter `sig` is required" }},
HTTP_PARAM_CERT_REQUIRED: { httpCode: 400, uerr: { ucode: 1108, message: "Parameter `cert` is required" }},
HTTP_PARAM_REVOCATION_REQUIRED: { httpCode: 400, uerr: { ucode: 1109, message: "Parameter `revocation` is required" }},
HTTP_PARAM_CONF_REQUIRED: { httpCode: 400, uerr: { ucode: 1110, message: "Parameter `conf` is required" }},
HTTP_PARAM_CPU_REQUIRED: { httpCode: 400, uerr: { ucode: 1111, message: "Parameter `cpu` is required" }},
// Business errors
NO_MATCHING_IDENTITY: { httpCode: 404, uerr: { ucode: 2001, message: "No matching identity" }},
SELF_PEER_NOT_FOUND: { httpCode: 404, uerr: { ucode: 2005, message: "Self peering was not found" }},
NOT_A_MEMBER: { httpCode: 400, uerr: { ucode: 2009, message: "Not a member" }},
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' }}
// New errors: range 3000-4000
}
}
\ No newline at end of file
import {Server} from "../../../../../server"
const dos2unix = require('../dos2unix')
export abstract class AbstractController {
constructor(protected server:Server) {
}
get conf() {
return this.server.conf
}
get logger() {
return this.server.logger
}
get BlockchainService() {
return this.server.BlockchainService
}
get IdentityService() {
return this.server.IdentityService
}
get PeeringService() {
return this.server.PeeringService
}
get MerkleService() {
return this.server.MerkleService
}
async pushEntity(req:any, rawer:(req:any)=>string, type:any) {
let rawDocument = rawer(req);
rawDocument = dos2unix(rawDocument);
const written = await this.server.writeRaw(rawDocument, type);
try {
return written.json();
} catch (e) {
this.logger.error('Written:', written);
this.logger.error(e);
throw e;
}
}
}
\ No newline at end of file
"use strict";
import {Server} from "../../../../../server"
import {AbstractController} from "./AbstractController"
import {ParametersService} from "../parameters"
import {BMAConstants} from "../constants"
const co = require('co');
const _ = require('underscore');
const common = require('duniter-common');
const http2raw = require('../http2raw');
const toJson = require('../tojson');
const Membership = common.document.Membership
export class BlockchainBinding extends AbstractController {
with:any
constructor(server:Server) {
super(server)
this.with = {
newcomers: this.getStat('newcomers'),
certs: this.getStat('certs'),
joiners: this.getStat('joiners'),
actives: this.getStat('actives'),
leavers: this.getStat('leavers'),
revoked: this.getStat('revoked'),
excluded: this.getStat('excluded'),
ud: this.getStat('ud'),
tx: this.getStat('tx')
}
}
parseMembership = (req:any) => this.pushEntity(req, http2raw.membership, BMAConstants.ENTITY_MEMBERSHIP);
parseBlock = (req:any) => this.pushEntity(req, http2raw.block, BMAConstants.ENTITY_BLOCK);
parameters = () => this.server.dal.getParameters();
private getStat(statName:string) {
return async () => {
let stat = await this.server.dal.getStat(statName);
return { result: toJson.stat(stat) };
}
}
async promoted(req:any) {
const number = await ParametersService.getNumberP(req);
const promoted = await this.BlockchainService.promoted(number);
return toJson.block(promoted);
}
async blocks(req:any) {
const params = ParametersService.getCountAndFrom(req);
const count = parseInt(params.count);
const from = parseInt(params.from);
let blocks = await this.BlockchainService.blocksBetween(from, count);
blocks = blocks.map((b:any) => toJson.block(b));
return blocks;
}
async current() {
const current = await this.server.dal.getCurrentBlockOrNull();
if (!current) throw BMAConstants.ERRORS.NO_CURRENT_BLOCK;
return toJson.block(current);
}
async hardship(req:any) {
let nextBlockNumber = 0;
const search = await ParametersService.getSearchP(req);
const idty = await this.IdentityService.findMemberWithoutMemberships(search);
if (!idty) {
throw BMAConstants.ERRORS.NO_MATCHING_IDENTITY;
}
if (!idty.member) {
throw BMAConstants.ERRORS.NOT_A_MEMBER;
}
const current = await this.BlockchainService.current();
if (current) {
nextBlockNumber = current ? current.number + 1 : 0;
}
const difficulty = await this.server.getBcContext().getIssuerPersonalizedDifficulty(idty.pubkey);
return {
"block": nextBlockNumber,
"level": difficulty
};
}
async difficulties() {
const current = await this.server.dal.getCurrentBlockOrNull();
const number = (current && current.number) || 0;
const issuers = await this.server.dal.getUniqueIssuersBetween(number - 1 - current.issuersFrame, number - 1);
const difficulties = [];
for (const issuer of issuers) {
const member = await this.server.dal.getWrittenIdtyByPubkey(issuer);
const difficulty = await this.server.getBcContext().getIssuerPersonalizedDifficulty(member.pubkey);
difficulties.push({
uid: member.uid,
level: difficulty
});
}
return {
"block": number + 1,
"levels": _.sortBy(difficulties, (diff:any) => diff.level)
};
}
async memberships(req:any) {
const search = await ParametersService.getSearchP(req);
const idty:any = await this.IdentityService.findMember(search);
const json = {
pubkey: idty.pubkey,
uid: idty.uid,
sigDate: idty.buid,
memberships: []
};
json.memberships = idty.memberships.map((msObj:any) => {
const ms = Membership.fromJSON(msObj);
return {
version: ms.version,
currency: this.conf.currency,
membership: ms.membership,
blockNumber: parseInt(ms.blockNumber),
blockHash: ms.blockHash,
written: (!msObj.written_number && msObj.written_number !== 0) ? null : msObj.written_number
};
});
json.memberships = _.sortBy(json.memberships, 'blockNumber');
json.memberships.reverse();
return json;
}
async branches() {
const branches = await this.BlockchainService.branches();
const blocks = branches.map((b) => toJson.block(b));
return {
blocks: blocks
};
}
}
import {AbstractController} from "./AbstractController"
import {BMAConstants} from "../constants"
const _ = require('underscore');
const http2raw = require('../http2raw');
export class NetworkBinding extends AbstractController {
async peer() {
const p = await this.PeeringService.peer();
if (!p) {
throw BMAConstants.ERRORS.SELF_PEER_NOT_FOUND;
}
return p.json();
}
async peersGet(req:any) {
let merkle = await this.server.dal.merkleForPeers();
return await this.MerkleService(req, merkle, async (hashes:string[]) => {
try {
let peers = await this.server.dal.findPeersWhoseHashIsIn(hashes);
const map:any = {};
peers.forEach((peer:any) => {
map[peer.hash] = peer;
});
if (peers.length == 0) {
throw BMAConstants.ERRORS.PEER_NOT_FOUND;
}
return map;
} catch (e) {
throw e;
}
})
}
peersPost(req:any) {
return this.pushEntity(req, http2raw.peer, BMAConstants.ENTITY_PEER)
}
async peers() {
let peers = await this.server.dal.listAllPeers();
return {
peers: peers.map((p:any) => {
return _.pick(p,
'version',
'currency',
'status',
'first_down',
'last_try',
'pubkey',
'block',
'signature',
'endpoints');
})
};
}
}
"use strict";
import {AbstractController} from "./AbstractController"
export class NodeBinding extends AbstractController {
summary = () => {
return {
"duniter": {
"software": "duniter",
"version": this.server.version,
"forkWindowSize": this.server.conf.forksize
}
}
}
async sandboxes() {
return {
identities: await sandboxIt(this.server.dal.idtyDAL.sandbox),
memberships: await sandboxIt(this.server.dal.msDAL.sandbox),
transactions: await sandboxIt(this.server.dal.txsDAL.sandbox)
}
}
}
async function sandboxIt(sandbox:any) {
return {
size: sandbox.maxSize,
free: await sandbox.getSandboxRoom()
}
}
import {AbstractController} from "./AbstractController"
import {ParametersService} from "../parameters"
import {Source} from "../entity/source"
const _ = require('underscore');
const common = require('duniter-common');
const http2raw = require('../http2raw');
const Transaction = common.document.Transaction
export class TransactionBinding extends AbstractController {
parseTransaction(req:any) {
return this.pushEntity(req, http2raw.transaction, constants.ENTITY_TRANSACTION)
}
async getSources(req:any) {
const pubkey = await ParametersService.getPubkeyP(req);
const sources = await this.server.dal.getAvailableSourcesByPubkey(pubkey);
const result:any = {
"currency": this.conf.currency,
"pubkey": pubkey,
"sources": []
};
sources.forEach(function (src:any) {
result.sources.push(new Source(src).json());
});
return result;
}
async getByHash(req:any) {
const hash = ParametersService.getHash(req);
const tx = await this.server.dal.getTxByHash(hash);
if (!tx) {
throw constants.ERRORS.TX_NOT_FOUND;
}
if (tx.block_number) {
tx.written_block = tx.block_number
}
tx.inputs = tx.inputs.map((i:any) => i.raw || i)
tx.outputs = tx.outputs.map((o:any) => o.raw || o)
return tx;
}
async getHistory(req:any) {
const pubkey = await ParametersService.getPubkeyP(req);
return this.getFilteredHistory(pubkey, (results:any) => results);
}
async getHistoryBetweenBlocks(req:any) {
const pubkey = await ParametersService.getPubkeyP(req);
const from = await ParametersService.getFromP(req);
const to = await ParametersService.getToP(req);
return this.getFilteredHistory(pubkey, (res:any) => {
const histo = res.history;
histo.sent = _.filter(histo.sent, function(tx:any){ return tx && tx.block_number >= from && tx.block_number <= to; });
histo.received = _.filter(histo.received, function(tx:any){ return tx && tx.block_number >= from && tx.block_number <= to; });
_.extend(histo, { sending: [], receiving: [] });
return res;
});
}
async getHistoryBetweenTimes(req:any) {
const pubkey = await ParametersService.getPubkeyP(req);
const from = await ParametersService.getFromP(req);
const to = await ParametersService.getToP(req);
return this.getFilteredHistory(pubkey, (res:any) => {
const histo = res.history;
histo.sent = _.filter(histo.sent, function(tx:any){ return tx && tx.time >= from && tx.time <= to; });
histo.received = _.filter(histo.received, function(tx:any){ return tx && tx.time >= from && tx.time <= to; });
_.extend(histo, { sending: [], receiving: [] });
return res;
});
}
async getPendingForPubkey(req:any) {
const pubkey = await ParametersService.getPubkeyP(req);
return this.getFilteredHistory(pubkey, function(res:any) {
const histo = res.history;
_.extend(histo, { sent: [], received: [] });
return res;
});
}
async getPending() {
const pending = await this.server.dal.getTransactionsPending();
const res = {
"currency": this.conf.currency,
"pending": pending
};
pending.map(function(tx:any, index:number) {
pending[index] = _.omit(Transaction.fromJSON(tx).json(), 'currency', 'raw');
});
return res;
}
private async getFilteredHistory(pubkey:string, filter:any) {
let history:any = await this.server.dal.getTransactionsHistory(pubkey);
let result = {
"currency": this.conf.currency,
"pubkey": pubkey,
"history": history
};
_.keys(history).map((key:any) => {
history[key].map((tx:any, index:number) => {
history[key][index] = _.omit(Transaction.fromJSON(tx).json(), 'currency', 'raw');
_.extend(history[key][index], {block_number: tx && tx.block_number, time: tx && tx.time});
});
});
return filter(result);
}
}
import {AbstractController} from "./AbstractController"
import {ParametersService} from "../parameters"
import {Source} from "../entity/source"
const _ = require('underscore');
export class UDBinding extends AbstractController {
async getHistory(req:any) {
const pubkey = await ParametersService.getPubkeyP(req);
return this.getUDSources(pubkey, (results:any) => results);
}
async getHistoryBetweenBlocks(req:any) {
const pubkey = await ParametersService.getPubkeyP(req);
const from = await ParametersService.getFromP(req);