Repository: bitcoin/bitcoin (language: cpp)

Issue:
rpc: loadtxoutset should return errors that currently go to logs
### Please describe the feature you'd like to see added.

`loadtxoutset` should return (more of) the errors that currently only appear in the log.

### Is your feature related to a problem, if so please describe it.

Currently most errors encountered during `loadtxoutset` can only be found via the log.

### Describe the solution you'd like

_No response_

### Describe any alternatives you've considered

_No response_

### Please leave any additional context

Errors could be shown by `loadtxoutset status` if the method is made async (see #28620), otherwise just returned. 


Discussion / hints:
Concept ACK.

eg. currently when re-try already processed and completed `loadtxoutset` on mainnet the returned error message is:
```
./src/bitcoin-cli -datadir=${AU_DATADIR} loadtxoutset ${AU_DATADIR}/utxo-800000.dat
error code: -32603
error message:
Unable to load UTXO snapshot .../.test_utxo_2/utxo-800000.dat
```
While in the logs a user would see:
```
2023-10-09T20:26:13Z [httpworker.2] [snapshot] waiting to see blockheader 00000000000000000002a7c4c1e48d76c5a37902165a270156b7a8d72728a054 in headers chain before snapshot activation
2023-10-09T20:26:13Z [httpworker.2] [snapshot] can't activate a snapshot-based chainstate more than once
```


I like also the idea to extend the `rpc` command with the `status` param:  `loadtxoutset status`, in order to see percentage of processing, errors or warnings, especially if we manage to make it async.
Perhaps a good first issue?

Fix patch:
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 37a28e414a0a9..51d0879b12335 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -2737,6 +2737,7 @@ static RPCHelpMan loadtxoutset()
         [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
 {
     NodeContext& node = EnsureAnyNodeContext(request.context);
+    ChainstateManager& chainman = EnsureChainman(node);
     fs::path path{AbsPathForConfigVal(EnsureArgsman(node), fs::u8path(request.params[0].get_str()))};
 
     FILE* file{fsbridge::fopen(path, "rb")};
@@ -2751,14 +2752,16 @@ static RPCHelpMan loadtxoutset()
     afile >> metadata;
 
     uint256 base_blockhash = metadata.m_base_blockhash;
+    if (!chainman.GetParams().AssumeutxoForBlockhash(base_blockhash).has_value()) {
+        throw JSONRPCError(RPC_INTERNAL_ERROR, strprintf("Unable to load UTXO snapshot, "
+            "assumeutxo block hash in snapshot metadata not recognized (%s)", base_blockhash.ToString()));
+    }
     int max_secs_to_wait_for_headers = 60 * 10;
     CBlockIndex* snapshot_start_block = nullptr;
 
     LogPrintf("[snapshot] waiting to see blockheader %s in headers chain before snapshot activation\n",
         base_blockhash.ToString());
 
-    ChainstateManager& chainman = EnsureChainman(node);
-
     while (max_secs_to_wait_for_headers > 0) {
         snapshot_start_block = WITH_LOCK(::cs_main,
             return chainman.m_blockman.LookupBlockIndex(base_blockhash));


Test patch:
diff --git a/test/functional/feature_assumeutxo.py b/test/functional/feature_assumeutxo.py
index c900736ae9627..376e35da3b626 100755
--- a/test/functional/feature_assumeutxo.py
+++ b/test/functional/feature_assumeutxo.py
@@ -73,17 +73,12 @@ def test_invalid_snapshot_scenarios(self, valid_snapshot_path):
         bad_snapshot_path = valid_snapshot_path + '.mod'
 
         self.log.info("  - snapshot file refering to a block that is not in the assumeutxo parameters")
-        # we can only test this with a block that is already known, as otherwise the `loadtxoutset` RPC
-        # would time out (waiting to see the hash in the headers chain), rather than error immediately
-        bad_snapshot_height = SNAPSHOT_BASE_HEIGHT - 1
+        bad_snapshot_block_hash = self.nodes[0].getblockhash(SNAPSHOT_BASE_HEIGHT - 1)
         with open(bad_snapshot_path, 'wb') as f:
-            bad_snapshot_block_hash = self.nodes[0].getblockhash(bad_snapshot_height)
             # block hash of the snapshot base is stored right at the start (first 32 bytes)
             f.write(bytes.fromhex(bad_snapshot_block_hash)[::-1] + valid_snapshot_contents[32:])
-
-        expected_log = f"assumeutxo height in snapshot metadata not recognized ({bad_snapshot_height}) - refusing to load snapshot"
-        with self.nodes[1].assert_debug_log([expected_log]):
-            assert_raises_rpc_error(-32603, "Unable to load UTXO snapshot", self.nodes[1].loadtxoutset, bad_snapshot_path)
+        error_details = f"assumeutxo block hash in snapshot metadata not recognized ({bad_snapshot_block_hash})"
+        assert_raises_rpc_error(-32603, f"Unable to load UTXO snapshot, {error_details}", self.nodes[1].loadtxoutset, bad_snapshot_path)
 
         self.log.info("  - snapshot file with wrong number of coins")
         valid_num_coins = struct.unpack("<I", valid_snapshot_contents[32:32 + 4])[0]