From 256dff4b663e89a23d3d795cb74360a85943e511 Mon Sep 17 00:00:00 2001 From: Benoit Lavenier <benoit.lavenier@e-is.pro> Date: Fri, 19 May 2023 18:15:45 +0200 Subject: [PATCH] [enh] Improve BMA '/tx/history' - close #1442 --- app/lib/dal/fileDAL.ts | 85 ++++++--- app/modules/bma/lib/bma.ts | 2 +- .../bma/lib/controllers/transactions.ts | 73 ++++---- neon/native/index.d.ts | 2 +- neon/native/server.d.ts | 10 +- neon/native/src/server.rs | 109 ++++++++++- .../duniter-server/src/legacy/tx_history.rs | 16 +- .../transactions/transactions-history.ts | 174 ++++++++++++++++++ 8 files changed, 396 insertions(+), 75 deletions(-) create mode 100644 test/integration/transactions/transactions-history.ts diff --git a/app/lib/dal/fileDAL.ts b/app/lib/dal/fileDAL.ts index 4d9ef79e7..5118b843f 100644 --- a/app/lib/dal/fileDAL.ts +++ b/app/lib/dal/fileDAL.ts @@ -80,7 +80,7 @@ import { LevelDBMindex } from "./indexDAL/leveldb/LevelDBMindex"; import { ConfDAO } from "./indexDAL/abstract/ConfDAO"; import { ServerDAO } from "./server-dao"; import { PeerDTO } from "../dto/PeerDTO"; -import { RustPendingTx } from "../../../neon/native/server"; +import { RustPendingTx, RustTxsHistory } from "../../../neon/native/server"; const readline = require("readline"); const indexer = require("../indexer").Indexer; @@ -1449,7 +1449,7 @@ export class FileDAL implements ServerDAO { return tx; } - async RustDbTxToDbTx(tx: RustDbTx): Promise<DBTx> { + private async rustDbTxToDbTx(tx: RustDbTx): Promise<DBTx> { let writtenBlockNumber = tx.writtenBlockNumber; let writtenTime = tx.writtenTime; let tx_dto = await this.computeTxBlockstampTime( @@ -1458,10 +1458,11 @@ export class FileDAL implements ServerDAO { let db_tx = DBTx.fromTransactionDTO(tx_dto); db_tx.block_number = writtenBlockNumber; db_tx.time = writtenTime; + db_tx.written = !!writtenBlockNumber; return db_tx; } - async RustPendingTxToDbTx(tx: RustPendingTx): Promise<DBTx> { + private async rustPendingTxToDbTx(tx: RustPendingTx): Promise<DBTx> { let receivedTime = tx.receivedTime; let tx_dto = await this.computeTxBlockstampTime( TransactionDTO.fromJSONObject(tx) @@ -1471,32 +1472,64 @@ export class FileDAL implements ServerDAO { return db_tx; } - async getTransactionsHistory(pubkey: string) { - const history: { - sent: DBTx[]; - received: DBTx[]; - sending: DBTx[]; - pending: DBTx[]; - } = { - sent: [], - received: [], - sending: [], - pending: [], + private async toDbTxHistory(source: RustTxsHistory) { + return < + { + sent: DBTx[]; + received: DBTx[]; + sending: DBTx[]; + pending: DBTx[]; + } + >{ + sent: await Promise.all( + source.sent.map(async (tx) => this.rustDbTxToDbTx(tx)) + ), + received: await Promise.all( + source.received.map(async (tx) => this.rustDbTxToDbTx(tx)) + ), + sending: await Promise.all( + source.sending.map(async (tx) => this.rustPendingTxToDbTx(tx)) + ), + pending: await Promise.all( + source.pending.map(async (tx) => this.rustPendingTxToDbTx(tx)) + ), }; - const res = this.rustServer.getTransactionsHistory(pubkey); - history.sent = await Promise.all( - res.sent.map(async (tx) => this.RustDbTxToDbTx(tx)) - ); - history.received = await Promise.all( - res.received.map(async (tx) => this.RustDbTxToDbTx(tx)) - ); - history.sending = await Promise.all( - res.sending.map(async (tx) => this.RustPendingTxToDbTx(tx)) + } + + async getTxHistoryByPubkey(pubkey: string) { + const res = this.rustServer.getTxHistoryByPubkey(pubkey); + return this.toDbTxHistory(res); + } + + async getTxHistoryByPubkeyBetweenBlocks( + pubkey: string, + from: number, + to: number + ) { + const res = this.rustServer.getTxHistoryByPubkeyBetweenBlocks( + pubkey, + +from, + +to ); - history.pending = await Promise.all( - res.pending.map(async (tx) => this.RustPendingTxToDbTx(tx)) + return this.toDbTxHistory(res); + } + + async getTxHistoryByPubkeyBetweenTimes( + pubkey: string, + from: number, + to: number + ) { + const res = this.rustServer.getTxHistoryByPubkeyBetweenTimes( + pubkey, + +from, + +to ); - return history; + return this.toDbTxHistory(res); + } + + async getTxHistoryMempool(pubkey: string) { + const res = this.rustServer.getTxHistoryMempool(pubkey); + return this.toDbTxHistory(res); } async getUDHistory(pubkey: string): Promise<{ history: HttpUD[] }> { diff --git a/app/modules/bma/lib/bma.ts b/app/modules/bma/lib/bma.ts index 013509fd1..147b74d6b 100644 --- a/app/modules/bma/lib/bma.ts +++ b/app/modules/bma/lib/bma.ts @@ -305,7 +305,7 @@ export const bma = function ( ); httpMethods.httpGET( "/tx/history/:pubkey/pending", - (req: any) => transactions.getPendingForPubkey(req), + (req: any) => transactions.getPendingByPubkey(req), BMALimitation.limitAsHighUsage() ); httpMethods.httpGET( diff --git a/app/modules/bma/lib/controllers/transactions.ts b/app/modules/bma/lib/controllers/transactions.ts index 907450992..8528d87c3 100644 --- a/app/modules/bma/lib/controllers/transactions.ts +++ b/app/modules/bma/lib/controllers/transactions.ts @@ -29,6 +29,10 @@ import { Underscore } from "../../../../lib/common-libs/underscore"; const http2raw = require("../http2raw"); export class TransactionBinding extends AbstractController { + get medianTimeOffset(): number { + return (this.conf.avgGenTime * this.conf.medianTimeBlocks) / 2; + } + async parseTransaction(req: any): Promise<HttpTransactionPending> { const res = await this.pushEntity( req, @@ -86,50 +90,40 @@ export class TransactionBinding extends AbstractController { async getHistory(req: any): Promise<HttpTxHistory> { const pubkey = await ParametersService.getPubkeyP(req); - return this.getFilteredHistory(pubkey, (results: any) => results); + const history = await this.server.dal.getTxHistoryByPubkey(pubkey); + return this.toHttpTxHistory(pubkey, history); } async getHistoryBetweenBlocks(req: any): Promise<HttpTxHistory> { const pubkey = await ParametersService.getPubkeyP(req); const from = await ParametersService.getFromP(req); const to = await ParametersService.getToP(req); - return this.getFilteredHistory(pubkey, (res: any) => { - const histo = res.history; - histo.sent = Underscore.filter(histo.sent, function (tx: any) { - return tx && tx.block_number >= from && tx.block_number <= to; - }); - histo.received = Underscore.filter(histo.received, function (tx: any) { - return tx && tx.block_number >= from && tx.block_number <= to; - }); - Underscore.extend(histo, { sending: [], receiving: [] }); - return res; - }); + + const history = await this.server.dal.getTxHistoryByPubkeyBetweenBlocks( + pubkey, + +from, + +to + ); + return this.toHttpTxHistory(pubkey, history); } async getHistoryBetweenTimes(req: any): Promise<HttpTxHistory> { const pubkey = await ParametersService.getPubkeyP(req); const from = await ParametersService.getFromP(req); const to = await ParametersService.getToP(req); - return this.getFilteredHistory(pubkey, (res: any) => { - const histo = res.history; - histo.sent = Underscore.filter(histo.sent, function (tx: any) { - return tx && tx.time >= from && tx.time <= to; - }); - histo.received = Underscore.filter(histo.received, function (tx: any) { - return tx && tx.time >= from && tx.time <= to; - }); - Underscore.extend(histo, { sending: [], receiving: [] }); - return res; - }); + const medianTimeOffset = this.medianTimeOffset || 0; // Need to convert time into medianTime, because GVA module use median_time + const history = await this.server.dal.getTxHistoryByPubkeyBetweenTimes( + pubkey, + +from - medianTimeOffset, + +to - medianTimeOffset + ); + return this.toHttpTxHistory(pubkey, history); } - async getPendingForPubkey(req: any): Promise<HttpTxHistory> { + async getPendingByPubkey(req: any): Promise<HttpTxHistory> { const pubkey = await ParametersService.getPubkeyP(req); - return this.getFilteredHistory(pubkey, function (res: any) { - const histo = res.history; - Underscore.extend(histo, { sent: [], received: [] }); - return res; - }); + const history = await this.server.dal.getTxHistoryMempool(pubkey); + return this.toHttpTxHistory(pubkey, history); } async getPending(): Promise<HttpTxPending> { @@ -156,22 +150,25 @@ export class TransactionBinding extends AbstractController { }; } - private async getFilteredHistory( + private async toHttpTxHistory( pubkey: string, - filter: any + dbTxHistory: { + sent: DBTx[]; + received: DBTx[]; + sending: DBTx[]; + pending: DBTx[]; + } ): Promise<HttpTxHistory> { - let history = await this.server.dal.getTransactionsHistory(pubkey); - let result = { + return { currency: this.conf.currency, pubkey: pubkey, history: { - sending: history.sending.map(dbtx2HttpTxOfHistory), - received: history.received.map(dbtx2HttpTxOfHistory), - sent: history.sent.map(dbtx2HttpTxOfHistory), - pending: history.pending.map(dbtx2HttpTxOfHistory), + sending: dbTxHistory.sending.map(dbtx2HttpTxOfHistory), + received: dbTxHistory.received.map(dbtx2HttpTxOfHistory), + sent: dbTxHistory.sent.map(dbtx2HttpTxOfHistory), + pending: dbTxHistory.pending.map(dbtx2HttpTxOfHistory), }, }; - return filter(result); } } diff --git a/neon/native/index.d.ts b/neon/native/index.d.ts index 2abc4e1a4..94f6ab4af 100644 --- a/neon/native/index.d.ts +++ b/neon/native/index.d.ts @@ -17,7 +17,7 @@ export import RustLogger = _logger.RustLogger; export import RustDbTx = _server.RustDbTx; export import RustServer = _server.RustServer; export import RustServerConf = _server.RustServerConf; -export import TxsHistory = _server.TxsHistory; +export import TxsHistory = _server.RustTxsHistory; export import TransactionDTOV10 = _transactions.TransactionDTOV10; export import rawTxParseAndVerify = _transactions.rawTxParseAndVerify; diff --git a/neon/native/server.d.ts b/neon/native/server.d.ts index 763b49308..7a863d0aa 100644 --- a/neon/native/server.d.ts +++ b/neon/native/server.d.ts @@ -1,6 +1,6 @@ /* tslint:disable */ -import { TransactionDTOV10 } from './transaction'; +import {TransactionDTOV10} from './transaction'; export class BlockDTOV10 { version: number; @@ -99,7 +99,6 @@ export class RustPendingTx { comment: string; receivedTime: number; } - export class RustServerConf { command: string | null currency: string @@ -107,7 +106,7 @@ export class RustServerConf { txsMempoolSize: number } -export class TxsHistory { +export class RustTxsHistory { sent: RustDbTx[]; received: RustDbTx[]; sending: RustPendingTx[]; @@ -136,7 +135,10 @@ export class RustServer { trimExpiredNonWrittenTxs(limitTime: number): void; // Transactions history (for BMA only) - getTransactionsHistory(pubkey: string): TxsHistory; + getTxHistoryByPubkey(pubkey: string): RustTxsHistory; + getTxHistoryByPubkeyBetweenBlocks(pubkey: string, from: number, to: number): RustTxsHistory; + getTxHistoryByPubkeyBetweenTimes(pubkey: string, from: number, to: number): RustTxsHistory; + getTxHistoryMempool(pubkey: string): RustTxsHistory; getTxByHash(hash: string): TransactionDTOV10 | null; // WS2Pv1: HEADs and peers diff --git a/neon/native/src/server.rs b/neon/native/src/server.rs index db30bc09d..33340a2cc 100644 --- a/neon/native/src/server.rs +++ b/neon/native/src/server.rs @@ -269,8 +269,8 @@ declare_types! { into_neon_res(&mut cx, res) } - // Transactions history (for BMA only) - method getTransactionsHistory(mut cx) { + // Get TX full history of a pubkey. For BMA only + method getTxHistoryByPubkey(mut cx) { let pubkey_str = cx.argument::<JsString>(0)?.value(); let pubkey = into_neon_res(&mut cx, PublicKey::from_base58(&pubkey_str))?; @@ -278,7 +278,7 @@ declare_types! { let res = { let guard = cx.lock(); let server = this.borrow(&guard); - server.server.get_transactions_history(pubkey) + server.server.get_txs_history_bma(pubkey) }; match res { Ok(txs_history) => { @@ -311,6 +311,109 @@ declare_types! { Err(e) => cx.throw_error(format!("{}", e)), } } + + // Get TX history by pubkey and block range. For BMA only + method getTxHistoryByPubkeyBetweenBlocks(mut cx) { + let pubkey_str = cx.argument::<JsString>(0)?.value(); + let pubkey = into_neon_res(&mut cx, PublicKey::from_base58(&pubkey_str))?; + let from = cx.argument::<JsNumber>(1)?.value() as u32; + let to = cx.argument::<JsNumber>(2)?.value() as u32; + + let this = cx.this(); + let res = { + let guard = cx.lock(); + let server = this.borrow(&guard); + server.server.get_txs_history_bma_by_blocks(pubkey, Some(from), Some(to)) + }; + match res { + Ok(txs_history) => { + let sent: Vec<_> = txs_history.sent + .into_iter() + .map(|(tx, wb, wt)| DbTx::v10(tx.to_string_object(), tx.get_hash(), wb.number.0, wt)) + .collect(); + let received: Vec<_> = txs_history.received + .into_iter() + .map(|(tx, wb, wt)| DbTx::v10(tx.to_string_object(), tx.get_hash(), wb.number.0, wt)) + .collect(); + + Ok(neon_serde::to_value(&mut cx, &TxsHistoryStringified { + sending: Vec::new(), + pending: Vec::new(), + sent, + received + })?) + }, + Err(e) => cx.throw_error(format!("{}", e)), + } + } + // Get TX history by pubkey and block range. For BMA only + method getTxHistoryByPubkeyBetweenTimes(mut cx) { + let pubkey_str = cx.argument::<JsString>(0)?.value(); + let pubkey = into_neon_res(&mut cx, PublicKey::from_base58(&pubkey_str))?; + let from = cx.argument::<JsNumber>(1)?.value() as u64; + let to = cx.argument::<JsNumber>(2)?.value() as u64; + + let this = cx.this(); + let res = { + let guard = cx.lock(); + let server = this.borrow(&guard); + server.server.get_txs_history_bma_by_times(pubkey, Some(from), Some(to)) + }; + match res { + Ok(txs_history) => { + let sent: Vec<_> = txs_history.sent + .into_iter() + .map(|(tx, wb, wt)| DbTx::v10(tx.to_string_object(), tx.get_hash(), wb.number.0, wt)) + .collect(); + let received: Vec<_> = txs_history.received + .into_iter() + .map(|(tx, wb, wt)| DbTx::v10(tx.to_string_object(), tx.get_hash(), wb.number.0, wt)) + .collect(); + + Ok(neon_serde::to_value(&mut cx, &TxsHistoryStringified { + sending: Vec::new(), + pending: Vec::new(), + sent, + received + })?) + }, + Err(e) => cx.throw_error(format!("{}", e)), + } + } + // Get TX in mempool by pubkey. For BMA only + method getTxHistoryMempool(mut cx) { + let pubkey_str = cx.argument::<JsString>(0)?.value(); + let pubkey = into_neon_res(&mut cx, PublicKey::from_base58(&pubkey_str))?; + + let this = cx.this(); + let res = { + let guard = cx.lock(); + let server = this.borrow(&guard); + server.server.get_txs_history_bma_mempool(pubkey) + }; + match res { + Ok(txs_history) => { + let sending: Vec<_> = txs_history + .sending + .into_iter() + .map(|(tx, received_time)| PendingTx::v10( tx.to_string_object(), tx.get_hash(), received_time)) + .collect(); + let pending: Vec<_> = txs_history + .pending + .into_iter() + .map(|(tx, received_time)| PendingTx::v10( tx.to_string_object(), tx.get_hash(), received_time)) + .collect(); + + Ok(neon_serde::to_value(&mut cx, &TxsHistoryStringified { + sent: Vec::new(), + received: Vec::new(), + sending, + pending + })?) + }, + Err(e) => cx.throw_error(format!("{}", e)), + } + } method getTxByHash(mut cx) { let hash_str = cx.argument::<JsString>(0)?.value(); let hash = into_neon_res(&mut cx, Hash::from_hex(&hash_str))?; diff --git a/rust-libs/duniter-server/src/legacy/tx_history.rs b/rust-libs/duniter-server/src/legacy/tx_history.rs index e06cf4445..c9b37bf58 100644 --- a/rust-libs/duniter-server/src/legacy/tx_history.rs +++ b/rust-libs/duniter-server/src/legacy/tx_history.rs @@ -16,8 +16,20 @@ use crate::*; impl DuniterServer { - pub fn get_transactions_history(&self, pubkey: PublicKey) -> KvResult<TxsHistoryForBma> { - get_transactions_history_for_bma(&self.dbs_pool, self.profile_path_opt.as_deref(), pubkey) + pub fn get_txs_history_bma(&self, pubkey: PublicKey) -> KvResult<TxsHistoryForBma> { + get_txs_history_bma(&self.dbs_pool, self.profile_path_opt.as_deref(), pubkey) + } + + pub fn get_txs_history_bma_by_blocks(&self, pubkey: PublicKey, from: Option<u32>, to: Option<u32>) -> KvResult<TxsHistoryForBma> { + get_txs_history_bma_by_blocks(self.profile_path_opt.as_deref(), pubkey, from, to) + } + + pub fn get_txs_history_bma_by_times(&self, pubkey: PublicKey, from: Option<u64>, to: Option<u64>) -> KvResult<TxsHistoryForBma> { + get_txs_history_bma_by_times(self.profile_path_opt.as_deref(), pubkey, from, to) + } + + pub fn get_txs_history_bma_mempool(&self, pubkey: PublicKey) -> KvResult<TxsHistoryForBma> { + get_txs_history_bma_mempool(&self.dbs_pool, pubkey) } pub fn get_tx_by_hash( diff --git a/test/integration/transactions/transactions-history.ts b/test/integration/transactions/transactions-history.ts new file mode 100644 index 000000000..1a3ecbe29 --- /dev/null +++ b/test/integration/transactions/transactions-history.ts @@ -0,0 +1,174 @@ +// 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 {TestUser} from "../tools/TestUser" +import {CommonConstants} from "../../../app/lib/common-libs/constants" +import {NewTestingServer, TestingServer} from "../tools/toolbox" +import {HttpBlock, HttpTxHistory} from "../../../app/modules/bma/lib/dtos" +import {Underscore} from "../../../app/lib/common-libs/underscore"; + +const should = require('should'); + +let s1:TestingServer, cat1:TestUser, tac1:TestUser + +describe("Transactions history", function() { + + const now = 1500000000 + const conf = { + udTime0: now, + dt: 30, + avgGenTime: 5000, + medianTimeBlocks: 2 + }; + + before(async () => { + + s1 = NewTestingServer(Underscore.extend({ + gva: {}, + currency: 'currency_one', + pair: { + pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', + sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP' + } + }, conf)); + + cat1 = new TestUser('cat', { pub: 'HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', sec: '51w4fEShBk1jCMauWu4mLpmDVfHksKmWcygpxriqCEZizbtERA6de4STKRkQBpxmMUwsKXRjSzuQ8ECwmqN1u2DP'}, { server: s1 }); + tac1 = new TestUser('tac', { pub: '2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', sec: '2HuRLWgKgED1bVio1tdpeXrf7zuUszv1yPHDsDj7kcMC4rVSN9RC58ogjtKNfTbH1eFz7rn38U1PywNs3m6Q7UxE'}, { server: s1 }); + + await s1.prepareForNetwork(); + + const now = parseInt(String(Date.now() / 1000)) + + // Publishing identities + await cat1.createIdentity(); + await tac1.createIdentity(); + await cat1.cert(tac1); + await tac1.cert(cat1); + await cat1.join(); + await tac1.join(); + await s1.commit(); + await s1.commit({ + time: now + conf.avgGenTime + }); + await s1.commit(); + await cat1.sendMoney(20, tac1); + }) + + after(() => { + return Promise.all([ + s1.closeCluster() + ]) + }) + + it('sending transactions should exist in /tx/history/:pubkey/pending', () => s1.expect('/tx/history/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd/pending', (res:HttpTxHistory) => { + res.history.should.have.property('sending').length(1); + res.history.should.have.property('pending').length(0); + })); + + it('pending transactions should exist in /tx/history/:pubkey/pending', () => s1.expect('/tx/history/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc/pending', (res:HttpTxHistory) => { + res.history.should.have.property('sending').length(0); + res.history.should.have.property('pending').length(1); + })); + + it('sent and received transactions should should exist', async () => { + await s1.commit(); + + // cat1 pending should be empty + await s1.expect('/tx/history/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd/pending', (res:HttpTxHistory) => { + res.history.should.have.property('sending').length(0); + res.history.should.have.property('pending').length(0); + }); + // cat1 sent should have one element + await s1.expect('/tx/history/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', (res:HttpTxHistory) => { + res.history.should.have.property('sent').length(1); + res.history.should.have.property('received').length(0); + }); + // tac1 sending should be empty + await s1.expect('/tx/history/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc/pending', (res:HttpTxHistory) => { + res.history.should.have.property('sending').length(0); + res.history.should.have.property('pending').length(0); + }); + // tac1 received should have one element + await s1.expect('/tx/history/2LvDg21dVXvetTD9GdkPLURavLYEqP3whauvPWX4c2qc', (res:HttpTxHistory) => { + res.history.should.have.property('sent').length(0); + res.history.should.have.property('received').length(1); + }); + }) + + it('get transactions by blocks slice', async () => { + + const firstBlock = await s1.commit(); + + // cat1 sent should have one element + await s1.expect('/tx/history/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd/blocks/0/' + firstBlock.number, (res:HttpTxHistory) => { + res.history.should.have.property('sent').length(1); + res.history.should.have.property('received').length(0); + }); + + // Add a pending TX from tac1 -> cat1 + await s1.commit({ + time: firstBlock.time + conf.avgGenTime + }); + await tac1.sendMoney(10, cat1); + const secondBlock = await s1.commit(); + + // Should not appear in sliced history + await s1.expect('/tx/history/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd/blocks/0/' + firstBlock.number, (res:HttpTxHistory) => { + res.history.should.have.property('sent').length(1); + res.history.should.have.property('received').length(0); + }); + await s1.expect('/tx/history/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd/blocks/' + (firstBlock.number + 1) + '/' + secondBlock.number, (res:HttpTxHistory) => { + res.history.should.have.property('sent').length(0); + res.history.should.have.property('received').length(1); + }); + + // Whole history + await s1.expect('/tx/history/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd', (res:HttpTxHistory) => { + res.history.should.have.property('sent').length(1); + res.history.should.have.property('received').length(1); + }); + }) + + it('get transactions by times slice', async () => { + + const medianTimeOffset = conf.avgGenTime * conf.medianTimeBlocks / 2; + const firstBlock = await s1.commit(); + const startTime = firstBlock.medianTime + medianTimeOffset; + + // Should not have TX yet + await s1.expect('/tx/history/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd/times/'+ startTime +'/' + (startTime + conf.avgGenTime - 1), (res:HttpTxHistory) => { + res.history.should.have.property('sent').length(0); + res.history.should.have.property('received').length(0); + }); + + // Add a pending TX from tac1 -> cat1 + await tac1.sendMoney(10, cat1); + const secondBlock = await s1.commit({ + time: firstBlock.time + conf.avgGenTime + }); + should(secondBlock).property('time').greaterThan(firstBlock.time); + const secondTime = secondBlock.medianTime + medianTimeOffset; + + // Previous range (before TX) should still be empty + await s1.expect('/tx/history/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd/times/'+ startTime +'/' + (secondTime - 1), (res:HttpTxHistory) => { + res.history.should.have.property('sent').length(0); + res.history.should.have.property('received').length(0); + }); + + // Should appear in next range + await s1.expect('/tx/history/HgTTJLAQ5sqfknMq7yLPZbehtuLSsKj9CxWN7k8QvYJd/times/' + (secondTime) + '/' + (secondTime + conf.avgGenTime), (res:HttpTxHistory) => { + res.history.should.have.property('sent').length(0); + res.history.should.have.property('received').length(1); + }); + }) +}) -- GitLab