You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

244 lines
7.4 KiB

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
};