diff --git a/app/lib/dal/indexDAL/leveldb/LevelDBCindex.ts b/app/lib/dal/indexDAL/leveldb/LevelDBCindex.ts index 8dfab34d878214c6be200dcbc9b775559bcd8702..0777ffe412241f47d4d23aa349987f9a3d6b8d37 100644 --- a/app/lib/dal/indexDAL/leveldb/LevelDBCindex.ts +++ b/app/lib/dal/indexDAL/leveldb/LevelDBCindex.ts @@ -96,7 +96,10 @@ export class LevelDBCindex extends LevelDBTable<LevelDBCindexEntry> implements C .filter(f => f.expired_on && f.writtenOn < belowNumber) .forEach(f => { maxExpired = Math.max(maxExpired, f.expired_on) - toRemove.push(LevelDBCindex.trimFullKey(f.issuer, f.receiver, f.created_on)) + // We must remove **all** the remaining entries for this issuer + receiver + entry.issued + .filter(e => e.issuer === f.issuer && e.receiver === f.receiver) + .forEach(e => toRemove.push(LevelDBCindex.trimFullKey(e.issuer, e.receiver, e.created_on))) }) if (toRemove.length) { // Trim the expired certs that won't be rolled back ever diff --git a/test/integration/certification/certification-expiry.ts b/test/integration/certification/certification-expiry.ts new file mode 100644 index 0000000000000000000000000000000000000000..518d1af28107dce614e9346bcd584451d2424250 --- /dev/null +++ b/test/integration/certification/certification-expiry.ts @@ -0,0 +1,105 @@ +// 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" + +describe('Certification expiry + trimming', () => writeBasicTestWithConfAnd2Users({ + sigReplay: 3, + sigPeriod: 0, + sigValidity: 10, +}, (test) => { + + const now = 1500000000 + + test('should be able to init with 2 blocks', async (s1, cat, tac, toc) => { + await cat.createIdentity() + await tac.createIdentity() + await toc.createIdentity() + // Circular certs + await cat.cert(tac) + await tac.cert(toc) + await toc.cert(cat) + await cat.join() + await tac.join() + await toc.join() + await s1.commit({ time: now, version: 10 }) + await s1.commit({ time: now }) + // Circular WoT + assertEqual(s1._server.dal.wotb.dumpWoT(), `[M] [E] [R] [I] -> Links[maxCert = 40] +[0] [1] [1] [1] -> 2 | +[1] [1] [1] [1] -> 0 | +[2] [1] [1] [1] -> 1 | +`) + }) + + test('some replays from tac at t+4 and t+6', async (s1, cat, tac, toc) => { + await s1.commit({ time: now + 4 }) + await s1.commit({ time: now + 4 }) // <-- it is now t+4 + await tac.cert(toc) + await s1.commit({ time: now + 6 }) + await s1.commit({ time: now + 6 }) // <-- it is now t+6 + await tac.cert(cat) + await s1.commit({ time: now + 8 }) + // Wot adds a certificat for tac to cat + assertEqual(s1._server.dal.wotb.dumpWoT(), `[M] [E] [R] [I] -> Links[maxCert = 40] +[0] [1] [2] [1] -> 2 | 1 | +[1] [1] [1] [2] -> 0 | +[2] [1] [1] [1] -> 1 | +`) + }) + + test('also, toc certify cat and tac later to keep the wot safe', async (s1, cat, tac, toc) => { + await s1.commit({ time: now + 8 }) // <-- it is now t+8 + await toc.cert(cat) + await s1.commit({ time: now + 9 }) + await s1.commit({ time: now + 9 }) // <-- it is now t+9 + await toc.cert(tac) + await s1.commit({ time: now + 9 }) + assertEqual(s1._server.dal.wotb.dumpWoT(), `[M] [E] [R] [I] -> Links[maxCert = 40] +[0] [1] [2] [1] -> 2 | 1 | +[1] [1] [2] [2] -> 0 | 2 | +[2] [1] [1] [2] -> 1 | +`) + }) + + test('at t+10, only cat -> tac cert should be removed (it has not been replayed)', async (s1) => { + await s1.commit({ time: now + 10 }) // Change `Time` + await s1.commit({ time: now + 10 }) // <-- it is now t+10 + assertEqual(s1._server.dal.wotb.dumpWoT(), `[M] [E] [R] [I] -> Links[maxCert = 40] +[0] [1] [2] [0] -> 2 | 1 | +[1] [1] [1] [2] -> 2 | +[2] [1] [1] [2] -> 1 | +`) + }) + + test('at t+14, tac -> toc cert should be removed', async (s1) => { + await s1.commit({ time: now + 14 }) // Change `Time` + await s1.commit({ time: now + 14 }) // Change `MedianTime` + assertEqual(s1._server.dal.wotb.dumpWoT(), `[M] [E] [R] [I] -> Links[maxCert = 40] +[0] [1] [2] [0] -> 2 | 1 | +[1] [1] [1] [1] -> 2 | +[2] [1] [0] [2] -> +`) + }) + + test('at t+16, tac -> cat cert should be removed without bug', async (s1) => { + await s1._server.dal.cindexDAL.trimExpiredCerts(16) // <-- **THIS** is what was triggering the core dump + await s1.commit({ time: now + 16 }) // Change `Time` + await s1.commit({ time: now + 16 }) // Change `MedianTime` + assertEqual(s1._server.dal.wotb.dumpWoT(), `[M] [E] [R] [I] -> Links[maxCert = 40] +[0] [1] [1] [0] -> 2 | +[1] [1] [1] [0] -> 2 | +[2] [0] [0] [2] -> +`) + }) +}))