Skip to content

Commit 6f24662

Browse files
committed
Merge bitcoin/bitcoin#31175: rpc: Remove submitblock pre-checks
73db95c kernel: Make bitcoin-chainstate's block validation mirror submitblock's (TheCharlatan) bb53ce9 tests: Add functional test for submitting a previously pruned block (Greg Sanders) 1f7fc73 rpc: Remove submitblock duplicate pre-check (TheCharlatan) e62a8ab rpc: Remove submitblock invalid-duplicate precheck (TheCharlatan) 36dbeba rpc: Remove submitblock coinbase pre-check (TheCharlatan) Pull request description: With the introduction of a mining ipc interface and the potential future introduction of a kernel library API it becomes increasingly important to offer common behaviour between them. An example of this is ProcessNewBlock, which is used by ipc, rpc, net_processing and (potentially) the kernel library. Having divergent behaviour on suggested pre-checks and checks for these functions is confusing to both developers and users and is a maintenance burden. The rpc interface for ProcessNewBlock (submitblock) currently pre-checks if the block has a coinbase transaction and whether it has been processed before. While the current example binary for how to use the kernel library, bitcoin-chainstate, imitates these checks, the other interfaces do not. The coinbase check is repeated again early during ProcessNewBlock. Pre-checking it may also shadow more fundamental problems with a block. In most cases the block header is checked first, before validating the transactions. Checking the coinbase first therefore masks potential issues with the header. Fix this by removing the pre-check. Similary the duplicate checks are repeated early in the contextual checks of ProcessNewBlock. If duplicate blocks are detected much of their validation is skipped. Depending on the constitution of the block, validating the merkle root of the block is part of the more intensive workload when validating a block. This could be an argument for moving the pre-checks into block processing. In net_processing this would have a smaller effect however, since the block mutation check, which also validates the merkle root, is done before. Testing spamming a node with valid, but duplicate unrequested blocks seems to exhaust a CPU thread, but does not seem to significantly impact keeping up with the tip. The benefits of adding these checks to net_processing are questionable, especially since there are other ways to trigger the more CPU-intensive checks without submitting a duplicate block. Since these DOS concerns apply even less to the RPC interface, which does not have banning mechanics built in, remove them too. Finally, also remove the pre-checks from `bitcoin-chainstate.cpp`. --- This PR is part of the [libbitcoinkernel project](bitcoin/bitcoin#27587). ACKs for top commit: Sjors: re-utACK 73db95c achow101: ACK 73db95c instagibbs: ACK 73db95c mzumsande: ACK 73db95c Tree-SHA512: 2d02e851cf402ecf6a1968c058df3576aac407e200cbf922a1a6391b7f97b4f42c6d9f6b0a78b9d1af0a6d40bdd529a7b11a1e6d88885bd7b8b090f6d1411861
2 parents 3867d24 + 73db95c commit 6f24662

File tree

5 files changed

+44
-44
lines changed

5 files changed

+44
-44
lines changed

doc/release-notes-31175.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
RPC
2+
---
3+
4+
Duplicate blocks submitted with `submitblock` will now persist their block data
5+
even if it was previously pruned. If pruning is activated, the data will be
6+
pruned again eventually once the block file it is persisted in is selected for
7+
pruning. This is consistent with the behaviour of `getblockfrompeer` where the
8+
block is persisted as well even when pruning.

src/bitcoin-chainstate.cpp

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -180,27 +180,6 @@ int main(int argc, char* argv[])
180180
break;
181181
}
182182

183-
if (block.vtx.empty() || !block.vtx[0]->IsCoinBase()) {
184-
std::cerr << "Block does not start with a coinbase" << std::endl;
185-
break;
186-
}
187-
188-
uint256 hash = block.GetHash();
189-
{
190-
LOCK(cs_main);
191-
const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash);
192-
if (pindex) {
193-
if (pindex->IsValid(BLOCK_VALID_SCRIPTS)) {
194-
std::cerr << "duplicate" << std::endl;
195-
break;
196-
}
197-
if (pindex->nStatus & BLOCK_FAILED_MASK) {
198-
std::cerr << "duplicate-invalid" << std::endl;
199-
break;
200-
}
201-
}
202-
}
203-
204183
{
205184
LOCK(cs_main);
206185
const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock);

src/rpc/mining.cpp

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,25 +1024,7 @@ static RPCHelpMan submitblock()
10241024
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block decode failed");
10251025
}
10261026

1027-
if (block.vtx.empty() || !block.vtx[0]->IsCoinBase()) {
1028-
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Block does not start with a coinbase");
1029-
}
1030-
10311027
ChainstateManager& chainman = EnsureAnyChainman(request.context);
1032-
uint256 hash = block.GetHash();
1033-
{
1034-
LOCK(cs_main);
1035-
const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash);
1036-
if (pindex) {
1037-
if (pindex->IsValid(BLOCK_VALID_SCRIPTS)) {
1038-
return "duplicate";
1039-
}
1040-
if (pindex->nStatus & BLOCK_FAILED_MASK) {
1041-
return "duplicate-invalid";
1042-
}
1043-
}
1044-
}
1045-
10461028
{
10471029
LOCK(cs_main);
10481030
const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock);

src/validation.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4323,7 +4323,7 @@ bool ChainstateManager::AcceptBlockHeader(const CBlockHeader& block, BlockValida
43234323
*ppindex = pindex;
43244324
if (pindex->nStatus & BLOCK_FAILED_MASK) {
43254325
LogDebug(BCLog::VALIDATION, "%s: block %s is marked invalid\n", __func__, hash.ToString());
4326-
return state.Invalid(BlockValidationResult::BLOCK_CACHED_INVALID, "duplicate");
4326+
return state.Invalid(BlockValidationResult::BLOCK_CACHED_INVALID, "duplicate-invalid");
43274327
}
43284328
return true;
43294329
}

test/functional/mining_basic.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,12 @@ def assert_template(node, block, expect, rehash=True):
5656

5757
class MiningTest(BitcoinTestFramework):
5858
def set_test_params(self):
59-
self.num_nodes = 2
59+
self.num_nodes = 3
60+
self.extra_args = [
61+
[],
62+
[],
63+
["-fastprune", "-prune=1"]
64+
]
6065
self.setup_clean_chain = True
6166
self.supports_cli = False
6267

@@ -169,6 +174,21 @@ def test_timewarp(self):
169174
bad_block.solve()
170175
node.submitheader(hexdata=CBlockHeader(bad_block).serialize().hex())
171176

177+
def test_pruning(self):
178+
self.log.info("Test that submitblock stores previously pruned block")
179+
prune_node = self.nodes[2]
180+
self.generate(prune_node, 400, sync_fun=self.no_op)
181+
pruned_block = prune_node.getblock(prune_node.getblockhash(2), verbosity=0)
182+
pruned_height = prune_node.pruneblockchain(400)
183+
assert_greater_than_or_equal(pruned_height, 2)
184+
pruned_blockhash = prune_node.getblockhash(2)
185+
186+
assert_raises_rpc_error(-1, 'Block not available (pruned data)', prune_node.getblock, pruned_blockhash)
187+
188+
result = prune_node.submitblock(pruned_block)
189+
assert_equal(result, "inconclusive")
190+
assert_equal(prune_node.getblock(pruned_blockhash, verbosity=0), pruned_block)
191+
172192
def run_test(self):
173193
node = self.nodes[0]
174194
self.wallet = MiniWallet(node)
@@ -240,9 +260,19 @@ def assert_submitblock(block, result_str_1, result_str_2=None):
240260
bad_block.vtx[0].rehash()
241261
assert_template(node, bad_block, 'bad-cb-missing')
242262

243-
self.log.info("submitblock: Test invalid coinbase transaction")
244-
assert_raises_rpc_error(-22, "Block does not start with a coinbase", node.submitblock, CBlock().serialize().hex())
245-
assert_raises_rpc_error(-22, "Block does not start with a coinbase", node.submitblock, bad_block.serialize().hex())
263+
self.log.info("submitblock: Test bad input hash for coinbase transaction")
264+
bad_block.solve()
265+
assert_equal("bad-cb-missing", node.submitblock(hexdata=bad_block.serialize().hex()))
266+
267+
self.log.info("submitblock: Test block with no transactions")
268+
no_tx_block = copy.deepcopy(block)
269+
no_tx_block.vtx.clear()
270+
no_tx_block.hashMerkleRoot = 0
271+
no_tx_block.solve()
272+
assert_equal("bad-blk-length", node.submitblock(hexdata=no_tx_block.serialize().hex()))
273+
274+
self.log.info("submitblock: Test empty block")
275+
assert_equal('high-hash', node.submitblock(hexdata=CBlock().serialize().hex()))
246276

247277
self.log.info("getblocktemplate: Test truncated final transaction")
248278
assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, {
@@ -377,6 +407,7 @@ def chain_tip(b_hash, *, status='headers-only', branchlen=1):
377407

378408
self.test_blockmintxfee_parameter()
379409
self.test_timewarp()
410+
self.test_pruning()
380411

381412

382413
if __name__ == '__main__':

0 commit comments

Comments
 (0)