diff --git a/indypool-client/package.json b/indypool-client/package.json index 6f5cfcd0..1e75a5d1 100644 --- a/indypool-client/package.json +++ b/indypool-client/package.json @@ -1,6 +1,6 @@ { "name": "indyscan-daemon", - "version": "4.1.3", + "version": "4.2.0", "author": "Patrik Staš", "license": "ISC", "description": "Application scanning Hyperledger Indy blockchain for fetching and processing transactions.", diff --git a/indyscan-api-client/demo/get-many-txs.js b/indyscan-api-client/demo/get-many-txs.js new file mode 100644 index 00000000..dd2de6c0 --- /dev/null +++ b/indyscan-api-client/demo/get-many-txs.js @@ -0,0 +1,25 @@ +const { getTxsV2 } = require('../src') + +const API_URL = process.env.API_URL || 'https://indyscan.io' +const NETWORK_ID = process.env.NETWORK_ID || 'SOVRIN_STAGINGNET' + +async function fetch_1000txs_in_39K_40K_seqNo_range () { // eslint-disable-line + return getTxsV2( + API_URL, + NETWORK_ID, + 'domain', + 0, + 1000, + [], + 39000, + 40000, + 'expansion', + undefined) +} + +fetch_1000txs_in_39K_40K_seqNo_range() // eslint-disable-line + .then(txs => { + const seqNos = txs.map(tx => tx.imeta.seqNo) + console.log(`Returned ${seqNos.length} txs.`) + console.log(`Fetched transaction sequence numbers: ${JSON.stringify(seqNos)}`) + }) diff --git a/indyscan-api-client/package.json b/indyscan-api-client/package.json index f66c576d..6f5b8970 100644 --- a/indyscan-api-client/package.json +++ b/indyscan-api-client/package.json @@ -1,6 +1,6 @@ { "name": "indyscan-api-client", - "version": "4.1.3", + "version": "4.2.0", "author": "Patrik Staš", "license": "ISC", "description": "IndyScan HTTP API client.", @@ -13,6 +13,8 @@ "istx": "node ./script/istx.js --url 'http://localhost:3000' --network SOVRIN_TESTNET -l domain -s 5", "isseries": "node ./script/isseries.js --url 'http://localhost:3000' --network SOVRIN_TESTNET -l domain", "test:integration": "ENVIRONMENT=localhost jest test/integration", + "test:integration:indyscanio": "ENVIRONMENT=indyscanio jest test/integration", + "demo": "node demo/get-many-txs.js", "test:unit": "echo 'TODO: Implement unit tests.'" }, "devDependencies": { diff --git a/indyscan-api-client/src/index.js b/indyscan-api-client/src/index.js index 22fdd28c..1daa1960 100644 --- a/indyscan-api-client/src/index.js +++ b/indyscan-api-client/src/index.js @@ -38,12 +38,17 @@ async function getTxCount (baseUrl, network, ledger, filterTxNames = [], search) return res.txCount } -async function getTxs (baseUrl, network, ledger, skip, size, filterTxNames = [], format = 'original', search, sortFromRecent) { +async function getTxs (baseUrl, network, ledger, skip, size, filterTxNames = [], format = 'serialized', search, sortFromRecent) { const query = qs.stringify({ sortFromRecent, skip, size, format, filterTxNames: JSON.stringify(filterTxNames), search }) return getRequest(`${baseUrl}/api/networks/${network}/ledgers/${ledger}/txs?${query}`) } -async function getTx (baseUrl, network, ledger, seqNo, format = 'original') { +async function getTxsV2 (baseUrl, network, ledger, skip, size, filterTxNames = [], seqNoGte, seqNoLt, format = 'serialized', search, sortFromRecent) { + const query = qs.stringify({ sortFromRecent, skip, size, format, filterTxNames: JSON.stringify(filterTxNames), seqNoGte, seqNoLt, search }) + return getRequest(`${baseUrl}/api/networks/${network}/ledgers/${ledger}/txs?${query}`) +} + +async function getTx (baseUrl, network, ledger, seqNo, format = 'serialized') { const query = qs.stringify({ format }) const axiosCall = async () => { return getRequest(`${baseUrl}/api/networks/${network}/ledgers/${ledger}/txs/${seqNo}?${query}`) @@ -55,5 +60,6 @@ module.exports.getNetworks = getNetworks module.exports.getNetwork = getNetwork module.exports.getDefaultNetwork = getDefaultNetwork module.exports.getTx = getTx +module.exports.getTxsV2 = getTxsV2 module.exports.getTxs = getTxs module.exports.getTxCount = getTxCount diff --git a/indyscan-api-client/test/config/indyscanio.env b/indyscan-api-client/test/config/indyscanio.env new file mode 100644 index 00000000..71609109 --- /dev/null +++ b/indyscan-api-client/test/config/indyscanio.env @@ -0,0 +1,2 @@ +API_URL='https://indyscan.io' +NETWORK_ID='SOVRIN_STAGINGNET' diff --git a/indyscan-api-client/test/config/localhost.env b/indyscan-api-client/test/config/localhost.env index 92afc385..db412fbe 100644 --- a/indyscan-api-client/test/config/localhost.env +++ b/indyscan-api-client/test/config/localhost.env @@ -1 +1,2 @@ -API_URL='http://localhost:3709' +API_URL='http://localhost:3708' +NETWORK_ID='SOVRIN_STAGINGNET' diff --git a/indyscan-api-client/test/integration/index.spec.js b/indyscan-api-client/test/integration/index.spec.js index dd568043..f555095c 100644 --- a/indyscan-api-client/test/integration/index.spec.js +++ b/indyscan-api-client/test/integration/index.spec.js @@ -1,5 +1,6 @@ /* eslint-env jest */ require('jest') +const { getTxsV2 } = require('../../src') const { getTxs } = require('../../src') const { getTxCount } = require('../../src') const { getNetwork } = require('../../src') @@ -29,22 +30,34 @@ describe('basic api test suite', () => { } function basicOriginalTxValidation (tx) { - expect(tx.rootHash).toBeDefined() - expect(tx.auditPath).toBeDefined() - expect(tx.txn).toBeDefined() - expect(tx.txn.type).toBeDefined() - expect(tx.txnMetadata).toBeDefined() + expect(tx.imeta).toBeDefined() + expect(tx.imeta.subledger).toBeDefined() + expect(tx.imeta.seqNo).toBeDefined() + expect(tx.idata).toBeDefined() + } + + function serializedTxValidation (tx) { + expect(tx.imeta).toBeDefined() + expect(tx.imeta.subledger).toBeDefined() + expect(tx.imeta.seqNo).toBeDefined() + expect(tx.idata).toBeDefined() + expect(tx.idata.json).toBeDefined() + const ledgerTx = JSON.parse(tx.idata.json) + expect(ledgerTx.reqSignature).toBeDefined() + expect(ledgerTx.rootHash).toBeDefined() + expect(ledgerTx.txn).toBeDefined() + expect(ledgerTx.txnMetadata).toBeDefined() } function basicIndyscanTxValidation (tx) { - expect(tx.rootHash).toBeDefined() - expect(tx.auditPath).toBeDefined() - expect(tx.txn).toBeDefined() - expect(tx.txn.type).toBeDefined() - expect(tx.txn.typeName).toBeDefined() - expect(tx.txnMetadata).toBeDefined() - expect(tx.subledger).toBeDefined() - expect(tx.meta).toBeDefined() + expect(tx.imeta).toBeDefined() + expect(tx.imeta.subledger).toBeDefined() + expect(tx.imeta.seqNo).toBeDefined() + + expect(tx.idata.txn).toBeDefined() + expect(tx.idata.txn.type).toBeDefined() + expect(tx.idata.txn.typeName).toBeDefined() + expect(tx.idata.txnMetadata).toBeDefined() } it('should get default network', async () => { @@ -59,13 +72,6 @@ describe('basic api test suite', () => { basicNetworkValidation(networks[0]) }) - it('should get all networks', async () => { - const networks = await getNetworks(process.env.API_URL) - expect(Array.isArray(networks)).toBeTruthy() - expect(networks.length).toBeGreaterThanOrEqual(1) - basicNetworkValidation(networks[0]) - }) - it('should get network by id', async () => { const networks = await getNetworks(process.env.API_URL) expect(Array.isArray(networks)).toBeTruthy() @@ -87,99 +93,86 @@ describe('basic api test suite', () => { it('should get config transaction as was found on ledger', async () => { const networks = await getNetworks(process.env.API_URL) - const firstNetworkId = networks[0].id - const tx = await getTx(process.env.API_URL, firstNetworkId, 'CONFIG', 1, 'original') + const networkId = process.env.NETWORK_ID || networks[0].id + const tx = await getTx(process.env.API_URL, networkId, 'config', 1, 'serialized') basicOriginalTxValidation(tx) }) it('should be case insensitive on subledger name', async () => { const networks = await getNetworks(process.env.API_URL) - const firstNetworkId = networks[0].id - const tx = await getTx(process.env.API_URL, firstNetworkId, 'conFIG', 1, 'original') - basicOriginalTxValidation(tx) + const networkId = process.env.NETWORK_ID || networks[0].id + const tx = await getTx(process.env.API_URL, networkId, 'config', 1, 'serialized') + serializedTxValidation(tx) }) - it('should get config transaction in indyscan format', async () => { + it('should get config transaction in full format', async () => { const networks = await getNetworks(process.env.API_URL) - const firstNetworkId = networks[0].id - const tx = await getTx(process.env.API_URL, firstNetworkId, 'config', 1, 'full') - expect(tx.original).toBeDefined() - const originalTxParsed = JSON.parse(tx.original) - basicOriginalTxValidation(originalTxParsed) - expect(tx.indyscan).toBeDefined() - basicIndyscanTxValidation(tx.indyscan) + const networkId = process.env.NETWORK_ID || networks[0].id + const tx = await getTx(process.env.API_URL, networkId, 'config', 1, 'full') + expect(tx.idata.serialized).toBeDefined() + expect(tx.idata.expansion).toBeDefined() + serializedTxValidation(tx.idata.serialized) + basicIndyscanTxValidation(tx.idata.expansion) }) it('should get domain transaction as was found on ledger', async () => { const networks = await getNetworks(process.env.API_URL) - const firstNetworkId = networks[0].id - const tx = await getTx(process.env.API_URL, firstNetworkId, 'domain', 1, 'original') - basicOriginalTxValidation(tx) + const networkId = process.env.NETWORK_ID || networks[0].id + const tx = await getTx(process.env.API_URL, networkId, 'domain', 1, 'serialized') + serializedTxValidation(tx) }) - it('should get domain transaction in indyscan format', async () => { + it('should get domain transaction in expansion format', async () => { const networks = await getNetworks(process.env.API_URL) - const firstNetworkId = networks[0].id - const tx = await getTx(process.env.API_URL, firstNetworkId, 'domain', 1, 'full') - expect(tx.original).toBeDefined() - const originalTxParsed = JSON.parse(tx.original) - basicOriginalTxValidation(originalTxParsed) - expect(tx.indyscan).toBeDefined() - basicIndyscanTxValidation(tx.indyscan) + const networkId = process.env.NETWORK_ID || networks[0].id + const tx = await getTx(process.env.API_URL, networkId, 'domain', 1, 'expansion') + basicIndyscanTxValidation(tx) }) it('should get transaction in ledger format by default', async () => { const networks = await getNetworks(process.env.API_URL) - const firstNetworkId = networks[0].id - const tx = await getTx(process.env.API_URL, firstNetworkId, 'pool', 1) - basicOriginalTxValidation(tx) + const networkId = process.env.NETWORK_ID || networks[0].id + const tx = await getTx(process.env.API_URL, networkId, 'pool', 1) + serializedTxValidation(tx) }) - it('should get transaction in ledger format', async () => { + it('should get pool transaction in full format', async () => { const networks = await getNetworks(process.env.API_URL) - const firstNetworkId = networks[0].id - const tx = await getTx(process.env.API_URL, firstNetworkId, 'pool', 1, 'original') - basicOriginalTxValidation(tx) - }) - - it('should get pool transaction in indyscan format', async () => { - const networks = await getNetworks(process.env.API_URL) - const firstNetworkId = networks[0].id - const tx = await getTx(process.env.API_URL, firstNetworkId, 'pool', 1, 'full') - expect(tx.original).toBeDefined() - const originalTxParsed = JSON.parse(tx.original) - basicOriginalTxValidation(originalTxParsed) - expect(tx.indyscan).toBeDefined() - basicIndyscanTxValidation(tx.indyscan) + const networkId = process.env.NETWORK_ID || networks[0].id + const tx = await getTx(process.env.API_URL, networkId, 'pool', 1, 'full') + expect(tx.idata.serialized).toBeDefined() + expect(tx.idata.expansion).toBeDefined() + serializedTxValidation(tx.idata.serialized) + basicIndyscanTxValidation(tx.idata.expansion) }) it('should get transaction count', async () => { const networks = await getNetworks(process.env.API_URL) - const firstNetworkId = networks[0].id - const count = await getTxCount(process.env.API_URL, firstNetworkId, 'domain') + const networkId = process.env.NETWORK_ID || networks[0].id + const count = await getTxCount(process.env.API_URL, networkId, 'domain') expect(count).toBeGreaterThan(5) }) it('should have higher total tx count than count of NYM transactions', async () => { const networks = await getNetworks(process.env.API_URL) - const firstNetworkId = networks[0].id - const countAll = await getTxCount(process.env.API_URL, firstNetworkId, 'domain') - const countNym = await getTxCount(process.env.API_URL, firstNetworkId, 'domain', ['NYM']) + const networkId = process.env.NETWORK_ID || networks[0].id + const countAll = await getTxCount(process.env.API_URL, networkId, 'domain') + const countNym = await getTxCount(process.env.API_URL, networkId, 'domain', ['NYM']) expect(countAll).toBeGreaterThan(countNym) }) - it('should have higher total tx count than count of NYM transactions', async () => { + it('should get 10 domain transactions', async () => { const networks = await getNetworks(process.env.API_URL) - const firstNetworkId = networks[0].id - const txs = await getTxs(process.env.API_URL, firstNetworkId, 'domain', 0, 10) + const networkId = process.env.NETWORK_ID || networks[0].id + const txs = await getTxs(process.env.API_URL, networkId, 'domain', 0, 10) expect(Array.isArray(txs)).toBeTruthy() expect(txs.length).toBe(10) }) it('should search NYM transactions containing DID', async () => { const networks = await getNetworks(process.env.API_URL) - const firstNetworkId = networks[0].id - const txs = await getTxs(process.env.API_URL, firstNetworkId, 'domain', 0, 100, ['NYM'], 'full', 'J4N1K1SEB8uY2muwmecY5q') + const networkId = process.env.NETWORK_ID || networks[0].id + const txs = await getTxs(process.env.API_URL, networkId, 'domain', 0, 100, ['NYM'], 'full', 'J4N1K1SEB8uY2muwmecY5q') expect(Array.isArray(txs)).toBeTruthy() for (const tx of txs) { expect(JSON.stringify(tx)).toEqual(expect.stringMatching(/J4N1K1SEB8uY2muwmecY5q/)) @@ -187,26 +180,42 @@ describe('basic api test suite', () => { } }) - it('should find 2 NYM transactions containing DID', async () => { + it('should search ATTRIB transactions containing DID', async () => { const networks = await getNetworks(process.env.API_URL) - const firstNetworkId = networks[0].id - const txs = await getTxs(process.env.API_URL, firstNetworkId, 'domain', 0, 2, ['NYM'], 'full', 'J4N1K1SEB8uY2muwmecY5q') + const networkId = process.env.NETWORK_ID || networks[0].id + const txs = await getTxs(process.env.API_URL, networkId, 'domain', 0, 100, ['ATTRIB'], 'full', 'J4N1K1SEB8uY2muwmecY5q') expect(Array.isArray(txs)).toBeTruthy() - expect(txs.length).toBe(2) for (const tx of txs) { expect(JSON.stringify(tx)).toEqual(expect.stringMatching(/J4N1K1SEB8uY2muwmecY5q/)) - expect(tx.indyscan.txn.typeName).toBe('NYM') + expect(tx.indyscan.txn.typeName).toBe('ATTRIB') } }) - it('should search ATTRIB transactions containing DID', async () => { + it('should search transaction capped by seqNo range', async () => { const networks = await getNetworks(process.env.API_URL) - const firstNetworkId = networks[0].id - const txs = await getTxs(process.env.API_URL, firstNetworkId, 'domain', 0, 100, ['ATTRIB'], 'full', 'J4N1K1SEB8uY2muwmecY5q') + const networkId = process.env.NETWORK_ID || networks[0].id + const txs = await getTxsV2(process.env.API_URL, networkId, 'domain', 0, 100, [], 10, 20, 'full') expect(Array.isArray(txs)).toBeTruthy() - for (const tx of txs) { - expect(JSON.stringify(tx)).toEqual(expect.stringMatching(/J4N1K1SEB8uY2muwmecY5q/)) - expect(tx.indyscan.txn.typeName).toBe('ATTRIB') - } + expect(txs.length).toBe(10) + expect(txs.find(tx => tx.idata.expansion.imeta.seqNo === 10)).toBeDefined() + expect(txs.find(tx => tx.idata.expansion.imeta.seqNo === 19)).toBeDefined() + expect(txs.find(tx => tx.idata.expansion.imeta.seqNo === 20)).toBeUndefined() + expect(txs.find(tx => tx.idata.expansion.imeta.seqNo === 9)).toBeUndefined() + }) + + it('should return no transactions if seqNo filter is set as "lt > gte"', async () => { + const networks = await getNetworks(process.env.API_URL) + const networkId = process.env.NETWORK_ID || networks[0].id + const txs = await getTxsV2(process.env.API_URL, networkId, 'domain', 0, 100, [], 20, 10, 'full') + expect(Array.isArray(txs)).toBeTruthy() + expect(txs.length).toBe(0) + }) + + it('should return less than filtered seqNo range if "size" parameter is smaller than the range', async () => { + const networks = await getNetworks(process.env.API_URL) + const networkId = process.env.NETWORK_ID || networks[0].id + const txs = await getTxsV2(process.env.API_URL, networkId, 'domain', 0, 2, [], 10, 20, 'full') + expect(Array.isArray(txs)).toBeTruthy() + expect(txs.length).toBe(2) }) }) diff --git a/indyscan-api/package.json b/indyscan-api/package.json index 6c8624b0..a5609ceb 100644 --- a/indyscan-api/package.json +++ b/indyscan-api/package.json @@ -1,6 +1,6 @@ { "name": "indyscan-api", - "version": "4.1.3", + "version": "4.2.0", "description": "Web application to browse Hyperledger Indy blockchain transactions.", "main": "index.js", "scripts": { diff --git a/indyscan-api/src/api/txs.js b/indyscan-api/src/api/txs.js index c77f262c..bd7ec2dc 100644 --- a/indyscan-api/src/api/txs.js +++ b/indyscan-api/src/api/txs.js @@ -19,6 +19,8 @@ function initTxsApi (app, networkManager, serviceTxs) { fromRecentTx: Joi.number(), toRecentTx: Joi.number(), filterTxNames: Joi.array().items(Joi.string()), + seqNoGte: Joi.number(), + seqNoLt: Joi.number(), search: Joi.string(), format: Joi.string().valid(['serialized', 'full', 'expansion']) } @@ -28,13 +30,15 @@ function initTxsApi (app, networkManager, serviceTxs) { const networkId = getNetworkId(req, res) const { ledger } = req.params console.log(JSON.stringify(req.query)) - const { skip, size, filterTxNames, search, format, sortFromRecent } = req.query + const { skip, size, filterTxNames, search, format, sortFromRecent, seqNoGte, seqNoLt } = req.query const txs = await serviceTxs.getTxs( networkId, ledger, skip || 0, size || 50, filterTxNames, + seqNoGte, + seqNoLt, search, format, (sortFromRecent === undefined || sortFromRecent === null) ? true : (sortFromRecent === 'true') diff --git a/indyscan-api/src/service/service-txs.js b/indyscan-api/src/service/service-txs.js index 00a81565..3c67f3ee 100644 --- a/indyscan-api/src/service/service-txs.js +++ b/indyscan-api/src/service/service-txs.js @@ -1,3 +1,6 @@ +const { esFilterSeqNoGteLtRange } = require('indyscan-storage/src/es/es-query-builder') +const { esFilterSeqNoGte } = require('indyscan-storage/src/es/es-query-builder') +const { esFilterSeqNoLt } = require('indyscan-storage/src/es/es-query-builder') const { esFilterByTxTypeNames, esFullTextsearch } = require('indyscan-storage/src/es/es-query-builder') function urlQueryTxNamesToEsQuery (urlQueryTxNames) { @@ -9,16 +12,29 @@ function urlQueryTxNamesToEsQuery (urlQueryTxNames) { } } +function createSeqnoFilter (seqNoGte, seqNoLt) { + if (seqNoGte && seqNoLt) { + return esFilterSeqNoGteLtRange(seqNoGte, seqNoLt) + } else if (seqNoGte) { + return esFilterSeqNoGte(seqNoGte) + } else if (seqNoLt) { + return esFilterSeqNoLt(seqNoGte) + } else { + return null + } +} + function createServiceTxs (ledgerStorageManager) { - async function getTxs (networkId, subledger, skip, size, filterTxNames, search, format, sortFromMostRecent = true) { + async function getTxs (networkId, subledger, skip, size, filterTxNames, seqnoGte, seqnoLt, search, format, sortFromMostRecent = true) { const txTypeQuery = urlQueryTxNamesToEsQuery(filterTxNames) + const txSeqnoQuery = createSeqnoFilter(seqnoGte, seqnoLt) const searchQuery = search ? esFullTextsearch(search) : null const sort = (sortFromMostRecent) ? { 'imeta.seqNo': { order: 'desc' } } : { 'imeta.seqNo': { order: 'asc' } } return ledgerStorageManager .getStorage(networkId) - .getManyTxs(subledger, skip, size, [txTypeQuery, searchQuery], sort, format) + .getManyTxs(subledger, skip, size, [txTypeQuery, txSeqnoQuery, searchQuery], sort, format) } async function getTx (networkId, subledger, seqNo, format) { diff --git a/indyscan-daemon-api-client/package.json b/indyscan-daemon-api-client/package.json index d64df053..84fda1d8 100644 --- a/indyscan-daemon-api-client/package.json +++ b/indyscan-daemon-api-client/package.json @@ -1,6 +1,6 @@ { "name": "indyscan-daemon-api-client", - "version": "4.1.3", + "version": "4.2.0", "author": "Patrik Staš", "license": "ISC", "description": "IndyScan Daemon HTTP API client.", diff --git a/indyscan-daemon-ui/package.json b/indyscan-daemon-ui/package.json index 9421fb0f..f4243d69 100644 --- a/indyscan-daemon-ui/package.json +++ b/indyscan-daemon-ui/package.json @@ -1,6 +1,6 @@ { "name": "indyscan-daemon-ui", - "version": "4.1.3", + "version": "4.2.0", "author": "Patrik Staš", "license": "ISC", "description": "UI to view and manage the state of indyscan-daemon.", diff --git a/indyscan-daemon/package.json b/indyscan-daemon/package.json index 05a82d3e..e166fe7e 100644 --- a/indyscan-daemon/package.json +++ b/indyscan-daemon/package.json @@ -1,6 +1,6 @@ { "name": "indyscan-daemon", - "version": "4.1.3", + "version": "4.2.0", "author": "Patrik Staš", "license": "ISC", "description": "Application scanning Hyperledger Indy blockchain for fetching and processing transactions.", diff --git a/indyscan-storage/package.json b/indyscan-storage/package.json index 065943e4..e92cb8be 100644 --- a/indyscan-storage/package.json +++ b/indyscan-storage/package.json @@ -1,6 +1,6 @@ { "name": "indyscan-storage", - "version": "4.1.3", + "version": "4.2.0", "author": "Patrik Staš", "license": "ISC", "description": "", @@ -11,6 +11,7 @@ "test": "jest test", "test:unit": "jest test/unit", "test:integration": "jest test/integration", + "test:integration:read": "jest test/integration/es/storage-read-es.spec.js", "import-mongo": "npm run import-config && npm run import-domain && npm run import-pool", "import-config": "mongoimport --db IS_STOR_TEST --collection txs-config data/config.txs", "import-domain": "mongoimport --db IS_STOR_TEST --collection txs-domain data/domain.txs", diff --git a/indyscan-storage/src/es/es-query-builder.js b/indyscan-storage/src/es/es-query-builder.js index 9814eb3d..a1f573ed 100644 --- a/indyscan-storage/src/es/es-query-builder.js +++ b/indyscan-storage/src/es/es-query-builder.js @@ -1,4 +1,3 @@ -// const txTypeUtils = require('indyscan-txtype') const _ = require('lodash') function esFilterByTxTypeNames (txNames) { @@ -45,6 +44,17 @@ function esFilterHasTimestamp () { } } +function esFilterSeqNoGteLtRange (gte, lt) { + return { + range: { + 'imeta.seqNo': { + gte, + lt + } + } + } +} + function esFilterSeqNoGte (seqNo) { return { range: { @@ -110,36 +120,6 @@ function esAndFilters (...filters) { return { bool: { filter: [...finalQueries] } } } -// -// function _toArrayOfQueries(query) { -// if (query === undefined || query === null) { -// return [] -// } -// if (Array.isArray(query)) { -// return query -// } -// if (typeof query === 'object') { -// return [query] -// } -// throw Error(`Can't processes query ${JSON.stringify(query, null, 2)}.`) -// } -// -// function toArrayOfQueries(...queries) { -// let final = queries.reduce((previous, current) => { -// console.log(`previous = ${JSON.stringify(previous)}`) -// console.log(`current = ${JSON.stringify(current)}`) -// if (current) { -// console.log('pushing current to previous') -// previous.push(_toArrayOfQueries(current)) -// } -// console.log(`returning previous = ${JSON.stringify(previous)}`) -// return previous -// }, []) -// console.log(`FINAL`) -// console.log(JSON.stringify(final)) -// return final.flat() -// } - module.exports.esFilterSubledgerName = esFilterSubledgerName module.exports.esFilterByTxTypeNames = esFilterByTxTypeNames module.exports.esFilterTxnAfterTime = esFilterTxnAfterTime @@ -148,8 +128,8 @@ module.exports.esFilterBySeqNo = esFilterBySeqNo module.exports.esFilterHasTimestamp = esFilterHasTimestamp module.exports.esFilterSeqNoGte = esFilterSeqNoGte module.exports.esFilterSeqNoLt = esFilterSeqNoLt +module.exports.esFilterSeqNoGteLtRange = esFilterSeqNoGteLtRange module.exports.esAndFilters = esAndFilters module.exports.esOrFilters = esOrFilters module.exports.esFullTextsearch = esFullTextsearch module.exports.esFilterContainsFormat = esFilterContainsFormat -// module.exports.toArrayOfQueries = toArrayOfQueries diff --git a/indyscan-storage/test/integration/es/storage-read-es.spec.js b/indyscan-storage/test/integration/es/storage-read-es.spec.js index c5989c90..dfdcf85e 100644 --- a/indyscan-storage/test/integration/es/storage-read-es.spec.js +++ b/indyscan-storage/test/integration/es/storage-read-es.spec.js @@ -6,6 +6,7 @@ const { createWinstonLoggerDummy } = require('../../../src/es/utils') const { createStorageReadEs } = require('../../../src/es/storage-read-es') const { createStorageWriteEs } = require('../../../src/es/storage-write-es') const toCanonicalJson = require('canonical-json') +const { esAndFilters } = require('../../../src/es/es-query-builder') const { Client } = require('@elastic/elasticsearch') @@ -151,7 +152,7 @@ describe('reading transaction formats from elasticsearch', () => { expect(toCanonicalJson(configTx4.idata.bar.idata)).toBe(toCanonicalJson({ bardata: 'bar-config-4444' })) }) - it('should retrieve range of transaction within a particlar format', async () => { + it('should retrieve range of transaction within a particular format', async () => { // arrange const logger = createWinstonLoggerDummy() const storageWriteEs = await createStorageWriteEs(esClient, index, 0, logger) @@ -217,6 +218,34 @@ describe('reading transaction formats from elasticsearch', () => { })) }) + it('should query transaction capped by seqNo filters', async () => { + // arrange + const logger = createWinstonLoggerDummy() + const storageWriteEs = await createStorageWriteEs(esClient, index, 0, logger) + const storageReadEs = await createStorageReadEs(esClient, index, logger) + await storageWriteEs.addTx('domain', 1, 'foo', { foodata: 'foo-domain-1111' }) + await storageWriteEs.addTx('domain', 2, 'foo', { foodata: 'foo-domain-2222' }) + await storageWriteEs.addTx('domain', 3, 'foo', { foodata: 'foo-domain-3333' }) + await storageWriteEs.addTx('domain', 4, 'foo', { foodata: 'foo-domain-4444' }) + await storageWriteEs.addTx('domain', 5, 'foo', { foodata: 'foo-domain-4444' }) + await storageWriteEs.addTx('domain', 6, 'foo', { foodata: 'foo-domain-6666' }) + await storageWriteEs.addTx('domain', 7, 'foo', { foodata: 'foo-domain-7777' }) + await storageWriteEs.addTx('domain', 8, 'foo', { foodata: 'foo-domain-8888' }) + await storageWriteEs.addTx('domain', 9, 'foo', { foodata: 'foo-domain-9999' }) + await storageWriteEs.addTx('domain', 10, 'foo', { foodata: 'foo-domain-10101010' }) + + await sleep(1000) // takes time to index the stuff + + // act + const rangeFilter = esAndFilters(esFilterSeqNoGte(4), esFilterSeqNoLt(8)) + + const domainTxs = await storageReadEs.getManyTxs('domain', 2, 2, rangeFilter, undefined, 'foo') + expect(Array.isArray(domainTxs)).toBeTruthy() + expect(domainTxs.length).toBe(2) + expect(domainTxs[0].imeta.seqNo).toBe(5) + expect(domainTxs[1].imeta.seqNo).toBe(4) + }) + it('should retrieve range of transaction within a particlar format and apply custom filter', async () => { // arrange const logger = createWinstonLoggerDummy() diff --git a/indyscan-txtype/package.json b/indyscan-txtype/package.json index 6b8b6fb6..207306ff 100644 --- a/indyscan-txtype/package.json +++ b/indyscan-txtype/package.json @@ -1,6 +1,6 @@ { "name": "indyscan-txtype", - "version": "4.1.3", + "version": "4.2.0", "description": "", "main": "index.js", "scripts": { diff --git a/indyscan-webapp/package.json b/indyscan-webapp/package.json index 8bc4961b..9e87437c 100644 --- a/indyscan-webapp/package.json +++ b/indyscan-webapp/package.json @@ -1,6 +1,6 @@ { "name": "indyscan-webapp", - "version": "4.1.3", + "version": "4.2.0", "description": "Web application to browse Hyperledger Indy blockchain transactions.", "main": "index.js", "scripts": { diff --git a/version.json b/version.json index 2c31acc4..ee35c170 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { "major": 4, - "minor": 1, - "patch": 3 + "minor": 2, + "patch": 0 }