Repository: bitcoin/bitcoin (language: cpp)

Issue:
Assertion pindexPrev && pindexPrev == chainstate.m_chain.Tip() when running regtest
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current behaviour

I'm running a regtest chain in my CI\CD for some tests, occasionally, I will see my bitcoind fail with an assertion 
`Assertion failed: pindexPrev && pindexPrev == chainstate.m_chain.Tip() (validation.cpp: TestBlockValidity: 4338)`

### Expected behaviour

The node to be stable and not crash

### Steps to reproduce

I don't have steps to reproduce, this happens very rearily as part of our CI\CD that runs a simple regtest chains, mines a few blocks, makes a few transactions and managing a few wallets

### Relevant log output

_No response_

### How did you obtain Bitcoin Core

Package manager

### What version of Bitcoin Core are you using?

27.0-r1

### Operating system and version

alpine:3.18

### Machine specifications

I'm building an alpine image version 3.18
and installing bitcoin in my docker file as such
```
RUN apk add bitcoin=25.1-r1 --repository=https://dl-cdn.alpinelinux.org/alpine/v3.19/community
RUN apk add bitcoin-cli
```
since 25 is now a bit old, I tried bumping the version, so I'm now running 
```
RUN apk add bitcoin=25.1-r1 --repository=https://dl-cdn.alpinelinux.org/alpine/v3.19/community
RUN apk add bitcoin-cli
```
but the issue persisted


Discussion / hints:
> I don't have steps to reproduce, this happens very rearily as part of our CI\CD that runs a simple regtest chains, mines a few blocks, makes a few transactions and managing a few wallets

Thanks for the report! I presume, you are calling the test-only `generateblock` RPC? If not, it would be good to have minimal reproducer, or a link to a log of the CI run, or the test code that hit the issue. Without a log or anything to reproduce, it will be harder to see where the bug happened. 

Fix patch:
diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp
index 3b05f84eee4f6..6cadd324c9c4c 100644
--- a/src/rpc/mining.cpp
+++ b/src/rpc/mining.cpp
@@ -1,5 +1,5 @@
 // Copyright (c) 2010 Satoshi Nakamoto
-// Copyright (c) 2009-2022 The Bitcoin Core developers
+// Copyright (c) 2009-present The Bitcoin Core developers
 // Distributed under the MIT software license, see the accompanying
 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
 
@@ -371,22 +371,22 @@ static RPCHelpMan generateblock()
 
     ChainstateManager& chainman = EnsureChainman(node);
     {
-        std::unique_ptr<BlockTemplate> block_template{miner.createNewBlock({.use_mempool = false, .coinbase_output_script = coinbase_output_script})};
-        CHECK_NONFATAL(block_template);
+        LOCK(chainman.GetMutex());
+        {
+            std::unique_ptr<BlockTemplate> block_template{miner.createNewBlock({.use_mempool = false, .coinbase_output_script = coinbase_output_script})};
+            CHECK_NONFATAL(block_template);
 
-        block = block_template->getBlock();
-    }
+            block = block_template->getBlock();
+        }
 
-    CHECK_NONFATAL(block.vtx.size() == 1);
+        CHECK_NONFATAL(block.vtx.size() == 1);
 
-    // Add transactions
-    block.vtx.insert(block.vtx.end(), txs.begin(), txs.end());
-    RegenerateCommitments(block, chainman);
+        // Add transactions
+        block.vtx.insert(block.vtx.end(), txs.begin(), txs.end());
+        RegenerateCommitments(block, chainman);
 
-    {
-        LOCK(::cs_main);
         BlockValidationState state;
-        if (!TestBlockValidity(state, chainman.GetParams(), chainman.ActiveChainstate(), block, chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock),  /*fCheckPOW=*/false, /*fCheckMerkleRoot=*/false)) {
+        if (!TestBlockValidity(state, chainman.GetParams(), chainman.ActiveChainstate(), block, chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock), /*fCheckPOW=*/false, /*fCheckMerkleRoot=*/false)) {
             throw JSONRPCError(RPC_VERIFY_ERROR, strprintf("TestBlockValidity failed: %s", state.ToString()));
         }
     }


Test patch:
diff --git a/test/functional/rpc_generate.py b/test/functional/rpc_generate.py
index 74f31f45716e0..618bbb2e00777 100755
--- a/test/functional/rpc_generate.py
+++ b/test/functional/rpc_generate.py
@@ -1,9 +1,11 @@
 #!/usr/bin/env python3
-# Copyright (c) 2020-2022 The Bitcoin Core developers
+# Copyright (c) 2020-present The Bitcoin Core developers
 # Distributed under the MIT software license, see the accompanying
 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
 """Test generate* RPCs."""
 
+from concurrent.futures import ThreadPoolExecutor
+
 from test_framework.test_framework import BitcoinTestFramework
 from test_framework.wallet import MiniWallet
 from test_framework.util import (
@@ -83,6 +85,13 @@ def test_generateblock(self):
         txid = block['tx'][1]
         assert_equal(node.getrawtransaction(txid=txid, verbose=False, blockhash=hash), rawtx)
 
+        # Ensure that generateblock can be called concurrently by many threads.
+        self.log.info('Generate blocks in parallel')
+        generate_50_blocks = lambda n: [n.generateblock(output=address, transactions=[]) for _ in range(50)]
+        rpcs = [node.cli for _ in range(6)]
+        with ThreadPoolExecutor(max_workers=len(rpcs)) as threads:
+            list(threads.map(generate_50_blocks, rpcs))
+
         self.log.info('Fail to generate block with out of order txs')
         txid1 = miniwallet.send_self_transfer(from_node=node)['txid']
         utxo1 = miniwallet.get_utxo(txid=txid1)