Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
93 changes: 93 additions & 0 deletions conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
diff --git a/packages/bitcore-cli/package.json b/packages/bitcore-cli/package.json
index e291ea3ba..875c2d210 100644
--- a/packages/bitcore-cli/package.json
+++ b/packages/bitcore-cli/package.json
@@ -30,8 +30,7 @@
Comment on lines +1 to +5
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file appears to be an accidentally committed terminal-colored git diff output (contains ANSI escape sequences) rather than source code/config. It should be removed from the repository (and the intended changes applied to the real files instead, if needed).

Copilot uses AI. Check for mistakes.
},

"scripts": {
- "test": "npm run compile && mocha 'build/test/**/*.js'",
- "coverage": "npm run compile && nyc mocha 'build/test/**/*.js'",
+ "test": "npm run compile && nyc mocha 'build/test/**/*.js' --timeout 5000",
"build": "tsc",
"postbuild": "node createBin -v",
"clean": "rm -rf ./build",
diff --git a/packages/bitcore-client/package.json b/packages/bitcore-client/package.json
index ffe3e25eb..12e2e6ff4 100644
--- a/packages/bitcore-client/package.json
+++ b/packages/bitcore-client/package.json
@@ -23,8 +23,7 @@
"watch": "tsc --watch",
"precompile": "cd ../bitcore-node && npm run clean && npm run build:prod",
"compile": "npm run clean && npm run build",
- "test": "npm run compile && mocha -r tsx --exit 'test/**/*.test.ts'",
- "coverage": "npm run compile && nyc mocha -r tsx --exit 'test/**/*.test.ts'",
+ "test": "npm run compile && nyc mocha --exit 'ts_build/test/**/*.test.js'",
"precommit": "npm run lint",
"lint": "eslint .",
"fix:errors": "eslint --fix --quiet .",
diff --git a/packages/bitcore-client/test/unit/wallet.test.ts b/packages/bitcore-client/test/unit/wallet.test.ts
index a9947ce4e..a75d02ed4 100644
--- a/packages/bitcore-client/test/unit/wallet.test.ts
+++ b/packages/bitcore-client/test/unit/wallet.test.ts
@@ -1,10 +1,10 @@
import * as chai from 'chai';
import * as CWC from '@bitpay-labs/crypto-wallet-core';
import { AddressTypes, Wallet } from '../../src/wallet';
-import { Api as bcnApi } from '../../../bitcore-node/build/src/services/api';
-import { Storage as bcnStorage } from '../../../bitcore-node/build/src/services/storage';
+import { Api as bcnApi } from '../../../bitcore-node/src/services/api';
+import { Storage as bcnStorage } from '../../../bitcore-node/src/services/storage';
import crypto from 'crypto';
-import { Modules } from '../../../bitcore-node/build/src/modules';
+import { Modules } from '../../../bitcore-node/src/modules';
import request from 'request-promise-native';
import requestStream from 'request';
import { Server } from 'http';
@@ -136,7 +136,7 @@ describe('Wallet', function() {
expect(err.message).to.equal('Must provide changeIdx for UTXO chains');
}
});
- 
+
it('should throw an error if neither rawTx nor txid is provided', async () => {
try {
await wallet.bumpTxFee({ changeIdx: 0 });
@@ -171,12 +171,12 @@ describe('Wallet', function() {
sandbox.stub(wallet, 'getTransactionByTxid').resolves({ '_id': '65982807d85e75f781a0d56f', 'txid': '8b78e4d2ac2211472454f940445210b6487aaa0f889e18066eb3f623352607f5', 'network': 'regtest', 'chain': 'BTC', 'blockHeight': -1, 'blockHash': '', 'blockTime': '2024-01-05T16:02:15.678Z', 'blockTimeNormalized': '2024-01-05T16:02:15.678Z', 'coinbase': false, 'locktime': -1, 'inputCount': 5, 'outputCount': 1, 'size': 780, 'fee': 2409, 'value': 950891, 'confirmations': 0, 'coins': { 'inputs': [{ '_id': '659825bed85e75f781a01812', 'chain': 'BTC', 'network': 'regtest', 'coinbase': false, 'mintIndex': 1, 'spentTxid': '8b78e4d2ac2211472454f940445210b6487aaa0f889e18066eb3f623352607f5', 'mintTxid': '330716a75c23512202e45ffee478bdce7a33f298edf7eef30a42dbde06746c48', 'mintHeight': 154, 'spentHeight': -1, 'address': 'n2vpyzbBPBEni9FgkeQSnXvAQAfnRmKxFZ', 'script': '76a914eade85745090388e64a9341a82d8f94371430d1a88ac', 'value': 244000, 'confirmations': -1, 'sequenceNumber': 4294967293 }, { '_id': '65982587d85e75f781a0046f', 'chain': 'BTC', 'network': 'regtest', 'coinbase': false, 'mintIndex': 1, 'spentTxid': '8b78e4d2ac2211472454f940445210b6487aaa0f889e18066eb3f623352607f5', 'mintTxid': '7b83f82a22f94fc3e615743a5e6fb2345ed79aad8a2b564af2ace49da1e27ebb', 'mintHeight': 152, 'spentHeight': -1, 'address': 'muEU9KaNEw5doymy59ScrgEE3Eq4CQ45U9', 'script': '76a91496739b4ef4e793c05a78624da38d96f2c4059c6b88ac', 'value': 239400, 'confirmations': -1, 'sequenceNumber': 4294967293 }, { '_id': '65982511d85e75f7819fdd40', 'chain': 'BTC', 'network': 'regtest', 'coinbase': false, 'mintIndex': 0, 'spentTxid': '8b78e4d2ac2211472454f940445210b6487aaa0f889e18066eb3f623352607f5', 'mintTxid': 'fb4d8039024c80a655fea29e01faf202f30e936af5738e31c5841a84215ca4d6', 'mintHeight': 149, 'spentHeight': -1, 'address': 'muEU9KaNEw5doymy59ScrgEE3Eq4CQ45U9', 'script': '76a91496739b4ef4e793c05a78624da38d96f2c4059c6b88ac', 'value': 239400, 'confirmations': -1, 'sequenceNumber': 4294967293 }, { '_id': '659824d7d85e75f7819fc75d', 'chain': 'BTC', 'network': 'regtest', 'coinbase': false, 'mintIndex': 0, 'spentTxid': '8b78e4d2ac2211472454f940445210b6487aaa0f889e18066eb3f623352607f5', 'mintTxid': '8221ed6891f6ba82ab72bb8298a661e21b47b04408861b0c3268c4a04a0435b3', 'mintHeight': 145, 'spentHeight': -1, 'address': 'miEzS8fSvtDjwkEjY14FEuWumFKg8nTPV9', 'script': '76a9141de381fc20d34d20e11a5543f536a4b74f495a9888ac', 'value': 228000, 'confirmations': -1, 'sequenceNumber': 4294967293 }, { '_id': '65982389d85e75f7819f57ff', 'chain': 'BTC', 'network': 'regtest', 'coinbase': false, 'mintIndex': 0, 'spentTxid': '8b78e4d2ac2211472454f940445210b6487aaa0f889e18066eb3f623352607f5', 'mintTxid': '95f713c2d7e09ab57ec3016bb767b26e6ee849cf487e86d8ed6126ae1b893e0f', 'mintHeight': 143, 'spentHeight': -1, 'address': 'msnM9VB5usfBahZNo57ZUq9uzwRxQXksUz', 'script': '76a914868ad3308906626182d1c6cd703cc7ce78b3a28d88ac', 'value': 2500, 'confirmations': -1, 'sequenceNumber': 4294967293 }], 'outputs': [{ '_id': '65982807d85e75f781a0d55d', 'chain': 'BTC', 'network': 'regtest', 'coinbase': false, 'mintIndex': 0, 'spentTxid': '', 'mintTxid': '8b78e4d2ac2211472454f940445210b6487aaa0f889e18066eb3f623352607f5', 'mintHeight': -1, 'spentHeight': -2, 'address': 'mwwrGzk9q8xugY97C8BiNbcgqXNyjH4kp8', 'script': '76a914b4376347e4cffb1e9a475b2661bbe74de3c1f86a88ac', 'value': 950891, 'confirmations': -1 }] } });
sandbox.stub(wallet.client, 'importAddresses').resolves();
sandbox.spy(CWC.BitcoreLib.Transaction.prototype, 'feePerByte');
- 
+
await wallet.generateAddressPair(1310066242, true);
await wallet.generateAddressPair(1087984800, true);
await wallet.generateAddressPair(1310064953, true);
await wallet.generateAddressPair(1310062823, true);
- 
+
const { tx: newTx } = await wallet.bumpTxFee({
txid: '8b78e4d2ac2211472454f940445210b6487aaa0f889e18066eb3f623352607f5',
changeIdx: 0,
@@ -204,7 +204,7 @@ describe('Wallet', function() {
await wallet.unlock('abc123');
});

- 
+
it('should throw an error if neither rawTx nor txid is provided', async () => {
try {
await wallet.bumpTxFee({ changeIdx: 0 });
@@ -230,7 +230,7 @@ describe('Wallet', function() {
it('should bump the fee of a transaction with feeRate', async function() {
sandbox.stub(wallet, 'getTransactionByTxid').resolves({ 'txid': '0x0cf410cfe7fb268ad06ae115edfa8a30a8dea3979336a647b09b5a789c4b53d5', 'network': 'regtest', 'chain': 'ETH', 'blockHeight': 43245, 'blockHash': '0x9edb8d10883a360f7ff0c26860b6a159f5b7a74226949a4365691a879fafcdfc', 'blockTime': '2024-01-08T16:31:46.000Z', 'blockTimeNormalized': '2024-01-08T16:31:46.000Z', 'fee': 42000000000000, 'value': 1000000000000000000, 'gasLimit': 200000, 'gasPrice': 2000000000, 'nonce': 0, 'to': '0x7ee308b49e36Ab516cd0186B3a47CFD31d2499A1', 'from': '0x5FbdD2712d05D1a73e0b3Eba5efE8c3d42a336C3', 'effects': [], 'data': '0x', 'internal': [], 'calls': [], 'confirmations': 33 });
sandbox.stub(wallet.client, 'importAddresses').resolves();
- 
+
const { tx: newTx, params } = await wallet.bumpTxFee({
txid: '0x0cf410cfe7fb268ad06ae115edfa8a30a8dea3979336a647b09b5a789c4b53d5',
feeRate: 300
@@ -549,4 +549,3 @@ describe('Wallet', function() {
});
});
});
-
2 changes: 1 addition & 1 deletion packages/bitcore-node/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ RUN npm run compile

# Start the server
WORKDIR /bitcore/packages/bitcore-node
CMD ["node", "./build/src/server.js"]
CMD ["node", ".*.tsserver.js"]
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CMD ["node", ".*.tsserver.js"] does not point at a valid built entrypoint and will break the container startup. This should likely remain node ./build/src/server.js (or the correct compiled JS path) after the build step.

Suggested change
CMD ["node", ".*.tsserver.js"]
CMD ["node", "./build/src/server.js"]

Copilot uses AI. Check for mistakes.
83 changes: 83 additions & 0 deletions packages/bitcore-node/scripts/reloadConfig.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/bin/sh

dir=$(pwd)
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script uses dir=$(pwd), so it only works when run from the directory containing pids/. Resolve the base directory relative to the script path instead (e.g., using dirname "$0") so it works from any cwd.

Suggested change
dir=$(pwd)
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
dir=$(CDPATH= cd -- "$script_dir/.." && pwd)

Copilot uses AI. Check for mistakes.

if [ $# = 0 ]; then
pid_paths=$dir/pids/*

for path in $pid_paths; do
pid=$(cat "$path")
Comment on lines +5 to +9
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there are no pid files, pid_paths=$dir/pids/* may not expand and the loop will try to cat a literal glob. Add an explicit check that pid files exist (or use a glob-safe listing) before iterating, and avoid calling kill with an empty pid list.

Copilot uses AI. Check for mistakes.
printf "$(basename "$path" .pid)::$pid "
pids="$pids $pid"
done
echo ''

kill -USR1 $pids &&
echo "Refreshed all workers"
exit 0
fi

if [ $1 = --help ]; then
cat << EOF
Usage: $(basename "$0") [OPTIONS] [WORKER...]

Reload configuration for bitcore workers

Options:
--help Show this help message and exit
list List all running workers

Arguments:
WORKER Name(s) of worker(s) to reload configs (e.g., all api p2p)
If no worker is specified, reload all running workers configs.

Examples:
$(basename "$0") Reload config for all workers
$(basename "$0") api p2p Reload config for 'api' and 'p2p' workers
$(basename "$0") list List all running workers
EOF
exit 0
fi

if [ $1 = list ]; then
pid_paths=$(ls $dir/pids/*.pid 2>/dev/null)
for path in $pid_paths; do
worker=$(basename "$path" .pid)
pid=$(cat "$path")
printf "%-3s %s\n" "$worker" "$pid"
done
exit 0
fi

for worker in $@; do
if [ ! -f "$dir/pids/$worker.pid" ]; then
echo "$worker is not running\n$worker.pid not found in $dir/pids"
case $worker in
all|api|p2p) ;;
*)
echo "$worker is not a standard worker\nstandard workers: all, api, p2p"
;;
esac
exit 1
fi
done

pid_paths=$(
for worker in $@; do
printf "$dir/pids/$worker.pid "
done
)

pids=$(
for path in $pid_paths; do
cat $path
printf ' '
done
)

kill -USR1 $pids &&

cat << EOF
Sent reload signal(s) SIGUSR1 to '$@'
pids: $pids
EOF
14 changes: 7 additions & 7 deletions packages/bitcore-node/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,28 @@ function findConfig(): ConfigType | undefined {
if (bitcoreConfigPath[0] === '~') {
bitcoreConfigPath = bitcoreConfigPath.replace('~', homedir());
}

if (!fs.existsSync(bitcoreConfigPath)) {
throw new Error(`No bitcore config exists at ${bitcoreConfigPath}`);
}

const bitcoreConfigStat = fs.statSync(bitcoreConfigPath);

if (bitcoreConfigStat.isDirectory()) {
if (!fs.existsSync(path.join(bitcoreConfigPath, 'bitcore.config.json'))) {
throw new Error(`No bitcore config exists in directory ${bitcoreConfigPath}`);
}
bitcoreConfigPath = path.join(bitcoreConfigPath, 'bitcore.config.json');
}
logger.info('Using config at: ' + bitcoreConfigPath);

let rawBitcoreConfig;
try {
rawBitcoreConfig = fs.readFileSync(bitcoreConfigPath).toString();
} catch (error) {
throw new Error(`Error in loading bitcore config\nFound file at ${bitcoreConfigPath}\n${error}`);
}

let bitcoreConfig;
try {
bitcoreConfig = JSON.parse(rawBitcoreConfig).bitcoreNode;
Expand Down Expand Up @@ -63,7 +63,7 @@ function setTrustedPeers(config: ConfigType): ConfigType {
}
return config;
}
const Config = function(): ConfigType {
const loadConfig = function(): ConfigType {
let config: ConfigType = {
maxPoolSize: 50,
port: 3000,
Expand Down Expand Up @@ -130,4 +130,4 @@ const Config = function(): ConfigType {
return config;
};

export default Config();
export default loadConfig;
34 changes: 34 additions & 0 deletions packages/bitcore-node/src/modules/bitcoin/p2p.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { StateStorage } from '../../models/state';
import { TransactionStorage } from '../../models/transaction';
import { ChainStateProvider } from '../../providers/chain-state';
import { Libs } from '../../providers/libs';
import { Config } from '../../services/config';
import { BaseP2PWorker } from '../../services/p2p';
import { SpentHeightIndicators } from '../../types/Coin';
import { IUtxoNetworkConfig } from '../../types/Config';
Expand Down Expand Up @@ -80,6 +81,9 @@ export class BitcoinP2PWorker extends BaseP2PWorker<IBtcBlock> {
);
this.events.emit('SYNCDONE');
});
process.on('SIGUSR1', async () => {
await this.reload();
});
}

cacheInv(type: number, hash: string): void {
Expand Down Expand Up @@ -217,6 +221,36 @@ export class BitcoinP2PWorker extends BaseP2PWorker<IBtcBlock> {
}
}

async reload() {
this.chainConfig = Config.chainConfig({ chain: this.chain, network: this.network }) as IUtxoNetworkConfig;
const configPeerUris: string[] = [];

for (const peer of Object.values(this.chainConfig.trustedPeers) as any[]) {
const uri = peer.host + ':' + peer.port;
configPeerUris.push(uri);
const hashes = Object.values(this.pool._addrs).map((a: any) => a.hash);
const addr = this.pool._addAddr({ ip: { v4: peer.host }, port: peer.port });
if (!hashes.includes(addr.hash)) {
Comment on lines +229 to +233
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reload() depends on non-public Pool internals (_addrs and _addAddr), which is brittle and may break on dependency upgrades. Prefer a public peer-management API if available, or encapsulate these internals behind an adapter so the rest of the worker isn’t coupled to private fields.

Copilot uses AI. Check for mistakes.
logger.info(`Adding peer ${uri}`);
}
}

for (const addr of Object.values(this.pool._addrs) as any[]) {
const uri = addr.ip.v4 + ':' + addr.port;
if (!configPeerUris.includes(uri)) {
this.pool._addrs = (this.pool._addrs as any[]).filter(({ hash }) => hash !== addr.hash);
if (this.pool._connectedPeers[addr.hash]) {
logger.info(`Removing peer ${uri}`);
} else {
logger.info(`Removing unconnected peer ${uri}`);
continue;
}
this.pool._connectedPeers[addr.hash].disconnect();
delete this.pool._connectedPeers[addr.hash];
}
};
}

public async getHeaders(candidateHashes: string[]): Promise<BitcoinHeaderObj[]> {
let received = false;
return new Promise<BitcoinHeaderObj[]>(async resolve => {
Expand Down
34 changes: 22 additions & 12 deletions packages/bitcore-node/src/modules/moralis/api/csp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import os from 'os';
import { Web3 } from '@bitpay-labs/crypto-wallet-core';
import { LRUCache } from 'lru-cache';
import request from 'request';
import config from '../../../config';
import { Config } from '../../../../src/services/config';
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import path for Config is incorrect here: src/modules/moralis/api/csp.ts is under src/modules/moralis/api, so ../../../../src/services/config resolves to src/src/services/config and will fail to compile. Import from ../../../services/config.

Suggested change
import { Config } from '../../../../src/services/config';
import { Config } from '../../../services/config';

Copilot uses AI. Check for mistakes.
import logger from '../../../logger';
import { MongoBound } from '../../../models/base';
import { CacheStorage } from '../../../models/cache';
Expand All @@ -21,8 +21,6 @@ import { isDateValid } from '../../../utils';
import { normalizeChainNetwork } from '../../../utils';
import { ReadableWithEventPipe } from '../../../utils/streamWithEventPipe';



export interface MoralisAddressSubscription {
id?: string;
message?: string;
Expand All @@ -32,8 +30,8 @@ export interface MoralisAddressSubscription {
export class MoralisStateProvider extends BaseEVMStateProvider {
baseUrl = 'https://deep-index.moralis.io/api/v2.2';
baseStreamUrl = 'https://api.moralis-streams.com/streams/evm';
apiKey = config.externalProviders?.moralis?.apiKey;
baseWebhookurl = config.externalProviders?.moralis?.webhookBaseUrl;
apiKey = Config.get().externalProviders?.moralis?.apiKey;
baseWebhookurl = Config.get().externalProviders?.moralis?.webhookBaseUrl;
headers = {
'Content-Type': 'application/json',
'X-API-Key': this.apiKey,
Expand All @@ -43,8 +41,20 @@ export class MoralisStateProvider extends BaseEVMStateProvider {

constructor(chain: string) {
super(chain);
this.loadConfig();
}

loadConfig() {
const config = Config.get();
this.apiKey = config.externalProviders?.moralis?.apiKey;
this.baseWebhookurl = config.externalProviders?.moralis?.webhookBaseUrl;
this.headers = {
'Content-Type': 'application/json',
'X-API-Key': this.apiKey,
};
}


// @override
async getBlockBeforeTime(params: GetBlockBeforeTimeParams): Promise<IBlock|null> {
const { chain, network, time } = params;
Expand Down Expand Up @@ -108,7 +118,7 @@ export class MoralisStateProvider extends BaseEVMStateProvider {
const blockRange = await this.getBlocksRange({ ...params, chainId });
const tipHeight = Number(await web3.eth.getBlockNumber());
let isReading = false;

const stream = new ReadableWithEventPipe({
objectMode: true,
async read() {
Expand Down Expand Up @@ -199,7 +209,7 @@ export class MoralisStateProvider extends BaseEVMStateProvider {
}
});
transactionStream = txStream.eventPipe(transactionStream);

// Do not await these promises. They are not critical to the stream.
WalletAddressStorage.updateLastQueryTime({ chain: this.chain, network, address })
.catch(e => logger.warn(`Failed to update ${this.chain}:${network} address lastQueryTime: %o`, e)),
Expand All @@ -224,7 +234,7 @@ export class MoralisStateProvider extends BaseEVMStateProvider {
convertedBlock.nextBlockHash = nextBlock?.hash!;
blocks.push(convertedBlock);
}

const tipHeight = Number(await web3.eth.getBlockNumber());
return { tipHeight, blocks };
}
Expand Down Expand Up @@ -480,10 +490,10 @@ export class MoralisStateProvider extends BaseEVMStateProvider {

/**
* Request wrapper for moralis Streams (subscriptions)
* @param method
* @param url
* @param body
* @returns
* @param method
* @param url
* @param body
* @returns
*/
_subsRequest(method: string, url: string, body?: any) {
return new Promise((resolve, reject) => {
Expand Down
Loading