Repository: bitcoin/bitcoin (language: cpp)

Issue:
Stuck chainstate when utxo_snapshot.sh fails
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current behaviour

IBD/chainstate gets stuck if utxo_snapshot.sh fails. No new blocks are downloaded. Restarting bitcoind does not help. Taking another snapshot __at an earlier height__ or reindex is required to repair.

utxo_snapshot.sh output:
```
Rewinding chain back to height 788440 (by invalidating 000000000000000000052ac0ce9b8f1a2e7691771e6386f770432c9443dc2af7); this may take a while
Generating UTXO snapshot...
error code: -8
error message:
/home/chris/Desktop/assumeutxo/test/utxo-788440.dat already exists. If you are sure this is what you want, move it out of the way first
```

Note that moving the file or choosing another filename and rerunning does not fix. But taking another snapshot at an earlier height will.

### Expected behaviour

If utxo_snapshot.sh fails, bitcoind should cancel rewind and recover.

Also, if a filepath that already exists is passed to utxosnapshot.sh, it should early exit before rewinding. This could perhaps be considered a separate issue.

### Steps to reproduce

1. Create a dummy file for the purpose of the test e.g. `echo "some bytes so file is not empty" > ./utxo-788440.dat`
2. Run bitcoind and wait for it to sync
3. Run utxosnapshot.sh with some blockheight and a filepath __that already exists__ e.g. `./contrib/devtools/utxo_snapshot.sh 788440 ./utxo-788440.dat ./src/bitcoin-cli`
4. Wait for chain rewind to happen, snapshot to be generated and write to disk fail
5. Observe bitcoind is now stuck with an invalid block at the rewind height

### Relevant log output

```
2023-06-08T23:51:50Z [httpworker.3] UpdateTip: new best=000000000000000000014a9d36f6a19aca593f0c228ae1b1b6fe33361fa67be1 height=788440 version=0x2001e000 log2_work=94.162204 tx=832727241 date='2023-05-06T02:54:13Z' progress=0.987258 cache=790.3MiB(1245160txo)
2023-06-08T23:51:50Z [httpworker.3] InvalidChainFound: invalid block=000000000000000000052ac0ce9b8f1a2e7691771e6386f770432c9443dc2af7  height=788441  log2_work=94.162217  date=2023-05-06T02:55:43Z
2023-06-08T23:51:50Z [httpworker.3] InvalidChainFound:  current best=000000000000000000014a9d36f6a19aca593f0c228ae1b1b6fe33361fa67be1  height=788440  log2_work=94.162204  date=2023-05-06T02:54:13Z
```

```
Rewinding chain back to height 788440 (by invalidating 000000000000000000052ac0ce9b8f1a2e7691771e6386f770432c9443dc2af7); this may take a while
Generating UTXO snapshot...
error code: -8
error message:
/home/chris/Desktop/assumeutxo/test/utxo-788440.dat already exists. If you are sure this is what you want, move it out of the way first
```

### How did you obtain Bitcoin Core

Compiled from source

### What version of Bitcoin Core are you using?

v25.99.0-3dc6fd620e28

### Operating system and version

Linux 6.3.1-arch2-1

### Machine specifications

_No response_


Discussion / hints:
Thanks for the report. I think to solve this without a reindex, you just need to run `reconsiderblock 000000000000000000052ac0ce9b8f1a2e7691771e6386f770432c9443dc2af7`. We should probably amend the script to catch failures or interrupts and do this automatically.
Thanks @jamesob. I can confirm that `reconsiderblock` works as you expected. I'll have a go at updating the script with error handing.

Fix patch:
diff --git a/contrib/devtools/utxo_snapshot.sh b/contrib/devtools/utxo_snapshot.sh
index dee25ff67b61e..80336954a910b 100755
--- a/contrib/devtools/utxo_snapshot.sh
+++ b/contrib/devtools/utxo_snapshot.sh
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 #
-# Copyright (c) 2019 The Bitcoin Core developers
+# 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.
 #
@@ -8,6 +8,8 @@ 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
@@ -26,9 +28,60 @@ 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
+
+# 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}"
 
@@ -39,6 +92,3 @@ else
   (>&2 echo "Generating UTXO snapshot...")
   ${BITCOIN_CLI_CALL} dumptxoutset "${OUTPUT_PATH}"
 fi
-
-(>&2 echo "Restoring chain to original height; this may take a while")
-${BITCOIN_CLI_CALL} reconsiderblock "${PIVOT_BLOCKHASH}"


Test patch: