Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
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
53 changes: 53 additions & 0 deletions .github/workflows/check-package-lock.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Check Package Lock File

permissions:
contents: read

concurrency:
group: check-package-lock-${{ github.ref }}
cancel-in-progress: true

on:
push:
branches:
- main # Run on push to main branch only
pull_request:
branches:
- "**" # Run on PR to any branch

jobs:
verify-package-lock:
name: Verify package-lock.json exists
runs-on: ubuntu-latest
timeout-minutes: 5

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Check if package-lock.json exists
run: |
if [ ! -f "package-lock.json" ]; then
echo "ERROR: package-lock.json file is missing from the repository"
echo "This file is required to ensure consistent dependency versions across all environments"
echo "Please ensure package-lock.json is committed with your changes"
exit 1
fi
echo "SUCCESS: package-lock.json file is present"

- name: Verify package-lock.json is not empty
run: |
if [ ! -s "package-lock.json" ]; then
echo "ERROR: package-lock.json file exists but is empty"
echo "Please run 'npm install' to regenerate the lock file"
exit 1
fi
echo "SUCCESS: package-lock.json file is valid and not empty"

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'

- name: Validate package-lock.json is valid and in sync
run: npm ci --dry-run --ignore-scripts
5 changes: 5 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ jobs:
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3
steps:
- name: Checkout repository
uses: actions/checkout@v3
Expand Down
6 changes: 6 additions & 0 deletions src/modules/http-client/http-client-module-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ class HttpClientModuleManager extends BaseModuleManager {
return this.getImplementation().module.initializeAfterMiddlewares(this.authService);
}
}

async close() {
if (this.initialized && this.getImplementation().module.close) {
return this.getImplementation().module.close();
}
}
}

export default HttpClientModuleManager;
21 changes: 20 additions & 1 deletion src/modules/http-client/implementation/express-http-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,30 @@ class ExpressHttpClient {
);
this.httpsServer.listen(this.config.port);
} else {
this.app.listen(this.config.port);
this.server = this.app.listen(this.config.port);
}
this.logger.info(`Node listening on port: ${this.config.port}`);
}

async close() {
return new Promise((resolve, reject) => {
const serverToClose = this.httpsServer || this.server;
if (serverToClose) {
serverToClose.close((err) => {
if (err) {
this.logger.error(`Error closing HTTP server: ${err.message}`);
reject(err);
} else {
this.logger.info('HTTP server closed successfully');
resolve();
}
});
} else {
resolve();
}
});
}

selectMiddlewares(options) {
const middlewares = [];
if (options.rateLimit) middlewares.push(rateLimiterMiddleware(this.config.rateLimiter));
Expand Down
8 changes: 8 additions & 0 deletions src/modules/network/implementation/libp2p-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ class Libp2pService {
this.logger.info(`Network ID is ${this.config.id}, connection port is ${port}`);
}

async stop() {
if (this.node) {
this.logger.info('Stopping libp2p node...');
await this.node.stop();
this.logger.info('Libp2p node stopped');
}
}

async onPeerConnected(listener) {
this.node.connectionManager.on('peer:connect', listener);
}
Expand Down
6 changes: 6 additions & 0 deletions src/modules/network/network-module-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ class NetworkModuleManager extends BaseModuleManager {
}
}

async stop() {
if (this.initialized) {
return this.getImplementation().module.stop();
}
}

async onPeerConnected(listener) {
if (this.initialized) {
return this.getImplementation().module.onPeerConnected(listener);
Expand Down
37 changes: 37 additions & 0 deletions src/service/blockchain-interval-cleanup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Helper function to clean up blockchain-specific intervals.
*
* Services like ProofingService and ClaimRewardsService store intervals
* using a `${blockchainId}Interval` property pattern. This utility provides
* a consistent way to clean up those intervals.
*
* @param {Object} options - Configuration options
* @param {Object} options.service - The service instance containing the intervals
* @param {Object} options.blockchainModuleManager - The blockchain module manager
* @param {Object} options.logger - The logger instance
* @param {string} options.serviceName - Name of the service (for logging)
* @param {string} options.logPrefix - Log prefix e.g., '[CLAIM]' or '[PROOFING]'
*/
function cleanupBlockchainIntervals({
service,
blockchainModuleManager,
logger,
serviceName,
logPrefix,
}) {
logger.info(`${logPrefix} Starting ${serviceName} cleanup`);

for (const blockchainId of blockchainModuleManager.getImplementationNames()) {
const intervalKey = `${blockchainId}Interval`;
if (service[intervalKey]) {
logger.debug(`${logPrefix} Clearing interval for blockchain ${blockchainId}`);
clearInterval(service[intervalKey]);
// eslint-disable-next-line no-param-reassign
service[intervalKey] = null;
}
}

logger.info(`${logPrefix} ${serviceName} cleanup completed`);
}

export default cleanupBlockchainIntervals;
11 changes: 11 additions & 0 deletions src/service/claim-rewards-service.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CLAIM_REWARDS_BATCH_SIZE, CLAIM_REWARDS_INTERVAL } from '../constants/constants.js';
import cleanupBlockchainIntervals from './blockchain-interval-cleanup.js';

class ClaimRewardsService {
constructor(ctx) {
Expand Down Expand Up @@ -92,6 +93,16 @@ class ClaimRewardsService {
}
}

cleanup() {
cleanupBlockchainIntervals({
service: this,
blockchainModuleManager: this.blockchainModuleManager,
logger: this.logger,
serviceName: 'ClaimRewardsService',
logPrefix: '[CLAIM]',
});
}

async claimRewards(blockchainId) {
const identityId = await this.blockchainModuleManager.getIdentityId(blockchainId);
const nodeDelegatorAddresses = await this.blockchainModuleManager.getDelegators(
Expand Down
19 changes: 8 additions & 11 deletions src/service/proofing-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
TRIPLES_VISIBILITY,
PROOFING_MAX_ATTEMPTS,
} from '../constants/constants.js';
import cleanupBlockchainIntervals from './blockchain-interval-cleanup.js';

class ProofingService {
constructor(ctx) {
Expand Down Expand Up @@ -485,18 +486,14 @@ class ProofingService {
return `${blockchainId}-${epoch}-${activeProofPeriodStartBlock}`;
}

// Add cleanup method to stop intervals
cleanup() {
this.logger.info('[PROOFING] Starting ProofingService cleanup');
for (const blockchainId of this.blockchainModuleManager.getImplementationNames()) {
const intervalKey = `${blockchainId}Interval`;
if (this[intervalKey]) {
this.logger.debug(`Clearing interval for blockchain ${blockchainId}`);
clearInterval(this[intervalKey]);
this[intervalKey] = null;
}
}
this.logger.info('[PROOFING] ProofingService cleanup completed');
cleanupBlockchainIntervals({
service: this,
blockchainModuleManager: this.blockchainModuleManager,
logger: this.logger,
serviceName: 'ProofingService',
logPrefix: '[PROOFING]',
});
}
}

Expand Down
79 changes: 56 additions & 23 deletions test/bdd/steps/common.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,7 @@ Given(
if (response.error) {
assert.fail(`Error while initializing node${nodeIndex}: ${response.error}`);
} else {
// todo if started
const client = new DkgClientHelper({
endpoint: 'http://localhost',
port: rpcPort,
maxNumberOfRetries: 5,
frequency: 2,
contentType: 'all',
});
// Build blockchain options BEFORE creating the client
let clientBlockchainOptions = {};
Object.keys(this.state.localBlockchains).forEach((blockchainId, index) => {
const blockchain = this.state.localBlockchains[blockchainId];
Expand All @@ -87,6 +80,25 @@ Given(
};
});

// Get the first blockchain for the DKG client config
const firstBlockchainId = Object.keys(this.state.localBlockchains)[0];
const firstBlockchain = this.state.localBlockchains[firstBlockchainId];
const firstWallet = firstBlockchain.getWallets()[0];

const client = new DkgClientHelper({
endpoint: 'http://localhost',
port: rpcPort,
maxNumberOfRetries: 5,
frequency: 2,
contentType: 'all',
blockchain: {
name: firstBlockchainId,
publicKey: firstWallet.address,
privateKey: firstWallet.privateKey,
rpc: `http://localhost:${firstBlockchain.port}`,
},
});

this.state.nodes[nodeIndex] = {
client,
forkedNode,
Expand Down Expand Up @@ -145,23 +157,44 @@ Given(
fs.rmSync(appDataPath, { recursive: true, force: true });

const nodeInstance = new MockOTNode(nodeConfiguration);
await nodeInstance.start(); // This will skip startNetworkModule

try {
await nodeInstance.start(); // This will skip startNetworkModule

// Get the first blockchain for the DKG client config
const firstBlockchainId = Object.keys(this.state.localBlockchains)[0];
const firstBlockchain = this.state.localBlockchains[firstBlockchainId];
const firstWallet = firstBlockchain.getWallets()[0];

const client = new DkgClientHelper({
endpoint: 'http://localhost',
port: rpcPort,
useSSL: false,
timeout: 25,
loglevel: 'trace',
});
const client = new DkgClientHelper({
endpoint: 'http://localhost',
port: rpcPort,
useSSL: false,
timeout: 25,
loglevel: 'trace',
blockchain: {
name: firstBlockchainId,
publicKey: firstWallet.address,
privateKey: firstWallet.privateKey,
rpc: `http://localhost:${firstBlockchain.port}`,
},
});

this.state.bootstraps.push({
client,
otNodeInstance: nodeInstance,
configuration: nodeConfiguration,
nodeRpcUrl: `http://localhost:${rpcPort}`,
fileService: nodeInstance.fileService,
});
this.state.bootstraps.push({
client,
otNodeInstance: nodeInstance,
configuration: nodeConfiguration,
nodeRpcUrl: `http://localhost:${rpcPort}`,
fileService: nodeInstance.fileService,
});
} catch (error) {
// Ensure node is stopped if there's an error after starting
this.logger.error(`Error during bootstrap initialization: ${error.message}`);
if (nodeInstance.stop) {
await nodeInstance.stop();
}
throw error;
}
}
);
//
Expand Down
Loading
Loading