CheckBlock returns true right away when block.fChecked is set (validation.cpp:3930). The signet solution is only checked inside CheckBlock. PoW is checked again for new headers in AcceptBlockHeader, the signet solution isn't.
In the kernel API a btck_Block wraps a shared CBlock, and btck_block_copy only adds a reference. Once a block passes btck_block_check or btck_chainstate_manager_process_block under one set of consensus params, the flag stays set on the shared object, and every later check on it passes, whatever params or flags are passed:
- a regtest block fails
btck_block_checkwith mainnet params andALLflags. After it has been checked once with regtest params, the same call with mainnet params returns 1. - a signet block that satisfies an
OP_TRUEchallenge but not anOP_RETURNone: processing it on a chainstate manager with theOP_TRUEchallenge first, then on a second one with theOP_RETURNchallenge, connects it on both. The second chain then can't read its own tip back withbtck_block_read, becauseReadBlockdoes check the signet solution.
bitcoind isn't affected, since it only has one chainstate manager and one set of params. btck_block_check and the custom signet chain params are new in 32.0, and their header docs don't say a block may only be used with one set of params.
This runs both entry points on a copy of the block without the cached flags. The copy is built from the header plus the transaction list, so it only copies the transaction pointers. It also means btck_block_check no longer writes to the caller's block without cs_main (the comment at validation.cpp:4422).
Two cases are added to test_kernel.cpp that match the examples above. On master they give 4 failures, with the fix test_kernel passes.