Created
October 30, 2019 18:04
-
-
Save LNBIG-COM/f737fca4f3e28044e1d853072c754756 to your computer and use it in GitHub Desktop.
It's main code (w/o other submodules) for rebalancing of channels between LNBIG nodes (50% / 50%)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (c) 2019 LNBIG.com | |
* All rights reserved. | |
*/ | |
// Должен быть первым - загружает переменные | |
require('dotenv').config() | |
let program = require('commander') | |
var PromisePool = require('es6-promise-pool') | |
var _ = require('lodash') | |
const listChannels = require('../lib/listChannels') | |
//const describeGraph = require('../lib/describeGraph') | |
const getInfo = require('../lib/getInfo') | |
const MAX_SATOSHIS_PER_TRANSACTION = 4294967 | |
process.umask(0o77); | |
const nodeStorage = require('../global/nodeStorage'); | |
const debug = require('debug')('lnbig:paidRebalance') | |
let | |
myNodes = {} | |
let $listChannels, | |
//$describeGraph, | |
$getInfo, | |
successfulAmountOurRebalanced = 0, | |
errorAmountOurRebalanced = 0 | |
program | |
.version('0.1.0') | |
program | |
.option('--our-nodes', 'Ребаланс только между нашими узлами') | |
.option('-n, --dry-run', 'Проверочный запуск без действий для открытия каналов') | |
program | |
.parse(process.argv); | |
main() | |
.then( () => { | |
console.log("Все задачи выполнены") | |
process.exit(0) | |
}) | |
.catch( (e) => { | |
console.error("ERROR: %s\n%s", e.message, e.stack) | |
process.exit(1) | |
}) | |
async function main () { | |
if (process.env.CRYPT_PASSWORD) { | |
// The password for crypted macaroon files in env settings (.env file for example) | |
await _main(process.env.CRYPT_PASSWORD) | |
} else { | |
// Or prompt the password from terminal | |
var read = require("read"); | |
read( | |
{ | |
prompt: 'Password: ', | |
silent: true, | |
replace: '*', | |
terminal: true | |
}, | |
async (error, password) => { | |
if (error) | |
throw new Error(error); | |
await _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) | |
//$describeGraph = describeGraph(nodeStorage) | |
$getInfo = getInfo(nodeStorage) | |
debug('Ожидается завершение асинхронных команд listChannels...') | |
$listChannels = await $listChannels | |
//$describeGraph = await $describeGraph | |
$getInfo = await $getInfo | |
debug('Данные получены полностью, обработка') | |
if (program.ourNodes) { | |
await rebalanceBetweenOurNodes() | |
} | |
} | |
function* rebalanceBetweenOurNodePromise() { | |
// Проходим по каналам и собираем информацию для корректировки | |
let rebalanceCommands = [] | |
for (let key in nodeStorage.nodes) { | |
if (nodeStorage.nodes[key].client) | |
findChannelsBetweenOurNodes(key, rebalanceCommands) | |
} | |
for (let command of _.shuffle(rebalanceCommands)) { | |
yield rebalanceOneChannel(command) | |
} | |
} | |
async function rebalanceOneChannel(command) { | |
if (! program.dryRun) { | |
debug(`rebalanceOneChannel: начало ребаланса канала, команда: %o`, command) | |
let res = await nodeStorage.nodes[command.invoiceFrom.key].client.addInvoice({ | |
memo: `Rebalance from ${command.payWho.key} to ${command.invoiceFrom.key} ${command.amount} sats through ${command.chanId} channel`, | |
value: command.amount, | |
}) | |
command.decodedPayReq = await nodeStorage.nodes[command.payWho.key].client.decodePayReq({pay_req: res.payment_request}) | |
debug("Результат создания инвойса: %o (команда %o)", res, command) | |
let resPayment = await nodeStorage.nodes[command.payWho.key].client.sendPaymentSync({ | |
dest_string: command.decodedPayReq.destination, | |
payment_hash_string: command.decodedPayReq.payment_hash, | |
amt: command.decodedPayReq.num_satoshis, | |
final_cltv_delta: command.decodedPayReq.cltv_expiry, | |
fee_limit: {fixed: 0}, | |
outgoing_chan_id: command.chanId, | |
}) | |
debug("Результат оплаты канала: %o", resPayment) | |
if (resPayment.payment_error !== '') { | |
console.warn("Ошибка оплаты инвойса: %o", resPayment) | |
errorAmountOurRebalanced++ | |
} | |
else | |
successfulAmountOurRebalanced++ | |
} | |
else { | |
console.log(`Эмуляция ребалансировки канала: from ${command.payWho.key} to ${command.invoiceFrom.key} ${command.amount} sats through ${command.chanId} channel`) | |
} | |
} | |
async function rebalanceBetweenOurNodes() { | |
// The number of promises to process simultaneously. | |
let concurrency = 100 | |
// Create a pool. | |
let pool = new PromisePool(rebalanceBetweenOurNodePromise(), concurrency) | |
pool.addEventListener('fulfilled', function () { | |
// 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('rebalanceBetweenOurNodePromise: ОШИБКА: error: %o: ', event.data.error.message) | |
}) | |
console.log(`Запускается в параллель: ${concurrency}`) | |
// Start the pool. | |
let poolPromise = pool.start() | |
// Wait for the pool to settle. | |
await poolPromise | |
console.log( | |
`Всё завершено успешно | |
Количество успешно ребалансированных: ${successfulAmountOurRebalanced} | |
Количество неудачных: ${errorAmountOurRebalanced}` | |
) | |
} | |
function findChannelsBetweenOurNodes(key1, rebalanceCommands) { | |
let channel | |
let listChannels = $listChannels[key1], | |
//describeGraph = $describeGraph[key1], | |
getInfo = $getInfo[key1] | |
if (! getInfo.synced_to_chain) { | |
console.warn(`Сервер ${key1} не синхронизирован с цепью - игнорируем его`) | |
return | |
} | |
// Собираем статистику по каналам, которые уже есть и с теми условиями, с которыми нам надо | |
// В данном случае - учитываем те каналы, где есть средства с нашей стороны | |
let key2, localCommands = {}, command | |
for (channel of listChannels.channels) { | |
let lack = ((+channel.capacity -channel.commit_fee) / 2) - +channel.local_balance | |
if ((key2 = myNodes[channel.remote_pubkey]) && key1 !== key2 && lack > 1000 ) { | |
// Значит key1 - сторона для создания инвойса, а key2 - тот, кто платит | |
rebalanceCommands.push(command = { | |
invoiceFrom: {key: key1, pubKey: nodeStorage.nodes[key1].pubKey}, | |
payWho: {key: key2, pubKey: channel.remote_pubkey}, | |
amount: Math.round(Math.min(lack, MAX_SATOSHIS_PER_TRANSACTION)), | |
chanId: channel.chan_id, | |
capacity: +channel.capacity, | |
//edge: null, | |
blockHeight: getInfo.block_height, | |
decodedPayReq: null | |
}) | |
localCommands[command.chanId] = command | |
debug("Команда ребаланса: %o", command) | |
} | |
} | |
/*for (let edge of describeGraph.edges) { | |
if (localCommands[edge.channel_id]) | |
localCommands[edge.channel_id].edge = edge | |
}*/ | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment