`getblock` is slow on verbosity, it's happened because `LOCK(cs_main)` applied for whole function including JSON generation
https://github.com/bitcoin/bitcoin/blob/ef70f9b52b851c7997a9f1a0834714e3eebc1fd8/src/rpc/blockchain.cpp#L810

I added few lines which print spent time:
```cpp
    LOCK(cs_main);
    auto t1 = std::chrono::high_resolution_clock::now();
...
    auto t2 = std::chrono::high_resolution_clock::now();
    auto ret = blockToJSON(block, pblockindex, verbosity >= 2);
    auto t3 = std::chrono::high_resolution_clock::now();
    std::cout << "blockToJSON " << pblockindex->nHeight << ": " << std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count() << " " << std::chrono::duration_cast<std::chrono::microseconds>(t3 - t1).count() << std::endl;
    return ret;
```

and numbers looks like:
```
blockToJSON 570019: 14727 201870
blockToJSON 570022: 16049 225224
blockToJSON 570021: 33776 217398
blockToJSON 570020: 21754 191669
blockToJSON 570023: 30949 236941
blockToJSON 570025: 23346 223673
blockToJSON 570024: 18732 203002
blockToJSON 570026: 17467 216213
blockToJSON 570027: 16658 217395
blockToJSON 570028: 19015 219183
```

i.e. everything before JSON generation takes 15-30ms, while JSON takes minimum 190ms and lock `cs_main` for all this time. As result it's possible extract only ~5 blocks per second.

Looking on the code I think it's possible copy data from `CBlockIndex` to extra structure, so in this case `cs_main` can be unlocked right after `GetBlockChecked`.
https://github.com/bitcoin/bitcoin/blob/ef70f9b52b851c7997a9f1a0834714e3eebc1fd8/src/rpc/blockchain.cpp#L828
As result `getblock` in verbosity mode should be much faster (in case if `-rpcthreads` will be adjusted too).

Does this make sense?
UniValue desc_uni = find_value(scanobject, "desc");
                if (desc_uni.isNull()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor needs to be provided in scan object");
                desc_str = desc_uni.get_str();
                UniValue range_uni = find_value(scanobject, "range");
                if (!range_uni.isNull()) {
                    range = ParseRange(range_uni);
                    if (range.first < 0 || (range.second >> 31) != 0 || range.second >= range.first + 1000000) throw JSONRPCError(RPC_INVALID_PARAMETER, "range out of range");
                }
            } else {
                throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan object needs to be either a string or an object");
            }
            FlatSigningProvider provider;
            auto desc = Parse(desc_str, provider);
            if (!desc) {
                throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid descriptor '%s'", desc_str));
            }
            if (!desc->IsRange()) {
                range.first = 0;
                range.second = 0;
            }
            for (int i = range.first; i <= range.second; ++i) {
                std::vector<CScript> scripts;
                if (!desc->Expand(i, provider, scripts, provider)) {
                    throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys: '%s'", desc_str));
                }
                for (const auto& script : scripts) {
                    std::string inferred = InferDescriptor(script, provider)->ToString();
                    needles.emplace(script);
                    descriptors.emplace(std::move(script), std::move(inferred));
                }