Commit 6f30eab3 authored by Cédric Moreau's avatar Cédric Moreau
Browse files

[fix] #1037 Migrating Entity Transaction

parent c3e26eae
......@@ -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);
}
......
......@@ -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);
}))
......
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));
......
......@@ -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, [], [], [], [], [], "")
}
......
"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
};
};
}
"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;
......@@ -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
......
......@@ -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;
......
......@@ -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) {
......
......@@ -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]
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment