mirror of
https://github.com/Retropex/umbrel-bitcoin.git
synced 2025-05-12 19:20:49 +02:00
394 lines
15 KiB
JavaScript
394 lines
15 KiB
JavaScript
const merge = require('lodash.merge');
|
|
|
|
const path = require("path");
|
|
const constants = require("utils/const.js");
|
|
const diskService = require("services/disk");
|
|
|
|
const GB_TO_MiB = 953.674;
|
|
const MB_TO_MiB = 0.953674;
|
|
|
|
const DEFAULT_ADVANCED_SETTINGS = {
|
|
// Peer Settings
|
|
clearnet: true,
|
|
torProxyForClearnet: false,
|
|
tor: true,
|
|
i2p: true,
|
|
incomingConnections: false,
|
|
peerblockfilters: true,
|
|
peerbloomfilters: false,
|
|
bantime: 86400,
|
|
maxconnections: 125,
|
|
maxreceivebuffer: 5000,
|
|
maxsendbuffer: 1000,
|
|
maxtimeadjustment: 4200,
|
|
peertimeout: 60,
|
|
timeout: 5000,
|
|
maxuploadtarget: 0,
|
|
// Optimization
|
|
cacheSizeMB: 450,
|
|
mempoolFullRbf: false,
|
|
prune: {
|
|
enabled: false,
|
|
pruneSizeGB: 300,
|
|
},
|
|
blockfilterindex: true,
|
|
maxmempool: 300,
|
|
mempoolexpiry: 336,
|
|
persistmempool: true,
|
|
datacarrier: true,
|
|
datacarriersize: 42,
|
|
permitbaremultisig: false,
|
|
rejectparasites: true,
|
|
rejecttokens: false,
|
|
minrelaytxfee: 0.00001,
|
|
bytespersigop: 20,
|
|
bytespersigopstrict: 20,
|
|
limitancestorcount: 25,
|
|
limitancestorsize: 101,
|
|
limitdescendantcount: 25,
|
|
limitdescendantsize: 101,
|
|
permitbarepubkey: false,
|
|
maxscriptsize: 1650,
|
|
datacarriercost: 1,
|
|
acceptnonstddatacarrier: false,
|
|
dustrelayfee: 0.00003,
|
|
maxorphantx: 100,
|
|
reindex: false,
|
|
// RPC/REST
|
|
rest: false,
|
|
rpcworkqueue: 128,
|
|
// Network Selection
|
|
network: constants.BITCOIN_DEFAULT_NETWORK
|
|
}
|
|
|
|
async function getJsonStore() {
|
|
try {
|
|
const jsonStore = await diskService.readJsonFile(constants.JSON_STORE_FILE);
|
|
return merge({}, DEFAULT_ADVANCED_SETTINGS, jsonStore);
|
|
} catch (error) {
|
|
return DEFAULT_ADVANCED_SETTINGS;
|
|
}
|
|
}
|
|
|
|
async function applyCustomBitcoinConfig(bitcoinConfig) {
|
|
await applyBitcoinConfig(bitcoinConfig, false);
|
|
}
|
|
|
|
async function applyDefaultBitcoinConfig() {
|
|
await applyBitcoinConfig(DEFAULT_ADVANCED_SETTINGS, true);
|
|
}
|
|
|
|
async function applyBitcoinConfig(bitcoinConfig, shouldOverwriteExistingFile) {
|
|
await Promise.all([
|
|
updateJsonStore(bitcoinConfig),
|
|
generateUmbrelBitcoinConfig(bitcoinConfig),
|
|
generateBitcoinConfig(shouldOverwriteExistingFile),
|
|
]);
|
|
}
|
|
|
|
// There's a race condition here if you do two updates in parallel but it's fine for our current use case
|
|
async function updateJsonStore(newProps) {
|
|
const jsonStore = await getJsonStore();
|
|
return diskService.writeJsonFile(constants.JSON_STORE_FILE, merge(jsonStore, newProps));
|
|
}
|
|
|
|
// creates umbrel-bitcoin.conf
|
|
function generateUmbrelBitcoinConfig(settings) {
|
|
const confString = settingsToMultilineConfString(settings);
|
|
return diskService.writePlainTextFile(constants.UMBREL_BITCOIN_CONF_FILEPATH, confString);
|
|
}
|
|
|
|
// creates bitcoin.conf with includeconf=umbrel-bitcoin.conf
|
|
async function generateBitcoinConfig(shouldOverwriteExistingFile = false) {
|
|
const baseName = path.basename(constants.UMBREL_BITCOIN_CONF_FILEPATH);
|
|
const includeConfString = `# Load additional configuration file, relative to the data directory.\nincludeconf=${baseName}`;
|
|
|
|
const fileExists = await diskService.fileExists(constants.BITCOIN_CONF_FILEPATH);
|
|
|
|
// if bitcoin.conf does not exist or should be overwritten, create it with includeconf=umbrel-bitcoin.conf
|
|
if (!fileExists || shouldOverwriteExistingFile) {
|
|
return await diskService.writePlainTextFile(constants.BITCOIN_CONF_FILEPATH, includeConfString);
|
|
}
|
|
|
|
const existingConfContents = await diskService.readUtf8File(constants.BITCOIN_CONF_FILEPATH);
|
|
|
|
// if bitcoin.conf exists but does not include includeconf=umbrel-bitcoin.conf, add includeconf=umbrel-bitcoin.conf to the top of the file
|
|
if (!existingConfContents.includes(includeConfString)) {
|
|
return await diskService.writePlainTextFile(constants.BITCOIN_CONF_FILEPATH, `${includeConfString}\n${existingConfContents}`);
|
|
}
|
|
|
|
// do nothing if bitcoin.conf exists and contains includeconf=umbrel-bitcoin.conf
|
|
}
|
|
|
|
function settingsToMultilineConfString(settings) {
|
|
const umbrelBitcoinConfig = [];
|
|
|
|
// [CHAIN]
|
|
umbrelBitcoinConfig.push("# [chain]");
|
|
if (settings.network !== 'main') {
|
|
umbrelBitcoinConfig.push(`chain=${settings.network}`)
|
|
}
|
|
|
|
// [CORE]
|
|
umbrelBitcoinConfig.push("");
|
|
umbrelBitcoinConfig.push("# [core]");
|
|
|
|
// dbcache
|
|
umbrelBitcoinConfig.push("# Maximum database cache size in MiB");
|
|
umbrelBitcoinConfig.push(`dbcache=${Math.round(settings.cacheSizeMB * MB_TO_MiB)}`);
|
|
|
|
// mempoolfullrbf
|
|
if (settings.mempoolFullRbf) {
|
|
umbrelBitcoinConfig.push("# Allow any transaction in the mempool of Bitcoin Node to be replaced with newer versions of the same transaction that include a higher fee.");
|
|
umbrelBitcoinConfig.push('mempoolfullrbf=1');
|
|
}
|
|
|
|
// prune
|
|
if (settings.prune.enabled) {
|
|
umbrelBitcoinConfig.push("# Reduce disk space requirements to this many MiB by enabling pruning (deleting) of old blocks. This mode is incompatible with -txindex and -coinstatsindex. WARNING: Reverting this setting requires re-downloading the entire blockchain. (default: 0 = disable pruning blocks, 1 = allow manual pruning via RPC, greater than or equal to 550 = automatically prune blocks to stay under target size in MiB).");
|
|
umbrelBitcoinConfig.push(`prune=${Math.round(settings.prune.pruneSizeGB * GB_TO_MiB)}`);
|
|
}
|
|
|
|
// Only enable txindex if pruning is disabled
|
|
const txindexEnabled = settings.prune.enabled ? '0' : '1';
|
|
umbrelBitcoinConfig.push(`txindex=${txindexEnabled}`);
|
|
|
|
// blockfilterindex
|
|
if (settings.blockfilterindex) {
|
|
umbrelBitcoinConfig.push("# Enable all compact filters.");
|
|
umbrelBitcoinConfig.push('blockfilterindex=1');
|
|
}
|
|
|
|
// maxmempool
|
|
umbrelBitcoinConfig.push("# Keep the transaction memory pool below this many megabytes.");
|
|
umbrelBitcoinConfig.push(`maxmempool=${settings.maxmempool}`);
|
|
|
|
// mempoolexpiry
|
|
umbrelBitcoinConfig.push("# Do not keep transactions in the mempool longer than this many hours.");
|
|
umbrelBitcoinConfig.push(`mempoolexpiry=${settings.mempoolexpiry}`);
|
|
|
|
// persistmempool
|
|
if (settings.persistmempool) {
|
|
umbrelBitcoinConfig.push("# Save the mempool on shutdown and load on restart.");
|
|
umbrelBitcoinConfig.push('persistmempool=1');
|
|
}
|
|
|
|
// datacarrier
|
|
if (!settings.datacarrier) {
|
|
umbrelBitcoinConfig.push("# Relay and mine data carrier transactions.");
|
|
umbrelBitcoinConfig.push('datacarrier=0');
|
|
}
|
|
|
|
// datacarriersize
|
|
umbrelBitcoinConfig.push("# Maximum size of arbitrary data to relay and mine.");
|
|
umbrelBitcoinConfig.push(`datacarriersize=${settings.datacarriersize}`);
|
|
|
|
// permitbaremultisig
|
|
if (settings.permitbaremultisig) {
|
|
umbrelBitcoinConfig.push("# Relay non-P2SH multisig.");
|
|
umbrelBitcoinConfig.push('permitbaremultisig=1');
|
|
}
|
|
|
|
// rejectparasites
|
|
if (!settings.rejectparasites) {
|
|
umbrelBitcoinConfig.push("# Do not relay transactions that are considered parasitic.");
|
|
umbrelBitcoinConfig.push('rejectparasites=0');
|
|
}
|
|
|
|
// rejecttokens
|
|
if (settings.rejecttokens) {
|
|
umbrelBitcoinConfig.push("# Reject transactions that create tokens.");
|
|
umbrelBitcoinConfig.push(`rejecttokens=1`);
|
|
}
|
|
|
|
// minrelaytxfee
|
|
umbrelBitcoinConfig.push("# Min Transaction Relay Fee");
|
|
umbrelBitcoinConfig.push(`minrelaytxfee=${settings.minrelaytxfee}`);
|
|
|
|
// bytespersigop
|
|
umbrelBitcoinConfig.push("# Equivalent bytes per sigop in transactions for relay and mining");
|
|
umbrelBitcoinConfig.push(`bytespersigop=${settings.bytespersigop}`);
|
|
|
|
// bytespersigopstrict
|
|
umbrelBitcoinConfig.push("# Minimum bytes per sigop in transactions we relay and mine");
|
|
umbrelBitcoinConfig.push(`bytespersigopstrict=${settings.bytespersigopstrict}`);
|
|
|
|
// limitancestorcount
|
|
umbrelBitcoinConfig.push("# Do not accept transactions if number of in-mempool ancestors is <n> or more");
|
|
umbrelBitcoinConfig.push(`limitancestorcount=${settings.limitancestorcount}`);
|
|
|
|
// limitancestorsize
|
|
umbrelBitcoinConfig.push("# Do not accept transactions whose size with all in-mempool ancestors exceeds <n> kilobytes");
|
|
umbrelBitcoinConfig.push(`limitancestorsize=${settings.limitancestorsize}`);
|
|
|
|
// limitdescendantcount
|
|
umbrelBitcoinConfig.push("# Do not accept transactions if any ancestor would have <n> or more in-mempool descendants");
|
|
umbrelBitcoinConfig.push(`limitdescendantcount=${settings.limitdescendantcount}`);
|
|
|
|
// limitdescendantsize
|
|
umbrelBitcoinConfig.push("# Do not accept transactions if any ancestor would have more than <n> kilobytes of in-mempool descendants");
|
|
umbrelBitcoinConfig.push(`limitdescendantsize=${settings.limitdescendantsize}`);
|
|
|
|
// permitbarepubkey
|
|
if (settings.permitbarepubkey) {
|
|
umbrelBitcoinConfig.push("# Relay legacy pubkey outputs");
|
|
umbrelBitcoinConfig.push('permitbarepubkey=1');
|
|
}
|
|
|
|
// maxscriptsize
|
|
umbrelBitcoinConfig.push("# Maximum size of scripts we relay and mine, in bytes");
|
|
umbrelBitcoinConfig.push(`maxscriptsize=${settings.maxscriptsize}`);
|
|
|
|
// datacarriercost
|
|
umbrelBitcoinConfig.push("# Treat extra data in transactions as at least N vbytes per actual byte");
|
|
umbrelBitcoinConfig.push(`datacarriercost=${settings.datacarriercost}`);
|
|
|
|
// acceptnonstddatacarrier
|
|
if (settings.acceptnonstddatacarrier) {
|
|
umbrelBitcoinConfig.push("# Relay and mine non-OP_RETURN datacarrier injection");
|
|
umbrelBitcoinConfig.push('acceptnonstddatacarrier=1');
|
|
}
|
|
|
|
// dustrelayfee
|
|
umbrelBitcoinConfig.push("# Fee rate (in BTC/kvB) used to define dust, the value of an output such that it will cost more than its value in fees at this fee rate to spend it.");
|
|
umbrelBitcoinConfig.push(`dustrelayfee=${settings.dustrelayfee}`);
|
|
|
|
// maxorphantx
|
|
umbrelBitcoinConfig.push("# Maximum number of orphan transactions to be kept in memory.");
|
|
umbrelBitcoinConfig.push(`maxorphantx=${settings.maxorphantx}`);
|
|
|
|
// reindex
|
|
if (settings.reindex) {
|
|
umbrelBitcoinConfig.push('# Rebuild chain state and block index from the blk*.dat files on disk.');
|
|
umbrelBitcoinConfig.push('reindex=1');
|
|
}
|
|
|
|
// [NETWORK]
|
|
umbrelBitcoinConfig.push("");
|
|
umbrelBitcoinConfig.push("# [network]");
|
|
|
|
// clearnet
|
|
if (settings.clearnet) {
|
|
umbrelBitcoinConfig.push('# Connect to peers over the clearnet.')
|
|
umbrelBitcoinConfig.push('onlynet=ipv4');
|
|
umbrelBitcoinConfig.push('onlynet=ipv6');
|
|
}
|
|
|
|
if (settings.torProxyForClearnet) {
|
|
umbrelBitcoinConfig.push('# Connect through <ip:port> SOCKS5 proxy.');
|
|
umbrelBitcoinConfig.push(`proxy=${constants.TOR_PROXY_IP}:${constants.TOR_PROXY_PORT}`);
|
|
}
|
|
|
|
// tor
|
|
if (settings.tor) {
|
|
umbrelBitcoinConfig.push('# Use separate SOCKS5 proxy <ip:port> to reach peers via Tor hidden services.');
|
|
umbrelBitcoinConfig.push('onlynet=onion');
|
|
umbrelBitcoinConfig.push(`onion=${constants.TOR_PROXY_IP}:${constants.TOR_PROXY_PORT}`);
|
|
umbrelBitcoinConfig.push('# Tor control <ip:port> and password to use when onion listening enabled.');
|
|
umbrelBitcoinConfig.push(`torcontrol=${constants.TOR_PROXY_IP}:${constants.TOR_PROXY_CONTROL_PORT}`);
|
|
umbrelBitcoinConfig.push(`torpassword=${constants.TOR_PROXY_CONTROL_PASSWORD}`);
|
|
}
|
|
|
|
// i2p
|
|
if (settings.i2p) {
|
|
umbrelBitcoinConfig.push('# I2P SAM proxy <ip:port> to reach I2P peers.');
|
|
umbrelBitcoinConfig.push(`i2psam=${constants.I2P_DAEMON_IP}:${constants.I2P_DAEMON_PORT}`);
|
|
umbrelBitcoinConfig.push('onlynet=i2p');
|
|
}
|
|
|
|
// incoming connections
|
|
umbrelBitcoinConfig.push('# Enable/disable incoming connections from peers.');
|
|
const listen = settings.incomingConnections ? 1 : 0;
|
|
umbrelBitcoinConfig.push(`listen=1`);
|
|
umbrelBitcoinConfig.push(`listenonion=${listen}`);
|
|
umbrelBitcoinConfig.push(`i2pacceptincoming=${listen}`);
|
|
|
|
// whitelist
|
|
umbrelBitcoinConfig.push(`# Whitelist peers connecting from local Umbrel IP range. Whitelisted peers cannot be DoS banned and their transactions are always relayed, even if they are already in the mempool.`);
|
|
umbrelBitcoinConfig.push(`whitelist=10.21.0.0/16`);
|
|
|
|
// peerblockfilters
|
|
if (settings.peerblockfilters) {
|
|
umbrelBitcoinConfig.push("# Serve compact block filters to peers per BIP 157.");
|
|
umbrelBitcoinConfig.push('peerblockfilters=1');
|
|
}
|
|
|
|
// peerbloomfilters
|
|
if (settings.peerbloomfilters) {
|
|
umbrelBitcoinConfig.push("# Support filtering of blocks and transactions with bloom filters.");
|
|
umbrelBitcoinConfig.push('peerbloomfilters=1');
|
|
}
|
|
|
|
// bantime
|
|
umbrelBitcoinConfig.push("# Number of seconds to keep misbehaving peers from reconnecting.");
|
|
umbrelBitcoinConfig.push(`bantime=${settings.bantime}`);
|
|
|
|
// maxconnections
|
|
umbrelBitcoinConfig.push("# Maintain at most this many connections to peers.");
|
|
umbrelBitcoinConfig.push(`maxconnections=${settings.maxconnections}`);
|
|
|
|
// maxreceivebuffer
|
|
umbrelBitcoinConfig.push("# Maximum per-connection receive buffer in KB.");
|
|
umbrelBitcoinConfig.push(`maxreceivebuffer=${settings.maxreceivebuffer}`);
|
|
|
|
// maxsendbuffer
|
|
umbrelBitcoinConfig.push("# Maximum per-connection send buffer in KB.");
|
|
umbrelBitcoinConfig.push(`maxsendbuffer=${settings.maxsendbuffer}`);
|
|
|
|
// maxtimeadjustment
|
|
umbrelBitcoinConfig.push("# Maximum allowed median peer time offset adjustment.");
|
|
umbrelBitcoinConfig.push(`maxtimeadjustment=${settings.maxtimeadjustment}`);
|
|
|
|
// peertimeout
|
|
umbrelBitcoinConfig.push("# The amount of time (in seconds) a peer may be inactive before the connection to it is dropped.");
|
|
umbrelBitcoinConfig.push(`peertimeout=${settings.peertimeout}`);
|
|
|
|
// timeout
|
|
umbrelBitcoinConfig.push("# Initial peer connection timeout in milliseconds.");
|
|
umbrelBitcoinConfig.push(`timeout=${settings.timeout}`);
|
|
|
|
// maxuploadtarget
|
|
umbrelBitcoinConfig.push("# Maximum total upload target in MB per 24hr period.");
|
|
umbrelBitcoinConfig.push(`maxuploadtarget=${settings.maxuploadtarget}`);
|
|
|
|
// [RPC]
|
|
umbrelBitcoinConfig.push("");
|
|
umbrelBitcoinConfig.push("# [rpc]");
|
|
// rest
|
|
if (settings.rest) {
|
|
umbrelBitcoinConfig.push("# Accept public REST requests.");
|
|
umbrelBitcoinConfig.push('rest=1');
|
|
}
|
|
|
|
// rpcworkqueue
|
|
umbrelBitcoinConfig.push("# Depth of the work queue to service RPC calls.");
|
|
umbrelBitcoinConfig.push(`rpcworkqueue=${settings.rpcworkqueue}`);
|
|
|
|
umbrelBitcoinConfig.push("");
|
|
umbrelBitcoinConfig.push(`# Required to configure Tor control port properly`);
|
|
umbrelBitcoinConfig.push(`[${settings.network}]`);
|
|
umbrelBitcoinConfig.push(`bind=0.0.0.0:8333`);
|
|
umbrelBitcoinConfig.push(`bind=${constants.BITCOIND_IP}:8334=onion`);
|
|
|
|
return umbrelBitcoinConfig.join('\n');
|
|
}
|
|
|
|
// checks to see if umbrel-bitcoin.conf is up to date, which we use to determine if we need to regenerate the config and restart bitcoind
|
|
async function isUmbrelBitcoinConfUpToDate(config) {
|
|
const newUmbrelBitcoinConf = await settingsToMultilineConfString(config);
|
|
|
|
let existingUmbrelBitcoinConf = await diskService.fileExists(constants.UMBREL_BITCOIN_CONF_FILEPATH)
|
|
? await diskService.readUtf8File(constants.UMBREL_BITCOIN_CONF_FILEPATH)
|
|
: '';
|
|
|
|
// compare new config to existing umbrel-bitcoin.conf
|
|
return newUmbrelBitcoinConf === existingUmbrelBitcoinConf;
|
|
}
|
|
|
|
module.exports = {
|
|
getJsonStore,
|
|
applyCustomBitcoinConfig,
|
|
applyDefaultBitcoinConfig,
|
|
isUmbrelBitcoinConfUpToDate
|
|
};
|