Skip to content

Instantly share code, notes, and snippets.

@LNBIG-COM
Last active June 5, 2019 09:59
Show Gist options
  • Save LNBIG-COM/441a8dea5b266100c74e79630b5f97bf to your computer and use it in GitHub Desktop.
Save LNBIG-COM/441a8dea5b266100c74e79630b5f97bf to your computer and use it in GitHub Desktop.
My autopilot code (sorry for russian language comments) - as example
// Должен быть первым - загружает переменные
require('dotenv').config()
var program = require('commander')
var _ = require('lodash');
program
.version('0.1.0')
.option('-n, --dry-run', 'Проверочный запуск без действий')
.parse(process.argv);
let listChannels = require('../lib/listChannels')
let listPeers = require('../lib/listPeers')
let pendingChannels = require('../lib/pendingChannels')
let describeGraph = require('../lib/describeGraph')
let getInfo = require('../lib/getInfo')
var PromisePool = require('es6-promise-pool')
let Long = require('long')
const MIN_CHAN_VALUE = 4000000;
const MAX_CHAN_VALUE = 2**24 - 1;
const MIN_SATOSHI_SENT = 100000;
process.umask(0o77);
const nodeStorage = require('../global/nodeStorage');
const debug = require('debug')('lnbig:dwoc')
let
myNodes = {},
openNodes = {},
//openedChannelByNodeID = {},
pendingChannelByNodeID = {},
nodeAddresses = {},
connectionNodes = {} // Для нод, которые не имеют публичного IP или Tor - сюда заносим наши сервера, где есть с ними коннект
let $listChannels, $pendingChannels, $describeGraph, $getInfo, currentBlock = 0
if (process.env.CRYPT_PASSWORD) {
// The password for crypted macaroon files in env settings (.env file for example)
main(process.env.CRYPT_PASSWORD)
} else {
// Or prompt the password from terminal
var read = require("read");
read(
{
prompt: 'Password: ',
silent: true,
replace: '*',
terminal: true
},
(error, password) => {
if (error)
throw new Error(error);
main(password);
}
)
}
async function main(password) {
// To create object for node storage
// load node storage data included crypted macaroon files, and decrypt macaroon files by password. After the password to be cleaned from memory
await nodeStorage.init(require('../global/nodesInfo'), password);
let key
for (key in nodeStorage.nodes)
myNodes[nodeStorage.nodes[key].pubKey] = key
debug("Мои ноды: %o", myNodes)
// To connect to nodes
await nodeStorage.connect({longsAsNumbers: false});
debug('Запускаются асинхронные команды listChannels...')
$listChannels = listChannels(nodeStorage)
$listPeers = listPeers(nodeStorage)
$pendingChannels = pendingChannels(nodeStorage)
$describeGraph = describeGraph(nodeStorage)
$getInfo = getInfo(nodeStorage)
debug('Ожидается завершение асинхронных команд listChannels...')
$listChannels = await $listChannels
$listPeers = await $listPeers
$pendingChannels = await $pendingChannels
$describeGraph = await $describeGraph
$getInfo = await $getInfo
debug('Данные получены полностью, обработка')
for (key in nodeStorage.nodes) {
if (nodeStorage.nodes[key].client) {
defineAddresses($describeGraph[key]);
}
}
for (key in nodeStorage.nodes) {
if (nodeStorage.nodes[key].client) {
defineConnectionNodes(key, $listPeers[key]);
}
}
for (key in nodeStorage.nodes) {
if (nodeStorage.nodes[key].client)
calculateByNodeID(key, $listChannels[key], $pendingChannels[key])
}
openNewChannels()
}
function* openChannelPromise() {
// Проходим по каналам и собираем информацию для корректировки
let openChannelData = []
for (let key in $listChannels) {
for (let channel of $listChannels[key].channels) {
if ( ! channel.private
&& ! myNodes.hasOwnProperty(channel.remote_pubkey)
)
{
// Внешняя нода и в неё было отправлено какое-то количество сатоши
debug("Анализируем канал: %o", channel)
let item = openNodes[channel.remote_pubkey] = openNodes[channel.remote_pubkey]
||
{
capacityAmnt: 0,
capacitySum: 0,
weAreInitiator: 0,
amountChannels: 0,
isPendingHTLC: false, // Если есть хотя бы один зависший HTLC - не будем создавать канал
pubKey: channel.remote_pubkey,
myNodes: {},
type: {
NotActive: 0,
Vacuum: 0,
Vampire: 0,
NewOpened: 0,
Router: 0,
Pending: pendingChannelByNodeID[channel.remote_pubkey] ? Object.keys(pendingChannelByNodeID[channel.remote_pubkey]).length : 0
},
}
item.blockHeight = Long.fromString(channel.chan_id, true)
item.chan_id = Long.fromString(channel.chan_id, true)
item.blockHeight = item.blockHeight.shru(40).toNumber()
if (! channel.active)
item.type.NotActive++
if (channel.initiator)
item.weAreInitiator++
if (channel.initiator && Number(channel.total_satoshis_sent) >= MIN_SATOSHI_SENT && Number(channel.total_satoshis_sent) === Number(channel.remote_balance)) {
// Канал открыли мы и он был использован только для передачи в одну сторону
item.type.Vacuum++
}
if (channel.initiator && Number(channel.total_satoshis_sent) === 0 && Number(channel.local_balance) > 0) {
if ((currentBlock - item.blockHeight) >= 144 * 14)
// Канал открыли мы, ему больше двух недель и он не был использован
item.type.Vampire++
else
// Канал открыли мы, но менее двух недель назад - рано делать выводы...
item.type.NewOpened++
}
if (Number(channel.local_balance) >= MIN_SATOSHI_SENT && Number(channel.remote_balance) >= MIN_SATOSHI_SENT && Number(channel.total_satoshis_sent) >= MIN_SATOSHI_SENT && Number(channel.total_satoshis_received) >= MIN_SATOSHI_SENT) {
// Канал похож на роутинговый узел, передающий в оба направления
item.type.Router++
}
channel.isPendingHTLC = channel.isPendingHTLC || ! ! channel.pending_htlcs.length
item.amountChannels++
item.capacitySum += Number(channel.total_satoshis_sent)
item.capacityAmnt++
item.myNodes[key] = {key: key, total_satoshis_sent: channel.total_satoshis_sent}
debug("Для канала получили данные: %o", item)
}
}
}
for (let pubKey in openNodes) {
let item = openNodes[pubKey]
item.capacityAverage = item.capacitySum / item.capacityAmnt
debug("Данные узла: %o", item)
// Сначала определяем тип ноды и отвечаем на вопрос: надо ли нам создавать там канал
let toOpenChannel = false
if (item.type.Vampire > 0) {
// С нодой есть вампир каналы
if (item.type.Vacuum > 0)
console.log("Vampire/Vacuum/NewOpened/Pending (%d/%d/%d/%d): %s", item.type.Vampire, item.type.Vacuum, item.type.NewOpened, item.type.Pending, pubKey)
else
console.log("Vampire/NewOpened/Pending (%d/%d/%d): %s", item.type.Vampire, item.type.NewOpened, item.type.Pending, pubKey)
}
else if (item.type.Vacuum > 0) {
if (item.type.Router > 0)
console.log("Vacuum/Router/NewOpened/Pending (%d/%d/%d/%d): %s", item.type.Vacuum, item.type.Router, item.type.NewOpened, item.type.Pending, pubKey)
else
console.log("Vacuum/NewOpened/Pending (%d/%d/%d): %s", item.type.Vacuum, item.type.NewOpened, item.type.Pending, pubKey)
toOpenChannel = item.capacitySum >= MIN_SATOSHI_SENT && item.type.NewOpened === 0 && item.type.Pending === 0 && item.type.NotActive === 0
}
else if (item.type.Router > 0) {
console.log("Router/NewOpened/Pending (%d/%d/%d): %s", item.type.Router, item.type.NewOpened, item.type.Pending, pubKey)
toOpenChannel = item.capacitySum >= MIN_SATOSHI_SENT && item.type.NewOpened === 0 && item.type.Pending === 0 && item.type.NotActive === 0
}
if (toOpenChannel) {
let data
if (nodeAddresses[pubKey]) {
// У неё есть IP address
let where = Object.keys(nodeStorage.nodes).filter(val => ! (pendingChannelByNodeID[pubKey] && pendingChannelByNodeID[pubKey][val]))
if (where.length)
data = {
where: where,
pubKey: pubKey,
address: nodeAddresses[pubKey],
amount: Math.round(item.capacitySum * 2)
}
}
else if (connectionNodes[pubKey]) {
data = {
where: connectionNodes[pubKey],
pubKey: pubKey,
address: null,
amount: Math.round(item.capacitySum * 2)
}
}
else {
console.log("Хочется открыть канал с нодой %s, но публичного IP4 у неё нет :(", pubKey)
}
if (data) {
if (data.amount < MIN_CHAN_VALUE)
data.amount = MIN_CHAN_VALUE
else if (data.amount > MAX_CHAN_VALUE)
data.amount = MAX_CHAN_VALUE
debug("Будет открыт канал на нодах %o с remote %s на сумму %d", data.where, data.pubKey, data.amount)
openChannelData.push(data)
}
}
}
openChannelData = _.shuffle(openChannelData)
console.log("Будет открыто %d каналов на сумму: %d BTC", openChannelData.length, openChannelData.reduce((acc,val) => {return acc + val.amount}, 0)/1E8)
for (let item of openChannelData) {
debug("Открытие канала %o", item)
if (program.dryRun)
yield Promise.resolve(1)
else
yield openChannelWithNodes(item.pubKey, item.where, item.address, item.amount)
}
}
async function openNewChannels() {
// The number of promises to process simultaneously.
let concurrency = 100
// Create a pool.
let pool = new PromisePool(openChannelPromise(), concurrency)
pool.addEventListener('fulfilled', function (event) {
// The event contains:
// - target: the PromisePool itself
// - data:
// - promise: the Promise that got fulfilled
// - result: the result of that Promise
//console.log('update policy: result: %o', event.data.result)
})
pool.addEventListener('rejected', function (event) {
// The event contains:
// - target: the PromisePool itself
// - data:
// - promise: the Promise that got rejected
// - error: the Error for the rejection
console.log('update policy - ОШИБКА: error: %o: ', event.data.error.message)
})
console.log(`Запускается update каналов (в параллель: ${concurrency})`)
// Start the pool.
let poolPromise = pool.start()
// Wait for the pool to settle.
await poolPromise
console.log('Всё завершено успешно')
}
function calculateByNodeID(key, listChannels, pendingChannels) {
let channel
currentBlock = Math.max(currentBlock, Number($getInfo[key].block_height))
// // Собираем статистику по каналам, которые уже есть и с теми условиями, с которыми нам надо
// // В данном случае - учитываем те каналы, где есть средства с нашей стороны
// for (channel of listChannels.channels) {
// if (channel.local_balance > 0) {
// openedChannelByNodeID[channel.remote_pubkey] = openedChannelByNodeID[channel.remote_pubkey] || {}
// openedChannelByNodeID[channel.remote_pubkey][key] = Math.max(openedChannelByNodeID[channel.remote_pubkey][key] || 0, channel.local_balance)
// }
// }
for (channel of pendingChannels.pending_open_channels) {
if (channel.channel.local_balance > 0) {
pendingChannelByNodeID[channel.channel.remote_node_pub] = pendingChannelByNodeID[channel.channel.remote_node_pub] || {}
pendingChannelByNodeID[channel.channel.remote_node_pub][key] = Math.max(pendingChannelByNodeID[channel.channel.remote_node_pub][key] || 0, channel.channel.local_balance)
}
}
for (channel of pendingChannels.pending_closing_channels) {
if (channel.channel.local_balance > 0) {
pendingChannelByNodeID[channel.channel.remote_node_pub] = pendingChannelByNodeID[channel.channel.remote_node_pub] || {}
pendingChannelByNodeID[channel.channel.remote_node_pub][key] = Math.max(pendingChannelByNodeID[channel.channel.remote_node_pub][key] || 0, channel.channel.local_balance)
}
}
for (channel of pendingChannels.pending_force_closing_channels) {
if (channel.channel.local_balance > 0) {
pendingChannelByNodeID[channel.channel.remote_node_pub] = pendingChannelByNodeID[channel.channel.remote_node_pub] || {}
pendingChannelByNodeID[channel.channel.remote_node_pub][key] = Math.max(pendingChannelByNodeID[channel.channel.remote_node_pub][key] || 0, channel.channel.local_balance)
}
}
for (channel of pendingChannels.waiting_close_channels) {
if (channel.channel.local_balance > 0) {
pendingChannelByNodeID[channel.channel.remote_node_pub] = pendingChannelByNodeID[channel.channel.remote_node_pub] || {}
pendingChannelByNodeID[channel.channel.remote_node_pub][key] = Math.max(pendingChannelByNodeID[channel.channel.remote_node_pub][key] || 0, channel.channel.local_balance)
}
}
}
async function openChannelWithNodes(pubKey, whereOpenArray, address, amnt) {
let myNode
for (let whereOpen of _.shuffle(whereOpenArray)) {
try {
myNode = nodeStorage.nodes[whereOpen]
let connected = false
try {
if (address) {
console.log("Коннект (%s) на ноду (%s @ %s) для открытия канала", address, pubKey, whereOpen)
let connect_res = await myNode.client.connectPeer({addr: {pubkey: pubKey, host: address}, perm: false})
debug("openChannelWithNodes(%s): результат коннекта канала: %o", pubKey, connect_res)
connected = true
}
else {
// address === null: там уже есть коннект с нодой whereOpen, а публичного адреса нет
console.log("На ноду %s не коннектимся, так как публичного IP у неё нет, но у нас с ней есть коннект на %s", pubKey, whereOpen)
connected = true
}
}
catch (e) {
if (/already connected to peer/.test(e.message))
connected = true
console.log("openChannelWithNodes(%s): коннект (%s) НЕУДАЧЕН (%s) - %s...", pubKey, address, e.message, connected ? 'уже подключены - попробуем создать канал' : 'игнорируем эту ноду')
if (/connection timed out/.test(e.message))
return null
if (/connection refused/.test(e.message))
return null
}
if (connected) {
try {
let res = await myNode.client.openChannelSync({
//node_pubkey: Buffer.from(pubKey, 'hex'),
node_pubkey_string: pubKey,
local_funding_amount: amnt,
push_sat: 0,
target_conf: 12,
private: false,
remote_csv_delay: 40,
min_htlc_msat: 1,
min_confs: 0,
spend_unconfirmed: true
})
debug("openChannelWithNodes(%s): результат открытия канала: %o", pubKey, res)
console.log("Канал (%s <--> %s / %d sats) успешно открыт!", whereOpen, pubKey, amnt)
return res
}
catch (e) {
let res
if ((res = /chan size of ([\d\.]+) BTC is below min chan size of ([\d\.]+) BTC/.exec(e.message)) !== null) {
// Удалённый узел требует минимального размера канала - пробуем удовлетворить его просьбу
console.log("Удалённый узел требует минимального размера канала (%d BTC) - пробуем удовлетворить его просьбу", res[2])
amnt = Number(res[2]) * 100000000
if (amnt < MIN_CHAN_VALUE)
amnt = MIN_CHAN_VALUE
else if (amnt > MAX_CHAN_VALUE)
amnt = MAX_CHAN_VALUE
try {
let res = await myNode.client.openChannelSync({
//node_pubkey: Buffer.from(pubKey, 'hex'),
node_pubkey_string: pubKey,
local_funding_amount: amnt,
push_sat: 0,
target_conf: 12,
private: false,
remote_csv_delay: 40,
min_htlc_msat: 1,
min_confs: 0,
spend_unconfirmed: true
})
debug("openChannelWithNodes(%s): результат открытия канала: %o", pubKey, res)
console.log("Канал (%s <--> %s / %d sats) успешно открыт!", whereOpen, pubKey, amnt)
return res
}
catch (e) {
console.log("openChannelWithNodes(%s): ошибка (%s) открытия канала (коннект есть), возможно кончились средства (%s)", pubKey, e.message, whereOpen)
continue
}
}
else {
if (/Multiple channels unsupported/.test(e.message)) {
// Достаточно продолжить открытие на другом узле
console.log("Узел %s не поддерживает несколько каналов с одной нодой (%s) - пробуем дальше", pubKey, whereOpen)
}
else {
console.log("openChannelWithNodes(%s): ошибка (%s) открытия канала (коннект есть), возможно кончились средства (%s)", pubKey, e.message, whereOpen)
}
}
continue
}
}
else {
continue
}
}
catch (e) {
console.log("Пойманная ошибка openChannelWithNodes(%s) @ %s: %o", pubKey, myNode.key, e)
throw Error(`Ошибка openChannelWithNodes(${pubKey}): ${e.message}, ${e.stack}`)
}
}
return null
}
function defineAddresses(describeGraph) {
for(let node of describeGraph.nodes) {
if ( ! nodeAddresses.hasOwnProperty(node.pub_key)) {
let addr = node.addresses.filter( val => val.network == 'tcp' && /^\d+\.\d+\.\d+\.\d+:\d+$/.test(val.addr) )[0]
if (addr) {
nodeAddresses[node.pub_key] = addr.addr
}
}
}
}
function defineConnectionNodes(key, listPeers) {
for(let peer of listPeers.peers) {
if (! nodeAddresses.hasOwnProperty(peer.pub_key)) {
console.log("Узел %s не имеет публичного IP, но он имеет коннект на %s - будем в том числе там создавать канал", peer.pub_key, key)
connectionNodes[peer.pub_key] = connectionNodes[peer.pub_key] || []
connectionNodes[peer.pub_key].push(key)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment