When validation is waiting for parent chain daemon, "stall".

Currently, if -validatepegin is given, and block validation can't proceed
because the parent chain is not synced, we mark the block invalid and put
it in a queue to be "revalidated" later. Unfortunately, marking a block
invalid has downstream consequences, in particular causing descendant blocks
to be marked invalid, which are not currently fixed by the queue.

Instead, we'll use a different strategy: if the mainchain daemon isn't
sufficiently synced to validate a block, we will "stall" connecting that
block to the chain, and have ActivateBestChain simply keep the tip at the
previous block until we're ready.

We can still download and validate (partly) blocks past this point while
we're waiting. They will be connected once the parent chain daemon catches
up.
This commit is contained in:
Andrew Poelstra 2021-09-07 17:01:33 +00:00
parent 532d55d059
commit 313f73d5b2
10 changed files with 77 additions and 117 deletions

View file

@ -2083,6 +2083,29 @@ static int64_t nTimeCallbacks = 0;
static int64_t nTimeTotal = 0;
static int64_t nBlocksTotal = 0;
bool CheckPeginRipeness(const CBlock& block, const std::vector<std::pair<CScript, CScript>>& fedpegscripts) {
for (unsigned int i = 0; i < block.vtx.size(); i++) {
const CTransaction &tx = *(block.vtx[i]);
if (!tx.IsCoinBase()) {
for (unsigned int i = 0; i < tx.vin.size(); ++i) {
if (tx.vin[i].m_is_pegin) {
std::string err;
bool depth_failed = false;
if ((tx.witness.vtxinwit.size() <= i) || !IsValidPeginWitness(tx.witness.vtxinwit[i].m_pegin_witness, fedpegscripts, tx.vin[i].prevout, err, true, &depth_failed)) {
if (depth_failed) {
return false; // Pegins not ripe.
} else {
return true; // Some other failure; details later.
}
}
}
}
}
}
return true;
}
/** Apply the effects of this block (with given index) on the UTXO set represented by coins.
* Validity checks that depend on the UTXO set are also done; ConnectBlock()
* can fail if those validity checks fail (among other reasons). */
@ -2820,7 +2843,7 @@ public:
*
* The block is added to connectTrace if connection succeeds.
*/
bool CChainState::ConnectTip(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions &disconnectpool)
bool CChainState::ConnectTip(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexNew, const std::shared_ptr<const CBlock>& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions &disconnectpool, bool& fStall)
{
AssertLockHeld(cs_main);
AssertLockHeld(m_mempool.cs);
@ -2838,6 +2861,14 @@ bool CChainState::ConnectTip(BlockValidationState& state, const CChainParams& ch
pthisBlock = pblock;
}
const CBlock& blockConnecting = *pthisBlock;
const auto& fedpegscripts = GetValidFedpegScripts(pindexNew, chainparams.GetConsensus(), false /* nextblock_validation */);
if (!CheckPeginRipeness(blockConnecting, fedpegscripts)) {
LogPrintf("STALLING further progress in ConnectTip while waiting for parent chain daemon to catch up! Chain will not grow until this is remedied!\n");
fStall = true;
return true;
}
// Apply the block atomically to the chain state.
int64_t nTime2 = GetTimeMicros(); nTimeReadFromDisk += nTime2 - nTime1;
int64_t nTime3;
@ -2854,29 +2885,6 @@ bool CChainState::ConnectTip(BlockValidationState& state, const CChainParams& ch
if (!rv) {
if (state.IsInvalid()) {
InvalidBlockFound(pindexNew, state);
// ELEMENTS:
// Possibly result of RPC to mainchain bitcoind failure
// or unseen Bitcoin blocks.
// These blocks are later re-evaluated at an interval
// set by `-recheckpeginblockinterval`.
if (state.GetRejectReason() == "bad-pegin-witness") {
//Write queue of invalid blocks that
//must be cleared to continue operation
std::vector<uint256> vinvalidBlocks;
pblocktree->ReadInvalidBlockQueue(vinvalidBlocks);
bool blockAlreadyInvalid = false;
for (uint256& hash : vinvalidBlocks) {
if (hash == blockConnecting.GetHash()) {
blockAlreadyInvalid = true;
break;
}
}
if (!blockAlreadyInvalid) {
vinvalidBlocks.push_back(blockConnecting.GetHash());
pblocktree->WriteInvalidBlockQueue(vinvalidBlocks);
}
}
}
return error("%s: ConnectBlock %s failed, %s", __func__, pindexNew->GetBlockHash().ToString(), state.ToString());
}
@ -2988,7 +2996,7 @@ void CChainState::PruneBlockIndexCandidates() {
*
* @returns true unless a system error occurred
*/
bool CChainState::ActivateBestChainStep(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace)
bool CChainState::ActivateBestChainStep(BlockValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexMostWork, const std::shared_ptr<const CBlock>& pblock, bool& fInvalidFound, ConnectTrace& connectTrace, bool& fStall)
{
AssertLockHeld(cs_main);
AssertLockHeld(m_mempool.cs);
@ -3033,7 +3041,7 @@ bool CChainState::ActivateBestChainStep(BlockValidationState& state, const CChai
// Connect new blocks.
for (CBlockIndex *pindexConnect : reverse_iterate(vpindexToConnect)) {
if (!ConnectTip(state, chainparams, pindexConnect, pindexConnect == pindexMostWork ? pblock : std::shared_ptr<const CBlock>(), connectTrace, disconnectpool)) {
if (!ConnectTip(state, chainparams, pindexConnect, pindexConnect == pindexMostWork ? pblock : std::shared_ptr<const CBlock>(), connectTrace, disconnectpool, fStall)) {
if (state.IsInvalid()) {
// The block violates a consensus rule.
if (state.GetResult() != BlockValidationResult::BLOCK_MUTATED) {
@ -3051,6 +3059,12 @@ bool CChainState::ActivateBestChainStep(BlockValidationState& state, const CChai
return false;
}
} else {
if (fStall) {
// We didn't make progress because the parent chain is not
// synced enough to check pegins. Try again later.
fContinue = false;
break;
}
PruneBlockIndexCandidates();
if (!pindexOldTip || m_chain.Tip()->nChainWork > pindexOldTip->nChainWork) {
// We're in a better position than we were. Return temporarily to release the lock.
@ -3130,6 +3144,8 @@ bool CChainState::ActivateBestChain(BlockValidationState &state, const CChainPar
CBlockIndex *pindexMostWork = nullptr;
CBlockIndex *pindexNewTip = nullptr;
int nStopAtHeight = gArgs.GetArg("-stopatheight", DEFAULT_STOPATHEIGHT);
bool fStall = false;
do {
// Block until the validation queue drains. This should largely
// never happen in normal operation, however may happen during
@ -3160,7 +3176,7 @@ bool CChainState::ActivateBestChain(BlockValidationState &state, const CChainPar
bool fInvalidFound = false;
std::shared_ptr<const CBlock> nullBlockPtr;
if (!ActivateBestChainStep(state, chainparams, pindexMostWork, pblock && pblock->GetHash() == pindexMostWork->GetBlockHash() ? pblock : nullBlockPtr, fInvalidFound, connectTrace)) {
if (!ActivateBestChainStep(state, chainparams, pindexMostWork, pblock && pblock->GetHash() == pindexMostWork->GetBlockHash() ? pblock : nullBlockPtr, fInvalidFound, connectTrace, fStall)) {
// A system error occurred
return false;
}
@ -3176,6 +3192,11 @@ bool CChainState::ActivateBestChain(BlockValidationState &state, const CChainPar
assert(trace.pblock && trace.pindex);
GetMainSignals().BlockConnected(trace.pblock, trace.pindex);
}
if (fStall) {
// Stuck waiting for parent chain daemon, twiddle our thumbs for awhile.
break;
}
} while (!m_chain.Tip() || (starting_tip && CBlockIndexWorkComparator()(m_chain.Tip(), starting_tip)));
if (!blocks_connected) return true;
@ -3194,6 +3215,11 @@ bool CChainState::ActivateBestChain(BlockValidationState &state, const CChainPar
}
// When we reach this point, we switched to a new tip (stored in pindexNewTip).
if (fStall) {
// Stuck waiting for parent chain daemon, twiddle our thumbs for awhile.
break;
}
if (nStopAtHeight && pindexNewTip && pindexNewTip->nHeight >= nStopAtHeight) StartShutdown();
// We check shutdown only after giving ActivateBestChainStep a chance to run once so that we
@ -5711,35 +5737,12 @@ void ChainstateManager::MaybeRebalanceCaches()
}
// ELEMENTS:
/* This function has two major purposes:
* 1) Checks that the RPC connection to the parent chain node
/* This function checks that the RPC connection to the parent chain node
* can be attained, and is returning back reasonable answers.
* 2) Re-evaluates a list of blocks that have been deemed "bad"
* from the perspective of peg-in witness validation. Blocks are
* added to this queue in ConnectTip based on the error code returned.
*/
bool MainchainRPCCheck(const bool init)
{
// First, we can clear out any blocks thatsomehow are now deemed valid
// eg reconsiderblock rpc call manually
std::vector<uint256> vblocksToReconsider;
pblocktree->ReadInvalidBlockQueue(vblocksToReconsider);
std::vector<uint256> vblocksToReconsiderAgain;
for(uint256& blockhash : vblocksToReconsider) {
LOCK(cs_main);
ChainstateManager& chainman = g_chainman;
if (chainman.BlockIndex().count(blockhash)) {
CBlockIndex* pblockindex = chainman.BlockIndex()[blockhash];
if ((pblockindex->nStatus & BLOCK_FAILED_MASK)) {
vblocksToReconsiderAgain.push_back(blockhash);
}
}
}
vblocksToReconsider = vblocksToReconsiderAgain;
vblocksToReconsiderAgain.clear();
pblocktree->WriteInvalidBlockQueue(vblocksToReconsider);
// Next, check for working and valid rpc
// Check for working and valid rpc
if (gArgs.GetBoolArg("-validatepegin", Params().GetConsensus().has_parent_chain)) {
// During init try until a non-RPC_IN_WARMUP result
while (true) {
@ -5790,50 +5793,5 @@ bool MainchainRPCCheck(const bool init)
}
}
//Sanity startup check won't reconsider queued blocks
if (init) {
return true;
}
// Getting this far means we either aren't validating pegins(so let's make sure that's why
// it failed previously) or we successfully connected to bitcoind
// Time to reconsider blocks
if (vblocksToReconsider.size() > 0) {
BlockValidationState state;
for(const uint256& blockhash : vblocksToReconsider) {
{
LOCK(cs_main);
ChainstateManager& chainman = g_chainman;
if (chainman.BlockIndex().count(blockhash) == 0)
continue;
CBlockIndex* pblockindex = chainman.BlockIndex()[blockhash];
ResetBlockFailureFlags(pblockindex);
}
}
//All blocks are now being reconsidered
ActivateBestChain(state, Params());
//This simply checks for DB errors
if (!state.IsValid()) {
//Something scary?
}
//Now to clear out now-valid blocks
for(const uint256& blockhash : vblocksToReconsider) {
LOCK(cs_main);
ChainstateManager& chainman = g_chainman;
if (chainman.BlockIndex().count(blockhash)) {
CBlockIndex* pblockindex = chainman.BlockIndex()[blockhash];
//Marked as invalid still, put back into queue
if((pblockindex->nStatus & BLOCK_FAILED_MASK)) {
vblocksToReconsiderAgain.push_back(blockhash);
}
}
}
//Write back remaining blocks
pblocktree->WriteInvalidBlockQueue(vblocksToReconsiderAgain);
}
return true;
}