mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-24 18:23:26 -03:00
Merge bitcoin/bitcoin#29553: assumeutxo: Add dumptxoutset height param, remove shell scripts
94b0adcc37
rpc, refactor: Prevent potential race conditions in dumptxoutset (Fabian Jahr)e868a6e070
doc: Improve assumeutxo guide and add more docs/comments (Fabian Jahr)b29c21fc92
assumeutxo: Remove devtools/utxo_snapshot.sh (Fabian Jahr)20a1c77aa7
contrib: Remove test_utxo_snapshots.sh (Fabian Jahr)8426850352
test: Test for dumptxoutset at specific height (Fabian Jahr)993cafe7e4
RPC: Add type parameter to dumptxoutset (Fabian Jahr)fccf4f91d2
RPC: Extract ReconsiderBlock helper (Fabian Jahr)446ce51c21
RPC: Extract InvalidateBlock helper (Fabian Jahr) Pull request description: This adds a height parameter to the `dumptxoutset` RPC. This internalizes the workflow that was previously done by scripts: roll back the chain to the height we actually want the snapshot from, create the snapshot, roll forward to the real tip again. The nice thing about internalizing this functionality is that we can write tests for the code and it gives us more options to make the functionality robust. The shell scripts we have so far will be more cumbersome to maintain in the long run, especially since we will only notice later when we have broken them. I think it's safe to remove these `test_utxo_snapshots.sh` as well when we have this option in `dumptxoutset` because we have also added some good additional functional test coverage for this functionality. ACKs for top commit: Sjors: re-utACK94b0adcc37
achow101: ACK94b0adcc37
mzumsande: ACK94b0adcc37
pablomartin4btc: re-ACK94b0adcc37
Tree-SHA512: a4c9af5f687d1ca7bfb579a36f363882823386b5fa80c05de531b05a2782b5da6ff5baf3ada4bca8f32f63975d86f1948175abed9affe51fc958472b5f838dab
This commit is contained in:
commit
fa5fc71199
11 changed files with 355 additions and 418 deletions
|
@ -1,209 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
# Demonstrate the creation and usage of UTXO snapshots.
|
||||
#
|
||||
# A server node starts up, IBDs up to a certain height, then generates a UTXO
|
||||
# snapshot at that point.
|
||||
#
|
||||
# The server then downloads more blocks (to create a diff from the snapshot).
|
||||
#
|
||||
# We bring a client up, load the UTXO snapshot, and we show the client sync to
|
||||
# the "network tip" and then start a background validation of the snapshot it
|
||||
# loaded. We see the background validation chainstate removed after validation
|
||||
# completes.
|
||||
#
|
||||
# The shellcheck rule SC2086 (quoted variables) disablements are necessary
|
||||
# since this rule needs to be violated in order to get bitcoind to pick up on
|
||||
# $EARLY_IBD_FLAGS for the script to work.
|
||||
|
||||
export LC_ALL=C
|
||||
set -e
|
||||
|
||||
BASE_HEIGHT=${1:-30000}
|
||||
INCREMENTAL_HEIGHT=20000
|
||||
FINAL_HEIGHT=$((BASE_HEIGHT + INCREMENTAL_HEIGHT))
|
||||
|
||||
SERVER_DATADIR="$(pwd)/utxodemo-data-server-$BASE_HEIGHT"
|
||||
CLIENT_DATADIR="$(pwd)/utxodemo-data-client-$BASE_HEIGHT"
|
||||
UTXO_DAT_FILE="$(pwd)/utxo.$BASE_HEIGHT.dat"
|
||||
|
||||
# Chosen to try to not interfere with any running bitcoind processes.
|
||||
SERVER_PORT=8633
|
||||
SERVER_RPC_PORT=8632
|
||||
|
||||
CLIENT_PORT=8733
|
||||
CLIENT_RPC_PORT=8732
|
||||
|
||||
SERVER_PORTS="-port=${SERVER_PORT} -rpcport=${SERVER_RPC_PORT}"
|
||||
CLIENT_PORTS="-port=${CLIENT_PORT} -rpcport=${CLIENT_RPC_PORT}"
|
||||
|
||||
# Ensure the client exercises all indexes to test that snapshot use works
|
||||
# properly with indexes.
|
||||
ALL_INDEXES="-txindex -coinstatsindex -blockfilterindex=1"
|
||||
|
||||
if ! command -v jq >/dev/null ; then
|
||||
echo "This script requires jq to parse JSON RPC output. Please install it."
|
||||
echo "(e.g. sudo apt install jq)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DUMP_OUTPUT="dumptxoutset-output-$BASE_HEIGHT.json"
|
||||
|
||||
finish() {
|
||||
echo
|
||||
echo "Killing server and client PIDs ($SERVER_PID, $CLIENT_PID) and cleaning up datadirs"
|
||||
echo
|
||||
rm -f "$UTXO_DAT_FILE" "$DUMP_OUTPUT"
|
||||
rm -rf "$SERVER_DATADIR" "$CLIENT_DATADIR"
|
||||
kill -9 "$SERVER_PID" "$CLIENT_PID"
|
||||
}
|
||||
|
||||
trap finish EXIT
|
||||
|
||||
# Need to specify these to trick client into accepting server as a peer
|
||||
# it can IBD from, otherwise the default values prevent IBD from the server node.
|
||||
EARLY_IBD_FLAGS="-maxtipage=9223372036854775207 -minimumchainwork=0x00"
|
||||
|
||||
server_rpc() {
|
||||
./src/bitcoin-cli -rpcport=$SERVER_RPC_PORT -datadir="$SERVER_DATADIR" "$@"
|
||||
}
|
||||
client_rpc() {
|
||||
./src/bitcoin-cli -rpcport=$CLIENT_RPC_PORT -datadir="$CLIENT_DATADIR" "$@"
|
||||
}
|
||||
server_sleep_til_boot() {
|
||||
while ! server_rpc ping >/dev/null 2>&1; do sleep 0.1; done
|
||||
}
|
||||
client_sleep_til_boot() {
|
||||
while ! client_rpc ping >/dev/null 2>&1; do sleep 0.1; done
|
||||
}
|
||||
server_sleep_til_shutdown() {
|
||||
while server_rpc ping >/dev/null 2>&1; do sleep 0.1; done
|
||||
}
|
||||
|
||||
mkdir -p "$SERVER_DATADIR" "$CLIENT_DATADIR"
|
||||
|
||||
echo "Hi, welcome to the assumeutxo demo/test"
|
||||
echo
|
||||
echo "We're going to"
|
||||
echo
|
||||
echo " - start up a 'server' node, sync it via mainnet IBD to height ${BASE_HEIGHT}"
|
||||
echo " - create a UTXO snapshot at that height"
|
||||
echo " - IBD ${INCREMENTAL_HEIGHT} more blocks on top of that"
|
||||
echo
|
||||
echo "then we'll demonstrate assumeutxo by "
|
||||
echo
|
||||
echo " - starting another node (the 'client') and loading the snapshot in"
|
||||
echo " * first you'll have to modify the code slightly (chainparams) and recompile"
|
||||
echo " * don't worry, we'll make it easy"
|
||||
echo " - observing the client sync ${INCREMENTAL_HEIGHT} blocks on top of the snapshot from the server"
|
||||
echo " - observing the client validate the snapshot chain via background IBD"
|
||||
echo
|
||||
read -p "Press [enter] to continue" _
|
||||
|
||||
echo
|
||||
echo "-- Starting the demo. You might want to run the two following commands in"
|
||||
echo " separate terminal windows:"
|
||||
echo
|
||||
echo " watch -n0.1 tail -n 30 $SERVER_DATADIR/debug.log"
|
||||
echo " watch -n0.1 tail -n 30 $CLIENT_DATADIR/debug.log"
|
||||
echo
|
||||
read -p "Press [enter] to continue" _
|
||||
|
||||
echo
|
||||
echo "-- IBDing the blocks (height=$BASE_HEIGHT) required to the server node..."
|
||||
# shellcheck disable=SC2086
|
||||
./src/bitcoind -logthreadnames=1 $SERVER_PORTS \
|
||||
-datadir="$SERVER_DATADIR" $EARLY_IBD_FLAGS -stopatheight="$BASE_HEIGHT" >/dev/null
|
||||
|
||||
echo
|
||||
echo "-- Creating snapshot at ~ height $BASE_HEIGHT ($UTXO_DAT_FILE)..."
|
||||
server_sleep_til_shutdown # wait for stopatheight to be hit
|
||||
# shellcheck disable=SC2086
|
||||
./src/bitcoind -logthreadnames=1 $SERVER_PORTS \
|
||||
-datadir="$SERVER_DATADIR" $EARLY_IBD_FLAGS -connect=0 -listen=0 >/dev/null &
|
||||
SERVER_PID="$!"
|
||||
|
||||
server_sleep_til_boot
|
||||
server_rpc dumptxoutset "$UTXO_DAT_FILE" > "$DUMP_OUTPUT"
|
||||
cat "$DUMP_OUTPUT"
|
||||
kill -9 "$SERVER_PID"
|
||||
|
||||
RPC_BASE_HEIGHT=$(jq -r .base_height < "$DUMP_OUTPUT")
|
||||
RPC_AU=$(jq -r .txoutset_hash < "$DUMP_OUTPUT")
|
||||
RPC_NCHAINTX=$(jq -r .nchaintx < "$DUMP_OUTPUT")
|
||||
RPC_BLOCKHASH=$(jq -r .base_hash < "$DUMP_OUTPUT")
|
||||
|
||||
server_sleep_til_shutdown
|
||||
|
||||
echo
|
||||
echo "-- Now: add the following to CMainParams::m_assumeutxo_data"
|
||||
echo " in src/kernel/chainparams.cpp, and recompile:"
|
||||
echo
|
||||
echo " {.height = ${RPC_BASE_HEIGHT}, .hash_serialized = AssumeutxoHash{uint256{\"${RPC_AU}\"}}, .m_chain_tx_count = ${RPC_NCHAINTX}, .blockhash = consteval_ctor(uint256{\"${RPC_BLOCKHASH}\"})},"
|
||||
echo
|
||||
echo
|
||||
echo "-- IBDing more blocks to the server node (height=$FINAL_HEIGHT) so there is a diff between snapshot and tip..."
|
||||
# shellcheck disable=SC2086
|
||||
./src/bitcoind $SERVER_PORTS -logthreadnames=1 -datadir="$SERVER_DATADIR" \
|
||||
$EARLY_IBD_FLAGS -stopatheight="$FINAL_HEIGHT" >/dev/null
|
||||
|
||||
echo
|
||||
echo "-- Starting the server node to provide blocks to the client node..."
|
||||
# shellcheck disable=SC2086
|
||||
./src/bitcoind $SERVER_PORTS -logthreadnames=1 -debug=net -datadir="$SERVER_DATADIR" \
|
||||
$EARLY_IBD_FLAGS -connect=0 -listen=1 >/dev/null &
|
||||
SERVER_PID="$!"
|
||||
server_sleep_til_boot
|
||||
|
||||
echo
|
||||
echo "-- Okay, what you're about to see is the client starting up and activating the snapshot."
|
||||
echo " I'm going to display the top 14 log lines from the client on top of an RPC called"
|
||||
echo " getchainstates, which is like getblockchaininfo but for both the snapshot and "
|
||||
echo " background validation chainstates."
|
||||
echo
|
||||
echo " You're going to first see the snapshot chainstate sync to the server's tip, then"
|
||||
echo " the background IBD chain kicks in to validate up to the base of the snapshot."
|
||||
echo
|
||||
echo " Once validation of the snapshot is done, you should see log lines indicating"
|
||||
echo " that we've deleted the background validation chainstate."
|
||||
echo
|
||||
echo " Once everything completes, exit the watch command with CTRL+C."
|
||||
echo
|
||||
read -p "When you're ready for all this, hit [enter]" _
|
||||
|
||||
echo
|
||||
echo "-- Starting the client node to get headers from the server, then load the snapshot..."
|
||||
# shellcheck disable=SC2086
|
||||
./src/bitcoind $CLIENT_PORTS $ALL_INDEXES -logthreadnames=1 -datadir="$CLIENT_DATADIR" \
|
||||
-connect=0 -addnode=127.0.0.1:$SERVER_PORT -debug=net $EARLY_IBD_FLAGS >/dev/null &
|
||||
CLIENT_PID="$!"
|
||||
client_sleep_til_boot
|
||||
|
||||
echo
|
||||
echo "-- Initial state of the client:"
|
||||
client_rpc getchainstates
|
||||
|
||||
echo
|
||||
echo "-- Loading UTXO snapshot into client. Calling RPC in a loop..."
|
||||
while ! client_rpc loadtxoutset "$UTXO_DAT_FILE" ; do sleep 10; done
|
||||
|
||||
watch -n 0.3 "( tail -n 14 $CLIENT_DATADIR/debug.log ; echo ; ./src/bitcoin-cli -rpcport=$CLIENT_RPC_PORT -datadir=$CLIENT_DATADIR getchainstates) | cat"
|
||||
|
||||
echo
|
||||
echo "-- Okay, now I'm going to restart the client to make sure that the snapshot chain reloads "
|
||||
echo " as the main chain properly..."
|
||||
echo
|
||||
echo " Press CTRL+C after you're satisfied to exit the demo"
|
||||
echo
|
||||
read -p "Press [enter] to continue"
|
||||
|
||||
client_sleep_til_boot
|
||||
# shellcheck disable=SC2086
|
||||
./src/bitcoind $CLIENT_PORTS $ALL_INDEXES -logthreadnames=1 -datadir="$CLIENT_DATADIR" -connect=0 \
|
||||
-addnode=127.0.0.1:$SERVER_PORT "$EARLY_IBD_FLAGS" >/dev/null &
|
||||
CLIENT_PID="$!"
|
||||
client_sleep_til_boot
|
||||
|
||||
watch -n 0.3 "( tail -n 14 $CLIENT_DATADIR/debug.log ; echo ; ./src/bitcoin-cli -rpcport=$CLIENT_RPC_PORT -datadir=$CLIENT_DATADIR getchainstates) | cat"
|
||||
|
||||
echo
|
||||
echo "-- Done!"
|
|
@ -1,104 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Copyright (c) 2019-2023 The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
#
|
||||
export LC_ALL=C
|
||||
|
||||
set -ueo pipefail
|
||||
|
||||
NETWORK_DISABLED=false
|
||||
|
||||
if (( $# < 3 )); then
|
||||
echo 'Usage: utxo_snapshot.sh <generate-at-height> <snapshot-out-path> <bitcoin-cli-call ...>'
|
||||
echo
|
||||
echo " if <snapshot-out-path> is '-', don't produce a snapshot file but instead print the "
|
||||
echo " expected assumeutxo hash"
|
||||
echo
|
||||
echo 'Examples:'
|
||||
echo
|
||||
echo " ./contrib/devtools/utxo_snapshot.sh 570000 utxo.dat ./src/bitcoin-cli -datadir=\$(pwd)/testdata"
|
||||
echo ' ./contrib/devtools/utxo_snapshot.sh 570000 - ./src/bitcoin-cli'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
GENERATE_AT_HEIGHT="${1}"; shift;
|
||||
OUTPUT_PATH="${1}"; shift;
|
||||
# Most of the calls we make take a while to run, so pad with a lengthy timeout.
|
||||
BITCOIN_CLI_CALL="${*} -rpcclienttimeout=9999999"
|
||||
|
||||
# Check if the node is pruned and get the pruned block height
|
||||
PRUNED=$( ${BITCOIN_CLI_CALL} getblockchaininfo | awk '/pruneheight/ {print $2}' | tr -d ',' )
|
||||
|
||||
if (( GENERATE_AT_HEIGHT < PRUNED )); then
|
||||
echo "Error: The requested snapshot height (${GENERATE_AT_HEIGHT}) should be greater than the pruned block height (${PRUNED})."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check current block height to ensure the node has synchronized past the required block
|
||||
CURRENT_BLOCK_HEIGHT=$(${BITCOIN_CLI_CALL} getblockcount)
|
||||
PIVOT_BLOCK_HEIGHT=$(( GENERATE_AT_HEIGHT + 1 ))
|
||||
|
||||
if (( PIVOT_BLOCK_HEIGHT > CURRENT_BLOCK_HEIGHT )); then
|
||||
(>&2 echo "Error: The node has not yet synchronized to block height ${PIVOT_BLOCK_HEIGHT}.")
|
||||
(>&2 echo "Please wait until the node has synchronized past this block height and try again.")
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Early exit if file at OUTPUT_PATH already exists
|
||||
if [[ -e "$OUTPUT_PATH" ]]; then
|
||||
(>&2 echo "Error: $OUTPUT_PATH already exists or is not a valid path.")
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate that the path is correct
|
||||
if [[ "${OUTPUT_PATH}" != "-" && ! -d "$(dirname "${OUTPUT_PATH}")" ]]; then
|
||||
(>&2 echo "Error: The directory $(dirname "${OUTPUT_PATH}") does not exist.")
|
||||
exit 1
|
||||
fi
|
||||
|
||||
function cleanup {
|
||||
(>&2 echo "Restoring chain to original height; this may take a while")
|
||||
${BITCOIN_CLI_CALL} reconsiderblock "${PIVOT_BLOCKHASH}"
|
||||
|
||||
if $NETWORK_DISABLED; then
|
||||
(>&2 echo "Restoring network activity")
|
||||
${BITCOIN_CLI_CALL} setnetworkactive true
|
||||
fi
|
||||
}
|
||||
|
||||
function early_exit {
|
||||
(>&2 echo "Exiting due to Ctrl-C")
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Prompt the user to disable network activity
|
||||
read -p "Do you want to disable network activity (setnetworkactive false) before running invalidateblock? (Y/n): " -r
|
||||
if [[ "$REPLY" =~ ^[Yy]*$ || -z "$REPLY" ]]; then
|
||||
# User input is "Y", "y", or Enter key, proceed with the action
|
||||
NETWORK_DISABLED=true
|
||||
(>&2 echo "Disabling network activity")
|
||||
${BITCOIN_CLI_CALL} setnetworkactive false
|
||||
else
|
||||
(>&2 echo "Network activity remains enabled")
|
||||
fi
|
||||
|
||||
# Block we'll invalidate/reconsider to rewind/fast-forward the chain.
|
||||
PIVOT_BLOCKHASH=$($BITCOIN_CLI_CALL getblockhash $(( GENERATE_AT_HEIGHT + 1 )) )
|
||||
|
||||
# Trap for normal exit and Ctrl-C
|
||||
trap cleanup EXIT
|
||||
trap early_exit INT
|
||||
|
||||
(>&2 echo "Rewinding chain back to height ${GENERATE_AT_HEIGHT} (by invalidating ${PIVOT_BLOCKHASH}); this may take a while")
|
||||
${BITCOIN_CLI_CALL} invalidateblock "${PIVOT_BLOCKHASH}"
|
||||
|
||||
if [[ "${OUTPUT_PATH}" = "-" ]]; then
|
||||
(>&2 echo "Generating txoutset info...")
|
||||
${BITCOIN_CLI_CALL} gettxoutsetinfo | grep hash_serialized_3 | sed 's/^.*: "\(.\+\)\+",/\1/g'
|
||||
else
|
||||
(>&2 echo "Generating UTXO snapshot...")
|
||||
${BITCOIN_CLI_CALL} dumptxoutset "${OUTPUT_PATH}"
|
||||
fi
|
85
doc/assumeutxo.md
Normal file
85
doc/assumeutxo.md
Normal file
|
@ -0,0 +1,85 @@
|
|||
# Assumeutxo Usage
|
||||
|
||||
Assumeutxo is a feature that allows fast bootstrapping of a validating bitcoind
|
||||
instance.
|
||||
|
||||
For notes on the design of Assumeutxo, please refer to [the design doc](/doc/assumeutxo.md).
|
||||
|
||||
## Loading a snapshot
|
||||
|
||||
There is currently no canonical source for snapshots, but any downloaded snapshot
|
||||
will be checked against a hash that's been hardcoded in source code. If there is
|
||||
no source for the snapshot you need, you can generate it yourself using
|
||||
`dumptxoutset` on another node that is already synced (see
|
||||
[Generating a snapshot](#generating-a-snapshot)).
|
||||
|
||||
Once you've obtained the snapshot, you can use the RPC command `loadtxoutset` to
|
||||
load it.
|
||||
|
||||
```
|
||||
$ bitcoin-cli loadtxoutset /path/to/input
|
||||
```
|
||||
|
||||
After the snapshot has loaded, the syncing process of both the snapshot chain
|
||||
and the background IBD chain can be monitored with the `getchainstates` RPC.
|
||||
|
||||
### Pruning
|
||||
|
||||
A pruned node can load a snapshot. To save space, it's possible to delete the
|
||||
snapshot file as soon as `loadtxoutset` finishes.
|
||||
|
||||
The minimum `-prune` setting is 550 MiB, but this functionality ignores that
|
||||
minimum and uses at least 1100 MiB.
|
||||
|
||||
As the background sync continues there will be temporarily two chainstate
|
||||
directories, each multiple gigabytes in size (likely growing larger than the
|
||||
downloaded snapshot).
|
||||
|
||||
### Indexes
|
||||
|
||||
Indexes work but don't take advantage of this feature. They always start building
|
||||
from the genesis block and can only apply blocks in order. Once the background
|
||||
validation reaches the snapshot block, indexes will continue to build all the
|
||||
way to the tip.
|
||||
|
||||
|
||||
For indexes that support pruning, note that these indexes only allow blocks that
|
||||
were already indexed to be pruned. Blocks that are not indexed yet will also
|
||||
not be pruned.
|
||||
|
||||
This means that, if the snapshot is old, then a lot of blocks after the snapshot
|
||||
block will need to be downloaded, and these blocks can't be pruned until they
|
||||
are indexed, so they could consume a lot of disk space until indexing catches up
|
||||
to the snapshot block.
|
||||
|
||||
## Generating a snapshot
|
||||
|
||||
The RPC command `dumptxoutset` can be used to generate a snapshot for the current
|
||||
tip (using type "latest") or a recent height (using type "rollback"). A generated
|
||||
snapshot from one node can then be loaded
|
||||
on any other node. However, keep in mind that the snapshot hash needs to be
|
||||
listed in the chainparams to make it usable. If there is no snapshot hash for
|
||||
the height you have chosen already, you will need to change the code there and
|
||||
re-compile.
|
||||
|
||||
Using the type parameter "rollback", `dumptxoutset` can also be used to verify the
|
||||
hardcoded snapshot hash in the source code by regenerating the snapshot and
|
||||
comparing the hash.
|
||||
|
||||
Example usage:
|
||||
|
||||
```
|
||||
$ bitcoin-cli -rpcclienttimeout=0 dumptxoutset /path/to/output rollback
|
||||
```
|
||||
|
||||
For most of the duration of `dumptxoutset` running the node is in a temporary
|
||||
state that does not actually reflect reality, i.e. blocks are marked invalid
|
||||
although we know they are not invalid. Because of this it is discouraged to
|
||||
interact with the node in any other way during this time to avoid inconsistent
|
||||
results and race conditions, particularly RPCs that interact with blockstorage.
|
||||
This inconsistent state is also why network activity is temporarily disabled,
|
||||
causing us to disconnect from all peers.
|
||||
|
||||
`dumptxoutset` takes some time to complete, independent of hardware and
|
||||
what parameter is chosen. Because of that it is recommended to increase the RPC
|
||||
client timeout value (use `-rpcclienttimeout=0` for no timeout).
|
|
@ -1,47 +1,6 @@
|
|||
# assumeutxo
|
||||
# Assumeutxo Design
|
||||
|
||||
Assumeutxo is a feature that allows fast bootstrapping of a validating bitcoind
|
||||
instance.
|
||||
|
||||
## Loading a snapshot
|
||||
|
||||
There is currently no canonical source for snapshots, but any downloaded snapshot
|
||||
will be checked against a hash that's been hardcoded in source code.
|
||||
|
||||
Once you've obtained the snapshot, you can use the RPC command `loadtxoutset` to
|
||||
load it.
|
||||
|
||||
### Pruning
|
||||
|
||||
A pruned node can load a snapshot. To save space, it's possible to delete the
|
||||
snapshot file as soon as `loadtxoutset` finishes.
|
||||
|
||||
The minimum `-prune` setting is 550 MiB, but this functionality ignores that
|
||||
minimum and uses at least 1100 MiB.
|
||||
|
||||
As the background sync continues there will be temporarily two chainstate
|
||||
directories, each multiple gigabytes in size (likely growing larger than the
|
||||
downloaded snapshot).
|
||||
|
||||
### Indexes
|
||||
|
||||
Indexes work but don't take advantage of this feature. They always start building
|
||||
from the genesis block. Once the background validation reaches the snapshot block,
|
||||
indexes will continue to build all the way to the tip.
|
||||
|
||||
For indexes that support pruning, note that no pruning will take place between
|
||||
the snapshot and the tip, until the background sync has completed - after which
|
||||
everything is pruned. Depending on how old the snapshot is, this may temporarily
|
||||
use a significant amount of disk space.
|
||||
|
||||
## Generating a snapshot
|
||||
|
||||
The RPC command `dumptxoutset` can be used to generate a snapshot. This can be used
|
||||
to create a snapshot on one node that you wish to load on another node.
|
||||
It can also be used to verify the hardcoded snapshot hash in the source code.
|
||||
|
||||
The utility script
|
||||
`./contrib/devtools/utxo_snapshot.sh` may be of use.
|
||||
For notes on the usage of Assumeutxo, please refer to [the usage doc](/doc/assumeutxo.md).
|
||||
|
||||
## General background
|
||||
|
||||
|
@ -79,7 +38,7 @@ data.
|
|||
### "Normal" operation via initial block download
|
||||
|
||||
`ChainstateManager` manages a single Chainstate object, for which
|
||||
`m_snapshot_blockhash` is null. This chainstate is (maybe obviously)
|
||||
`m_from_snapshot_blockhash` is `std::nullopt`. This chainstate is (maybe obviously)
|
||||
considered active. This is the "traditional" mode of operation for bitcoind.
|
||||
|
||||
| | |
|
||||
|
|
|
@ -75,6 +75,22 @@ static GlobalMutex cs_blockchange;
|
|||
static std::condition_variable cond_blockchange;
|
||||
static CUpdatedBlock latestblock GUARDED_BY(cs_blockchange);
|
||||
|
||||
std::tuple<std::unique_ptr<CCoinsViewCursor>, CCoinsStats, const CBlockIndex*>
|
||||
PrepareUTXOSnapshot(
|
||||
Chainstate& chainstate,
|
||||
const std::function<void()>& interruption_point = {})
|
||||
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
|
||||
|
||||
UniValue WriteUTXOSnapshot(
|
||||
Chainstate& chainstate,
|
||||
CCoinsViewCursor* pcursor,
|
||||
CCoinsStats* maybe_stats,
|
||||
const CBlockIndex* tip,
|
||||
AutoFile& afile,
|
||||
const fs::path& path,
|
||||
const fs::path& temppath,
|
||||
const std::function<void()>& interruption_point = {});
|
||||
|
||||
/* Calculate the difficulty for a given block index.
|
||||
*/
|
||||
double GetDifficulty(const CBlockIndex& blockindex)
|
||||
|
@ -1577,6 +1593,27 @@ static RPCHelpMan preciousblock()
|
|||
};
|
||||
}
|
||||
|
||||
void InvalidateBlock(ChainstateManager& chainman, const uint256 block_hash) {
|
||||
BlockValidationState state;
|
||||
CBlockIndex* pblockindex;
|
||||
{
|
||||
LOCK(chainman.GetMutex());
|
||||
pblockindex = chainman.m_blockman.LookupBlockIndex(block_hash);
|
||||
if (!pblockindex) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
|
||||
}
|
||||
}
|
||||
chainman.ActiveChainstate().InvalidateBlock(state, pblockindex);
|
||||
|
||||
if (state.IsValid()) {
|
||||
chainman.ActiveChainstate().ActivateBestChain(state);
|
||||
}
|
||||
|
||||
if (!state.IsValid()) {
|
||||
throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
static RPCHelpMan invalidateblock()
|
||||
{
|
||||
return RPCHelpMan{"invalidateblock",
|
||||
|
@ -1591,33 +1628,35 @@ static RPCHelpMan invalidateblock()
|
|||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
uint256 hash(ParseHashV(request.params[0], "blockhash"));
|
||||
BlockValidationState state;
|
||||
|
||||
ChainstateManager& chainman = EnsureAnyChainman(request.context);
|
||||
CBlockIndex* pblockindex;
|
||||
{
|
||||
LOCK(cs_main);
|
||||
pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
|
||||
if (!pblockindex) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
|
||||
}
|
||||
}
|
||||
chainman.ActiveChainstate().InvalidateBlock(state, pblockindex);
|
||||
uint256 hash(ParseHashV(request.params[0], "blockhash"));
|
||||
|
||||
if (state.IsValid()) {
|
||||
chainman.ActiveChainstate().ActivateBestChain(state);
|
||||
}
|
||||
|
||||
if (!state.IsValid()) {
|
||||
throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
|
||||
}
|
||||
InvalidateBlock(chainman, hash);
|
||||
|
||||
return UniValue::VNULL;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
void ReconsiderBlock(ChainstateManager& chainman, uint256 block_hash) {
|
||||
{
|
||||
LOCK(chainman.GetMutex());
|
||||
CBlockIndex* pblockindex = chainman.m_blockman.LookupBlockIndex(block_hash);
|
||||
if (!pblockindex) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
|
||||
}
|
||||
|
||||
chainman.ActiveChainstate().ResetBlockFailureFlags(pblockindex);
|
||||
}
|
||||
|
||||
BlockValidationState state;
|
||||
chainman.ActiveChainstate().ActivateBestChain(state);
|
||||
|
||||
if (!state.IsValid()) {
|
||||
throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
static RPCHelpMan reconsiderblock()
|
||||
{
|
||||
return RPCHelpMan{"reconsiderblock",
|
||||
|
@ -1636,22 +1675,7 @@ static RPCHelpMan reconsiderblock()
|
|||
ChainstateManager& chainman = EnsureAnyChainman(request.context);
|
||||
uint256 hash(ParseHashV(request.params[0], "blockhash"));
|
||||
|
||||
{
|
||||
LOCK(cs_main);
|
||||
CBlockIndex* pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
|
||||
if (!pblockindex) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
|
||||
}
|
||||
|
||||
chainman.ActiveChainstate().ResetBlockFailureFlags(pblockindex);
|
||||
}
|
||||
|
||||
BlockValidationState state;
|
||||
chainman.ActiveChainstate().ActivateBestChain(state);
|
||||
|
||||
if (!state.IsValid()) {
|
||||
throw JSONRPCError(RPC_DATABASE_ERROR, state.ToString());
|
||||
}
|
||||
ReconsiderBlock(chainman, hash);
|
||||
|
||||
return UniValue::VNULL;
|
||||
},
|
||||
|
@ -2640,6 +2664,25 @@ static RPCHelpMan getblockfilter()
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* RAII class that disables the network in its constructor and enables it in its
|
||||
* destructor.
|
||||
*/
|
||||
class NetworkDisable
|
||||
{
|
||||
CConnman& m_connman;
|
||||
public:
|
||||
NetworkDisable(CConnman& connman) : m_connman(connman) {
|
||||
m_connman.SetNetworkActive(false);
|
||||
if (m_connman.GetNetworkActive()) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Network activity could not be suspended.");
|
||||
}
|
||||
};
|
||||
~NetworkDisable() {
|
||||
m_connman.SetNetworkActive(true);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Serialize the UTXO set to a file for loading elsewhere.
|
||||
*
|
||||
|
@ -2649,9 +2692,20 @@ static RPCHelpMan dumptxoutset()
|
|||
{
|
||||
return RPCHelpMan{
|
||||
"dumptxoutset",
|
||||
"Write the serialized UTXO set to a file.",
|
||||
"Write the serialized UTXO set to a file. This can be used in loadtxoutset afterwards if this snapshot height is supported in the chainparams as well.\n\n"
|
||||
"Unless the the \"latest\" type is requested, the node will roll back to the requested height and network activity will be suspended during this process. "
|
||||
"Because of this it is discouraged to interact with the node in any other way during the execution of this call to avoid inconsistent results and race conditions, particularly RPCs that interact with blockstorage.\n\n"
|
||||
"This call may take several minutes. Make sure to use no RPC timeout (bitcoin-cli -rpcclienttimeout=0)",
|
||||
{
|
||||
{"path", RPCArg::Type::STR, RPCArg::Optional::NO, "Path to the output file. If relative, will be prefixed by datadir."},
|
||||
{"type", RPCArg::Type::STR, RPCArg::Default(""), "The type of snapshot to create. Can be \"latest\" to create a snapshot of the current UTXO set or \"rollback\" to temporarily roll back the state of the node to a historical block before creating the snapshot of a historical UTXO set. This parameter can be omitted if a separate \"rollback\" named parameter is specified indicating the height or hash of a specific historical block. If \"rollback\" is specified and separate \"rollback\" named parameter is not specified, this will roll back to the latest valid snapshot block that currently be loaded with loadtxoutset."},
|
||||
{"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "",
|
||||
{
|
||||
{"rollback", RPCArg::Type::NUM, RPCArg::Optional::OMITTED,
|
||||
"Height or hash of the block to roll back to before creating the snapshot. Note: The further this number is from the tip, the longer this process will take. Consider setting a higher -rpcclienttimeout value in this case.",
|
||||
RPCArgOptions{.skip_type_check = true, .type_str = {"", "string or numeric"}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
|
@ -2665,10 +2719,33 @@ static RPCHelpMan dumptxoutset()
|
|||
}
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("dumptxoutset", "utxo.dat")
|
||||
HelpExampleCli("-rpcclienttimeout=0 dumptxoutset", "utxo.dat latest") +
|
||||
HelpExampleCli("-rpcclienttimeout=0 dumptxoutset", "utxo.dat rollback") +
|
||||
HelpExampleCli("-rpcclienttimeout=0 -named dumptxoutset", R"(utxo.dat rollback=853456)")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||
const CBlockIndex* tip{WITH_LOCK(::cs_main, return node.chainman->ActiveChain().Tip())};
|
||||
const CBlockIndex* target_index{nullptr};
|
||||
const std::string snapshot_type{self.Arg<std::string>("type")};
|
||||
const UniValue options{request.params[2].isNull() ? UniValue::VOBJ : request.params[2]};
|
||||
if (options.exists("rollback")) {
|
||||
if (!snapshot_type.empty() && snapshot_type != "rollback") {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid snapshot type \"%s\" specified with rollback option", snapshot_type));
|
||||
}
|
||||
target_index = ParseHashOrHeight(options["rollback"], *node.chainman);
|
||||
} else if (snapshot_type == "rollback") {
|
||||
auto snapshot_heights = node.chainman->GetParams().GetAvailableSnapshotHeights();
|
||||
CHECK_NONFATAL(snapshot_heights.size() > 0);
|
||||
auto max_height = std::max_element(snapshot_heights.begin(), snapshot_heights.end());
|
||||
target_index = ParseHashOrHeight(*max_height, *node.chainman);
|
||||
} else if (snapshot_type == "latest") {
|
||||
target_index = tip;
|
||||
} else {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid snapshot type \"%s\" specified. Please specify \"rollback\" or \"latest\"", snapshot_type));
|
||||
}
|
||||
|
||||
const ArgsManager& args{EnsureAnyArgsman(request.context)};
|
||||
const fs::path path = fsbridge::AbsPathJoin(args.GetDataDirNet(), fs::u8path(request.params[0].get_str()));
|
||||
// Write to a temporary path and then move into `path` on completion
|
||||
|
@ -2690,10 +2767,73 @@ static RPCHelpMan dumptxoutset()
|
|||
"Couldn't open file " + temppath.utf8string() + " for writing.");
|
||||
}
|
||||
|
||||
NodeContext& node = EnsureAnyNodeContext(request.context);
|
||||
UniValue result = CreateUTXOSnapshot(
|
||||
node, node.chainman->ActiveChainstate(), afile, path, temppath);
|
||||
fs::rename(temppath, path);
|
||||
CConnman& connman = EnsureConnman(node);
|
||||
const CBlockIndex* invalidate_index{nullptr};
|
||||
std::unique_ptr<NetworkDisable> disable_network;
|
||||
|
||||
// If the user wants to dump the txoutset of the current tip, we don't have
|
||||
// to roll back at all
|
||||
if (target_index != tip) {
|
||||
// If the node is running in pruned mode we ensure all necessary block
|
||||
// data is available before starting to roll back.
|
||||
if (node.chainman->m_blockman.IsPruneMode()) {
|
||||
LOCK(node.chainman->GetMutex());
|
||||
const CBlockIndex* current_tip{node.chainman->ActiveChain().Tip()};
|
||||
const CBlockIndex* first_block{node.chainman->m_blockman.GetFirstBlock(*current_tip, /*status_mask=*/BLOCK_HAVE_MASK)};
|
||||
if (first_block->nHeight > target_index->nHeight) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Could not roll back to requested height since necessary block data is already pruned.");
|
||||
}
|
||||
}
|
||||
|
||||
// Suspend network activity for the duration of the process when we are
|
||||
// rolling back the chain to get a utxo set from a past height. We do
|
||||
// this so we don't punish peers that send us that send us data that
|
||||
// seems wrong in this temporary state. For example a normal new block
|
||||
// would be classified as a block connecting an invalid block.
|
||||
disable_network = std::make_unique<NetworkDisable>(connman);
|
||||
|
||||
invalidate_index = WITH_LOCK(::cs_main, return node.chainman->ActiveChain().Next(target_index));
|
||||
InvalidateBlock(*node.chainman, invalidate_index->GetBlockHash());
|
||||
}
|
||||
|
||||
Chainstate* chainstate;
|
||||
std::unique_ptr<CCoinsViewCursor> cursor;
|
||||
CCoinsStats stats;
|
||||
UniValue result;
|
||||
UniValue error;
|
||||
{
|
||||
// Lock the chainstate before calling PrepareUtxoSnapshot, to be able
|
||||
// to get a UTXO database cursor while the chain is pointing at the
|
||||
// target block. After that, release the lock while calling
|
||||
// WriteUTXOSnapshot. The cursor will remain valid and be used by
|
||||
// WriteUTXOSnapshot to write a consistent snapshot even if the
|
||||
// chainstate changes.
|
||||
LOCK(node.chainman->GetMutex());
|
||||
chainstate = &node.chainman->ActiveChainstate();
|
||||
// In case there is any issue with a block being read from disk we need
|
||||
// to stop here, otherwise the dump could still be created for the wrong
|
||||
// height.
|
||||
// The new tip could also not be the target block if we have a stale
|
||||
// sister block of invalidate_index. This block (or a descendant) would
|
||||
// be activated as the new tip and we would not get to new_tip_index.
|
||||
if (target_index != chainstate->m_chain.Tip()) {
|
||||
LogInfo("Failed to roll back to requested height, reverting to tip.\n");
|
||||
error = JSONRPCError(RPC_MISC_ERROR, "Could not roll back to requested height.");
|
||||
} else {
|
||||
std::tie(cursor, stats, tip) = PrepareUTXOSnapshot(*chainstate, node.rpc_interruption_point);
|
||||
}
|
||||
}
|
||||
|
||||
if (error.isNull()) {
|
||||
result = WriteUTXOSnapshot(*chainstate, cursor.get(), &stats, tip, afile, path, temppath, node.rpc_interruption_point);
|
||||
fs::rename(temppath, path);
|
||||
}
|
||||
if (invalidate_index) {
|
||||
ReconsiderBlock(*node.chainman, invalidate_index->GetBlockHash());
|
||||
}
|
||||
if (!error.isNull()) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
result.pushKV("path", path.utf8string());
|
||||
return result;
|
||||
|
@ -2701,12 +2841,10 @@ static RPCHelpMan dumptxoutset()
|
|||
};
|
||||
}
|
||||
|
||||
UniValue CreateUTXOSnapshot(
|
||||
NodeContext& node,
|
||||
std::tuple<std::unique_ptr<CCoinsViewCursor>, CCoinsStats, const CBlockIndex*>
|
||||
PrepareUTXOSnapshot(
|
||||
Chainstate& chainstate,
|
||||
AutoFile& afile,
|
||||
const fs::path& path,
|
||||
const fs::path& temppath)
|
||||
const std::function<void()>& interruption_point)
|
||||
{
|
||||
std::unique_ptr<CCoinsViewCursor> pcursor;
|
||||
std::optional<CCoinsStats> maybe_stats;
|
||||
|
@ -2716,7 +2854,7 @@ UniValue CreateUTXOSnapshot(
|
|||
// We need to lock cs_main to ensure that the coinsdb isn't written to
|
||||
// between (i) flushing coins cache to disk (coinsdb), (ii) getting stats
|
||||
// based upon the coinsdb, and (iii) constructing a cursor to the
|
||||
// coinsdb for use below this block.
|
||||
// coinsdb for use in WriteUTXOSnapshot.
|
||||
//
|
||||
// Cursors returned by leveldb iterate over snapshots, so the contents
|
||||
// of the pcursor will not be affected by simultaneous writes during
|
||||
|
@ -2725,11 +2863,11 @@ UniValue CreateUTXOSnapshot(
|
|||
// See discussion here:
|
||||
// https://github.com/bitcoin/bitcoin/pull/15606#discussion_r274479369
|
||||
//
|
||||
LOCK(::cs_main);
|
||||
AssertLockHeld(::cs_main);
|
||||
|
||||
chainstate.ForceFlushStateToDisk();
|
||||
|
||||
maybe_stats = GetUTXOStats(&chainstate.CoinsDB(), chainstate.m_blockman, CoinStatsHashType::HASH_SERIALIZED, node.rpc_interruption_point);
|
||||
maybe_stats = GetUTXOStats(&chainstate.CoinsDB(), chainstate.m_blockman, CoinStatsHashType::HASH_SERIALIZED, interruption_point);
|
||||
if (!maybe_stats) {
|
||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
|
||||
}
|
||||
|
@ -2738,6 +2876,19 @@ UniValue CreateUTXOSnapshot(
|
|||
tip = CHECK_NONFATAL(chainstate.m_blockman.LookupBlockIndex(maybe_stats->hashBlock));
|
||||
}
|
||||
|
||||
return {std::move(pcursor), *CHECK_NONFATAL(maybe_stats), tip};
|
||||
}
|
||||
|
||||
UniValue WriteUTXOSnapshot(
|
||||
Chainstate& chainstate,
|
||||
CCoinsViewCursor* pcursor,
|
||||
CCoinsStats* maybe_stats,
|
||||
const CBlockIndex* tip,
|
||||
AutoFile& afile,
|
||||
const fs::path& path,
|
||||
const fs::path& temppath,
|
||||
const std::function<void()>& interruption_point)
|
||||
{
|
||||
LOG_TIME_SECONDS(strprintf("writing UTXO snapshot at height %s (%s) to file %s (via %s)",
|
||||
tip->nHeight, tip->GetBlockHash().ToString(),
|
||||
fs::PathToString(path), fs::PathToString(temppath)));
|
||||
|
@ -2773,7 +2924,7 @@ UniValue CreateUTXOSnapshot(
|
|||
pcursor->GetKey(key);
|
||||
last_hash = key.hash;
|
||||
while (pcursor->Valid()) {
|
||||
if (iter % 5000 == 0) node.rpc_interruption_point();
|
||||
if (iter % 5000 == 0) interruption_point();
|
||||
++iter;
|
||||
if (pcursor->GetKey(key) && pcursor->GetValue(coin)) {
|
||||
if (key.hash != last_hash) {
|
||||
|
@ -2804,6 +2955,17 @@ UniValue CreateUTXOSnapshot(
|
|||
return result;
|
||||
}
|
||||
|
||||
UniValue CreateUTXOSnapshot(
|
||||
node::NodeContext& node,
|
||||
Chainstate& chainstate,
|
||||
AutoFile& afile,
|
||||
const fs::path& path,
|
||||
const fs::path& tmppath)
|
||||
{
|
||||
auto [cursor, stats, tip]{WITH_LOCK(::cs_main, return PrepareUTXOSnapshot(chainstate, node.rpc_interruption_point))};
|
||||
return WriteUTXOSnapshot(chainstate, cursor.get(), &stats, tip, afile, path, tmppath, node.rpc_interruption_point);
|
||||
}
|
||||
|
||||
static RPCHelpMan loadtxoutset()
|
||||
{
|
||||
return RPCHelpMan{
|
||||
|
|
|
@ -48,7 +48,7 @@ UniValue blockheaderToJSON(const CBlockIndex& tip, const CBlockIndex& blockindex
|
|||
void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES], std::vector<std::pair<CAmount, int64_t>>& scores, int64_t total_weight);
|
||||
|
||||
/**
|
||||
* Helper to create UTXO snapshots given a chainstate and a file handle.
|
||||
* Test-only helper to create UTXO snapshots given a chainstate and a file handle.
|
||||
* @return a UniValue map containing metadata about the snapshot.
|
||||
*/
|
||||
UniValue CreateUTXOSnapshot(
|
||||
|
|
|
@ -187,6 +187,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||
{ "gettxoutproof", 0, "txids" },
|
||||
{ "gettxoutsetinfo", 1, "hash_or_height" },
|
||||
{ "gettxoutsetinfo", 2, "use_index"},
|
||||
{ "dumptxoutset", 2, "options" },
|
||||
{ "dumptxoutset", 2, "rollback" },
|
||||
{ "lockunspent", 0, "unlock" },
|
||||
{ "lockunspent", 1, "transactions" },
|
||||
{ "lockunspent", 2, "persistent" },
|
||||
|
|
|
@ -914,7 +914,7 @@ private:
|
|||
//! Internal helper for ActivateSnapshot().
|
||||
//!
|
||||
//! De-serialization of a snapshot that is created with
|
||||
//! CreateUTXOSnapshot() in rpc/blockchain.cpp.
|
||||
//! the dumptxoutset RPC.
|
||||
//! To reduce space the serialization format of the snapshot avoids
|
||||
//! duplication of tx hashes. The code takes advantage of the guarantee by
|
||||
//! leveldb that keys are lexicographically sorted.
|
||||
|
|
|
@ -22,6 +22,7 @@ from test_framework.util import (
|
|||
assert_approx,
|
||||
assert_equal,
|
||||
assert_raises_rpc_error,
|
||||
sha256sum_file,
|
||||
)
|
||||
from test_framework.wallet import (
|
||||
getnewdestination,
|
||||
|
@ -295,7 +296,7 @@ class AssumeutxoTest(BitcoinTestFramework):
|
|||
assert_equal(n1.getblockcount(), START_HEIGHT)
|
||||
|
||||
self.log.info(f"Creating a UTXO snapshot at height {SNAPSHOT_BASE_HEIGHT}")
|
||||
dump_output = n0.dumptxoutset('utxos.dat')
|
||||
dump_output = n0.dumptxoutset('utxos.dat', "latest")
|
||||
|
||||
self.log.info("Test loading snapshot when the node tip is on the same block as the snapshot")
|
||||
assert_equal(n0.getblockcount(), SNAPSHOT_BASE_HEIGHT)
|
||||
|
@ -320,12 +321,16 @@ class AssumeutxoTest(BitcoinTestFramework):
|
|||
for n in self.nodes:
|
||||
assert_equal(n.getblockchaininfo()["headers"], SNAPSHOT_BASE_HEIGHT)
|
||||
|
||||
assert_equal(
|
||||
dump_output['txoutset_hash'],
|
||||
"a4bf3407ccb2cc0145c49ebba8fa91199f8a3903daf0883875941497d2493c27")
|
||||
assert_equal(dump_output["nchaintx"], blocks[SNAPSHOT_BASE_HEIGHT].chain_tx)
|
||||
assert_equal(n0.getblockchaininfo()["blocks"], SNAPSHOT_BASE_HEIGHT)
|
||||
|
||||
def check_dump_output(output):
|
||||
assert_equal(
|
||||
output['txoutset_hash'],
|
||||
"a4bf3407ccb2cc0145c49ebba8fa91199f8a3903daf0883875941497d2493c27")
|
||||
assert_equal(output["nchaintx"], blocks[SNAPSHOT_BASE_HEIGHT].chain_tx)
|
||||
|
||||
check_dump_output(dump_output)
|
||||
|
||||
# Mine more blocks on top of the snapshot that n1 hasn't yet seen. This
|
||||
# will allow us to test n1's sync-to-tip on top of a snapshot.
|
||||
self.generate(n0, nblocks=100, sync_fun=self.no_op)
|
||||
|
@ -335,6 +340,39 @@ class AssumeutxoTest(BitcoinTestFramework):
|
|||
|
||||
assert_equal(n0.getblockchaininfo()["blocks"], FINAL_HEIGHT)
|
||||
|
||||
self.log.info(f"Check that dumptxoutset works for past block heights")
|
||||
# rollback defaults to the snapshot base height
|
||||
dump_output2 = n0.dumptxoutset('utxos2.dat', "rollback")
|
||||
check_dump_output(dump_output2)
|
||||
assert_equal(sha256sum_file(dump_output['path']), sha256sum_file(dump_output2['path']))
|
||||
|
||||
# Rollback with specific height
|
||||
dump_output3 = n0.dumptxoutset('utxos3.dat', rollback=SNAPSHOT_BASE_HEIGHT)
|
||||
check_dump_output(dump_output3)
|
||||
assert_equal(sha256sum_file(dump_output['path']), sha256sum_file(dump_output3['path']))
|
||||
|
||||
# Specified height that is not a snapshot height
|
||||
prev_snap_height = SNAPSHOT_BASE_HEIGHT - 1
|
||||
dump_output4 = n0.dumptxoutset(path='utxos4.dat', rollback=prev_snap_height)
|
||||
assert_equal(
|
||||
dump_output4['txoutset_hash'],
|
||||
"8a1db0d6e958ce0d7c963bc6fc91ead596c027129bacec68acc40351037b09d7")
|
||||
assert sha256sum_file(dump_output['path']) != sha256sum_file(dump_output4['path'])
|
||||
|
||||
# Use a hash instead of a height
|
||||
prev_snap_hash = n0.getblockhash(prev_snap_height)
|
||||
dump_output5 = n0.dumptxoutset('utxos5.dat', rollback=prev_snap_hash)
|
||||
assert_equal(sha256sum_file(dump_output4['path']), sha256sum_file(dump_output5['path']))
|
||||
|
||||
# TODO: This is a hack to set m_best_header to the correct value after
|
||||
# dumptxoutset/reconsiderblock. Otherwise the wrong error messages are
|
||||
# returned in following tests. It can be removed once this bug is
|
||||
# fixed. See also https://github.com/bitcoin/bitcoin/issues/26245
|
||||
self.restart_node(0, ["-reindex"])
|
||||
|
||||
# Ensure n0 is back at the tip
|
||||
assert_equal(n0.getblockchaininfo()["blocks"], FINAL_HEIGHT)
|
||||
|
||||
self.test_snapshot_with_less_work(dump_output['path'])
|
||||
self.test_invalid_mempool_state(dump_output['path'])
|
||||
self.test_invalid_snapshot_scenarios(dump_output['path'])
|
||||
|
|
|
@ -27,7 +27,7 @@ class DumptxoutsetTest(BitcoinTestFramework):
|
|||
self.generate(node, COINBASE_MATURITY)
|
||||
|
||||
FILENAME = 'txoutset.dat'
|
||||
out = node.dumptxoutset(FILENAME)
|
||||
out = node.dumptxoutset(FILENAME, "latest")
|
||||
expected_path = node.datadir_path / self.chain / FILENAME
|
||||
|
||||
assert expected_path.is_file()
|
||||
|
@ -51,10 +51,14 @@ class DumptxoutsetTest(BitcoinTestFramework):
|
|||
|
||||
# Specifying a path to an existing or invalid file will fail.
|
||||
assert_raises_rpc_error(
|
||||
-8, '{} already exists'.format(FILENAME), node.dumptxoutset, FILENAME)
|
||||
-8, '{} already exists'.format(FILENAME), node.dumptxoutset, FILENAME, "latest")
|
||||
invalid_path = node.datadir_path / "invalid" / "path"
|
||||
assert_raises_rpc_error(
|
||||
-8, "Couldn't open file {}.incomplete for writing".format(invalid_path), node.dumptxoutset, invalid_path)
|
||||
-8, "Couldn't open file {}.incomplete for writing".format(invalid_path), node.dumptxoutset, invalid_path, "latest")
|
||||
|
||||
self.log.info(f"Test that dumptxoutset with unknown dump type fails")
|
||||
assert_raises_rpc_error(
|
||||
-8, 'Invalid snapshot type "bogus" specified. Please specify "rollback" or "latest"', node.dumptxoutset, 'utxos.dat', "bogus")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -93,7 +93,7 @@ class AssumeutxoTest(BitcoinTestFramework):
|
|||
|
||||
self.log.info(
|
||||
f"Creating a UTXO snapshot at height {SNAPSHOT_BASE_HEIGHT}")
|
||||
dump_output = n0.dumptxoutset('utxos.dat')
|
||||
dump_output = n0.dumptxoutset('utxos.dat', "latest")
|
||||
|
||||
assert_equal(
|
||||
dump_output['txoutset_hash'],
|
||||
|
|
Loading…
Add table
Reference in a new issue