diff --git a/app/lib/dal/indexDAL/leveldb/LevelDBCindex.ts b/app/lib/dal/indexDAL/leveldb/LevelDBCindex.ts index 0777ffe412241f47d4d23aa349987f9a3d6b8d37..98862713598b3257526e408248ec6ec41c6eeeb9 100644 --- a/app/lib/dal/indexDAL/leveldb/LevelDBCindex.ts +++ b/app/lib/dal/indexDAL/leveldb/LevelDBCindex.ts @@ -146,13 +146,13 @@ export class LevelDBCindex extends LevelDBTable<LevelDBCindexEntry> implements C } } // Remove the "received" arrays - await Promise.all(toRemove.map(async e => { + for (const e of toRemove) { const entry = await this.get(e.receiver) // Remove the certification entry.received = entry.received.filter(issuer => issuer !== e.issuer) // Persist await this.put(e.receiver, entry) - })) + } // Remove the expires_on index entries const expires = Underscore.uniq(toRemove.filter(e => e.expires_on).map(e => e.expires_on)) await Promise.all(expires.map(async e => this.indexForExpiresOn.del(LevelDBCindex.trimExpiredOnKey(e)))) diff --git a/app/modules/prover/lib/blockGenerator.ts b/app/modules/prover/lib/blockGenerator.ts index f22103495ae29609e5f64b6723f53ca5a1f76084..547ded67434f0b1a16024cabfdb852c85ce401a3 100644 --- a/app/modules/prover/lib/blockGenerator.ts +++ b/app/modules/prover/lib/blockGenerator.ts @@ -460,7 +460,7 @@ export class BlockGenerator { exclusions:any, wereExcluded:any, transactions:any, - manualValues:any) { + manualValues:ForcedBlockValues) { if (manualValues && manualValues.excluded) { exclusions = manualValues.excluded; @@ -630,6 +630,16 @@ export class BlockGenerator { } } + // Forced joiners (by tests) + if (manualValues && manualValues.joiners) { + block.joiners = block.joiners.concat(manualValues.joiners.map(j => j.inline())) + } + + // Forced certifications (by tests) + if (manualValues && manualValues.certifications) { + block.certifications = block.certifications.concat(manualValues.certifications.map(c => c.inline())) + } + // Final number of members block.membersCount = previousCount + block.joiners.length - block.excluded.length; @@ -679,7 +689,7 @@ export class BlockGenerator { block.issuersFrameVar = vHEAD.issuersFrameVar; // Manual values before hashing if (manualValues) { - Underscore.extend(block, Underscore.omit(manualValues, 'time')); + Underscore.extend(block, Underscore.omit(manualValues, 'time', 'certifications', 'joiners')); } // InnerHash block.time = block.medianTime; @@ -847,3 +857,13 @@ class ManualRootGenerator implements BlockGeneratorInterface { } } } + +export interface ForcedBlockValues { + time?: number + version?: number + medianTime?: number + excluded?: string[] + revoked?: string[] + certifications?: CertificationDTO[] + joiners?: MembershipDTO[] +} \ No newline at end of file diff --git a/test/integration/fork-resolution/block-with-comebacker-revert.ts b/test/integration/fork-resolution/block-with-comebacker-revert.ts new file mode 100644 index 0000000000000000000000000000000000000000..05cb8574764f269648d4d18c3e930abdd9c4611d --- /dev/null +++ b/test/integration/fork-resolution/block-with-comebacker-revert.ts @@ -0,0 +1,118 @@ +// 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 {CommonConstants} from "../../../app/lib/common-libs/constants" + +const currentVersion = CommonConstants.BLOCK_GENERATED_VERSION + +describe('Block revert with a comebacker in it', () => writeBasicTestWithConfAnd2Users({ + sigQty: 2, + sigReplay: 0, + sigPeriod: 0, + sigValidity: 10, + dtDiffEval: 1, + forksize: 0, +}, (test) => { + + const now = 1500000000 + + test('(t = 0) should init with a 4 members WoT with bidirectionnal certs', async (s1, cat, tac, toc) => { + CommonConstants.BLOCK_GENERATED_VERSION = 11 + await cat.createIdentity() + await tac.createIdentity() + await toc.createIdentity() + await cat.cert(tac) + await cat.cert(toc) + await tac.cert(cat) + await tac.cert(toc) + await toc.cert(cat) + await toc.cert(tac) + await cat.join() + await tac.join() + await toc.join() + const b0 = await s1.commit({ time: now }) + assertEqual(b0.certifications.length, 6) + const b1 = await s1.commit({ time: now }) + assertEqual(b1.membersCount, 3) + }) + + test('(t = 5) cat & tac certify each other', async (s1, cat, tac, toc) => { + await s1.commit({ time: now + 5 }) + await s1.commit({ time: now + 5 }) + await new Promise(resolve => setTimeout(resolve, 500)) + // cat and tac certify each other to stay in the WoT + await tac.cert(cat) + await toc.cert(cat) // <-- toc adds the 2nd certification + const b1 = await s1.commit({ time: now + 6 }) + assertEqual(b1.certifications.length, 2) + await cat.cert(tac) + await toc.cert(tac) // <-- toc adds the 2nd certification + const b2 = await s1.commit({ time: now + 6 }) + assertEqual(b2.certifications.length, 2) + // // /!\/!\/!\ + // // toc gets certified by cat, to a have a remaining valid certification in the blockchain: THIS WONT BE ENOUGH! + await cat.cert(toc) + // // /!\/!\/!\ + const b4 = await s1.commit({ time: now + 6 }) + assertEqual(b4.certifications.length, 1) + }) + + test('(t = 12) toc is excluded for lack of certifications', async (s1, cat, tac, toc) => { + await s1.commit({ time: now + 12 }) + await s1.commit({ time: now + 12 }) + const b = await s1.commit({ time: now + 12 }) + assertEqual(b.excluded.length, 1) + assertEqual(b.excluded[0], toc.pub) + }) + + test('(t = 12) we want some blocs to trim CINDEX', async (s1) => { + for (let i = 0; i < 10; i++) { + await s1.commit({ time: now + 12 }) + } + }) + + test('(t = 13 #1) toc is coming back with 2 certs, whose 1 is a renewal', async (s1, cat, tac, toc) => { + await s1.commit({ time: now + 13 }) + await s1.commit({ time: now + 13 }) + const c1 = await cat.makeCert(toc) // <-- a renewal ==> this is what we want to observe + const join = await toc.makeMembership('IN') + const b = await s1.commit({ + time: now + 13, + joiners: [join], + certifications: [c1] + }) + assertEqual(b.membersCount, 3) + assertEqual(b.number, 22) + }) + + test('(t = 12 #2) revert successfuly', async (s1) => { + await s1.revert() + const b = await s1.dal.getBlockCurrent() + assertEqual(b.membersCount, 2) + assertEqual(b.number, 21) + }) + + test('(t = 12 #3) resolution should work', async (s1) => { + await assertThrows(s1.resolve(), 'BLOCK_WASNT_COMMITTED') + const b = await s1.dal.getBlockCurrent() + assertEqual(b.membersCount, 2) + assertEqual(b.number, 21) + }) + + after(() => { + CommonConstants.BLOCK_GENERATED_VERSION = currentVersion + }) +})) + diff --git a/test/integration/tools/toolbox.ts b/test/integration/tools/toolbox.ts index 39e3669e5d5fe3eb7ea4a1af07214ad6ae5aecf4..40d4a36c22f4074ddd34984c360c2086fcc2ce83 100644 --- a/test/integration/tools/toolbox.ts +++ b/test/integration/tools/toolbox.ts @@ -60,6 +60,7 @@ import {WebSocketServer} from "../../../app/lib/common-libs/websocket" import {CommonConstants} from "../../../app/lib/common-libs/constants" import {WS2PRequester} from "../../../app/modules/ws2p/lib/WS2PRequester" import {WS2PDependency} from "../../../app/modules/ws2p/index" +import {ForcedBlockValues} from "../../../app/modules/prover/lib/blockGenerator" const assert = require('assert'); const rp = require('request-promise'); @@ -239,7 +240,7 @@ export const NewTestingServer = (conf:any) => { remoteipv4: host, currency: conf.currency || CURRENCY_NAME, httpLogs: true, - forksize: conf.forksize || 3, + forksize: conf.forksize !== undefined ? conf.forksize : 3, nonWoTPeersLimit: CommonConstants.DEFAULT_NON_WOT_PEERS_LIMIT, }; if (conf.sigQty === undefined) { @@ -508,7 +509,7 @@ export class TestingServer { return proven } - async commit(options:any = null) { + async commit(options:ForcedBlockValues|null = null) { const proven = await this.generateNext(options) await this.server.writeBlock(proven, true, true) // The resolution is done manually const blocksResolved = await this.server.BlockchainService.blockResolution() diff --git a/test/unit-tools.ts b/test/unit-tools.ts index c154cc34c32e8f6d594ea9dfa55eb3b4e3c05596..e7bbf1aba4dc7a2301f99da554d8465852055739 100644 --- a/test/unit-tools.ts +++ b/test/unit-tools.ts @@ -12,6 +12,7 @@ // GNU Affero General Public License for more details. import * as assert from 'assert' + const should = require('should') export async function shouldThrow(promise:Promise<any>) { @@ -58,7 +59,7 @@ export const assertThrows = async (promise:Promise<any>, message:string|null = n if (e === "Should have thrown") { throw e } - assert.equal(e, message) + assert.equal(e.message || e, message) } }