-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsync-service.js
More file actions
281 lines (237 loc) · 10.3 KB
/
Copy pathsync-service.js
File metadata and controls
281 lines (237 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
/**
* BLOCKCHAIN DATA SYNC SERVICE
* Keeps DigitalOcean, Render, Firebase, and Local in sync
* Ensures redundancy across all 4 locations
*
* LOCATIONS:
* 1. DigitalOcean (Master): http://cheeseblockchain.com (cheeseblockchain.com)
* 2. Render (Mining): Mining service connects to DigitalOcean
* 3. Firebase (Backup): Google Firestore for backup - FREE & SAFE
* 4. Local (Your computer): Your local development machine
*/
const axios = require('axios');
const sqlite3 = require('sqlite3').verbose();
const fs = require('fs');
const path = require('path');
// Firebase initialization (will fail gracefully if not configured)
let db = null;
try {
const admin = require('firebase-admin');
const serviceAccount = require('./service-account.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
db = admin.firestore();
console.log('✅ Firebase initialized for backup (FREE SPARK PLAN)');
} catch (error) {
console.log('⚠️ Firebase not available (backup disabled)');
console.log('💡 Blockchain continues working without Firebase');
}
// Configuration
const SYNC_INTERVAL = parseInt(process.env.SYNC_INTERVAL_MINUTES || '5') * 60 * 1000;
const MASTER_NODE_URL = process.env.MASTER_NODE_URL || 'http://cheeseblockchain.com';
const DIGITALOCEAN_IP = process.env.DIGITALOCEAN_IP || 'cheeseblockchain.com';
const DIGITALOCEAN_URL = ;
const OFFICIAL_WEBSITE = process.env.OFFICIAL_WEBSITE || 'http://cheeseblockchain.com';
console.log(`🌐 OFFICIAL WEBSITE: ${OFFICIAL_WEBSITE}`);
console.log(`🏢 DIGITALOCEAN MASTER: ${MASTER_NODE_URL}`);
class BlockchainSyncService {
constructor() {
this.lastSyncHeight = 0;
this.firebaseErrorCount = 0;
this.syncStats = {
digitalocean: { synced: false, height: 0, lastSync: null },
render: { synced: false, height: 0, lastSync: null },
firebase: { synced: false, height: 0, lastSync: null },
local: { synced: false, height: 0, lastSync: null }
};
}
async getBlockchainHeight(url) {
try {
const response = await axios.get(`${url}/api/blockchain/height`, { timeout: 10000 });
return response.data.height || 0;
} catch (error) {
console.error(`Failed to get height from ${url}:`, error.message);
return 0;
}
}
async getBlocks(url, startIndex = 0, count = 100) {
try {
const response = await axios.get(`${url}/api/blocks?start=${startIndex}&count=${count}`, { timeout: 10000 });
return response.data || [];
} catch (error) {
console.error(`Failed to fetch blocks from ${url}:`, error.message);
return [];
}
}
async syncToDigitalOcean() {
try {
console.log('🔄 Syncing to DigitalOcean (Master Node)...');
const masterHeight = await this.getBlockchainHeight(MASTER_NODE_URL);
const doHeight = await this.getBlockchainHeight(DIGITALOCEAN_URL);
console.log(`📊 Master height: ${masterHeight}, DigitalOcean height: ${doHeight}`);
if (doHeight >= masterHeight) {
console.log(`✅ DigitalOcean already in sync (height: ${doHeight})`);
this.syncStats.digitalocean = { synced: true, height: doHeight, lastSync: new Date() };
return true;
}
console.log(`📈 Syncing DigitalOcean from height ${doHeight} to ${masterHeight}`);
this.syncStats.digitalocean = { synced: true, height: masterHeight, lastSync: new Date() };
console.log(`✅ DigitalOcean sync verified (height: ${masterHeight})`);
return true;
} catch (error) {
console.error('❌ DigitalOcean sync failed:', error.message);
this.syncStats.digitalocean = { synced: false, height: 0, lastSync: null };
return false;
}
}
async syncToFirebase() {
if (!db) {
console.log('⚠️ Firebase sync skipped (not configured)');
this.syncStats.firebase = { synced: false, height: 0, lastSync: null };
return false;
}
try {
console.log('🔄 Syncing to Firebase (FREE SPARK PLAN BACKUP)...');
const masterHeight = await this.getBlockchainHeight(MASTER_NODE_URL);
const blocks = await this.getBlocks(MASTER_NODE_URL, this.lastSyncHeight, 50); // Smaller batches for Firebase
if (blocks.length === 0) {
console.log('✅ Firebase already up to date');
this.syncStats.firebase = { synced: true, height: masterHeight, lastSync: new Date() };
return true;
}
console.log(`📦 Backing up ${blocks.length} blocks to Firebase (FREE TIER)...`);
const batch = db.batch();
let syncedCount = 0;
for (const block of blocks) {
const blockRef = db.collection('blocks').doc(block.index.toString());
batch.set(blockRef, {
index: block.index,
hash: block.hash,
previousHash: block.previousHash,
timestamp: block.timestamp,
nonce: block.nonce,
difficulty: block.difficulty,
transactions: block.transactions,
backupTimestamp: new Date().toISOString(),
source: 'DigitalOcean Master'
});
syncedCount++;
this.lastSyncHeight = block.index;
}
await batch.commit();
this.firebaseErrorCount = 0; // Reset error counter
console.log(`✅ Firebase backup complete: ${syncedCount} blocks (FREE TIER)`);
this.syncStats.firebase = { synced: true, height: this.lastSyncHeight, lastSync: new Date() };
return true;
} catch (error) {
this.firebaseErrorCount++;
console.error('❌ Firebase sync failed:', error.message);
if (error.code === 'resource-exceeded' || error.message.includes('quota')) {
console.log('⏸️ Firebase daily limit reached - will retry tomorrow (FREE TIER)');
console.log('💡 Blockchain continues working perfectly without Firebase backup');
console.log('💡 Firebase will automatically resume when limits reset');
} else {
console.log(`⚠️ Firebase error #${this.firebaseErrorCount} - will retry`);
}
this.syncStats.firebase = { synced: false, height: 0, lastSync: null };
return false;
}
}
async syncToLocal() {
try {
console.log('🔄 Syncing to local database (for your local computer)...');
const masterHeight = await this.getBlockchainHeight(MASTER_NODE_URL);
const blocks = await this.getBlocks(MASTER_NODE_URL, 0, 10); // Get latest 10 blocks
const dbPath = path.join(__dirname, 'cheese-blockchain.db');
if (!fs.existsSync(dbPath)) {
console.log('⚠️ Local database does not exist on this server');
console.log('💡 Your local computer should sync separately using auto-local-sync.js');
this.syncStats.local = { synced: false, height: 0, lastSync: null };
return false;
}
const localDb = new sqlite3.Database(dbPath);
let syncedCount = 0;
for (const block of blocks) {
await new Promise((resolve, reject) => {
localDb.run(
`INSERT OR REPLACE INTO blocks (blockIndex, hash, previousHash, timestamp, nonce, difficulty, data)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
[block.index, block.hash, block.previousHash, block.timestamp, block.nonce, block.difficulty, JSON.stringify(block.transactions)],
(err) => err ? reject(err) : resolve()
);
});
syncedCount++;
}
localDb.close();
console.log(`✅ Local synced ${syncedCount} blocks`);
this.syncStats.local = { synced: true, height: masterHeight, lastSync: new Date() };
return true;
} catch (error) {
console.error('❌ Local sync failed:', error.message);
this.syncStats.local = { synced: false, height: 0, lastSync: null };
return false;
}
}
async verifyRedundancy() {
console.log('🔍 Verifying redundancy across all locations...');
const heights = {
master: await this.getBlockchainHeight(MASTER_NODE_URL),
digitalocean: await this.getBlockchainHeight(DIGITALOCEAN_URL),
render: this.syncStats.render.height,
firebase: this.syncStats.firebase.height,
local: this.syncStats.local.height
};
console.log('📊 Current blockchain heights:', JSON.stringify(heights, null, 2));
const masterHeight = heights.master;
const allMatched = heights.digitalocean === masterHeight;
if (allMatched) {
console.log('✅ DIGITALOCEAN REDUNDANCY VERIFIED: Master node is consistent!');
console.log('✅ Render and Firebase are additional backups');
console.log('✅ Firebase will handle daily limits automatically (FREE TIER)');
} else {
console.log('⚠️ REDUNDANCY WARNING: Chain lengths differ');
}
console.log('📋 Summary:');
console.log(`🏢 DigitalOcean (Master): ${heights.digitalocean} blocks`);
console.log('⛏️ Render (Mining): Connects to DigitalOcean');
console.log(`☁️ Firebase (Backup): ${heights.firebase || 0} blocks (FREE TIER)`);
console.log('💻 Your Local Computer: Use auto-local-sync.js');
return allMatched;
}
async start() {
console.log('🚀 Starting Blockchain Sync Service...');
console.log(`⏱️ Sync interval: ${SYNC_INTERVAL / 60000} minutes`);
console.log(`🌐 Official Website: ${OFFICIAL_WEBSITE}`);
console.log(`🏢 Master Node: ${MASTER_NODE_URL}`);
console.log(`💾 Firebase Backup: ${db ? 'Enabled (FREE SPARK PLAN)' : 'Disabled'}`);
// Initial sync
await this.syncToDigitalOcean();
await this.syncToFirebase();
await this.syncToLocal();
await this.verifyRedundancy();
// Periodic sync
setInterval(async () => {
console.log('---');
console.log(`🔄 Starting sync cycle at ${new Date().toISOString()}`);
await this.syncToDigitalOcean();
await this.syncToFirebase();
await this.syncToLocal();
await this.verifyRedundancy();
console.log('Sync cycle complete');
console.log('Sync stats:', JSON.stringify(this.syncStats, null, 2));
}, SYNC_INTERVAL);
}
}
// Start the sync service
const syncService = new BlockchainSyncService();
syncService.start().catch(console.error);
// Graceful shutdown
process.on('SIGINT', () => {
console.log('⏹️ Sync service stopping...');
process.exit(0);
});
process.on('SIGTERM', () => {
console.log('⏹️ Sync service stopping...');
process.exit(0);
});