Unverified Commit 8af5355d authored by Pavel Ševčík's avatar Pavel Ševčík
Browse files

Merge remote-tracking branch 'upstream/master' into develop

# Conflicts:
#	docker/my-dojo/bitcoin/Dockerfile
parents bc11f222 c1a5d3ea
......@@ -3,6 +3,7 @@
## Releases ##
- [v1.12.0](#1_12_0)
- [v1.11.0](#1_11_0)
- [v1.10.1](#1_10_1)
- [v1.10.0](#1_10_0)
......@@ -18,6 +19,45 @@
- [v1.2.0](#1_2_0)
- [v1.1.0](#1_1_0)
<a name="1_12_0"/>
## Samourai Dojo v1.12.0 ##
### Notable changes ###
#### Upgrade of bitcoind to v22.0 ####
Upgrade to Bitcoin Core v22.0
#### Upgrade of Tor to v0.4.6.7 ####
Upgrade to Tor v0.4.6.7 which removes support for outdated v2 onion services
#### Upgrade of BTC-RPC Explorer to v3.2.0 ####
Upgrade to BTC-RPC Explorer v3.2.0
#### Stability improvements ####
Dojo stability has been improved by raising RPC timeout value and fixing uncaught promise rejections.
Stability issues have been encountered on non-standard installations which contain LND.
### Change log ###
#### Features ####
- [#mr252](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/252) updated Tor to 0.4.6.7
- [#mr249](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/249) updated Nginx to 1.21.3
- [#mr247](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/247) updated Bitcoin Core to 22.0
- [#mr246](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/246) updated BTC-RPC Explorer to 3.2.0
- [#mr248](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/248) added uacomment to identify Dojo bitcoind nodes on the network
#### Bug fixes ####
- [#mr251](https://code.samourai.io/dojo/samourai-dojo/-/merge_requests/251) raised RPC timeout value, fixed uncaught promise rejections
#### Credits ####
- pajasevi
- Ketominer
<a name="1_11_0"/>
## Samourai Dojo v1.11.0 ##
......
......@@ -11,13 +11,13 @@
COMPOSE_CONVERT_WINDOWS_PATHS=1
COMPOSE_HTTP_TIMEOUT=240
DOJO_VERSION_TAG=1.11.0
DOJO_VERSION_TAG=1.12.0
DOJO_DB_VERSION_TAG=1.3.0
DOJO_BITCOIND_VERSION_TAG=1.12.0
DOJO_NODEJS_VERSION_TAG=1.11.0
DOJO_NGINX_VERSION_TAG=1.6.0
DOJO_TOR_VERSION_TAG=1.10.0
DOJO_EXPLORER_VERSION_TAG=1.7.0
DOJO_BITCOIND_VERSION_TAG=1.13.0
DOJO_NODEJS_VERSION_TAG=1.12.0
DOJO_NGINX_VERSION_TAG=1.7.0
DOJO_TOR_VERSION_TAG=1.11.0
DOJO_EXPLORER_VERSION_TAG=1.8.0
DOJO_INDEXER_VERSION_TAG=1.3.0
DOJO_WHIRLPOOL_VERSION_TAG=1.4.0
......
......@@ -5,12 +5,18 @@ FROM debian:buster-slim
# INSTALL BITCOIN
#################################################################
ENV BITCOIN_HOME /home/bitcoin
ENV BITCOIN_VERSION 0.21.1
ENV BITCOIN_URL https://bitcoincore.org/bin/bitcoin-core-0.21.1/bitcoin-0.21.1-aarch64-linux-gnu.tar.gz
ENV BITCOIN_SHA256 28264751c982d30b9330e6c1475ddb9ed28be6a2601e8a5f33b6ba49a3d9f5f2
ENV BITCOIN_ASC_URL https://bitcoincore.org/bin/bitcoin-core-0.21.1/SHA256SUMS.asc
ENV BITCOIN_PGP_KS_URI hkp://keyserver.ubuntu.com:80
ENV BITCOIN_PGP_KEY 01EA5486DE18A882D4C2684590C8019E36C2E964
ENV BITCOIN_VERSION 22.0
ENV BITCOIN_URL https://bitcoincore.org/bin/bitcoin-core-22.0
ENV BITCOIN_FILE bitcoin-22.0-aarch64-linux-gnu.tar.gz
ENV BITCOIN_SHASUMS SHA256SUMS
ENV BITCOIN_SHASUMS_ASC SHA256SUMS.asc
# Bitcoin keys (all)
ENV KEYS 71A3B16735405025D447E8F274810B012346C9A6 01EA5486DE18A882D4C2684590C8019E36C2E964 0CCBAAFD76A2ECE2CCD3141DE2FFD5B1D88CA97D 152812300785C96444D3334D17565732E08E5E41 0AD83877C1F0CD1EE9BD660AD7CC770B81FD22A8 590B7292695AFFA5B672CBB2E13FC145CD3F4304 28F5900B1BB5D1A4B6B6D1A9ED357015286A333D CFB16E21C950F67FA95E558F2EEB9F5CC09526C1 6E01EEC9656903B0542B8F1003DB6322267C373B D1DBF2C4B96F2DEBF4C16654410108112E7EA81F 9D3CC86A72F8494342EA5FD10A41BDC3F4FAFF1C 74E2DEF5D77260B98BC19438099BAD163C70FBFA 637DB1E23370F84AFF88CCE03152347D07DA627C 82921A4B88FD454B7EB8CE3C796C4109063D4EAF
# keys to fetch from ubuntu keyserver
ENV KEYS1 71A3B16735405025D447E8F274810B012346C9A6 01EA5486DE18A882D4C2684590C8019E36C2E964 0CCBAAFD76A2ECE2CCD3141DE2FFD5B1D88CA97D 152812300785C96444D3334D17565732E08E5E41 0AD83877C1F0CD1EE9BD660AD7CC770B81FD22A8 590B7292695AFFA5B672CBB2E13FC145CD3F4304 28F5900B1BB5D1A4B6B6D1A9ED357015286A333D CFB16E21C950F67FA95E558F2EEB9F5CC09526C1 6E01EEC9656903B0542B8F1003DB6322267C373B D1DBF2C4B96F2DEBF4C16654410108112E7EA81F 9D3CC86A72F8494342EA5FD10A41BDC3F4FAFF1C 74E2DEF5D77260B98BC19438099BAD163C70FBFA
# keys to fetch from keys.openpgp.org
ENV KEYS2 637DB1E23370F84AFF88CCE03152347D07DA627C 82921A4B88FD454B7EB8CE3C796C4109063D4EAF
ARG BITCOIND_LINUX_UID
ARG BITCOIND_LINUX_GID
......@@ -25,12 +31,16 @@ RUN set -ex && \
# Build and install bitcoin binaries
RUN set -ex && \
cd /tmp && \
wget -qO bitcoin.tar.gz "$BITCOIN_URL" && \
echo "$BITCOIN_SHA256 bitcoin.tar.gz" | sha256sum -c - && \
gpg --batch --keyserver "$BITCOIN_PGP_KS_URI" --recv-keys "$BITCOIN_PGP_KEY" && \
wget -qO bitcoin.asc "$BITCOIN_ASC_URL" && \
gpg --batch --verify bitcoin.asc && \
tar -xzvf bitcoin.tar.gz -C /usr/local --strip-components=1 --exclude=*-qt && \
gpg --batch --keyserver keyserver.ubuntu.com --recv-keys $KEYS1 && \
gpg --batch --keyserver keys.openpgp.org --recv-keys $KEYS2 && \
gpg --list-keys | tail -n +3 | tee /tmp/keys.txt && \
gpg --list-keys $KEYS | diff - /tmp/keys.txt && \
wget -qO "$BITCOIN_SHASUMS" "$BITCOIN_URL/$BITCOIN_SHASUMS" && \
wget -qO "$BITCOIN_SHASUMS_ASC" "$BITCOIN_URL/$BITCOIN_SHASUMS_ASC" && \
wget -qO "$BITCOIN_FILE" "$BITCOIN_URL/$BITCOIN_FILE" && \
gpg --batch --verify "$BITCOIN_SHASUMS_ASC" "$BITCOIN_SHASUMS" && \
sha256sum --ignore-missing --check "$BITCOIN_SHASUMS" && \
tar -xzvf "$BITCOIN_FILE" -C /usr/local --strip-components=1 --exclude=*-qt && \
rm -rf /tmp/*
# Create groups bitcoin & tor
......
......@@ -13,6 +13,7 @@ bitcoind_options=(
-disablewallet=1
-dns=$BITCOIND_DNS
-dnsseed=$BITCOIND_DNSSEED
-uacomment="Samourai Dojo $DOJO_VERSION_TAG"
-maxconnections=$BITCOIND_MAX_CONNECTIONS
-maxmempool=$BITCOIND_MAX_MEMPOOL
-mempoolexpiry=$BITCOIND_MEMPOOL_EXPIRY
......
......@@ -5,7 +5,7 @@ ENV NODE_ENV production
ENV APP_DIR /home/node/app
ENV EXPLORER_URL https://github.com/janoside/btc-rpc-explorer/archive
ENV EXPLORER_VERSION 3.1.1
ENV EXPLORER_VERSION 3.2.0
# Install netcat
RUN set -ex && \
......
FROM nginx:1.15.10-alpine
FROM nginx:1.21.3-alpine
# Copy configuration files
COPY ./nginx.conf /etc/nginx/nginx.conf
......@@ -10,4 +10,4 @@ COPY ./dojo-whirlpool.conf /etc/nginx/sites-enabled/dojo-whirlpool.conf
COPY ./wait-for /wait-for
RUN chmod u+x /wait-for && \
chmod g+x /wait-for
\ No newline at end of file
chmod g+x /wait-for
#!/bin/bash
cd /home/node/app/accounts
forever start -a -l /dev/stdout -o /dev/null -e /dev/null index.js
forever start -c 'node --unhandled-rejections=strict' -a -l /dev/stdout -o /dev/null -e /dev/null index.js
cd /home/node/app/pushtx
forever start -a -l /dev/stdout -o /dev/null -e /dev/null index.js
forever start -a -l /dev/stdout -o /dev/null -e /dev/null index-orchestrator.js
forever start -c 'node --unhandled-rejections=strict' -a -l /dev/stdout -o /dev/null -e /dev/null index.js
forever start -c 'node --unhandled-rejections=strict' -a -l /dev/stdout -o /dev/null -e /dev/null index-orchestrator.js
cd /home/node/app/tracker
forever start -a -l /dev/stdout -o /dev/null -e /dev/null index.js
forever start -c 'node --unhandled-rejections=strict' -a -l /dev/stdout -o /dev/null -e /dev/null index.js
# Keep the container up
while true
do
sleep 1
done
\ No newline at end of file
done
......@@ -3,7 +3,7 @@ FROM debian:buster-slim
ENV TOR_HOME /var/lib/tor
ENV TOR_URL https://dist.torproject.org
ENV TOR_MIRROR_URL https://tor.eff.org/dist
ENV TOR_VERSION 0.4.6.6
ENV TOR_VERSION 0.4.6.7
ENV TOR_GPG_KS_URI hkp://keyserver.ubuntu.com:80
ENV TOR_GPG_KEY1 0xEB5A896A28988BF5
ENV TOR_GPG_KEY2 0xC218525819F78451
......
......@@ -16,7 +16,7 @@ module.exports = {
/*
* Dojo version
*/
dojoVersion: '1.11.0',
dojoVersion: '1.12.0',
/*
* Bitcoind
*/
......@@ -233,7 +233,7 @@ module.exports = {
* Testnet parameters
*/
testnet: {
dojoVersion: '1.11.0',
dojoVersion: '1.12.0',
bitcoind: {
rpc: {
user: 'user',
......
......@@ -4,12 +4,11 @@
*/
'use strict'
const util = require('../util')
const errors = require('../errors')
const Logger = require('../logger')
const network = require('../bitcoin/network')
const keys = require('../../keys')[network.key]
const { createRpcClient } = require('./rpc-client')
const { createRpcClient, waitForBitcoindRpcApi } = require('./rpc-client')
const latestBlock = require('./latest-block')
......@@ -24,12 +23,20 @@ class Fees {
constructor() {
this.block = -1
this.targets = [2, 4, 6, 12, 24]
this.fees = {}
this.fees = {
2: 0,
4: 0,
6: 0,
12: 0,
24: 0
}
this.feeType = keys.bitcoind.feeType
this.rpcClient = createRpcClient()
this.refresh()
waitForBitcoindRpcApi().then(() => {
this.refresh()
})
}
/**
......@@ -53,15 +60,18 @@ class Fees {
* @returns {Promise<void>}
*/
async refresh() {
await util.parallelCall(this.targets, async tgt => {
try {
const level = await this.rpcClient.estimatesmartfee({ conf_target: tgt, estimate_mode: this.feeType })
this.fees[tgt] = (level.errors && level.errors.length > 0) ? 0 : Math.round(level.feerate * 1e5)
} catch(e) {
Logger.error(e, 'Bitcoind RPC : Fees.refresh()')
this.fees[tgt] = 0
}
})
try {
const requests = this.targets.map((target) => {
return { method: 'estimatesmartfee', params: { conf_target: target, estimate_mode: this.feeType }, id: target }
})
const responses = await this.rpcClient.batch(requests)
responses.forEach((fee) => {
this.fees[fee.id] = (fee.result.errors && fee.result.errors.length > 0) ? 0 : Math.round(fee.result.feerate * 1e5)
})
} catch(e) {
Logger.error(e, 'Bitcoind RPC : Fees.refresh()')
}
this.block = latestBlock.height
}
......
......@@ -9,7 +9,7 @@ const Logger = require('../logger')
const util = require('../util')
const network = require('../bitcoin/network')
const keys = require('../../keys')[network.key]
const { createRpcClient } = require('./rpc-client')
const { createRpcClient, waitForBitcoindRpcApi } = require('./rpc-client')
/**
......@@ -29,8 +29,10 @@ class LatestBlock {
// Initialize the rpc client
this.rpcClient = createRpcClient()
// Gets the latest block from bitcoind
this.rpcClient.getbestblockhash().then(hash => this.onBlockHash(hash))
waitForBitcoindRpcApi().then(() => {
// Gets the latest block from bitcoind
this.rpcClient.getbestblockhash().then((hash) => this.onBlockHash(hash))
})
// Initializes zmq socket
this.sock = zmq.socket('sub')
......@@ -54,14 +56,20 @@ class LatestBlock {
* @returns {Promise}
*/
async onBlockHash(hash) {
const header = await this.rpcClient.getblockheader({ blockhash: hash })
try {
const header = await this.rpcClient.getblockheader({ blockhash: hash })
this.height = header.height
this.hash = hash
this.time = header.mediantime
this.diff = header.difficulty
this.height = header.height
this.hash = hash
this.time = header.mediantime
this.diff = header.difficulty
Logger.info(`Bitcoind RPC : Block ${this.height} ${this.hash}`)
Logger.info(`Bitcoind RPC : Block ${this.height} ${this.hash}`)
} catch (err) {
Logger.error(err, 'Bitcoind RPC : LatestBlock.onBlockHash()')
await util.delay(2000)
return this.onBlockHash(hash)
}
}
}
......
......@@ -10,9 +10,13 @@ const keys = require('../../keys')[network.key]
const util = require('../util')
const Logger = require('../logger')
/**
* @typedef {import('rpc-bitcoin').RPCIniOptions} RPCIniOptions
*/
/**
* Wrapper for bitcoind rpc client
* @param {RPCIniOptions} options
*/
const createRpcClient = (options = {}) => {
return new RPCClient({
......@@ -20,6 +24,7 @@ const createRpcClient = (options = {}) => {
port: keys.bitcoind.rpc.port,
user: keys.bitcoind.rpc.user,
pass: keys.bitcoind.rpc.pass,
timeout: 2 * 60 * 1000, // 2 minutes
...options
})
}
......
......@@ -45,13 +45,14 @@ class Transactions {
*/
async getTransactions(txids, fees) {
try {
const rpcCalls = txids.map(txid => {
const rpcCalls = txids.map((txid, index) => {
return {
method: 'getrawtransaction',
params: {
txid,
verbose: true
}
},
id: index
}
})
......
{
"name": "samourai-dojo",
"version": "1.11.0",
"version": "1.12.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "samourai-dojo",
"version": "1.11.0",
"version": "1.12.0",
"license": "AGPL-3.0-only",
"dependencies": {
"@tinyhttp/app": "1.3.13",
......
{
"name": "samourai-dojo",
"version": "1.11.0",
"version": "1.12.0",
"description": "Backend server for Samourai Wallet",
"main": "accounts/index.js",
"engines": {
......
......@@ -11,7 +11,7 @@ const util = require('../lib/util')
const Logger = require('../lib/logger')
const db = require('../lib/db/mysql-db-wrapper')
const network = require('../lib/bitcoin/network')
const { createRpcClient } = require('../lib/bitcoind-rpc/rpc-client')
const { createRpcClient, waitForBitcoindRpcApi } = require('../lib/bitcoind-rpc/rpc-client')
const keys = require('../keys')[network.key]
const Block = require('./block')
const blocksProcessor = require('./blocks-processor')
......@@ -60,16 +60,23 @@ class BlockchainProcessor {
* @returns {Promise<void>}
*/
async catchup() {
const [highest, info] = await Promise.all([db.getHighestBlock(), this.client.getblockchaininfo()])
const daemonNbHeaders = info.headers
// Consider that we are in IBD mode if Dojo is far in the past (> 13,000 blocks)
this.isIBD = (highest.blockHeight < 681000) || (highest.blockHeight < daemonNbHeaders - 13000)
try {
await waitForBitcoindRpcApi()
const [highest, info] = await Promise.all([db.getHighestBlock(), this.client.getblockchaininfo()])
const daemonNbHeaders = info.headers
if (this.isIBD)
return this.catchupIBDMode()
else
return this.catchupNormalMode()
// Consider that we are in IBD mode if Dojo is far in the past (> 13,000 blocks)
this.isIBD = (highest.blockHeight < 681000) || (highest.blockHeight < daemonNbHeaders - 13000)
if (this.isIBD)
return this.catchupIBDMode()
else
return this.catchupNormalMode()
} catch (err) {
Logger.error(err, 'Tracker : BlockchainProcessor.catchup()');
await util.delay(2000)
return this.catchup()
}
}
/**
......
......@@ -259,31 +259,27 @@ class MempoolProcessor {
const unconfirmedTxs = await db.getUnconfirmedTransactions()
if (unconfirmedTxs.length > 0) {
const unconfirmedTxLists = util.splitList(unconfirmedTxs, 10)
const unconfirmedTxLists = util.splitList(unconfirmedTxs, 20)
await util.seriesCall(unconfirmedTxLists, async (txList) => {
return await util.parallelCall(txList, tx => {
try {
return this.client.getrawtransaction( { txid: tx.txnTxid, verbose: true })
.then(async rtx => {
if (!rtx.blockhash) return null
// Transaction is confirmed
const block = await db.getBlockByHash(rtx.blockhash)
if (block && block.blockID) {
Logger.info(`Tracker : Marking TXID ${tx.txnTxid} confirmed`)
return db.confirmTransactions([tx.txnTxid], block.blockID)
}
},
(e) => {
Logger.error(e, 'Tracker : MempoolProcessor.checkUnconfirmed()')
// Transaction not in mempool. Update LRU cache and database
TransactionsBundle.cache.del(tx.txnTxid)
// TODO: Notify clients of orphaned transaction
return db.deleteTransaction(tx.txnTxid)
}
)
} catch(e) {
Logger.error(e, 'Tracker : MempoolProcessor.checkUnconfirmed()')
const rpcRequests = txList.map((tx) => ({ method: 'getrawtransaction', params: { txid: tx.txnTxid, verbose: true }, id: tx.txnTxid }))
const txs = await this.client.batch(rpcRequests)
return await util.parallelCall(txs, async (rtx) => {
if (rtx.error) {
Logger.error(rtx.error.message, 'Tracker : MempoolProcessor.checkUnconfirmed()')
// Transaction not in mempool. Update LRU cache and database
TransactionsBundle.cache.del(rtx.id)
// TODO: Notify clients of orphaned transaction
return db.deleteTransaction(rtx.id)
} else {
if (!rtx.result.blockhash) return null
// Transaction is confirmed
const block = await db.getBlockByHash(rtx.result.blockhash)
if (block && block.blockID) {
Logger.info(`Tracker : Marking TXID ${rtx.id} confirmed`)
return db.confirmTransactions([rtx.id], block.blockID)
}
}
})
})
......@@ -303,16 +299,20 @@ class MempoolProcessor {
async _refreshActiveStatus() {
// Get highest header in the blockchain
// Get highest block processed by the tracker
const [highestBlock, info] = await Promise.all([db.getHighestBlock(), this.client.getblockchaininfo()])
const highestHeader = info.headers
try {
const [highestBlock, info] = await Promise.all([db.getHighestBlock(), this.client.getblockchaininfo()])
const highestHeader = info.headers
if (highestBlock == null || highestBlock.blockHeight === 0) {
this.isActive = false
return
}
if (highestBlock == null || highestBlock.blockHeight === 0) {
this.isActive = false
return
}
// Tolerate a delay of 6 blocks
this.isActive = (highestHeader >= 550000) && (highestHeader <= highestBlock.blockHeight + 6)
// Tolerate a delay of 6 blocks
this.isActive = (highestHeader >= 550000) && (highestHeader <= highestBlock.blockHeight + 6)
} catch (err) {
Logger.error(err, 'Tracker : MempoolProcessor._refreshActiveStatus()')
}
}
/**
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment