Skip to content

Commit 66385ce

Browse files
feat(service): support multiple sync nodes
failover p2p service support for clusters of bitcore nodes
1 parent 4f24286 commit 66385ce

File tree

2 files changed

+47
-4
lines changed

2 files changed

+47
-4
lines changed

packages/bitcore-node/src/models/state.ts

+24
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { BaseModel } from './base';
22
import { ObjectID } from 'mongodb';
3+
import os from 'os';
34

45
export type IState = {
56
_id?: ObjectID;
@@ -13,6 +14,29 @@ export class State extends BaseModel<IState> {
1314
allowedPaging = [];
1415

1516
onConnect() {}
17+
18+
async getSingletonState() {
19+
return this.collection.findOneAndUpdate(
20+
{},
21+
{ $setOnInsert: { created: new Date()}},
22+
{ upsert: true }
23+
);
24+
}
25+
26+
async getSyncingNode(params: { chain: string, network: string }): Promise<string> {
27+
const { chain, network } = params;
28+
const state = await this.getSingletonState();
29+
return state.value![`syncingNode:${chain}:${network}`];
30+
}
31+
32+
async selfNominateSyncingNode(params: { chain: string, network: string, lastHeartBeat: any }) {
33+
const { chain, network, lastHeartBeat } = params;
34+
const singleState = await this.getSingletonState();
35+
this.collection.findOneAndUpdate(
36+
{ _id: singleState.value!._id, $or: [{ [`syncingNode:${chain}:${network}`]: { $exists: false } }, { [`syncingNode:${chain}:${network}`]: lastHeartBeat }]},
37+
{ $set: { [`syncingNode:${chain}:${network}`]: `${os.hostname}:${process.pid}:${Date.now()}` } }
38+
);
39+
}
1640
}
1741

1842
export let StateModel = new State();

packages/bitcore-node/src/services/p2p.ts

+23-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { TransactionModel } from '../models/transaction';
77
import { Bitcoin } from '../types/namespaces/Bitcoin';
88
import { StateModel } from '../models/state';
99
import { SpentHeightIndicators } from '../types/Coin';
10+
import os from 'os';
1011
const Chain = require('../chain');
1112
const LRU = require('lru-cache');
1213

@@ -22,6 +23,7 @@ export class P2pService {
2223
private pool: any;
2324
private invCache: any;
2425
private initialSyncComplete: boolean;
26+
private isSyncingNode: boolean;
2527
constructor(params) {
2628
const { chain, network, chainConfig } = params;
2729
this.chain = chain;
@@ -32,6 +34,7 @@ export class P2pService {
3234
this.events = new EventEmitter();
3335
this.syncing = false;
3436
this.initialSyncComplete = false;
37+
this.isSyncingNode = false;
3538
this.invCache = new LRU({ max: 10000 });
3639
this.messages = new this.bitcoreP2p.Messages({
3740
network: this.bitcoreLib.Networks.get(this.network)
@@ -76,7 +79,7 @@ export class P2pService {
7679
network: this.network,
7780
hash
7881
});
79-
if (!this.invCache.get(hash)) {
82+
if (this.isSyncingNode && !this.invCache.get(hash)) {
8083
this.processTransaction(message.transaction);
8184
this.events.emit('transaction', message.transaction);
8285
}
@@ -93,7 +96,7 @@ export class P2pService {
9396
hash
9497
});
9598

96-
if (!this.invCache.get(hash)) {
99+
if (this.isSyncingNode && !this.invCache.get(hash)) {
97100
this.invCache.set(hash);
98101
this.events.emit(hash, message.block);
99102
this.events.emit('block', message.block);
@@ -112,7 +115,7 @@ export class P2pService {
112115
});
113116

114117
this.pool.on('peerinv', (peer, message) => {
115-
if (!this.syncing) {
118+
if (this.isSyncingNode && !this.syncing) {
116119
const filtered = message.inventory.filter(inv => {
117120
const hash = this.bitcoreLib.encoding
118121
.BufferReader(inv.hash)
@@ -304,6 +307,22 @@ export class P2pService {
304307
logger.debug(`Started worker for chain ${this.chain}`);
305308
this.setupListeners();
306309
await this.connect();
307-
this.sync();
310+
setInterval(async () => {
311+
const syncingNode = await StateModel.getSyncingNode({chain: this.chain, network: this.network});
312+
const [hostname, pid] = syncingNode.split(':');
313+
const amSyncingNode = (hostname === os.hostname() && pid === process.pid.toString());
314+
if (amSyncingNode) {
315+
StateModel.selfNominateSyncingNode({ chain: this.chain, network: this.network, lastHeartBeat: syncingNode });
316+
if (!this.isSyncingNode) {
317+
logger.info(`This worker is now the syncing node for ${this.chain} ${this.network}`);
318+
this.isSyncingNode = true;
319+
this.sync();
320+
}
321+
} else {
322+
setTimeout(() => {
323+
StateModel.selfNominateSyncingNode({ chain: this.chain, network: this.network, lastHeartBeat: syncingNode });
324+
}, 5000)
325+
}
326+
}, 1000);
308327
}
309328
}

0 commit comments

Comments
 (0)