diff --git a/app/lib/common-libs/errors.ts b/app/lib/common-libs/errors.ts index 412c07ccd94c8fb5d6b6117af5bffab20a711a61..2e5798d1706032a440b2801faf0c49dd758a0e15 100755 --- a/app/lib/common-libs/errors.ts +++ b/app/lib/common-libs/errors.ts @@ -1,4 +1,7 @@ export enum DataErrors { + TRANSACTION_WINDOW_IS_PASSED, + MEMBERSHIP_WINDOW_IS_PASSED, + CERT_WINDOW_IS_PASSED, INVALID_LEVELDB_IINDEX_DATA_WAS_KICKED, INVALID_LEVELDB_IINDEX_DATA_TO_BE_KICKED, IDENTITY_UID_NOT_FOUND, diff --git a/app/lib/rules/global_rules.ts b/app/lib/rules/global_rules.ts index 57721cfb1b7bfc6be0f7c18cc6c1da27815769f7..10ee6aca0cee501f60bab2013ae820e0add88ad1 100644 --- a/app/lib/rules/global_rules.ts +++ b/app/lib/rules/global_rules.ts @@ -24,6 +24,7 @@ import {hashf} from "../common" import {Indexer, SimpleTxInput} from "../indexer" import {DBTx} from "../db/DBTx" import {Tristamp} from "../common/Tristamp" +import {DataErrors} from "../common-libs/errors" const constants = CommonConstants @@ -231,7 +232,7 @@ export const GLOBAL_RULES_HELPERS = { tx.blockstampTime = basedBlock.medianTime; const current = await dal.getCurrentBlockOrNull(); if (current && current.medianTime > basedBlock.medianTime + constants.TX_WINDOW) { - throw "Transaction has expired"; + throw DataErrors[DataErrors.TRANSACTION_WINDOW_IS_PASSED]; } } } diff --git a/app/service/MembershipService.ts b/app/service/MembershipService.ts index 3dfe33fd6d9d27f261c30e088d34167539077795..a1cdaa8c5297f38e860063be2fe10e209bc0dfe5 100644 --- a/app/service/MembershipService.ts +++ b/app/service/MembershipService.ts @@ -20,6 +20,7 @@ import {GLOBAL_RULES_HELPERS} from "../lib/rules/global_rules"; import {MembershipDTO} from "../lib/dto/MembershipDTO"; import {FIFOService} from "./FIFOService"; import {DBBlock} from "../lib/db/DBBlock" +import {DataErrors} from "../lib/common-libs/errors" const constants = require('../lib/constants'); @@ -75,6 +76,10 @@ export class MembershipService extends FIFOService { }, this.conf.pair && this.conf.pair.pub))) { throw constants.ERRORS.SANDBOX_FOR_MEMERSHIP_IS_FULL; } + const expires_on = basedBlock ? basedBlock.medianTime + this.conf.msWindow : 0 + if (current && expires_on < current.medianTime) { + throw DataErrors[DataErrors.MEMBERSHIP_WINDOW_IS_PASSED] + } // Saves entry await this.dal.savePendingMembership({ issuers: [entry.pubkey], @@ -90,7 +95,7 @@ export class MembershipService extends FIFOService { idtyHash: entry.getIdtyHash(), written: false, written_number: null, - expires_on: basedBlock ? basedBlock.medianTime + this.conf.msWindow : 0, + expires_on, signature: entry.signature, expired: false, block_number: entry.number diff --git a/test/integration/sandbox/expired-certifications.ts b/test/integration/sandbox/expired-certifications.ts new file mode 100644 index 0000000000000000000000000000000000000000..e3af0d5f5925bbfe8673db830a5f3b3147bbb075 --- /dev/null +++ b/test/integration/sandbox/expired-certifications.ts @@ -0,0 +1,63 @@ +// 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 {writeBasicTestWithConfAnd2Users} from "../tools/test-framework" +import {assertThrows} from "../../unit-tools" +import {CommonConstants} from "../../../app/lib/common-libs/constants" +import {CertificationDTO} from "../../../app/lib/dto/CertificationDTO" + +describe('Expired certifications', () => writeBasicTestWithConfAnd2Users({ + sigReplay: 3, + sigPeriod: 0, + sigValidity: 10, + sigWindow: 2, // <--- this is the important parameter for this test! +}, (test) => { + + before(() => { + CommonConstants.BLOCK_NEW_GENERATED_VERSION = 11 + }) + + const now = 1500000000 + let certToResend: CertificationDTO + + 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 accept toc, a new member at t+2', async (s1, cat, tac, toc) => { + await s1.commit({ time: now + 2 }) + await s1.commit({ time: now + 2 }) + await toc.createIdentity() + certToResend = await cat.makeCert(toc) + await cat.sendCert(certToResend) + await toc.join() + await s1.commit({ time: now + 2 }) + }) + + test('should **NOT** be able to send again a written cert, even if replay time is passed', async (s1, cat, tac) => { + await s1.commit({ time: now + 6 }) + await s1.commit({ time: now + 6 }) + await assertThrows(cat.sendCert(certToResend), '{\n "ucode": 1002,\n "message": "CERT_WINDOW_IS_PASSED"\n}') + }) + + after(() => { + CommonConstants.BLOCK_NEW_GENERATED_VERSION = 10 + }) +})) diff --git a/test/integration/sandbox/expired-identity.ts b/test/integration/sandbox/expired-identity.ts new file mode 100644 index 0000000000000000000000000000000000000000..5d8af970213a677a3dfd133bdaf5e7bfdb36b5b9 --- /dev/null +++ b/test/integration/sandbox/expired-identity.ts @@ -0,0 +1,52 @@ +// 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 {writeBasicTestWithConfAnd2Users} from "../tools/test-framework" +import {assertThrows} from "../../unit-tools" +import {CommonConstants} from "../../../app/lib/common-libs/constants" + +describe('Expired identities', () => writeBasicTestWithConfAnd2Users({ + sigReplay: 3, + sigPeriod: 0, + sigValidity: 10, + idtyWindow: 2, // <--- this is the important parameter for this test! +}, (test) => { + + before(() => { + CommonConstants.BLOCK_NEW_GENERATED_VERSION = 11 + }) + + 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 **NOT** accept an expired identity', async (s1, cat, tac, toc) => { + const idty = await toc.makeIdentity() + await s1.commit({ time: now + 6 }) + await s1.commit({ time: now + 6 }) + await assertThrows(toc.submitIdentity(idty), '{\n "ucode": 1002,\n "message": "Identity is too old and cannot be written"\n}') + }) + + after(() => { + CommonConstants.BLOCK_NEW_GENERATED_VERSION = 10 + }) +})) diff --git a/test/integration/sandbox/expired-membership.ts b/test/integration/sandbox/expired-membership.ts new file mode 100644 index 0000000000000000000000000000000000000000..b98ebe3ab1517f805aa27881ea2aefa329581a00 --- /dev/null +++ b/test/integration/sandbox/expired-membership.ts @@ -0,0 +1,63 @@ +// 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 {writeBasicTestWithConfAnd2Users} from "../tools/test-framework" +import {assertThrows} from "../../unit-tools" +import {CommonConstants} from "../../../app/lib/common-libs/constants" + +describe('Expired membership', () => writeBasicTestWithConfAnd2Users({ + sigReplay: 3, + sigPeriod: 0, + sigValidity: 10, + msWindow: 2, // <--- this is the important parameter for this test! +}, (test) => { + + before(() => { + CommonConstants.BLOCK_NEW_GENERATED_VERSION = 11 + }) + + 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 accept toc, a new member at t+2', async (s1, cat, tac, toc) => { + await s1.commit({ time: now + 2 }) + await s1.commit({ time: now + 2 }) + await toc.createIdentity() + await cat.cert(toc) + await toc.join() + await s1.commit({ time: now + 2 }) + }) + + test('should **NOT** be able to send an expired membership', async (s1, cat, tac, toc) => { + await s1.commit({ time: now + 3 }) + await s1.commit({ time: now + 3 }) // <---- Time pass, so that the new membership becomes legit + const expiredMsAtTplus6 = await toc.makeMembership('IN') + await s1.commit({ time: now + 6 }) + await s1.commit({ time: now + 6 }) // <---- But we want to make it expire + await assertThrows(toc.sendMembership(expiredMsAtTplus6), '{\n "ucode": 1002,\n "message": "MEMBERSHIP_WINDOW_IS_PASSED"\n}') + }) + + after(() => { + CommonConstants.BLOCK_NEW_GENERATED_VERSION = 10 + }) +})) diff --git a/test/integration/sandbox/expired-transactions.ts b/test/integration/sandbox/expired-transactions.ts new file mode 100644 index 0000000000000000000000000000000000000000..fe950d99b375696770b013d6a3cd358e301f9688 --- /dev/null +++ b/test/integration/sandbox/expired-transactions.ts @@ -0,0 +1,54 @@ +// 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 {writeBasicTestWithConfAnd2Users} from "../tools/test-framework" +import {assertThrows} from "../../unit-tools" +import {CommonConstants} from "../../../app/lib/common-libs/constants" + +describe('Expired transactions', () => writeBasicTestWithConfAnd2Users({ + udTime0: 1500000000 - 1, +}, (test) => { + + let oldTxWindowValue: number + + before(() => { + CommonConstants.BLOCK_NEW_GENERATED_VERSION = 11 + oldTxWindowValue = CommonConstants.TX_WINDOW + CommonConstants.TX_WINDOW = 2 // We need a low value to pass time bounds rules + }) + + 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 **NOT** accept an expired transaction', async (s1, cat, tac, toc) => { + const rawTX = await cat.prepareITX(100, tac) + await s1.commit({ time: now + CommonConstants.TX_WINDOW + 1 }) + await s1.commit({ time: now + CommonConstants.TX_WINDOW + 1 }) // <---- This is the important change! Make the TX expire + await assertThrows(toc.sendTX(rawTX), '{\n "ucode": 1002,\n "message": "TRANSACTION_WINDOW_IS_PASSED"\n}') + }) + + after(() => { + CommonConstants.BLOCK_NEW_GENERATED_VERSION = 10 + CommonConstants.TX_WINDOW = oldTxWindowValue + }) +})) diff --git a/test/integration/tools/TestUser.ts b/test/integration/tools/TestUser.ts index 74941a78a7675e26ad6aee2bc43b1e42674cce31..cf25abe881bb9b9cf0d26129812cb1e5fc171cae 100644 --- a/test/integration/tools/TestUser.ts +++ b/test/integration/tools/TestUser.ts @@ -69,6 +69,11 @@ export class TestUser { } public async createIdentity(useRoot?:boolean|null, fromServer?:any) { + const idty = await this.makeIdentity(useRoot) + await this.submitIdentity(idty, fromServer); + } + + public async makeIdentity(useRoot?:boolean|null) { if (!this.pub) { this.init(() => {}) } @@ -80,11 +85,10 @@ export class TestUser { issuer: this.pub, currency: this.node.server.conf.currency }).getRawUnSigned() - this.createdIdentity += KeyGen(this.pub, this.sec).signSync(this.createdIdentity) + '\n' - await this.submitIdentity(this.createdIdentity, fromServer); + return this.createdIdentity += KeyGen(this.pub, this.sec).signSync(this.createdIdentity) + '\n' } - public submitIdentity(raw:string, fromServer:any) { + public submitIdentity(raw:string, fromServer?: TestingServer) { return this.doPost('/wot/add', { "identity": raw }, fromServer) @@ -118,17 +122,21 @@ export class TestUser { public async cert(user:TestUser, fromServer?:TestingServer, toServer?:TestingServer) { const cert = await this.makeCert(user, fromServer) - await this.doPost('/wot/certify', { + await this.sendCert(cert, toServer) + } + + public async sendCert(cert: CertificationDTO, toServer?: TestingServer) { + return this.doPost('/wot/certify', { "cert": cert.getRawSigned() - }, toServer); + }, toServer) } public async join() { - return await this.sendMembership("IN") + return await this.publishMembership("IN") } public async leave() { - return await this.sendMembership("OUT") + return await this.publishMembership("OUT") } public async makeRevocation(givenLookupIdty?:HttpLookup, overrideProps?:any) { @@ -181,11 +189,15 @@ export class TestUser { return MembershipDTO.fromJSONObject(join) } - public async sendMembership(type:string) { + public async publishMembership(type:string) { const ms = await this.makeMembership(type); - await this.post('/blockchain/membership', { + await this.sendMembership(ms) + } + + public async sendMembership(ms: MembershipDTO, toServer?: TestingServer) { + return this.doPost('/blockchain/membership', { "membership": ms.getRawSigned() - }) + }, toServer) } public async sendMoney(amount:number, recipient:TestUser, comment?:string) { @@ -194,8 +206,7 @@ export class TestUser { } public async sendTX(rawTX:string) { - let http = await this.getContacter() - return http.processTransaction(rawTX) + return this.doPost('/tx/process', { transaction: rawTX }) } public async prepareUTX(previousTX:string, unlocks:string[], outputs:TestOutput[], opts:any) {