diff --git a/logic/config.js b/logic/config.js index 336f05b..e4537bb 100644 --- a/logic/config.js +++ b/logic/config.js @@ -8,19 +8,39 @@ 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, - rest: false, prune: { enabled: false, pruneSizeGB: 300, }, + blockfilterindex: true, + maxmempool: 300, + mempoolexpiry: 336, + persistmempool: true, + maxorphantx: 100, reindex: false, + // RPC/REST + rest: false, + rpcworkqueue: 128, + // Network Selection network: constants.BITCOIN_DEFAULT_NETWORK } @@ -116,13 +136,36 @@ function settingsToMultilineConfString(settings) { 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'); + } + + // 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]"); @@ -167,14 +210,63 @@ function settingsToMultilineConfString(settings) { 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}]`); diff --git a/routes/v1/bitcoind/system.js b/routes/v1/bitcoind/system.js index 5e7ed05..991ed55 100644 --- a/routes/v1/bitcoind/system.js +++ b/routes/v1/bitcoind/system.js @@ -5,6 +5,7 @@ const systemLogic = require('logic/system.js'); const configLogic = require('logic/config'); const bitcoindLogic = require('logic/bitcoind.js'); const safeHandler = require('utils/safeHandler'); +const validateSettingsRequest = require('utils/validateSettingsRequest'); router.get('/bitcoin-p2p-connection-details', safeHandler(async(req, res) => { const connectionDetails = systemLogic.getBitcoinP2PConnectionDetails(); @@ -29,6 +30,11 @@ router.post('/update-bitcoin-config', safeHandler(async(req, res) => { // store old bitcoinConfig in memory to revert to in case of errors setting new config and restarting bitcoind const oldBitcoinConfig = await configLogic.getJsonStore(); const newBitcoinConfig = req.body.bitcoinConfig; + + const validationErrors = validateSettingsRequest(newBitcoinConfig); + if (validationErrors.length > 0) { + return res.status(400).json({success: false, validationErrors}); + } try { await configLogic.applyCustomBitcoinConfig(newBitcoinConfig); diff --git a/ui/src/components/AdvancedSettingsModal.vue b/ui/src/components/AdvancedSettingsModal.vue index 185071c..4e4c0c1 100644 --- a/ui/src/components/AdvancedSettingsModal.vue +++ b/ui/src/components/AdvancedSettingsModal.vue @@ -177,6 +177,400 @@ + + +
+
+
+ +
+
+ +
+
+ +

+ Share compact block filter data with connected light clients (like wallets) connected to your node, allowing them to get only the transaction information they are + interested in from your node without having to download the entire blockchain. Enabling this will automatically enable Block Filter Index below. +

+

+ Note: If you disable Peer Block Filters, you will need to also manually toggle off Block Filter Index if you + want to stop storing block filter data. +

+
+
+
+ + + +
+
+
+ +
+
+ +
+
+ +

+ Store an index of compact block filters which allows faster wallet re-scanning. + In order to serve compact block filters to peers, you must also enable Peer Block Filters above. +

+

+ Note: To use 'Block Filter Index' with a pruned node, you must enable it when you start the 'Prune Old Blocks' process under the Optimization category. + If your node is already pruned and 'Block Filter Index' is off, enabling it will prevent your node from starting. To fix this while keeping 'Block Filter Index' on, you will need to either reindex your node or turn off 'Prune Old Blocks'. +

+
+
+
+ + + +
+
+
+ +
+
+ +
+
+ +

+ Enable support for BIP37, a feature used by older light clients (like wallets) to get only the transaction information they are interested in from your node without having to download the entire blockchain. +

+

+ Note: Bloom filters can have privacy and denial-of-service (DoS) risks, especially if your node is publicly reachable; its use is discouraged in favour of the more modern compact block filters. +

+
+
+
+ + + +
+
+
+ +
+
+ + + +
+
+ + Set the duration (in seconds) that a peer will be banned from connecting to your node if they violate protocol rules or exhibit suspicious behavior. + By adjusting bantime, you can maintain your node's security and network integrity, while preventing repeat offenders from causing disruptions. + A longer bantime increases the ban period, discouraging misbehavior, while a shorter bantime allows for quicker reconnections but may require more frequent manual monitoring of peer activity. + +
+
+ + + +
+
+
+ +
+
+ + + +
+
+ + Set the maximum number of peers your node can connect to simultaneously. By managing this, you can optimize your node's network usage and system resources based on your device's capacity. + A higher value enables your node to maintain more connections, potentially improving network stability and data sharing. A lower value conserves system resources and bandwidth, + which may be beneficial for devices with limited capabilities. + +
+
+ + + +
+
+
+ +
+
+ + + +
+
+ + Set the maximum amount of memory (in kilobytes) allocated for storing incoming data from other nodes in the network. + A larger buffer size allows your node to handle more incoming data simultaneously, while a smaller size reduces memory consumption but may limit the amount of data your node can process at once. + +
+
+ + + +
+
+
+ +
+
+ + + +
+
+ + Set the maximum memory (in kilobytes) dedicated to storing outgoing data sent to other nodes in the network. + A larger buffer size enables your node to send more data simultaneously, while a smaller size conserves memory but may + restrict the volume of data your node can transmit at once. + +
+
+ + + +
+
+
+ +
+
+ + + +
+
+ + Set the maximum allowed time adjustment (in seconds) your node can make based on the time data received from other nodes in the network. + By controlling it, you can maintain your node's time accuracy while reducing the risk of incorrect time adjustments that could affect block validation and other time-sensitive processes. + A lower value provides a stricter limit on time corrections, while a higher value allows more flexibility based on network data. + +
+
+ + + +
+
+
+ +
+
+ + + +
+
+ + Set the maximum time (in seconds) that your node will wait for a response from a connected peer before considering it unresponsive and disconnecting. + Adjusting peertimeout helps you maintain stable connections with responsive peers while ensuring your node doesn't waste resources on unresponsive ones. + A shorter timeout value allows for quicker disconnection from unresponsive peers, while a longer timeout provides more time for slow-responding peers to maintain a connection. + +
+
+ + + +
+
+
+ +
+
+ + + +
+
+ + Set the maximum time (in seconds) that your node will wait for a response from a newly connecting peer during the initial handshake process before considering it unresponsive and disconnecting. + Fine-tuning it helps you ensure your node establishes stable connections with responsive peers while avoiding unresponsive ones. A shorter timeout value leads to faster disconnection from unresponsive peers, + while a longer timeout allows more time for slow-responding peers to complete the handshake. + +
+
+ + + +
+
+
+ +
+
+ + + +
+
+ + +

+ Limit the maximum amount of data (in MB) your node will upload to other peers in the network within a 24-hour period. Setting this to 0 (default) means that there is no limit. + By adjusting it, you can optimize your node's bandwidth usage and maintain a balance between sharing data with the network and conserving your internet resources. + A higher upload target allows your node to contribute more data to the network, while a lower target helps you save bandwidth for other uses. +

+

+ Note: Peers that are whitelisted are exempt from this limit. By default, your node whitelists apps on your Umbrel (e.g., Electrs). + However, external apps and wallets that are connected via the P2P port may fail to receive data from your node if your node hits the 24-hour upload limit. +

+
+
+
+ @@ -231,6 +625,7 @@ +
@@ -302,6 +697,142 @@
+ + +
+
+
+ +
+
+ + + +
+
+ + Set the maximum size that your node will allocate (in RAM) for storing unconfirmed transactions before they are included in a block. + By adjusting maxmempool, you can optimize your node's performance and balance memory usage based on your device's capabilities. + A larger maxmempool allows your node to store more unconfirmed transactions, providing more accurate statistics on explorer apps like Mempool. + +
+
+ + + +
+
+
+ +
+
+ + + +
+
+ + Set the time threshold (in hours) for unconfirmed transactions to remain in your node's mempool before being removed. + By adjusting it, you can manage your node's memory usage and ensure outdated, unconfirmed transactions are discarded. + A shorter expiry time helps keep your mempool up-to-date and reduces memory usage, while a longer expiry time allows transactions + to remain in the pool for an extended period in case of network congestion or delayed confirmations. + +
+
+ + + +
+
+
+ +
+
+ +
+
+ + Saves unconfirmed transactions in your node's mempool when it's shutting down and reloads them upon startup. + Enabling this setting helps maintain a consistent mempool and prevents the loss of unconfirmed transactions during a restart. + Disabling this setting will clear the mempool upon restart, which may reduce startup time but requires your node to rebuild its mempool from scratch. + +
+
+ + + +
+
+
+ +
+
+ + + +
+
+ + Set the maximum number of orphan transactions (transactions missing one or more of their inputs) that your node will keep in memory. + By fine-tuning it, you can optimize your node's memory usage and manage its performance based on your device's capabilities. + A larger limit allows your node to store more orphan transactions, potentially increasing the chances of finding missing inputs. + A smaller limit conserves memory but will result in your node evicting some orphan transactions from memory when the limit is reached. + +
+
+ @@ -319,8 +850,7 @@ - - +
@@ -344,10 +874,45 @@
- Public REST API can help you connect certain wallets and apps to your node. However, because the REST API access is unauthenticated, it can lead to unauthorized access, denial-of-service (DoS) attacks, and such. + Enabling the public REST API can help you connect certain wallets and apps to your node. However, because the REST API access is unauthenticated, it can lead to unauthorized access, privacy degradation, and denial-of-service (DoS) attacks.
+ + + +
+
+
+ +
+
+ + + +
+
+ + Set the maximum number of queued Remote Procedure Call (RPC) requests your node can handle + (e.g., from connected wallets or other apps), helping you strike a balance between performance and resource usage. + Higher values can improve processing speed at the cost of increased system resources. + +
+
+ @@ -403,10 +968,13 @@ - - - Please choose at least one source for outgoing connections (Clearnet, Tor, or I2P). - + +

Please fix the following errors and try again:

+
@@ -444,8 +1012,7 @@ export default { { value: "signet", text: "signet" }, { value: "regtest", text: "regtest" } ], - maxPruneSizeGB: 300, - showOutgoingConnectionsError: false + maxPruneSizeGB: 300 }; }, computed: { @@ -466,20 +1033,43 @@ export default { } else { return ""; } - } + }, + isPeerBlockFiltersEnabled() { + return this.settings.peerblockfilters; + }, + blockFilterIndexTooltip() { + if (this.settings.peerblockfilters) { + return "Peer Block Filters must be disabled to turn this off."; + } else { + return ""; + } + }, }, watch: { isTorProxyDisabled(value) { if (!value) return; this.settings.torProxyForClearnet = false; + }, + // if peerblockfilters is enabled, blockfilterindex must be enabled for bitcoind to start + isPeerBlockFiltersEnabled(value) { + if (!value) return; + this.settings.blockfilterindex = true; } }, props: { isSettingsDisabled: { type: Boolean, default: false + }, + validationErrors: { + type: Array, + default: () => [] } }, + beforeDestroy() { + // clear validationErrors when component is destroyed + this.$emit("clearErrors"); + }, created() { this.setSettings(); }, @@ -489,8 +1079,6 @@ export default { }, methods: { submit() { - this.showOutgoingConnectionsError = false; - if (!this.isOutgoingConnectionsValid()) return this.showOutgoingConnectionsError = true; this.$emit("submit", this.settings); }, clickRestoreDefaults() { diff --git a/ui/src/views/Bitcoin.vue b/ui/src/views/Bitcoin.vue index b54a23f..09e6ee8 100644 --- a/ui/src/views/Bitcoin.vue +++ b/ui/src/views/Bitcoin.vue @@ -186,7 +186,7 @@ - +
@@ -209,7 +209,8 @@ export default { data() { return { isRestartPending: false, - showRestartError: false + showRestartError: false, + validationErrors: [] }; }, computed: { @@ -281,6 +282,7 @@ export default { }, async saveSettingsAndRestartBitcoin(bitcoinConfig) { try { + this.validationErrors = []; this.isRestartPending = true; this.$store.dispatch("user/updateBitcoinConfig", bitcoinConfig); @@ -298,17 +300,24 @@ export default { this.showRestartError = true; this.$bvModal.hide("advanced-settings-modal"); this.isRestartPending = false; - } + } } catch (error) { - console.error(error); this.fetchBitcoinConfigSettings(); + this.isRestartPending = false; + + if (error.response.status === 400) { + console.log("validation error woop woop!") + this.validationErrors = error.response.data.validationErrors; + return; + } + this.showRestartError = true; this.$bvModal.hide("advanced-settings-modal"); - this.isRestartPending = false; } }, async restoreDefaultSettingsAndRestartBitcoin() { try { + this.validationErrors = []; this.isRestartPending = true; const response = await API.post( diff --git a/utils/validateSettingsRequest.js b/utils/validateSettingsRequest.js new file mode 100644 index 0000000..b428699 --- /dev/null +++ b/utils/validateSettingsRequest.js @@ -0,0 +1,129 @@ +function validateSettingsRequest(settings) { + const errors = []; + + // PEER SETTINGS + + // outgoing connections to clearnet peers + checkBooleanSetting({ setting: settings.clearnet, settingName: "Outgoing connections to clearnet peers" }); + + // outgoing connections to tor peers + checkBooleanSetting({ setting: settings.tor, settingName: "Outgoing connections to tor peers" }); + + // Make All Outgoing Connections to Clearnet Peers Over Tor + checkBooleanSetting({ setting: settings.torProxyForClearnet, settingName: "Make All Outgoing Connections to Clearnet Peers Over Tor" }); + + // Outgoing Connections to I2P Peers + checkBooleanSetting({ setting: settings.i2p, settingName: "Outgoing Connections to I2P Peers" }); + + // We check that at least one source of outgoing connections is set (Clearnet, Tor, or I2P). + if (!settings.clearnet && !settings.tor && !settings.i2p) { + errors.push("You must enable at least one source of outgoing connections (Clearnet, Tor, or I2P)."); + } + + // Incoming Connections + checkBooleanSetting({ setting: settings.incomingConnections, settingName: "Incoming Connections" }); + + // Peer Block Filters + checkBooleanSetting({ setting: settings.peerblockfilters, settingName: "Peer Block Filters" }); + // blockfilterindex must be enabled if peerblockfilters is enabled for bitcoind to start + if (settings.peerblockfilters && !settings.blockfilterindex) { + errors.push("You must enable Block Filter Index if Peer Block Filters is enabled."); + } + + // Peer Bloom Filters + checkBooleanSetting({ setting: settings.peerbloomfilters, settingName: "Peer Bloom Filters" }); + + // Peer Ban Time + // min of 1. No max specified + checkNumberSetting({ setting: settings.bantime, settingName: "Peer Ban Time", min: 1 }); + + // Max Peer Connections + // min of 0 (no connections allowed). No max specified. + checkNumberSetting({ setting: settings.maxconnections, settingName: "Max Peer Connections", min: 0 }); + + // Max Receive Buffer + // min of 1. No max specified + checkNumberSetting({ setting: settings.maxreceivebuffer, settingName: "Max Receive Buffer", min: 1 }); + + // Max Send Buffer + // min of 1. No max specified. + checkNumberSetting({ setting: settings.maxsendbuffer, settingName: "Max Send Buffer", min: 1 }); + + // Max Time Adjustment + // min of 0 (no adjustment allowed). No max specified. + checkNumberSetting({ setting: settings.maxtimeadjustment, settingName: "Max Time Adjustment", min: 0 }); + + // Peer Timeout + // min of 1. No max specified. + checkNumberSetting({ setting: settings.peertimeout, settingName: "Peer Timeout", min: 1 }); + + // Connection Timeout + // min of 1. No max specified. + checkNumberSetting({ setting: settings.timeout, settingName: "Connection Timeout", min: 1 }); + + // Max Upload Target + // min of 0 (no limit). No max specified. + checkNumberSetting({ setting: settings.maxuploadtarget, settingName: "Max Upload Target", min: 0 }); + + // OPTIMIZATION SETTINGS + + // Cache Size + // min of 4 (no limit). No max specified. + checkNumberSetting({ setting: settings.cacheSizeMB, settingName: "Cache Size", min: 4 }); + + // Replace-By-Fee (RBF) + checkBooleanSetting({ setting: settings.mempoolFullRbf, settingName: "Replace-By-Fee" }); + + // Prune Old Blocks + checkBooleanSetting({ setting: settings.prune.enabled, settingName: "Prune Old Blocks" }); + // min of 550 MiB (0.5767168 GB). No max specified. + checkNumberSetting({ setting: settings.prune.pruneSizeGB, settingName: "Prune Target Size", min: 0.6 }); + + // Block Filter Index + checkBooleanSetting({ setting: settings.blockfilterindex, settingName: "Block Filter Index" }); + + // Maximum Mempool Size + // 5 MiB when blocksonly mode is set, and 300 MiB when blocksonly mode is not set. No max specified. + checkNumberSetting({ setting: settings.maxmempool, settingName: "Maximum Mempool Size", min: 300 }); + + // Mempool Expiration + // No min or max specified. We have set a practical min of 1 hour. + checkNumberSetting({ setting: settings.mempoolexpiry, settingName: "Mempool Expiration", min: 1 }); + + // Persist Mempool + checkBooleanSetting({ setting: settings.persistmempool, settingName: "Persist Mempool" }); + + // Max Orphan Transactions + // No min or max specified. + checkNumberSetting({ setting: settings.maxorphantx, settingName: "Max Orphan Transactions", min: 0 }); + + // Public REST API + checkBooleanSetting({ setting: settings.rest, settingName: "Public REST API" }); + + // RPC Work Queue Size + // No min or max specified. We have set a practical min of 1. + checkNumberSetting({ setting: settings.rpcworkqueue, settingName: "RPC Work Queue Size", min: 1 }); + + // NETWORK SELECTION + // custom. settings.network MUST BE one of the following strings: "mainnet", "testnet", "regtest", "signet" + if (settings.network !== "main" && settings.network !== "test" && settings.network !== "regtest" && settings.network !== "signet") { + errors.push(`Invalid value for Network chain. Please try toggling it to a different value.`); + } + + return errors; + + function checkBooleanSetting({ setting, settingName }) { + if (typeof setting !== "boolean") { + errors.push(`Invalid value for ${settingName}. Please try toggling it on/off again.`); + } + } + + function checkNumberSetting({ setting, settingName, min, max }) { + if (typeof setting !== "number" || setting < min || (max !== undefined && setting > max)) { + errors.push(`${settingName} must be ${max ? `between ${min} and ${max}` : `at least ${min}`}.`); + } + } + +} + +module.exports = validateSettingsRequest; \ No newline at end of file