diff --git a/.gitignore b/.gitignore index 9b656c46522606b19e13f3c4310831863ca3426b..1a8aef2074a1563edf3994813d403786f81879ff 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,7 @@ test/integration/documents-currency.d.ts test/integration/forwarding.js test/integration/branches_switch.js test/integration/branches2.js +test/integration/transactions-chaining.js test/fast/modules/crawler/block_pulling.js* test/fast/modules/crawler/block_pulling.d.ts test/fast/fork*.js* @@ -72,3 +73,6 @@ test/fast/modules/common/grammar.d.ts test/fast/prover/pow-1-cluster.d.ts test/fast/prover/pow-1-cluster.js test/fast/prover/pow-1-cluster.js.map +test/fast/protocol-local-rule-chained-tx-depth.js +test/fast/protocol-local-rule-chained-tx-depth.js.map +test/fast/protocol-local-rule-chained-tx-depth.d.ts diff --git a/app/lib/common-libs/constants.ts b/app/lib/common-libs/constants.ts index b34cb6278407c8679b19c508cc6b86ce7f5473f2..e1be3f6661f221523d06aad8460cb82d3c36f6a4 100644 --- a/app/lib/common-libs/constants.ts +++ b/app/lib/common-libs/constants.ts @@ -285,6 +285,8 @@ export const CommonConstants = { BLOCK: find("Block: (" + INTEGER + "-" + FINGERPRINT + ")"), SPECIAL_BLOCK }, + + BLOCK_MAX_TX_CHAINING_DEPTH: 5 } function exact (regexpContent:string) { diff --git a/app/lib/rules/index.ts b/app/lib/rules/index.ts index 44ca3d2535efb9cd8d8733317a255ad80f74cf5e..28d13899b467bb69fe4a8c9be2e9c0bcd021bcac 100644 --- a/app/lib/rules/index.ts +++ b/app/lib/rules/index.ts @@ -33,6 +33,7 @@ export const ALIAS = { await LOCAL_RULES_FUNCTIONS.checkTxRecipients(block); await LOCAL_RULES_FUNCTIONS.checkTxAmounts(block); await LOCAL_RULES_FUNCTIONS.checkTxSignature(block); + await LOCAL_RULES_FUNCTIONS.checkMaxTransactionChainingDepth(block, conf, index); }, ALL_LOCAL_BUT_POW_AND_SIGNATURE: async (block:BlockDTO, conf:ConfDTO, index:IndexEntry[]) => { @@ -60,6 +61,7 @@ export const ALIAS = { await LOCAL_RULES_FUNCTIONS.checkTxRecipients(block); await LOCAL_RULES_FUNCTIONS.checkTxAmounts(block); await LOCAL_RULES_FUNCTIONS.checkTxSignature(block); + await LOCAL_RULES_FUNCTIONS.checkMaxTransactionChainingDepth(block, conf, index); } } diff --git a/app/lib/rules/local_rules.ts b/app/lib/rules/local_rules.ts index 41d19f163d8b3a377fb2e6832c7be37dae36d2d3..5a608112485b7afc5b732204aecf0d0fcef3374c 100644 --- a/app/lib/rules/local_rules.ts +++ b/app/lib/rules/local_rules.ts @@ -379,28 +379,70 @@ export const LOCAL_RULES_FUNCTIONS = { } } return true; + }, + + checkMaxTransactionChainingDepth: async (block:BlockDTO, conf:ConfDTO, index:IndexEntry[]) => { + const sindex = Indexer.sindex(index) + const max = getMaxTransactionDepth(sindex) + // + const allowedMax = block.medianTime > 1517443200 ? CommonConstants.BLOCK_MAX_TX_CHAINING_DEPTH : 1 + if (max > allowedMax) { + throw "The maximum transaction chaining length per block is " + CommonConstants.BLOCK_MAX_TX_CHAINING_DEPTH + } + return true + } +} + +export interface SindexShortEntry { + op:string, + identifier:string, + pos:number, + tx:string|null +} + +function getMaxTransactionDepth(sindex:SindexShortEntry[]) { + const ids = _.uniq(_.pluck(sindex, 'tx')) + let maxTxChainingDepth = 0 + for (let id of ids) { + maxTxChainingDepth = Math.max(maxTxChainingDepth, getTransactionDepth(id, sindex, 0)) } + return maxTxChainingDepth +} + +function getTransactionDepth(txHash:string, sindex:SindexShortEntry[], localDepth = 0) { + const inputs = _.filter(sindex, (s:SindexShortEntry) => s.op === 'UPDATE' && s.tx === txHash) + let depth = localDepth + for (let input of inputs) { + const consumedOutput = _.findWhere(sindex, { op: 'CREATE', identifier: input.identifier, pos: input.pos }) + if (consumedOutput) { + if (localDepth < 5) { + const subTxDepth = getTransactionDepth(consumedOutput.tx, sindex, localDepth + 1) + depth = Math.max(depth, subTxDepth) + } else { + depth++ + } + } + } + return depth } function checkSingleMembershipSignature(ms:any) { return verify(ms.getRaw(), ms.signature, ms.issuer); } -function checkBunchOfTransactions(transactions:TransactionDTO[], done:any = undefined){ - const block:any = { transactions }; +function checkBunchOfTransactions(transactions:TransactionDTO[], conf:ConfDTO, options?:{ dontCareAboutChaining?:boolean }){ + const block:any = { transactions, identities: [], joiners: [], actives: [], leavers: [], revoked: [], excluded: [], certifications: [] }; + const index = Indexer.localIndex(block, conf) return (async () => { - try { - let local_rule = LOCAL_RULES_FUNCTIONS; - await local_rule.checkTxLen(block); - await local_rule.checkTxIssuers(block); - await local_rule.checkTxSources(block); - await local_rule.checkTxRecipients(block); - await local_rule.checkTxAmounts(block); - await local_rule.checkTxSignature(block); - done && done(); - } catch (err) { - if (done) return done(err); - throw err; + let local_rule = LOCAL_RULES_FUNCTIONS; + await local_rule.checkTxLen(block); + await local_rule.checkTxIssuers(block); + await local_rule.checkTxSources(block); + await local_rule.checkTxRecipients(block); + await local_rule.checkTxAmounts(block); + await local_rule.checkTxSignature(block); + if (!options || !options.dontCareAboutChaining) { + await local_rule.checkMaxTransactionChainingDepth(block, conf, index); } })() } @@ -411,9 +453,13 @@ export const LOCAL_RULES_HELPERS = { checkSingleMembershipSignature: checkSingleMembershipSignature, - checkBunchOfTransactions: checkBunchOfTransactions, + checkBunchOfTransactions, + + getTransactionDepth, + + getMaxTransactionDepth, - checkSingleTransactionLocally: (tx:any, done:any = undefined) => checkBunchOfTransactions([tx], done), + checkSingleTransactionLocally: (tx:any, conf:ConfDTO) => checkBunchOfTransactions([tx], conf), checkTxAmountsValidity: (tx:TransactionDTO) => { const inputs = tx.inputsAsObjects() diff --git a/app/modules/prover/lib/blockGenerator.ts b/app/modules/prover/lib/blockGenerator.ts index c41dfb24283028cae37c2d7cd09d4fad815a6fdc..39d9cb45aee91ab8d19df5bdd52f9ccee77944d3 100644 --- a/app/modules/prover/lib/blockGenerator.ts +++ b/app/modules/prover/lib/blockGenerator.ts @@ -65,7 +65,7 @@ export class BlockGenerator { const wereExcludeds = await this.dal.getRevokedPubkeys(); const newCertsFromWoT = await generator.findNewCertsFromWoT(current); const newcomersLeavers = await this.findNewcomersAndLeavers(current, (joinersData:any) => generator.filterJoiners(joinersData)); - const transactions = await this.findTransactions(current); + const transactions = await this.findTransactions(current, manualValues); const joinData = newcomersLeavers[2]; const leaveData = newcomersLeavers[3]; const newCertsFromNewcomers = newcomersLeavers[4]; @@ -104,7 +104,8 @@ export class BlockGenerator { return [cur, newWoTMembers, finalJoinData, leavers, updates]; } - private async findTransactions(current:DBBlock) { + private async findTransactions(current:DBBlock, options:{ dontCareAboutChaining?:boolean }) { + const ALSO_CHECK_PENDING_TXS = true const versionMin = current ? Math.min(CommonConstants.LAST_VERSION_FOR_TX, current.version) : CommonConstants.DOCUMENTS_VERSION; const txs = await this.dal.getTransactionsPending(versionMin); const transactions = []; @@ -113,14 +114,9 @@ export class BlockGenerator { obj.currency = this.conf.currency const tx = TransactionDTO.fromJSONObject(obj); try { - await new Promise((resolve, reject) => { - LOCAL_RULES_HELPERS.checkBunchOfTransactions(passingTxs.concat(tx), (err:any, res:any) => { - if (err) return reject(err) - return resolve(res) - }) - }) + await LOCAL_RULES_HELPERS.checkBunchOfTransactions(passingTxs.concat(tx), this.conf, options) const nextBlockWithFakeTimeVariation = { medianTime: current.medianTime + 1 }; - await GLOBAL_RULES_HELPERS.checkSingleTransaction(tx, nextBlockWithFakeTimeVariation, this.conf, this.dal); + await GLOBAL_RULES_HELPERS.checkSingleTransaction(tx, nextBlockWithFakeTimeVariation, this.conf, this.dal, ALSO_CHECK_PENDING_TXS); await GLOBAL_RULES_HELPERS.checkTxBlockStamp(tx, this.dal); transactions.push(tx); passingTxs.push(tx); diff --git a/app/service/BlockchainService.ts b/app/service/BlockchainService.ts index ba652048307abf4a5d3bbacc9da9f89edd562bce..e8148d81d2945f241a7f7be44384050b6b75991d 100644 --- a/app/service/BlockchainService.ts +++ b/app/service/BlockchainService.ts @@ -217,8 +217,9 @@ export class BlockchainService extends FIFOService { } catch (e) { this.logger.error(e) added = false + const theError = e && (e.message || e) this.push({ - blockResolutionError: e && e.message + blockResolutionError: theError }) } i++ diff --git a/app/service/TransactionsService.ts b/app/service/TransactionsService.ts index e6ddf1263234920e509ca684438cc19cec1ecb5f..3068c1b71430fb521ad15146ff6fae11fb879965 100644 --- a/app/service/TransactionsService.ts +++ b/app/service/TransactionsService.ts @@ -41,7 +41,7 @@ export class TransactionService extends FIFOService { // Start checks... const nextBlockWithFakeTimeVariation = { medianTime: current.medianTime + 1 }; const dto = TransactionDTO.fromJSONObject(tx) - await LOCAL_RULES_HELPERS.checkSingleTransactionLocally(dto) + await LOCAL_RULES_HELPERS.checkSingleTransactionLocally(dto, this.conf) await GLOBAL_RULES_HELPERS.checkTxBlockStamp(tx, this.dal); await GLOBAL_RULES_HELPERS.checkSingleTransaction(dto, nextBlockWithFakeTimeVariation, this.conf, this.dal, CHECK_PENDING_TRANSACTIONS); const server_pubkey = this.conf.pair && this.conf.pair.pub; diff --git a/doc/Protocol.md b/doc/Protocol.md index bc424f2d774934b30086c142123e2380cc3ecf5d..68a5300a4c3744b878f4c44cf058d8c89764e801 100644 --- a/doc/Protocol.md +++ b/doc/Protocol.md @@ -1549,6 +1549,40 @@ TRUE > Functionally: we cannot create nor lose money through transactions. We can only transfer coins we own. > Functionally: also, we cannot convert a superiod unit base into a lower one. +##### Transactions chaining max depth + + FUNCTION `getTransactionDepth(txHash, LOCAL_DEPTH)`: + + INPUTS = LOCAL_SINDEX[op='UPDATE',tx=txHash] + DEPTH = LOCAL_DEPTH + + FOR EACH `INPUT` OF `INPUTS` + CONSUMED = LOCAL_SINDEX[op='CREATE',identifier=INPUT.identifier,pos=INPUT.pos] + IF (CONSUMED != NULL) + IF (LOCAL_DEPTH < 5) + DEPTH = MAX(DEPTH, getTransactionDepth(CONSUMED.tx, LOCAL_DEPTH +1) + ELSE + DEPTH++ + END_IF + END_IF + END_FOR + + RETURN DEPTH + + END_FUNCTION + +Then: + + maxTxChainingDepth = 0 + +For each `TX_HASH` of `UNIQ(PICK(LOCAL_SINDEX, 'tx))`: + + maxTxChainingDepth = MAX(maxTxChainingDepth, getTransactionDepth(TX_HASH, 0)) + +Rule: + + maxTxChainingDepth <= 5 + #### Global Global validation verifies the coherence of a locally-validated block, in the context of the whole blockchain, including the block. @@ -1580,6 +1614,7 @@ Function references: > If values count is even, the median is computed over the 2 centered values by an arithmetical median on them, *NOT* rounded. * *UNIQ* returns a list of the unique values in a list of values +* *PICK* returns a list of the values by picking a particular property on each record * *INTEGER_PART* return the integer part of a number * *FIRST* return the first element in a list of values matching the given condition * *REDUCE* merges a set of elements into a single one, by extending the non-null properties from each record into the resulting record. diff --git a/test/fast/protocol-local-rule-chained-tx-depth.ts b/test/fast/protocol-local-rule-chained-tx-depth.ts new file mode 100644 index 0000000000000000000000000000000000000000..a13f0fb3c45bc7d979aa3c98e7c914fe8657a640 --- /dev/null +++ b/test/fast/protocol-local-rule-chained-tx-depth.ts @@ -0,0 +1,45 @@ +import {LOCAL_RULES_HELPERS} from "../../app/lib/rules/local_rules" + +const _ = require('underscore') +const assert = require('assert') + +describe("Protocol BR_G110 - chained tx depth", () => { + + const sindex = [ + { tx: 'A', op: 'UPDATE', identifier: 'UD1', pos: 0 }, + { tx: 'A', op: 'CREATE', identifier: 'TXA', pos: 0 }, + { tx: 'B', op: 'UPDATE', identifier: 'TXA', pos: 0 }, + { tx: 'B', op: 'CREATE', identifier: 'TXB', pos: 0 }, + { tx: 'C', op: 'UPDATE', identifier: 'TXB', pos: 0 }, + { tx: 'C', op: 'CREATE', identifier: 'TXC', pos: 0 }, + { tx: 'D', op: 'UPDATE', identifier: 'TXC', pos: 0 }, + { tx: 'D', op: 'CREATE', identifier: 'TXD', pos: 0 }, + { tx: 'E', op: 'UPDATE', identifier: 'TXD', pos: 0 }, + { tx: 'E', op: 'CREATE', identifier: 'TXE', pos: 0 }, + { tx: 'F', op: 'UPDATE', identifier: 'TXE', pos: 0 }, + { tx: 'F', op: 'CREATE', identifier: 'TXF', pos: 0 }, + { tx: 'G', op: 'UPDATE', identifier: 'TXF', pos: 0 }, + { tx: 'G', op: 'CREATE', identifier: 'TXG', pos: 0 }, + { tx: 'H', op: 'UPDATE', identifier: 'TXG', pos: 0 }, + { tx: 'H', op: 'CREATE', identifier: 'TXH', pos: 0 }, + ] + + it('should detected normal depth', () => { + assert.equal(0, LOCAL_RULES_HELPERS.getTransactionDepth('A', sindex)) + assert.equal(1, LOCAL_RULES_HELPERS.getTransactionDepth('B', sindex)) + assert.equal(2, LOCAL_RULES_HELPERS.getTransactionDepth('C', sindex)) + assert.equal(3, LOCAL_RULES_HELPERS.getTransactionDepth('D', sindex)) + assert.equal(4, LOCAL_RULES_HELPERS.getTransactionDepth('E', sindex)) + assert.equal(5, LOCAL_RULES_HELPERS.getTransactionDepth('F', sindex)) + assert.equal(6, LOCAL_RULES_HELPERS.getTransactionDepth('G', sindex)) + }) + + it('should detected max the depth to 6', () => { + assert.equal(6, LOCAL_RULES_HELPERS.getTransactionDepth('H', sindex)) + }) + + it('should find the max depth globally', () => { + assert.equal(6, LOCAL_RULES_HELPERS.getMaxTransactionDepth(sindex)) + }) +}) + diff --git a/test/integration/transactions-chaining.js b/test/integration/transactions-chaining.js deleted file mode 100644 index 66a02c1ca402f6046a8ece5faaa057bbd26efba0..0000000000000000000000000000000000000000 --- a/test/integration/transactions-chaining.js +++ /dev/null @@ -1,92 +0,0 @@ -"use strict"; - -const co = require('co'); -const _ = require('underscore'); -const should = require('should'); -const assert = require('assert'); -const constants = require('../../app/lib/constants'); -const bma = require('../../app/modules/bma').BmaDependency.duniter.methods.bma; -const CommonConstants = require('../../app/lib/common-libs/constants').CommonConstants -const toolbox = require('./tools/toolbox'); -const node = require('./tools/node'); -const TestUser = require('./tools/TestUser').TestUser -const unit = require('./tools/unit'); -const http = require('./tools/http'); - -describe("Transaction chaining", function() { - - const now = 1456644632; - - let s1, tic, toc - - before(() => co(function*() { - - s1 = toolbox.server({ - pair: { - pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', - sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7' - }, - dt: 3600, - udTime0: now + 3600, - ud0: 1200, - c: 0.1 - }); - - tic = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s1 }); - toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); - - yield s1.initDalBmaConnections(); - yield tic.createIdentity(); - yield toc.createIdentity(); - yield tic.cert(toc); - yield toc.cert(tic); - yield tic.join(); - yield toc.join(); - yield s1.commit({ time: now }); - yield s1.commit({ time: now + 7210 }); - yield s1.commit({ time: now + 7210 }); - })); - - after(() => { - return Promise.all([ - s1.closeCluster() - ]) - }) - - describe("Sources", function(){ - - it('it should exist block#2 with UD of 1200', () => s1.expect('/blockchain/block/2', (block) => { - should.exists(block); - assert.equal(block.number, 2); - assert.equal(block.dividend, 1200); - })); - }); - - describe("Chaining", function(){ - - it('with SIG and XHX', () => co(function *() { - // Current state - let current = yield s1.get('/blockchain/current'); - (yield s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(1); - let tx1 = yield toc.prepareITX(1040, tic); // Rest = 1200 - 1040 = 160 - let tx2 = yield toc.prepareUTX(tx1, ['SIG(0)'], [{ qty: 160, base: 0, lock: 'SIG(' + tic.pub + ')' }], { - comment: 'also take the remaining 160 units', - blockstamp: [current.number, current.hash].join('-'), - theseOutputsStart: 1 - }); - const tmp = CommonConstants.TRANSACTION_MAX_TRIES; - CommonConstants.TRANSACTION_MAX_TRIES = 2; - yield unit.shouldNotFail(toc.sendTX(tx1)); - yield unit.shouldNotFail(toc.sendTX(tx2)); - (yield s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(1); - (yield s1.get('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')).should.have.property('sources').length(1); - yield s1.commit({ time: now + 7210 }); // TX1 commited - (yield s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(1); // The 160 remaining units - (yield s1.get('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')).should.have.property('sources').length(2); // The UD + 1040 units sent by toc - yield s1.commit({ time: now + 7210 }); // TX2 commited - (yield s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(0); - (yield s1.get('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')).should.have.property('sources').length(3); // The UD + 1040 + 160 units sent by toc - CommonConstants.TRANSACTION_MAX_TRIES = tmp; - })); - }); -}); diff --git a/test/integration/transactions-chaining.ts b/test/integration/transactions-chaining.ts new file mode 100644 index 0000000000000000000000000000000000000000..cbcbddf8fad80dcdbefe0440839365cde84efa9d --- /dev/null +++ b/test/integration/transactions-chaining.ts @@ -0,0 +1,109 @@ +import {CommonConstants} from "../../app/lib/common-libs/constants" +import {TestUser} from "./tools/TestUser" +import {TestingServer} from "./tools/toolbox" +import {NewLogger} from "../../app/lib/logger" + +const should = require('should'); +const assert = require('assert'); +const toolbox = require('./tools/toolbox'); +const unit = require('./tools/unit'); + +describe("Transaction chaining", () => { + + const now = 1456644632; + + let s1:TestingServer, tic:TestUser, toc:TestUser + + before(async () => { + + s1 = toolbox.server({ + pair: { + pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', + sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7' + }, + dt: 3600, + udTime0: now + 3600, + ud0: 1200, + c: 0.1 + }); + + tic = new TestUser('tic', { pub: 'DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV', sec: '468Q1XtTq7h84NorZdWBZFJrGkB18CbmbHr9tkp9snt5GiERP7ySs3wM8myLccbAAGejgMRC9rqnXuW3iAfZACm7'}, { server: s1 }); + toc = new TestUser('toc', { pub: 'DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo', sec: '64EYRvdPpTfLGGmaX5nijLXRqWXaVz8r1Z1GtaahXwVSJGQRn7tqkxLb288zwSYzELMEG5ZhXSBYSxsTsz1m9y8F'}, { server: s1 }); + + await s1.initDalBmaConnections(); + await tic.createIdentity(); + await toc.createIdentity(); + await tic.cert(toc); + await toc.cert(tic); + await tic.join(); + await toc.join(); + await s1.commit({ time: now }); + await s1.commit({ time: now + 7210 }); + await s1.commit({ time: now + 7210 }); + }) + + after(() => { + return s1.closeCluster() + }) + + describe("Sources", () => { + + it('it should exist block#2 with UD of 1200', () => s1.expect('/blockchain/block/2', (block: { number:number, dividend:number }) => { + should.exists(block); + assert.equal(block.number, 2); + assert.equal(block.dividend, 1200); + })) + }) + + describe("Chaining", () => { + + it('with SIG and XHX', async () => { + // Current state + let current = await s1.get('/blockchain/current'); + (await s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(1); + let tx1 = await toc.prepareITX(1040, tic); // Rest = 1200 - 1040 = 160 + let tx2 = await toc.prepareUTX(tx1, ['SIG(0)'], [{ qty: 160, base: 0, lock: 'SIG(' + tic.pub + ')' }], { + comment: 'also take the remaining 160 units', + blockstamp: [current.number, current.hash].join('-'), + theseOutputsStart: 1 + }); + const tmp = CommonConstants.TRANSACTION_MAX_TRIES; + CommonConstants.TRANSACTION_MAX_TRIES = 2; + await unit.shouldNotFail(toc.sendTX(tx1)); + await unit.shouldNotFail(toc.sendTX(tx2)); + (await s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(1); + (await s1.get('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')).should.have.property('sources').length(1); + await s1.commit({ time: now + 7210 }); // TX1 + TX2 commited + (await s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(0); + (await s1.get('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')).should.have.property('sources').length(3); // The UD + 1040 + 160 units sent by toc + CommonConstants.TRANSACTION_MAX_TRIES = tmp; + }) + + it('should refuse a block with more than 5 chained tx in it', async () => { + // Current state + let current = await s1.get('/blockchain/current'); + const blockstamp = [current.number, current.hash].join('-'); + (await s1.get('/tx/sources/DKpQPUL4ckzXYdnDRvCRKAm1gNvSdmAXnTrJZ7LvM5Qo')).should.have.property('sources').length(0); + (await s1.get('/tx/sources/DNann1Lh55eZMEDXeYt59bzHbA3NJR46DeQYCS2qQdLV')).should.have.property('sources').length(3); + // Ping-pong of 1200 units + let tx1 = await tic.prepareITX(1200, toc, "PING-PONG TX1"); + let tx2 = await toc.prepareUTX(tx1, ['SIG(0)'], [{ qty: 1200, base: 0, lock: 'SIG(' + tic.pub + ')' }], { blockstamp, comment: "PING-PONG TX2" }); + let tx3 = await tic.prepareUTX(tx2, ['SIG(0)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { blockstamp, comment: "PING-PONG TX3" }); + let tx4 = await toc.prepareUTX(tx3, ['SIG(0)'], [{ qty: 1200, base: 0, lock: 'SIG(' + tic.pub + ')' }], { blockstamp, comment: "PING-PONG TX4" }); + let tx5 = await tic.prepareUTX(tx4, ['SIG(0)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { blockstamp, comment: "PING-PONG TX5" }); + let tx6 = await toc.prepareUTX(tx5, ['SIG(0)'], [{ qty: 1200, base: 0, lock: 'SIG(' + tic.pub + ')' }], { blockstamp, comment: "PING-PONG TX6" }); + let tx7 = await tic.prepareUTX(tx6, ['SIG(0)'], [{ qty: 1200, base: 0, lock: 'SIG(' + toc.pub + ')' }], { blockstamp, comment: "PING-PONG TX7" }); + const tmp = CommonConstants.TRANSACTION_MAX_TRIES; + CommonConstants.TRANSACTION_MAX_TRIES = 2; + await unit.shouldNotFail(toc.sendTX(tx1)); + await unit.shouldNotFail(toc.sendTX(tx2)); + await unit.shouldNotFail(toc.sendTX(tx3)); + await unit.shouldNotFail(toc.sendTX(tx4)); + await unit.shouldNotFail(toc.sendTX(tx5)); + await unit.shouldNotFail(toc.sendTX(tx6)); + await unit.shouldNotFail(toc.sendTX(tx7)); + await s1.commitWaitError({ dontCareAboutChaining: true }, 'The maximum transaction chaining length per block is 5') + CommonConstants.TRANSACTION_MAX_TRIES = tmp; + }) + }); +});