From 6f30eab39791363e47c400f5185504b65c95f3e2 Mon Sep 17 00:00:00 2001 From: cgeek <cem.moreau@gmail.com> Date: Wed, 19 Jul 2017 18:05:29 +0200 Subject: [PATCH] [fix] #1037 Migrating Entity Transaction --- app/lib/blockchain/DuniterBlockchain.ts | 6 +- app/lib/dal/fileDAL.ts | 3 +- app/lib/dal/sqliteDAL/TxsDAL.ts | 27 +++-- app/lib/dto/TransactionDTO.ts | 82 +++++++++++++- app/lib/entity/source.js | 38 ------- app/lib/entity/transaction.js | 138 ------------------------ app/lib/streams/multicaster.ts | 6 +- app/service/BlockchainService.ts | 7 -- app/service/TransactionsService.ts | 17 +-- test/integration/tools/user.js | 6 +- 10 files changed, 118 insertions(+), 212 deletions(-) delete mode 100644 app/lib/entity/source.js delete mode 100644 app/lib/entity/transaction.js diff --git a/app/lib/blockchain/DuniterBlockchain.ts b/app/lib/blockchain/DuniterBlockchain.ts index bb269b3d7..5f2739205 100644 --- a/app/lib/blockchain/DuniterBlockchain.ts +++ b/app/lib/blockchain/DuniterBlockchain.ts @@ -10,10 +10,10 @@ import {RevocationDTO} from "../dto/RevocationDTO" import {IdentityDTO} from "../dto/IdentityDTO" import {CertificationDTO} from "../dto/CertificationDTO" import {MembershipDTO} from "../dto/MembershipDTO" +import {TransactionDTO} from "../dto/TransactionDTO" const _ = require('underscore') const common = require('duniter-common') -const Transaction = require('../entity/transaction') export class DuniterBlockchain extends MiscIndexedBlockchain { @@ -403,7 +403,7 @@ export class DuniterBlockchain extends MiscIndexedBlockchain { async undoDeleteTransactions(block:BlockDTO, dal:any) { for (const obj of block.transactions) { obj.currency = block.currency; - let tx = new Transaction(obj); + let tx = TransactionDTO.fromJSONObject(obj) await dal.saveTransaction(tx); } } @@ -453,7 +453,7 @@ export class DuniterBlockchain extends MiscIndexedBlockchain { async deleteTransactions(block:BlockDTO, dal:any) { for (const obj of block.transactions) { obj.currency = block.currency; - const tx = new Transaction(obj); + const tx = TransactionDTO.fromJSONObject(obj) const txHash = tx.getHash(); await dal.removeTxByHash(txHash); } diff --git a/app/lib/dal/fileDAL.ts b/app/lib/dal/fileDAL.ts index 883e3318a..b6d910be8 100644 --- a/app/lib/dal/fileDAL.ts +++ b/app/lib/dal/fileDAL.ts @@ -22,7 +22,6 @@ const _ = require('underscore'); const common = require('duniter-common'); const indexer = require('../indexer').Indexer const logger = require('../logger').NewLogger('filedal'); -const Transaction = require('../entity/transaction'); const constants = require('../constants'); export interface FileDALParams { @@ -692,7 +691,7 @@ export class FileDAL { return Promise.all(txs.map(async (tx) => { const sp = tx.blockstamp.split('-'); tx.blockstampTime = (await this.getBlockByNumberAndHash(parseInt(sp[0]), sp[1])).medianTime; - const txEntity = new Transaction(tx); + const txEntity = TransactionDTO.fromJSONObject(tx) txEntity.computeAllHashes(); return this.txsDAL.addLinked(TransactionDTO.fromJSONObject(txEntity), block_number, medianTime); })) diff --git a/app/lib/dal/sqliteDAL/TxsDAL.ts b/app/lib/dal/sqliteDAL/TxsDAL.ts index 1d2a542b0..4b73d3b52 100644 --- a/app/lib/dal/sqliteDAL/TxsDAL.ts +++ b/app/lib/dal/sqliteDAL/TxsDAL.ts @@ -1,12 +1,11 @@ -import {AbstractSQLite} from "./AbstractSQLite"; -import {SQLiteDriver} from "../drivers/SQLiteDriver"; -import {TransactionDTO} from "../../dto/TransactionDTO"; -import {SandBox} from "./SandBox"; +import {AbstractSQLite} from "./AbstractSQLite" +import {SQLiteDriver} from "../drivers/SQLiteDriver" +import {TransactionDTO} from "../../dto/TransactionDTO" +import {SandBox} from "./SandBox" const _ = require('underscore'); const moment = require('moment'); const constants = require('../../constants'); -const Transaction = require('../../entity/transaction'); export class DBTx { hash: string @@ -47,10 +46,22 @@ export class DBTx { dbTx.recipients = tx.outputsAsRecipients() dbTx.written = false dbTx.removed = false - dbTx.output_base = tx.outputs.reduce((sum, output) => sum + parseInt(output.split(':')[0]), 0) - dbTx.output_amount = tx.outputs.reduce((maxBase, output) => Math.max(maxBase, parseInt(output.split(':')[1])), 0) + dbTx.output_base = tx.output_base + dbTx.output_amount = tx.output_amount return dbTx } + + static setRecipients(txs:DBTx[]) { + // Each transaction must have a good "recipients" field for future searchs + txs.forEach((tx) => tx.recipients = DBTx.outputs2recipients(tx)) + } + + static outputs2recipients(tx:DBTx) { + return tx.outputs.map(function(out) { + const recipent = out.match('SIG\\((.*)\\)') + return (recipent && recipent[1]) || 'UNKNOWN' + }) + } } export class TxsDAL extends AbstractSQLite<DBTx> { @@ -201,7 +212,7 @@ export class TxsDAL extends AbstractSQLite<DBTx> { insertBatchOfTxs(txs:DBTx[]) { // // Be sure the recipients field are correctly updated - Transaction.statics.setRecipients(txs); + DBTx.setRecipients(txs); const queries = []; const insert = this.getInsertHead(); const values = txs.map((cert) => this.getInsertValue(cert)); diff --git a/app/lib/dto/TransactionDTO.ts b/app/lib/dto/TransactionDTO.ts index eca3a2cf3..b0c5dafad 100644 --- a/app/lib/dto/TransactionDTO.ts +++ b/app/lib/dto/TransactionDTO.ts @@ -46,6 +46,18 @@ export class TransactionDTO { } } + get signature() { + return this.signatures[0] + } + + get output_base() { + return this.outputs.reduce((sum, output) => sum + parseInt(output.split(':')[0]), 0) + } + + get output_amount() { + return this.outputs.reduce((maxBase, output) => Math.max(maxBase, parseInt(output.split(':')[1])), 0) + } + getLen() { return 2 // header + blockstamp + this.issuers.length * 2 // issuers + signatures @@ -96,6 +108,36 @@ export class TransactionDTO { }) } + getRaw() { + let raw = "" + raw += "Version: " + (this.version) + "\n" + raw += "Type: Transaction\n" + raw += "Currency: " + this.currency + "\n" + raw += "Blockstamp: " + this.blockstamp + "\n" + raw += "Locktime: " + this.locktime + "\n" + raw += "Issuers:\n"; + (this.issuers || []).forEach((issuer) => { + raw += issuer + '\n' + }) + raw += "Inputs:\n"; + this.inputs.forEach((input) => { + raw += input + '\n' + }) + raw += "Unlocks:\n"; + this.unlocks.forEach((unlock) => { + raw += unlock + '\n' + }) + raw += "Outputs:\n"; + this.outputs.forEach((output) => { + raw += output + '\n' + }) + raw += "Comment: " + (this.comment || "") + "\n"; + this.signatures.forEach((signature) => { + raw += signature + '\n' + }) + return raw + } + getCompactVersion() { let issuers = this.issuers; let raw = ["TX", this.version, issuers.length, this.inputs.length, this.unlocks.length, this.outputs.length, this.comment ? 1 : 0, this.locktime || 0].join(':') + '\n'; @@ -120,10 +162,32 @@ export class TransactionDTO { return raw } - static fromJSONObject(obj:any) { + computeAllHashes() { + this.hash = hashf(this.getRaw()).toUpperCase(); + } + + json() { + return { + 'version': this.version, + 'currency': this.currency, + 'issuers': this.issuers, + 'inputs': this.inputs, + 'unlocks': this.unlocks, + 'outputs': this.outputs, + 'comment': this.comment, + 'locktime': this.locktime, + 'blockstamp': this.blockstamp, + 'blockstampTime': this.blockstampTime, + 'signatures': this.signatures, + 'raw': this.getRaw(), + 'hash': this.hash + } + } + + static fromJSONObject(obj:any, currency:string = "") { return new TransactionDTO( obj.version || 10, - obj.currency || "", + currency || obj.currency || "", obj.locktime || 0, obj.hash || "", obj.blockstamp || "", @@ -169,6 +233,20 @@ export class TransactionDTO { return raw } + static outputObj2Str(o:OutputDTO) { + return [o.amount, o.base, o.conditions].join(':') + } + + static outputStr2Obj(outputStr:string) { + const sp = outputStr.split(':'); + return { + amount: parseInt(sp[0]), + base: parseInt(sp[1]), + conditions: sp[2], + raw: outputStr + }; + } + static mock() { return new TransactionDTO(1, "", 0, "", "", 0, [], [], [], [], [], "") } diff --git a/app/lib/entity/source.js b/app/lib/entity/source.js deleted file mode 100644 index 05b6fcf45..000000000 --- a/app/lib/entity/source.js +++ /dev/null @@ -1,38 +0,0 @@ -"use strict"; -const _ = require('underscore'); - -module.exports = Source; - -function Source(json) { - - _(json || {}).keys().forEach((key) => { - let value = json[key]; - if (key == "number") { - value = parseInt(value); - } - else if (key == "consumed") { - value = !!value; - } - this[key] = value; - }); - - this.json = function () { - return { - "type": this.type, - "noffset": this.pos, - "identifier": this.identifier, - "amount": this.amount, - "base": this.base - }; - }; - - this.UDjson = function () { - return { - "block_number": this.number, - "consumed": this.consumed, - "time": this.time, - "amount": this.amount, - "base": this.base - }; - }; -} diff --git a/app/lib/entity/transaction.js b/app/lib/entity/transaction.js deleted file mode 100644 index a6c2bd9c0..000000000 --- a/app/lib/entity/transaction.js +++ /dev/null @@ -1,138 +0,0 @@ -"use strict"; -let _ = require('underscore'); -let rawer = require('duniter-common').rawer; -let hashf = require('duniter-common').hashf; - -let Transaction = function(obj, currency) { - - let json = obj || {}; - - this.locktime = 0; - this.inputs = []; - this.unlocks = []; - this.outputs = []; - this.issuers = []; - - _(json).keys().forEach((key) => { - this[key] = json[key]; - }); - - // Store the maximum output base - this.output_amount = this.outputs.reduce((sum, output) => sum + parseInt((output.raw || output).split(':')[0]), 0); - this.output_base = this.outputs.reduce((maxBase, output) => Math.max(maxBase, parseInt((output.raw || output).split(':')[1])), 0); - - this.currency = currency || this.currency; - - this.json = () => { - return { - 'version': parseInt(this.version, 10), - 'currency': this.currency, - 'issuers': this.issuers, - 'inputs': this.inputs, - 'unlocks': this.unlocks, - 'outputs': this.outputs, - 'comment': this.comment, - 'locktime': this.locktime, - 'blockstamp': this.blockstamp, - 'blockstampTime': this.blockstampTime, - 'signatures': this.signatures, - 'raw': this.getRaw(), - 'hash': this.hash - }; - }; - - this.getTransaction = () => { - const tx = {}; - tx.hash = this.hash; - tx.version = this.version; - tx.currency = this.currency; - tx.issuers = this.issuers; - tx.signatures = this.signatures; - // Inputs - tx.inputs = []; - this.inputs.forEach((input) => { - const sp = input.split(':'); - tx.inputs.push({ - amount: sp[0], - base: sp[1], - type: sp[2], - identifier: sp[3], - pos: parseInt(sp[4]), - raw: input - }); - }); - // Unlocks - tx.unlocks = this.unlocks; - // Outputs - tx.outputs = []; - this.outputs.forEach(function (output) { - tx.outputs.push(Transaction.statics.outputStr2Obj(output)); - }); - tx.comment = this.comment; - tx.blockstamp = this.blockstamp; - tx.blockstampTime = this.blockstampTime; - tx.locktime = this.locktime; - return tx; - }; - - this.getRaw = () => rawer.getTransaction(this); - - this.getHash = (recompute) => { - if (recompute || !this.hash) { - this.hash = hashf(rawer.getTransaction(this)).toUpperCase(); - } - return this.hash; - }; - - this.computeAllHashes = () => { - this.hash = hashf(rawer.getTransaction(this)).toUpperCase(); - }; - - this.compact = () => rawer.getCompactTransaction(this); - - this.hash = this.hash || hashf(this.getRaw()).toUpperCase(); -}; - -Transaction.statics = {}; - -Transaction.statics.fromJSON = (json) => new Transaction(json); - -Transaction.statics.outputs2recipients = (tx) => tx.outputs.map(function(out) { - const recipent = (out.raw || out).match('SIG\\((.*)\\)'); - return (recipent && recipent[1]) || 'UNKNOWN'; -}); - -Transaction.statics.outputStr2Obj = (outputStr) => { - const sp = outputStr.split(':'); - return { - amount: parseInt(sp[0]), - base: parseInt(sp[1]), - conditions: sp[2], - raw: outputStr - }; -}; - -Transaction.statics.outputObj2Str = (o) => [o.amount, o.base, o.conditions].join(':') - -Transaction.statics.setRecipients = (txs) => { - // Each transaction must have a good "recipients" field for future searchs - txs.forEach((tx) => tx.recipients = Transaction.statics.outputs2recipients(tx)); -}; - -Transaction.statics.cleanSignatories = (txs) => { - // Remove unused signatories - see https://github.com/duniter/duniter/issues/494 - txs.forEach((tx) => { - if (tx.signatories) { - delete tx.signatories; - } - return tx; - }); -}; - -Transaction.statics.getLen = (tx) => 2 // header + blockstamp - + tx.issuers.length * 2 // issuers + signatures - + tx.inputs.length * 2 // inputs + unlocks - + (tx.comment ? 1 : 0) - + tx.outputs.length; - -module.exports = Transaction; diff --git a/app/lib/streams/multicaster.ts b/app/lib/streams/multicaster.ts index 61f4822a9..9b855017d 100644 --- a/app/lib/streams/multicaster.ts +++ b/app/lib/streams/multicaster.ts @@ -6,11 +6,11 @@ import {RevocationDTO} from "../dto/RevocationDTO" import {IdentityDTO} from "../dto/IdentityDTO" import {CertificationDTO} from "../dto/CertificationDTO" import {MembershipDTO} from "../dto/MembershipDTO" +import {TransactionDTO} from "../dto/TransactionDTO" const request = require('request'); const constants = require('../../lib/constants'); const Peer = require('../../lib/entity/peer'); -const Transaction = require('../../lib/entity/transaction'); const logger = require('../logger').NewLogger('multicaster'); const WITH_ISOLATION = true; @@ -87,10 +87,10 @@ export class Multicaster extends stream.Transform { async txForward(doc:any, peers:DBPeer[]) { return this.forward({ - transform: Transaction.statics.fromJSON, + transform: (obj:any) => TransactionDTO.fromJSONObject(obj), type: 'Transaction', uri: '/tx/process', - getObj: (transaction:any) => { + getObj: (transaction:TransactionDTO) => { return { "transaction": transaction.getRaw(), "signature": transaction.signature diff --git a/app/service/BlockchainService.ts b/app/service/BlockchainService.ts index b1ecc035c..b2195897c 100644 --- a/app/service/BlockchainService.ts +++ b/app/service/BlockchainService.ts @@ -13,7 +13,6 @@ const _ = require('underscore'); const co = require('co'); const parsers = require('duniter-common').parsers; const constants = require('../lib/constants'); -const Transaction = require('../lib/entity/transaction'); const CHECK_ALL_RULES = true; @@ -130,12 +129,6 @@ export class BlockchainService { } else { this.conf.currency = obj.currency; } - try { - Transaction.statics.cleanSignatories(obj.transactions); - } - catch (e) { - throw e; - } let existing = await this.dal.getBlockByNumberAndHashOrNull(obj.number, obj.hash); if (existing) { throw constants.ERRORS.BLOCK_ALREADY_PROCESSED; diff --git a/app/service/TransactionsService.ts b/app/service/TransactionsService.ts index 9b7757d87..4d57ccf23 100644 --- a/app/service/TransactionsService.ts +++ b/app/service/TransactionsService.ts @@ -5,9 +5,9 @@ import {FileDAL} from "../lib/dal/fileDAL" import {TransactionDTO} from "../lib/dto/TransactionDTO" import {LOCAL_RULES_HELPERS} from "../lib/rules/local_rules" import {GLOBAL_RULES_HELPERS} from "../lib/rules/global_rules" +import {DBTx} from "../lib/dal/sqliteDAL/TxsDAL" const constants = require('../lib/constants'); -const Transaction = require('../lib/entity/transaction'); const CHECK_PENDING_TRANSACTIONS = true export class TransactionService { @@ -24,7 +24,7 @@ export class TransactionService { processTx(txObj:any) { return GlobalFifoPromise.pushFIFO(async () => { - const tx = new Transaction(txObj, this.conf.currency); + const tx = TransactionDTO.fromJSONObject(txObj, this.conf.currency) try { this.logger.info('⬇ TX %s:%s from %s', tx.output_amount, tx.output_base, tx.issuers); const existing = await this.dal.getTxByHash(tx.hash); @@ -33,19 +33,20 @@ export class TransactionService { throw constants.ERRORS.TX_ALREADY_PROCESSED; } // Start checks... - const transaction = tx.getTransaction(); const nextBlockWithFakeTimeVariation = { medianTime: current.medianTime + 1 }; const dto = TransactionDTO.fromJSONObject(tx) await LOCAL_RULES_HELPERS.checkSingleTransactionLocally(dto) - await GLOBAL_RULES_HELPERS.checkTxBlockStamp(transaction, this.dal); + 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; - transaction.pubkey = transaction.issuers.indexOf(server_pubkey) !== -1 ? server_pubkey : ''; - if (!(await this.dal.txsDAL.sandbox.acceptNewSandBoxEntry(transaction, server_pubkey))) { + if (!(await this.dal.txsDAL.sandbox.acceptNewSandBoxEntry({ + pubkey: tx.issuers.indexOf(server_pubkey) !== -1 ? server_pubkey : '', + output_base: tx.output_base, + output_amount: tx.output_amount + }, server_pubkey))) { throw constants.ERRORS.SANDBOX_FOR_TRANSACTION_IS_FULL; } - tx.blockstampTime = transaction.blockstampTime; - await this.dal.saveTransaction(tx); + await this.dal.saveTransaction(DBTx.fromTransactionDTO(tx)); this.logger.info('✔ TX %s:%s from %s', tx.output_amount, tx.output_base, tx.issuers); return tx; } catch (e) { diff --git a/test/integration/tools/user.js b/test/integration/tools/user.js index fb9540d7a..72bb1fc8b 100644 --- a/test/integration/tools/user.js +++ b/test/integration/tools/user.js @@ -14,7 +14,7 @@ const CertificationDTO = require('../../../app/lib/dto/CertificationDTO').Certif const MembershipDTO = require('../../../app/lib/dto/MembershipDTO').MembershipDTO const RevocationDTO = require('../../../app/lib/dto/RevocationDTO').RevocationDTO const Peer = require('../../../app/lib/entity/peer'); -const Transaction = require('../../../app/lib/entity/transaction'); +const TransactionDTO = require('../../../app/lib/dto/TransactionDTO').TransactionDTO module.exports = function (uid, url, node) { return new User(uid, url, node); @@ -183,7 +183,7 @@ function User (uid, options, node) { outputsToConsume = outputsToConsume.slice(opts.theseOutputsStart); } let inputs = outputsToConsume.map((out, index) => { - const output = Transaction.statics.outputStr2Obj(out); + const output = TransactionDTO.outputStr2Obj(out); return { src: [output.amount, output.base, 'T', obj.hash, (opts.theseOutputsStart || 0) + index].join(':'), unlock: unlocks[index] @@ -196,7 +196,7 @@ function User (uid, options, node) { let obj = parsers.parseTransaction.syncWrite(previousTX); // Unlocks inputs with given "unlocks" strings let inputs = obj.outputs.map((out, index) => { - const output = Transaction.statics.outputStr2Obj(out); + const output = TransactionDTO.outputStr2Obj(out); return { src: [output.amount, output.base, 'T', obj.hash, index].join(':'), unlock: unlocks[index] -- GitLab