3 changed files with 305 additions and 0 deletions
@ -0,0 +1,32 @@ |
|||
class Clients { |
|||
constructor() { |
|||
this.network = "bitcoin"; |
|||
this.mainClient = { |
|||
bitcoin: false, |
|||
bitcoinTestnet: false |
|||
}; |
|||
this.peer = { |
|||
bitcoin: { port: 0, host: "", protocol: "" }, |
|||
bitcoinTestnet: { port: 0, host: "", protocol: "" } |
|||
}; |
|||
this.peers = { |
|||
bitcoin: [], |
|||
bitcoinTestnet: [] |
|||
}; |
|||
} |
|||
|
|||
updateNetwork(network) { |
|||
this.network = network; |
|||
} |
|||
|
|||
updateMainClient(mainClient) { |
|||
this.mainClient = mainClient; |
|||
} |
|||
|
|||
updatePeer(peer) { |
|||
this.peer = peer; |
|||
} |
|||
|
|||
} |
|||
|
|||
module.exports = new Clients(); |
@ -0,0 +1,244 @@ |
|||
const ElectrumClient = require("../lib/electrum_client"); |
|||
const clients = require("./clients"); |
|||
|
|||
let electrumKeepAlive = () => null; |
|||
let electrumKeepAliveInterval = 60000; |
|||
|
|||
const getTimeout = ({ arr = undefined, timeout = 1000 } = {}) => { |
|||
try { |
|||
if (arr && Array.isArray(arr)) return arr.length * timeout; |
|||
return timeout; |
|||
} catch { |
|||
return timeout; |
|||
} |
|||
} |
|||
|
|||
const promiseTimeout = (ms, promise) => { |
|||
let id; |
|||
let timeout = new Promise((resolve) => { |
|||
id = setTimeout(() => { |
|||
resolve({ error: true, data: "Timed Out." }); |
|||
}, ms); |
|||
}); |
|||
|
|||
return Promise.race([ |
|||
promise, |
|||
timeout |
|||
]).then((result) => { |
|||
clearTimeout(id); |
|||
try {if ("error" in result && "data" in result) return result;} catch {} |
|||
return { error: false, data: result }; |
|||
}); |
|||
}; |
|||
|
|||
const pauseExecution = (duration = 500) => { |
|||
return new Promise(async (resolve) => { |
|||
try { |
|||
const wait = () => resolve({error: false}); |
|||
await setTimeout(wait, duration); |
|||
} catch (e) { |
|||
console.log(e); |
|||
resolve({error: true}); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
const getDefaultPeers = (network, protocol) => { |
|||
return require("./peers.json")[network].map(peer => { |
|||
try { |
|||
return { ...peer, protocol }; |
|||
} catch {} |
|||
}); |
|||
}; |
|||
|
|||
const pingServer = ({ id = Math.random() } = {}) => { |
|||
const method = "pingServer"; |
|||
return new Promise(async (resolve) => { |
|||
try { |
|||
if (clients.mainClient[clients.network] === false) await connectToRandomPeer(clients.network, clients.peers[clients.network]); |
|||
const { error, data } = await promiseTimeout(getTimeout(), clients.mainClient[clients.network].server_ping()); |
|||
resolve({ id, error, method, data }); |
|||
} catch (e) { |
|||
resolve({ id, error: true, method, data: e }); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
//peers = A list of peers acquired from default electrum servers using the getPeers method.
|
|||
//customPeers = A list of peers added by the user to connect to by default in lieu of the default peer list.
|
|||
const start = ({ id = Math.random(), network = "", peers = [], customPeers = []} = {}) => { |
|||
const method = "connectToPeer"; |
|||
return new Promise(async (resolve) => { |
|||
try { |
|||
if (!network) resolve({error: true, data: {}}); |
|||
//Clear/Remove any previous keep-alive message.
|
|||
try {clearInterval(electrumKeepAlive);} catch {} |
|||
clients.network = network; |
|||
let customPeersLength = 0; |
|||
try {customPeersLength = customPeers.length;} catch {} |
|||
//Attempt to connect to specified peer
|
|||
let connectionResponse = { error: true, data: "" }; |
|||
if (customPeersLength > 0) { |
|||
const { host = "", port = "", protocol = "ssl" } = customPeers[0]; |
|||
connectionResponse = await connectToPeer({ host, port, protocol, network }); |
|||
} else { |
|||
//Attempt to connect to random peer if none specified
|
|||
connectionResponse = await connectToRandomPeer(network, peers); |
|||
} |
|||
resolve({ |
|||
...connectionResponse, |
|||
id, |
|||
method: "connectToPeer", |
|||
customPeers, |
|||
network |
|||
}); |
|||
} catch (e) { |
|||
console.log(e); |
|||
resolve({ error: true, method, data: e }); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
const connectToPeer = ({ port = 50002, host = "", protocol = "ssl", network = "bitcoin" } = {}) => { |
|||
return new Promise(async (resolve) => { |
|||
try { |
|||
clients.network = network; |
|||
let needToConnect = clients.mainClient[network] === false; |
|||
let connectionResponse = { error: false, data: clients.peer[network] }; |
|||
if (!needToConnect) { |
|||
//Ensure the server is still alive
|
|||
const pingResponse = await pingServer(); |
|||
if (pingResponse.error) { |
|||
await disconnectFromPeer({ network }); |
|||
needToConnect = true; |
|||
} |
|||
} |
|||
if (needToConnect) { |
|||
clients.mainClient[network] = new ElectrumClient(port, host, protocol); |
|||
connectionResponse = await promiseTimeout(1000, clients.mainClient[network].connect()); |
|||
if (!connectionResponse.error) { |
|||
try { |
|||
//Clear/Remove Electrum's keep-alive message.
|
|||
clearInterval(electrumKeepAlive); |
|||
//Start Electrum's keep-alive function. It’s sent every minute as a keep-alive message.
|
|||
electrumKeepAlive = setInterval(async () => { |
|||
try {pingServer({ id: Math.random() });} catch {} |
|||
}, electrumKeepAliveInterval); |
|||
} catch (e) {} |
|||
clients.peer[network] = { port, host, protocol }; |
|||
} |
|||
} |
|||
await pauseExecution(); |
|||
resolve(connectionResponse); |
|||
} catch (e) {resolve({ error: true, data: e });} |
|||
}); |
|||
}; |
|||
|
|||
const connectToRandomPeer = async (network, peers = [], protocol = "ssl") => { |
|||
//Peers can be found in peers.json.
|
|||
//Additional Peers can be located here in servers.json & servers_testnet.json for reference: https://github.com/spesmilo/electrum/tree/master/electrum
|
|||
let hasPeers = false; |
|||
try { |
|||
hasPeers = (Array.isArray(peers) && peers.length) || (Array.isArray(clients.peers[network]) && clients.peers[network].length); |
|||
} catch {} |
|||
if (hasPeers) { |
|||
if (Array.isArray(peers) && peers.length) { |
|||
//Update peer list
|
|||
clients.peers[network] = peers; |
|||
} else { |
|||
//Set the saved peer list
|
|||
peers = clients.peers[network]; |
|||
} |
|||
} else { |
|||
//Use the default peer list for a connection if no other peers were passed down and no saved peer list is present.
|
|||
peers = getDefaultPeers(network, protocol); |
|||
} |
|||
const initialPeerLength = peers.length; //Acquire length of our default peers.
|
|||
//Attempt to connect to a random default peer. Continue to iterate through default peers at random if unable to connect.
|
|||
for (let i = 0; i <= initialPeerLength; i++) { |
|||
try { |
|||
const randomIndex = peers.length * Math.random() | 0; |
|||
const peer = peers[randomIndex]; |
|||
let port = "50002"; |
|||
let host = ""; |
|||
if (hasPeers) { |
|||
port = peer.port; |
|||
host = peer.host; |
|||
protocol = peer.protocol; |
|||
} else { |
|||
port = peer[peer.protocol]; |
|||
host = peer.host; |
|||
protocol = peer.protocol; |
|||
} |
|||
const connectionResponse = await connectToPeer({ port, host, protocol, network }); |
|||
if (connectionResponse.error === false && connectionResponse.data) { |
|||
return { |
|||
error: connectionResponse.error, |
|||
method: "connectToRandomPeer", |
|||
data: connectionResponse.data, |
|||
network |
|||
}; |
|||
} else { |
|||
//clients.mainClient[network].close && clients.mainClient[network].close();
|
|||
clients.mainClient[network] = false; |
|||
if (peers.length === 1) { |
|||
return { |
|||
error: true, |
|||
method: "connectToRandomPeer", |
|||
data: connectionResponse.data, |
|||
network |
|||
}; |
|||
} |
|||
peers.splice(randomIndex, 1); |
|||
} |
|||
} catch (e) {console.log(e);} |
|||
} |
|||
return { error: true, method: "connectToRandomPeer", data: "Unable to connect to any peer." }; |
|||
}; |
|||
|
|||
const stop = async ({ network = "" } = {}) => { |
|||
return new Promise(async (resolve) => { |
|||
try { |
|||
//Clear/Remove Electrum's keep-alive message.
|
|||
clearInterval(electrumKeepAlive); |
|||
//Disconnect from peer
|
|||
const response = await disconnectFromPeer({ network }); |
|||
resolve(response); |
|||
} catch (e) { |
|||
resolve({ error: true, data: e }); |
|||
} |
|||
}); |
|||
|
|||
}; |
|||
|
|||
const disconnectFromPeer = async ({ id = Math.random(), network = "" } = {}) => { |
|||
const failure = (data = {}) => { |
|||
return { error: true, id, method: "disconnectFromPeer", data }; |
|||
}; |
|||
try { |
|||
//console.log("Disconnecting from any previous peer...");
|
|||
if (clients.mainClient[network] === false) { |
|||
//No peer to disconnect from...
|
|||
return { |
|||
error: false, |
|||
data: "No peer to disconnect from.", |
|||
id, |
|||
network, |
|||
method: "disconnectFromPeer" |
|||
}; |
|||
} |
|||
//Attempt to disconnect from peer...
|
|||
clients.mainClient[network].close(); |
|||
clients.mainClient[network] = false; |
|||
clients.network = ""; |
|||
await pauseExecution(); |
|||
return { error: false, id, method: "disconnectFromPeer", network, data: "Disconnected..." }; |
|||
} catch (e) { |
|||
failure(e); |
|||
} |
|||
}; |
|||
|
|||
module.exports = { |
|||
start, |
|||
stop |
|||
}; |
@ -0,0 +1,29 @@ |
|||
{ |
|||
"bitcoin": [ |
|||
{"host": "bitcoin.lukechilds.co", "ssl": 50002, "tcp": 50001}, |
|||
{"host": "electrumx.nmdps", "ssl": 50002, "tcp": 50001}, |
|||
{"host": "oneweek.duckdns.org", "ssl": 50002, "tcp": 50001}, |
|||
{"host": "electrum.vom-stausee.de", "ssl": 50002, "tcp": 50001}, |
|||
{"host": "electrum.hsmiths.com", "ssl": 50002, "tcp": 50001}, |
|||
{"host": "electrum.hodlister.co", "ssl": 50002, "tcp": 50001}, |
|||
{"host": "electrum3.hodlister.co", "ssl": 50002, "tcp": 50001}, |
|||
{"host": "btc.usebsv.com", "ssl": 50006, "tcp": 50001}, |
|||
{"host": "fortress.qtornado.com", "ssl": 443, "tcp": 50001}, |
|||
{"host": "ecdsa.net", "ssl": 110, "tcp": 50001}, |
|||
{"host": "electrum.be", "ssl": 50002, "tcp": 50001}, |
|||
{"host": "elex01.blackpole.online", "ssl": 50002, "tcp": 50001}, |
|||
{"host": "kirsche.emzy.de", "ssl": 50002, "tcp": 50001}, |
|||
{"host": "Electrum.hsmiths.com", "ssl": 50002, "tcp": 50001}, |
|||
{"host": "elec.luggs.co", "ssl": 443, "tcp": 50001}, |
|||
{"host": "btc.smsys.me", "ssl": 995, "tcp": 110} |
|||
], |
|||
"bitcoinTestnet": [ |
|||
{"host": "testnet.hsmiths.com", "ssl": 53012, "tcp": 53011}, |
|||
{"host": "testnet1.bauerj.eu", "ssl": 50002, "tcp": 50001}, |
|||
{"host": "tn.not.fyi", "ssl": 55002, "tcp": 55001}, |
|||
{"host": "bitcoin.cluelessperson.com", "ssl": 51002, "tcp": 51001}, |
|||
{"host": "electrum.blockstream.info", "ssl": 60002, "tcp": 60001}, |
|||
{"host": "testnet.aranguren.org", "ssl": 51002, "tcp": 51001}, |
|||
{"host": "blockstream.info", "ssl": 993, "tcp": 143} |
|||
] |
|||
} |
Loading…
Reference in new issue