Skip to content
This repository has been archived by the owner on Jun 11, 2024. It is now read-only.

Fix export microservice #2016

Merged
merged 26 commits into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1315b5f
:zap: Allow slack in index readiness when scheduling history export jobs
sameersubudhi Jan 24, 2024
7348e75
:zap: Do not re-schedule duplicate jobs
sameersubudhi Jan 25, 2024
9ac0d63
:pencil: Improve logging
sameersubudhi Jan 25, 2024
aaed0eb
:bug: Fix index readiness check logic for export job scheduling
sameersubudhi Jan 25, 2024
8b7feae
:racehorse: Optimize code
sameersubudhi Jan 25, 2024
6735f95
:hammer: Refactor code. Prefer early exit from the loop
sameersubudhi Jan 25, 2024
5fab2b4
:bug: Fix queue config and initialization
sameersubudhi Jan 25, 2024
8bf9fb8
:hammer: Automatically se-schedule a job if it timesout
sameersubudhi Jan 25, 2024
e7debd7
:wrench: Add microservice dependencies for export microservice
sameersubudhi Jan 25, 2024
4336100
:racehorse: Optimize genesis asset query
sameersubudhi Jan 25, 2024
8fe0ba7
:hammer: Fix broken unit tests
sameersubudhi Jan 25, 2024
c2d00ed
:heavy_check_mark: Fix unit tests
nagdahimanshu Jan 25, 2024
f28f824
:rotating_light: Add new unit tests
sameersubudhi Jan 26, 2024
1f8ec22
:ok_hand: Apply review suggestion
sameersubudhi Jan 26, 2024
5dc2596
:wrench: Limit indexing pace to assist in app node performance
sameersubudhi Jan 26, 2024
fb3ba7d
:racehorse: Use lighter endpoint invocations to verify account existence
sameersubudhi Jan 26, 2024
f652672
:hammer: Locally cache genesis token assets at init
sameersubudhi Jan 26, 2024
d63cd77
:heavy_check_mark: Fix unit tests
sameersubudhi Jan 26, 2024
4409285
:hammer: Clear any stale intervals
sameersubudhi Jan 26, 2024
d6d937f
:art: Add logs
sameersubudhi Jan 26, 2024
58fd9b4
:wrench: Revert ratelimiting on the indexing jobs
sameersubudhi Jan 26, 2024
0fe3729
:hammer: Increase query payload size
sameersubudhi Jan 26, 2024
1f0cc29
:white_check_mark: Add unit tests
nagdahimanshu Jan 26, 2024
b65e6d6
:bug: Add prefix handling when extracting transactionID from event to…
sameersubudhi Jan 27, 2024
3e32931
:rotating_light: Add more unit tests
sameersubudhi Jan 27, 2024
a551dfa
:pencil: Fix swagger docs
sameersubudhi Jan 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions services/blockchain-connector/methods/token.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const {
getTokenBalances,
getTokenInitializationFees,
tokenHasEscrowAccount,
getTokenBalanceAtGenesis,
} = require('../shared/sdk');

module.exports = [
Expand Down Expand Up @@ -84,4 +85,11 @@ module.exports = [
controller: async () => getTokenInitializationFees(),
params: {},
},
{
name: 'getTokenBalanceAtGenesis',
controller: async ({ address }) => getTokenBalanceAtGenesis(address),
params: {
address: { optional: false, type: 'string' },
},
},
];
4 changes: 3 additions & 1 deletion services/blockchain-connector/shared/sdk/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const {
getTotalSupply,
getTokenInitializationFees,
updateTokenInfo,
getTokenBalanceAtGenesis,
} = require('./token');

const {
Expand Down Expand Up @@ -178,7 +179,7 @@ module.exports = {
dryRunTransaction,
formatTransaction,

// Tokens
// Token
tokenHasUserAccount,
tokenHasEscrowAccount,
getTokenBalance,
Expand All @@ -187,6 +188,7 @@ module.exports = {
getSupportedTokens,
getTotalSupply,
getTokenInitializationFees,
getTokenBalanceAtGenesis,

// PoS
getAllPosValidators,
Expand Down
17 changes: 17 additions & 0 deletions services/blockchain-connector/shared/sdk/token.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
const { invokeEndpoint } = require('./client');
const { isMainchain } = require('./interoperability');
const { getGenesisAssetByModule } = require('./genesisBlock');
const { MODULE_NAME_TOKEN } = require('./constants/names');
Dismissed Show dismissed Hide dismissed

let escrowedAmounts;
let supportedTokens;
Expand Down Expand Up @@ -73,6 +75,20 @@ const updateTokenInfo = async () => {
totalSupply = await getTotalSupply(true);
};

const getTokenBalanceAtGenesis = async address => {
const MODULE_TOKEN_SUBSTORE_USER = 'userSubstore';
Dismissed Show dismissed Hide dismissed

const tokenModuleGenesisAssets = await getGenesisAssetByModule({
module: MODULE_NAME_TOKEN,
subStore: MODULE_TOKEN_SUBSTORE_USER,
});

const balancesAtGenesis = tokenModuleGenesisAssets[MODULE_TOKEN_SUBSTORE_USER];
const balancesByAddress = balancesAtGenesis.find(e => e.address === address);

return balancesByAddress;
};

module.exports = {
tokenHasUserAccount: hasUserAccount,
tokenHasEscrowAccount: hasEscrowAccount,
Expand All @@ -83,4 +99,5 @@ module.exports = {
getTotalSupply,
getTokenInitializationFees,
updateTokenInfo,
getTokenBalanceAtGenesis,
};
4 changes: 3 additions & 1 deletion services/blockchain-indexer/shared/utils/transactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ const getTransactionExecutionStatus = (tx, events) => {
e => e.topics.includes(EVENT_TOPIC_PREFIX.TX_ID.concat(tx.id)) || e.topics.includes(tx.id),
);
if (!txExecResultEvent)
throw Error(`Event unavailable to determine execution status for transaction: ${tx.id}.`);
throw Error(
`Event unavailable to determine execution status for transaction: ${tx.id}.\nEnsure that you have set 'system.keepEventsForHeights: -1' in your node config before syncing it with the network.`,
);

return txExecResultEvent.data.success ? TRANSACTION_STATUS.SUCCESSFUL : TRANSACTION_STATUS.FAILED;
};
Expand Down
13 changes: 12 additions & 1 deletion services/export/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
*
*/
const path = require('path');
const { Microservice, LoggerConfig, Logger } = require('lisk-service-framework');
const { Signals, Microservice, LoggerConfig, Logger } = require('lisk-service-framework');

const config = require('./config');

Expand All @@ -32,6 +32,17 @@ const app = Microservice({
timeout: config.brokerTimeout,
packageJson,
logger: config.log,
events: {
systemNodeInfo: async payload => {
logger.debug("Received a 'systemNodeInfo' moleculer event from connecter.");
Signals.get('nodeInfo').dispatch(payload);
},
'update.index.status': async payload => {
logger.debug("Received a 'update.index.status' moleculer event from indexer.");
Signals.get('updateIndexStatus').dispatch(payload);
},
},
dependencies: ['connector', 'indexer', 'app-registry'],
});

setAppContext(app);
Expand Down
11 changes: 10 additions & 1 deletion services/export/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,16 @@ config.excel.sheets = {
config.queue = {
scheduleTransactionExport: {
name: 'ScheduleTransactionExportQueue',
concurrency: 50,
concurrency: 10,
options: {
defaultJobOptions: {
attempts: 5,
timeout: 15 * 60 * 1000, // millisecs
removeOnComplete: true,
removeOnFail: true,
stackTraceLimit: 0,
},
},
},
defaults: {
jobOptions: {
Expand Down
6 changes: 6 additions & 0 deletions services/export/shared/helpers/chain.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,15 @@ const getUniqueChainIDs = async txs => {
return Array.from(chainIDs);
};

const getBlocks = async params => requestIndexer('blocks', params);

const getTransactions = async params => requestIndexer('transactions', params);

module.exports = {
getCurrentChainID,
resolveReceivingChainID,
getNetworkStatus,
getUniqueChainIDs,
getBlocks,
getTransactions,
};
7 changes: 6 additions & 1 deletion services/export/shared/helpers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ const {
resolveReceivingChainID,
getNetworkStatus,
getUniqueChainIDs,
getBlocks,
getTransactions,
} = require('./chain');

const { MODULE, COMMAND, EVENT, MODULE_SUB_STORE } = require('./constants');
Expand All @@ -48,7 +50,7 @@ const {
requestAppRegistry,
} = require('./request');

const { getDaysInMilliseconds, dateFromTimestamp, timeFromTimestamp } = require('./time');
const { getToday, getDaysInMilliseconds, dateFromTimestamp, timeFromTimestamp } = require('./time');

const {
normalizeTransactionAmount,
Expand All @@ -66,6 +68,8 @@ module.exports = {
resolveReceivingChainID,
getNetworkStatus,
getUniqueChainIDs,
getBlocks,
getTransactions,

MODULE,
COMMAND,
Expand All @@ -87,6 +91,7 @@ module.exports = {
requestConnector,
requestAppRegistry,

getToday,
getDaysInMilliseconds,
dateFromTimestamp,
timeFromTimestamp,
Expand Down
78 changes: 78 additions & 0 deletions services/export/shared/helpers/ready.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* LiskHQ/lisk-service
* Copyright © 2024 Lisk Foundation
*
* See the LICENSE file at the top-level directory of this distribution
* for licensing information.
*
* Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation,
* no part of this software, including this file, may be copied, modified,
* propagated, or distributed except according to the terms contained in the
* LICENSE file.
*
* Removal or modification of this copyright notice is prohibited.
*
*/
const Moment = require('moment');
const MomentRange = require('moment-range');

const { Signals, Logger } = require('lisk-service-framework');

const config = require('../../config');

const { requestIndexer, getToday, getBlocks } = require('.');

const moment = MomentRange.extendMoment(Moment);
const DATE_FORMAT = config.excel.dateFormat;

const logger = Logger();

let indexStatusCache;

const getIndexStatus = async () => {
if (!indexStatusCache) {
indexStatusCache = await requestIndexer('index.status');

const updateIndexStatusListener = payload => {
indexStatusCache = payload;
};
Signals.get('updateIndexStatus').add(updateIndexStatusListener);
}
return indexStatusCache;
};

const checkIfIndexReadyForInterval = async interval => {
try {
// Blockchain fully indexed
const { data: indexStatus } = await getIndexStatus();
if (indexStatus.percentageIndexed === 100) return true;

// Requested history for only until yesterday and blockchain index can already serve the information
const [, toDate] = interval.split(':');
const to = moment(toDate, DATE_FORMAT).endOf('day');
const today = moment(getToday(), DATE_FORMAT);

if (to < today) {
const response = await getBlocks({ height: indexStatus.lastIndexedBlockHeight });
const [lastIndexedBlock] = response.data;
const lastIndexedBlockGeneratedTime = lastIndexedBlock.timestamp;

if (to <= moment(lastIndexedBlockGeneratedTime * 1000)) return true;
}

// Allow job scheduling if the last few blocks have not been indexed yet
if (indexStatus.chainLength - indexStatus.numBlocksIndexed <= 10) {
return true;
}
} catch (err) {
logger.warn(`Index readiness check for export job scheduling failed due to: ${err.message}`);
logger.debug(err.stack);
}

return false;
};

module.exports = {
getIndexStatus,
checkIfIndexReadyForInterval,
};
14 changes: 10 additions & 4 deletions services/export/shared/helpers/time.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,28 @@ const moment = require('moment');

const config = require('../../config');

const DAY_IN_MILLISEC = moment().endOf('day').valueOf() - moment().startOf('day').valueOf() + 1;
const getDaysInMilliseconds = days => days * DAY_IN_MILLISEC;
const DATE_FORMAT = config.excel.dateFormat;
const TIME_FORMAT = config.excel.timeFormat;

const getToday = () => moment().format(DATE_FORMAT);

const DAY_IN_MILLISECS = moment().endOf('day').valueOf() - moment().startOf('day').valueOf() + 1;
const getDaysInMilliseconds = days => days * DAY_IN_MILLISECS;

const momentFromTimestamp = timestamp => moment.unix(timestamp);

const dateFromTimestamp = timestamp => {
const dateTime = momentFromTimestamp(timestamp);
return dateTime.utcOffset(0).format(config.excel.dateFormat);
return dateTime.utcOffset(0).format(DATE_FORMAT);
};

const timeFromTimestamp = timestamp => {
const dateTime = momentFromTimestamp(timestamp);
return dateTime.utcOffset(0).format(config.excel.timeFormat);
return dateTime.utcOffset(0).format(TIME_FORMAT);
};

module.exports = {
getToday,
getDaysInMilliseconds,
dateFromTimestamp,
timeFromTimestamp,
Expand Down
58 changes: 2 additions & 56 deletions services/export/shared/requestAll.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@
* Removal or modification of this copyright notice is prohibited.
*
*/
const { Utils } = require('lisk-service-framework');

const requestAllStandard = async (fn, params, limit) => {
const requestAll = async (fn, params, limit) => {
const defaultMaxAmount = limit || 1000;
const oneRequestLimit = params.limit || 100;
const firstRequest = await fn({
Expand Down Expand Up @@ -48,57 +47,4 @@ const requestAllStandard = async (fn, params, limit) => {
return data;
};

const requestAllCustom = async (fn, method, params, limit) => {
const maxAmount = limit || Number.MAX_SAFE_INTEGER;
const oneRequestLimit = params.limit || 100;
const firstRequest = await fn(method, {
...params,
...{
limit: oneRequestLimit,
offset: 0,
},
});
const totalResponse = firstRequest;
if (totalResponse && !totalResponse.error) {
if (maxAmount > oneRequestLimit) {
for (let page = 1; page < Math.ceil(maxAmount / oneRequestLimit); page++) {
const curOffset = oneRequestLimit * page;

const result = await fn(method, {
...params,
...{
limit: Math.min(oneRequestLimit, maxAmount - curOffset),
offset: curOffset,
},
});

// This check needs to be updated for dynamic exit based on emptiness of object properties
if (!result || Utils.isEmptyArray(result) || Utils.isEmptyObject(result)) {
break;
}

if (Array.isArray(totalResponse)) totalResponse.push(...result);
else if (Utils.isObject(totalResponse)) {
// When response is an object, we should traverse the properties and merge the values.
// We can safely assume that the properties would be of type array, so concatenation will
// result in the whole response. If property is not an array, the latest value is kept.
Object.entries(totalResponse).forEach(([dataKey, dataVal]) => {
if (Array.isArray(dataVal)) {
totalResponse[dataKey].push(...result[dataKey]);
} else if (Utils.isObject(dataVal)) {
totalResponse[dataKey] = { ...totalResponse[dataKey], ...result[dataKey] };
} else {
totalResponse[dataKey] = result[dataKey];
}
});
}
}
}
}
return totalResponse;
};

module.exports = {
requestAllStandard,
requestAllCustom,
};
module.exports = requestAll;
Loading
Loading