Commit 56cd017e authored by Cédric Moreau's avatar Cédric Moreau

[enh] Protocol: add BR_G44.2 - allowing certification replay before expiry...

[enh] Protocol: add BR_G44.2 - allowing certification replay before expiry (cherry-picked from 1.7 branch)
parent 9a54388d
const _underscore_ = require("underscore")
export interface Map<T> {
[k:string]: T
}
export interface UnderscoreClass<T> {
filter(filterFunc: (t: T) => boolean): UnderscoreClass<T>
where(props: { [k in keyof T]?: T[k] }): UnderscoreClass<T>
sortBy(sortFunc:(element:T) => number): UnderscoreClass<T>
pluck<K extends keyof T>(k:K): UnderscoreClass<T>
uniq<K extends keyof T>(isSorted?:boolean, iteratee?:(t:T) => K): UnderscoreClass<T>
value(): T[]
}
export const Underscore = {
filter: <T>(elements:T[], filterFunc: (t:T) => boolean): T[] => {
return _underscore_.filter(elements, filterFunc)
},
where: <T>(elements:T[], props: { [k in keyof T]?: T[k] }): T[] => {
return _underscore_.where(elements, props)
},
findWhere: <T>(elements:T[], props: { [k in keyof T]?: T[k] }): T|null => {
return _underscore_.findWhere(elements, props)
},
keys: <T>(map:T): (keyof T)[] => {
return _underscore_.keys(map)
},
values: <T>(map:{ [k:string]: T }): T[] => {
return _underscore_.values(map)
},
pluck: <T, K extends keyof T>(elements:T[], k:K): T[K][] => {
return _underscore_.pluck(elements, k)
},
pick: <T, K extends keyof T>(elements:T, ...k:K[]): T[K][] => {
return _underscore_.pick(elements, ...k)
},
omit: <T, K extends keyof T>(element:T, ...k:K[]): T[K][] => {
return _underscore_.omit(element, ...k)
},
uniq: <T, K>(elements:T[], isSorted:boolean = false, iteratee?:(t:T) => K): T[] => {
return _underscore_.uniq(elements, isSorted, iteratee)
},
clone: <T>(t:T): T => {
return _underscore_.clone(t)
},
mapObject: <T, K extends keyof T, L extends keyof (T[K])>(t:T, cb:(k:K) => (T[K])[L]): Map<T[K][L]> => {
return _underscore_.mapObject(t, cb)
},
mapObjectByProp: <T, K extends keyof T, L extends keyof (T[K])>(t:T, prop:L): Map<T[K][L]> => {
return _underscore_.mapObject(t, (o:T[K]) => o[prop])
},
sortBy: <T, K extends keyof T>(elements:T[], sortFunc:((element:T) => number|string)|K): T[] => {
return _underscore_.sortBy(elements, sortFunc)
},
difference: <T>(array1:T[], array2:T[]): T[] => {
return _underscore_.difference(array1, array2)
},
shuffle: <T>(elements:T[]): T[] => {
return _underscore_.shuffle(elements)
},
extend: <T, U>(t1:T, t2:U): T|U => {
return _underscore_.extend(t1, t2)
},
range: (count:number, end?:number): number[] => {
return _underscore_.range(count, end)
},
chain: <T>(element:T[]): UnderscoreClass<T> => {
return _underscore_.chain(element)
},
}
......@@ -108,7 +108,7 @@ module.exports = {
PEER: CommonConstants.PEER,
CURRENT_DB_VERSION: 26,
CURRENT_DB_VERSION: 28,
NETWORK: {
MAX_MEMBERS_TO_FORWARD_TO_FOR_SELF_DOCUMENTS: 10,
......@@ -146,6 +146,7 @@ module.exports = {
STEPMAX: 3,
SIGDELAY: 3600 * 24 * 365 * 5,
SIGPERIOD: 0, // Instant
SIGREPLAY: 0, // Instant
MSPERIOD: 0, // Instant
SIGSTOCK: 40,
SIGWINDOW: 3600 * 24 * 7, // a week
......
......@@ -29,6 +29,7 @@ import {DBMembership} from "./sqliteDAL/MembershipDAL"
import {MerkleDTO} from "../dto/MerkleDTO"
import {CommonConstants} from "../common-libs/constants"
import {PowDAL} from "./fileDALs/PowDAL";
import {CIndexDAL} from "./sqliteDAL/index/CIndexDAL"
const fs = require('fs')
const path = require('path')
......@@ -68,7 +69,7 @@ export class FileDAL {
mindexDAL:any
iindexDAL:any
sindexDAL:any
cindexDAL:any
cindexDAL:CIndexDAL
newDals:any
loadConfHook: (conf:ConfDTO) => Promise<void>
......@@ -533,8 +534,8 @@ export class FileDAL {
.value();
}
existsNonReplayableLink(from:string, to:string) {
return this.cindexDAL.existsNonReplayableLink(from, to)
existsNonReplayableLink(from:string, to:string, medianTime: number, version: number) {
return this.cindexDAL.existsNonReplayableLink(from, to, medianTime, version)
}
getSource(identifier:string, pos:number) {
......@@ -570,10 +571,13 @@ export class FileDAL {
return (ms && ms.leaving) || false;
}
async existsCert(cert:any) {
async existsCert(cert: DBCert, current: DBBlock|null) {
const existing = await this.certDAL.existsGivenCert(cert);
if (existing) return existing;
const existsLink = await this.cindexDAL.existsNonReplayableLink(cert.from, cert.to);
if (!current) {
return false
}
const existsLink = await this.cindexDAL.existsNonReplayableLink(cert.from, cert.to, current.medianTime, current.version)
return !!existsLink;
}
......
......@@ -27,6 +27,7 @@ import {IdentityDTO} from "../../dto/IdentityDTO"
import {rawer} from "../../common-libs/index"
import {CommonConstants} from "../../common-libs/constants"
import {TxsDAL} from "./TxsDAL"
import {CIndexDAL} from "./index/CIndexDAL"
const _ = require('underscore')
const logger = require('../../logger').NewLogger('metaDAL');
......@@ -408,6 +409,16 @@ export class MetaDAL extends AbstractSQLite<DBMeta> {
'WHERE hash = \'' + tx.hash + '\'')
}
},
26: 'BEGIN;' +
// Add a `massReeval` column
'ALTER TABLE c_index ADD COLUMN replayable_on INTEGER NULL;' +
'COMMIT;',
27: async (conf:ConfDTO) => {
const cindexDAL = new CIndexDAL(this.driverCopy)
await cindexDAL.query('UPDATE c_index SET replayable_on = (chainable_on - ? + ?) WHERE chainable_on IS NOT NULL', [conf.sigPeriod, conf.sigReplay])
},
};
async init() {
......
......@@ -16,7 +16,6 @@ import {SQLiteDriver} from "../../drivers/SQLiteDriver"
import {CindexEntry} from "../../../indexer"
import {CommonConstants} from "../../../common-libs/constants"
const constants = require('./../../../constants');
const indexer = require('../../../indexer').Indexer
export class CIndexDAL extends AbstractIndex<CindexEntry> {
......@@ -39,6 +38,7 @@ export class CIndexDAL extends AbstractIndex<CindexEntry> {
'expires_on',
'expired_on',
'chainable_on',
'replayable_on',
'from_wid',
'to_wid'
],
......@@ -77,17 +77,13 @@ export class CIndexDAL extends AbstractIndex<CindexEntry> {
async reducablesFrom(from:string) {
const reducables = await this.query('SELECT * FROM ' + this.table + ' WHERE issuer = ? ORDER BY CAST(written_on as integer) ASC', [from]);
return indexer.DUP_HELPERS.reduceBy(reducables, ['issuer', 'receiver', 'created_on']);
return indexer.DUP_HELPERS.reduceBy(reducables, ['issuer', 'receiver']);
}
async trimExpiredCerts(belowNumber:number) {
const toDelete = await this.query('SELECT * FROM ' + this.table + ' WHERE expired_on > ? AND CAST(written_on as int) < ?', [0, belowNumber])
for (const row of toDelete) {
await this.exec("DELETE FROM " + this.table + " " +
"WHERE issuer like '" + row.issuer + "' " +
"AND receiver = '" + row.receiver + "' " +
"AND created_on like '" + row.created_on + "'");
}
// Don't trim.
// Duniter 1.6 is not going to be optimized, 1.7 does it already.
// So CIndex will just accumulate certificatios.
}
getWrittenOn(blockstamp:string) {
......@@ -100,49 +96,43 @@ export class CIndexDAL extends AbstractIndex<CindexEntry> {
' SELECT * FROM c_index c2' +
' WHERE c1.issuer = c2.issuer' +
' AND c1.receiver = c2.receiver' +
' AND c1.created_on = c2.created_on' +
' AND c2.op = ?' +
' AND c1.expired_on IS NOT NULL' +
')', [medianTime, CommonConstants.IDX_UPDATE])
}
getValidLinksTo(receiver:string) {
return this.query('SELECT * FROM ' + this.table + ' c1 ' +
'WHERE c1.receiver = ? ' +
'AND c1.expired_on = 0 ' +
'AND NOT EXISTS (' +
' SELECT * FROM c_index c2' +
' WHERE c1.issuer = c2.issuer' +
' AND c1.receiver = c2.receiver' +
' AND c1.created_on = c2.created_on' +
' AND c2.op = ?' +
')', [receiver, CommonConstants.IDX_UPDATE])
async findByIssuer(issuer:string) {
return this.query('SELECT * FROM ' + this.table + ' c1 WHERE c1.issuer = ? ORDER BY CAST(written_on as integer) ASC', [issuer])
}
getValidLinksFrom(issuer:string) {
return this.query('SELECT * FROM ' + this.table + ' c1 ' +
'WHERE c1.issuer = ? ' +
'AND c1.expired_on = 0 ' +
'AND NOT EXISTS (' +
' SELECT * FROM c_index c2' +
' WHERE c1.issuer = c2.issuer' +
' AND c1.receiver = c2.receiver' +
' AND c1.created_on = c2.created_on' +
' AND c2.op = ?' +
')', [issuer, CommonConstants.IDX_UPDATE])
async getValidLinksTo(receiver:string) {
const reducables = await this.query('SELECT * FROM ' + this.table + ' c1 WHERE c1.receiver = ? ORDER BY CAST(written_on as integer) ASC', [receiver])
return indexer.DUP_HELPERS.reduceBy(reducables, ['issuer', 'receiver'])
.filter((c:CindexEntry) => !c.expired_on)
}
async getValidLinksFrom(issuer:string) {
const reducables = await this.query('SELECT * FROM ' + this.table + ' c1 WHERE c1.issuer = ? ORDER BY CAST(written_on as integer) ASC', [issuer])
return indexer.DUP_HELPERS.reduceBy(reducables, ['issuer', 'receiver'])
.filter((c:CindexEntry) => !c.expired_on)
}
async existsNonReplayableLink(issuer:string, receiver:string) {
const results = await this.query('SELECT * FROM ' + this.table + ' c1 ' +
async existsNonReplayableLink(issuer:string, receiver:string, medianTime: number, version: number) {
const reducables = await this.query('SELECT * FROM ' + this.table + ' c1 ' +
'WHERE c1.issuer = ? ' +
'AND c1.receiver = ? ' +
'AND NOT EXISTS (' +
' SELECT * FROM c_index c2' +
' WHERE c1.issuer = c2.issuer' +
' AND c1.receiver = c2.receiver' +
' AND c1.created_on = c2.created_on' +
' AND c2.op = ?' +
')', [issuer, receiver, CommonConstants.IDX_UPDATE]);
return results.length > 0;
'AND c1.receiver = ?' +
'ORDER BY CAST(written_on as integer) ASC', [issuer, receiver])
if (reducables.length === 0) {
return false
}
const link = indexer.DUP_HELPERS.reduce(reducables)
let replayable: boolean
if (version <= 10) {
replayable = !!link.expired_on
} else {
replayable = !!link.expired_on || link.replayable_on < medianTime
}
return !replayable
}
removeBlock(blockstamp:string) {
......
......@@ -278,7 +278,8 @@ export class BlockDTO implements Cloneable {
udReevalTime0: parseInt(sp[18]),
dtReeval: parseInt(sp[19]),
// New parameter, defaults to msWindow
msPeriod: parseInt(sp[9])
msPeriod: parseInt(sp[9]),
sigReplay: parseInt(sp[9]),
}
}
......
......@@ -37,6 +37,7 @@ export interface CurrencyConfDTO {
dt: number
ud0: number
sigPeriod: number
sigReplay: number
sigStock: number
sigWindow: number
sigValidity: number
......@@ -122,6 +123,7 @@ export class ConfDTO implements CurrencyConfDTO, KeypairConfDTO, NetworkConfDTO,
public udReevalTime0: number,
public stepMax: number,
public sigPeriod: number,
public sigReplay: number,
public msPeriod: number,
public sigValidity: number,
public msValidity: number,
......@@ -180,7 +182,7 @@ export class ConfDTO implements CurrencyConfDTO, KeypairConfDTO, NetworkConfDTO,
) {}
static mock() {
return new ConfDTO("", "", [], [], 0, 3600 * 1000, constants.PROOF_OF_WORK.DEFAULT.CPU, 1, constants.PROOF_OF_WORK.DEFAULT.PREFIX, 0, 0, constants.CONTRACT.DEFAULT.C, constants.CONTRACT.DEFAULT.DT, constants.CONTRACT.DEFAULT.DT_REEVAL, 0, constants.CONTRACT.DEFAULT.UD0, 0, 0, constants.CONTRACT.DEFAULT.STEPMAX, constants.CONTRACT.DEFAULT.SIGPERIOD, 0, constants.CONTRACT.DEFAULT.SIGVALIDITY, constants.CONTRACT.DEFAULT.MSVALIDITY, constants.CONTRACT.DEFAULT.SIGQTY, constants.CONTRACT.DEFAULT.SIGSTOCK, constants.CONTRACT.DEFAULT.X_PERCENT, constants.CONTRACT.DEFAULT.PERCENTROT, constants.CONTRACT.DEFAULT.POWDELAY, constants.CONTRACT.DEFAULT.AVGGENTIME, constants.CONTRACT.DEFAULT.MEDIANTIMEBLOCKS, false, 3000, false, constants.BRANCHES.DEFAULT_WINDOW_SIZE, constants.CONTRACT.DEFAULT.IDTYWINDOW, constants.CONTRACT.DEFAULT.MSWINDOW, constants.CONTRACT.DEFAULT.SIGWINDOW, 0, { pub:'', sec:'' }, null, "", "", 0, "", "", "", "", 0, "", "", null, false, "", true, true, false, new ProxiesConf(), undefined)
return new ConfDTO("", "", [], [], 0, 3600 * 1000, constants.PROOF_OF_WORK.DEFAULT.CPU, 1, constants.PROOF_OF_WORK.DEFAULT.PREFIX, 0, 0, constants.CONTRACT.DEFAULT.C, constants.CONTRACT.DEFAULT.DT, constants.CONTRACT.DEFAULT.DT_REEVAL, 0, constants.CONTRACT.DEFAULT.UD0, 0, 0, constants.CONTRACT.DEFAULT.STEPMAX, constants.CONTRACT.DEFAULT.SIGPERIOD, constants.CONTRACT.DEFAULT.SIGREPLAY, 0, constants.CONTRACT.DEFAULT.SIGVALIDITY, constants.CONTRACT.DEFAULT.MSVALIDITY, constants.CONTRACT.DEFAULT.SIGQTY, constants.CONTRACT.DEFAULT.SIGSTOCK, constants.CONTRACT.DEFAULT.X_PERCENT, constants.CONTRACT.DEFAULT.PERCENTROT, constants.CONTRACT.DEFAULT.POWDELAY, constants.CONTRACT.DEFAULT.AVGGENTIME, constants.CONTRACT.DEFAULT.MEDIANTIMEBLOCKS, false, 3000, false, constants.BRANCHES.DEFAULT_WINDOW_SIZE, constants.CONTRACT.DEFAULT.IDTYWINDOW, constants.CONTRACT.DEFAULT.MSWINDOW, constants.CONTRACT.DEFAULT.SIGWINDOW, 0, { pub:'', sec:'' }, null, "", "", 0, "", "", "", "", 0, "", "", null, false, "", true, true, false, new ProxiesConf(), undefined)
}
static defaultConf() {
......@@ -196,6 +198,7 @@ export class ConfDTO implements CurrencyConfDTO, KeypairConfDTO, NetworkConfDTO,
"ud0": constants.CONTRACT.DEFAULT.UD0,
"stepMax": constants.CONTRACT.DEFAULT.STEPMAX,
"sigPeriod": constants.CONTRACT.DEFAULT.SIGPERIOD,
"sigReplay": constants.CONTRACT.DEFAULT.SIGREPLAY,
"sigValidity": constants.CONTRACT.DEFAULT.SIGVALIDITY,
"msValidity": constants.CONTRACT.DEFAULT.MSVALIDITY,
"sigQty": constants.CONTRACT.DEFAULT.SIGQTY,
......
......@@ -85,6 +85,7 @@ export interface CindexEntry extends IndexEntry {
created_on: number,
sig: string,
chainable_on: number,
replayable_on: number,
expires_on: number,
expired_on: number,
from_wid: null, // <-These 2 fields are useless
......@@ -97,7 +98,20 @@ export interface CindexEntry extends IndexEntry {
toNewcomer?: boolean,
toLeaver?: boolean,
isReplay?: boolean,
isReplayable?: boolean,
sigOK?: boolean,
created_on_ref?: { medianTime: number },
}
export interface FullCindexEntry {
issuer: string
receiver: string
created_on: number
sig: string
chainable_on: number
expires_on: number
expired_on: number
replayable_on: number
}
export interface SindexEntry extends IndexEntry {
......@@ -138,7 +152,14 @@ function pushCindex(index: any[], entry: CindexEntry): void {
export class Indexer {
static localIndex(block:BlockDTO, conf:CurrencyConfDTO): IndexEntry[] {
static localIndex(block:BlockDTO, conf:{
sigValidity:number,
msValidity:number,
msPeriod:number,
sigPeriod:number,
sigReplay:number,
sigStock:number
}): IndexEntry[] {
/********************
* GENERAL BEHAVIOR
......@@ -352,6 +373,7 @@ export class Indexer {
unchainables: 0,
sig: cert.sig,
chainable_on: block.medianTime + conf.sigPeriod,
replayable_on: block.medianTime + conf.sigReplay,
expires_on: conf.sigValidity,
expired_on: 0,
from_wid: null,
......@@ -847,10 +869,17 @@ export class Indexer {
ENTRY.toLeaver = reduce(await dal.mindexDAL.reducable(ENTRY.receiver)).leaving
}))
// BR_G44
// BR_G44 + 44.2
await Promise.all(cindex.map(async (ENTRY: CindexEntry) => {
const reducable = await dal.cindexDAL.sqlFind({ issuer: ENTRY.issuer, receiver: ENTRY.receiver })
ENTRY.isReplay = count(reducable) > 0 && reduce(reducable).expired_on === 0
if (HEAD.number > 0 && HEAD_1.version > 10) {
ENTRY.isReplayable = count(reducable) === 0 || reduce(reducable).replayable_on < HEAD_1.medianTime
}
else {
// v10 blocks do not allow certification replay
ENTRY.isReplayable = false
}
}))
// BR_G45
......@@ -1340,7 +1369,7 @@ export class Indexer {
// BR_G71
static ruleCertificationReplay(cindex: CindexEntry[]) {
for (const ENTRY of cindex) {
if (ENTRY.isReplay) return false;
if (ENTRY.isReplay && !ENTRY.isReplayable) return false
}
return true
}
......@@ -1834,7 +1863,7 @@ function blockstamp(aNumber: number, aHash: string) {
return [aNumber, aHash].join('-');
}
function reduce(records: any[]) {
export function reduce(records: any[]) {
return records.reduce((obj:any, record) => {
const keys = Object.keys(record);
for (const k of keys) {
......
......@@ -22,10 +22,12 @@ module.exports = {
config: {
onLoading: async (conf:ConfDTO) => {
conf.msPeriod = conf.msWindow
conf.sigReplay = conf.msPeriod
conf.switchOnHeadAdvance = CommonConstants.SWITCH_ON_BRANCH_AHEAD_BY_X_BLOCKS
},
beforeSave: async (conf:ConfDTO) => {
conf.msPeriod = conf.msWindow
conf.sigReplay = conf.msPeriod
conf.switchOnHeadAdvance = CommonConstants.SWITCH_ON_BRANCH_AHEAD_BY_X_BLOCKS
}
},
......
......@@ -423,7 +423,7 @@ export class BlockGenerator {
}
}
// Already exists a link not replayable yet?
let exists = await this.dal.existsNonReplayableLink(cert.from, cert.to);
let exists = await this.dal.existsNonReplayableLink(cert.from, cert.to, current.medianTime, current.version)
if (exists) {
throw 'It already exists a similar certification written, which is not replayable yet';
}
......@@ -757,7 +757,7 @@ class NextBlockGenerator implements BlockGeneratorInterface {
let exists = false;
if (current) {
// Already exists a link not replayable yet?
exists = await this.dal.existsNonReplayableLink(cert.from, cert.to);
exists = await this.dal.existsNonReplayableLink(cert.from, cert.to, current.medianTime, current.version)
}
if (!exists) {
// Already exists a link not chainable yet?
......
......@@ -194,7 +194,7 @@ export class IdentityService extends FIFOService {
written_hash: null,
block: cert.block_number
}
let existingCert = await this.dal.existsCert(mCert);
let existingCert = await this.dal.existsCert(mCert, current)
if (!existingCert) {
if (!(await this.dal.certDAL.getSandboxForKey(cert.from).acceptNewSandBoxEntry(mCert, this.conf.pair && this.conf.pair.pub))) {
throw constants.ERRORS.SANDBOX_FOR_CERT_IS_FULL;
......
......@@ -1135,6 +1135,7 @@ udTime0 | Time of first UD.
udReevalTime0 | Time of first reevaluation of the UD.
sigPeriod | Minimum delay between 2 certifications of a same issuer, in seconds. Must be positive or zero.
msPeriod | Minimum delay between 2 memberships of a same issuer, in seconds. Must be positive or zero.
sigReplay | Minimum delay between 2 certifications of a same issuer to a same receiver, in seconds. Equals to `msPeriod`.
sigStock | Maximum quantity of active certifications made by member.
sigWindow | Maximum delay a certification can wait before being expired for non-writing.
sigValidity | Maximum age of an active signature (in seconds)
......@@ -1389,6 +1390,7 @@ Each certification produces 1 new entry:
sig = SIGNATURE
expires_on = MedianTime + sigValidity
chainable_on = MedianTime + sigPeriod
replayable_on = MedianTime + sigReplay
expired_on = 0
)
......@@ -2272,6 +2274,25 @@ If `count(reducable) == 0`:
Else:
ENTRY.isReplay = reduce(reducable).expired_on == 0
####### BR_G44.2 - ENTRY.isReplayable
If `HEAD.number > 0 && HEAD~1.version > 10` :
reducable = GLOBAL_CINDEX[issuer=ENTRY.issuer,receiver=ENTRY.receiver,expired_on=0]
If `count(reducable) == 0`:
ENTRY.isReplayable = true
Else:
ENTRY.isReplayable = reduce(reducable).replayable_on < HEAD~1.medianTime
Else:
ENTRY.isReplayable = false
EndIf
####### BR_G45 - ENTRY.sigOK
......@@ -2515,7 +2536,7 @@ Rule:
Rule:
ENTRY.isReplay == false
ENTRY.isReplay == false || ENTRY.isReplayable == true
###### BR_G72 - Certification signature
......
......@@ -108,10 +108,10 @@ describe("Triming", function(){
(yield dal.cindexDAL.sqlFind({ issuer: 'DNan' })).should.have.length(1);
}));
it('should be able to trim the cindex', () => co(function *() {
it('should be able to trim the cindex -> no more true', () => co(function *() {
// Triming
yield dal.trimIndexes(127);
(yield dal.cindexDAL.sqlFind({ issuer: 'HgTT' })).should.have.length(0);
(yield dal.cindexDAL.sqlFind({ issuer: 'HgTT' })).should.have.length(2);
// { op: 'UPDATE', issuer: 'DNan', receiver: 'HgTT', created_on: '125-H', written_on: '126-H', writtenOn: 126, expires_on: 3600, expired_on: null },/**/
(yield dal.cindexDAL.sqlFind({ issuer: 'DNan' })).should.have.length(1);
}));
......
......@@ -98,7 +98,15 @@ describe("v1.0 Local Index", function(){
before(() => {
block = parsers.parseBlock.syncWrite(raw);
index = indexer.localIndex(BlockDTO.fromJSONObject(block), { sigValidity: 100, msValidity: 40 });
index = indexer.localIndex(BlockDTO.fromJSONObject(block), {
sigValidity: 100,
msValidity: 40,
// We don't care about these in this test
msPeriod: 0,
sigPeriod: 0,
sigStock: 0,
sigReplay: 0,
});
});
it('should have 30 index entries', () => {
......
// Source file from duniter: Crypto-currency software to manage libre currency such as Ğ1
// Copyright (C) 2018 Cedric Moreau <cem.moreau@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
import {assertEqual, writeBasicTestWithConfAnd2Users} from "../tools/test-framework"
import {assertThrows} from "../../unit-tools"
import {reduce} from "../../../app/lib/indexer"
describe('Certification replay', () => writeBasicTestWithConfAnd2Users({
sigReplay: 3,
sigPeriod: 0,
sigValidity: 10,
}, (test) => {
const now = 1500000000
test('should be able to init with 2 blocks', async (s1, cat, tac) => {
await cat.createIdentity()
await tac.createIdentity()
await cat.cert(tac)
await tac.cert(cat)
await cat.join()
await tac.join()
await s1.commit({ time: now, version: 10 })
await s1.commit({ time: now })
})
test('should exist only 1 valid link from cat replyable at t + 3', async (s1, cat) => {
const reducableFromCat = await s1.dal.cindexDAL.reducablesFrom(cat.pub)