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