Skip to content

Commit dd07f47

Browse files
author
MarcoFalke
committed
Merge #10704: [tests] nits in dbcrash.py
27c63dc [tests] nits in dbcrash.py (John Newbery) Tree-SHA512: 2a75feeb65e6147e3337200cde982248bea8977a9585d5ee284d62bbc25f6d7c368754da0083aec37338c8f66cf698ee25bbd9e192df14a9fb976b8f75afa986
2 parents 2935b46 + 27c63dc commit dd07f47

File tree

2 files changed

+65
-49
lines changed

2 files changed

+65
-49
lines changed

test/functional/dbcrash.py

Lines changed: 61 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,7 @@
22
# Copyright (c) 2017 The Bitcoin Core developers
33
# Distributed under the MIT software license, see the accompanying
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5-
"""Test recovery from a crash during chainstate writing."""
6-
7-
from test_framework.test_framework import BitcoinTestFramework
8-
from test_framework.util import *
9-
from test_framework.script import *
10-
from test_framework.mininode import *
11-
import random
12-
try:
13-
import http.client as httplib
14-
except ImportError:
15-
import httplib
16-
import errno
17-
18-
'''
19-
Test structure:
5+
"""Test recovery from a crash during chainstate writing.
206
217
- 4 nodes
228
* node0, node1, and node2 will have different dbcrash ratios, and different
@@ -37,11 +23,26 @@
3723
* submit block to node
3824
* if node crashed on/after submitting:
3925
- restart until recovery succeeds
40-
- check that utxo matches node3 using gettxoutsetinfo
41-
'''
26+
- check that utxo matches node3 using gettxoutsetinfo"""
4227

43-
class ChainstateWriteCrashTest(BitcoinTestFramework):
28+
import errno
29+
import http.client
30+
import random
31+
import sys
32+
import time
33+
34+
from test_framework.mininode import *
35+
from test_framework.script import *
36+
from test_framework.test_framework import BitcoinTestFramework
37+
from test_framework.util import *
38+
39+
HTTP_DISCONNECT_ERRORS = [http.client.CannotSendRequest]
40+
try:
41+
HTTP_DISCONNECT_ERRORS.append(http.client.RemoteDisconnected)
42+
except AttributeError:
43+
pass
4444

45+
class ChainstateWriteCrashTest(BitcoinTestFramework):
4546
def __init__(self):
4647
super().__init__()
4748
self.num_nodes = 4
@@ -50,32 +51,28 @@ def __init__(self):
5051
# Set -maxmempool=0 to turn off mempool memory sharing with dbcache
5152
# Set -rpcservertimeout=900 to reduce socket disconnects in this
5253
# long-running test
53-
self.base_args = ["-limitdescendantsize=0", "-maxmempool=0", "-rpcservertimeout=900"]
54+
self.base_args = ["-limitdescendantsize=0", "-maxmempool=0", "-rpcservertimeout=900", "-dbbatchsize=200000"]
5455

5556
# Set different crash ratios and cache sizes. Note that not all of
5657
# -dbcache goes to pcoinsTip.
57-
self.node0_args = ["-dbcrashratio=8", "-dbcache=4", "-dbbatchsize=200000"] + self.base_args
58-
self.node1_args = ["-dbcrashratio=16", "-dbcache=8", "-dbbatchsize=200000"] + self.base_args
59-
self.node2_args = ["-dbcrashratio=24", "-dbcache=16", "-dbbatchsize=200000"] + self.base_args
58+
self.node0_args = ["-dbcrashratio=8", "-dbcache=4"] + self.base_args
59+
self.node1_args = ["-dbcrashratio=16", "-dbcache=8"] + self.base_args
60+
self.node2_args = ["-dbcrashratio=24", "-dbcache=16"] + self.base_args
6061

6162
# Node3 is a normal node with default args, except will mine full blocks
6263
self.node3_args = ["-blockmaxweight=4000000"]
6364
self.extra_args = [self.node0_args, self.node1_args, self.node2_args, self.node3_args]
6465

65-
# We'll track some test coverage statistics
66-
self.restart_counts = [0, 0, 0] # Track the restarts for nodes 0-2
67-
self.crashed_on_restart = 0 # Track count of crashes during recovery
68-
6966
def setup_network(self):
7067
self.setup_nodes()
7168
# Leave them unconnected, we'll use submitblock directly in this test
7269

73-
# Starts up a given node id, waits for the tip to reach the given block
74-
# hash, and calculates the utxo hash. Exceptions on startup should
75-
# indicate node crash (due to -dbcrashratio), in which case we try again.
76-
# Give up after 60 seconds.
77-
# Returns the utxo hash of the given node.
7870
def restart_node(self, node_index, expected_tip):
71+
"""Start up a given node id, wait for the tip to reach the given block hash, and calculate the utxo hash.
72+
73+
Exceptions on startup should indicate node crash (due to -dbcrashratio), in which case we try again. Give up
74+
after 60 seconds. Returns the utxo hash of the given node."""
75+
7976
time_start = time.time()
8077
while time.time() - time_start < 60:
8178
try:
@@ -99,14 +96,23 @@ def restart_node(self, node_index, expected_tip):
9996
# and make sure that recovery happens.
10097
raise AssertionError("Unable to successfully restart node %d in allotted time", node_index)
10198

102-
# Try submitting a block to the given node.
103-
# Catch any exceptions that indicate the node has crashed.
104-
# Returns true if the block was submitted successfully; false otherwise.
10599
def submit_block_catch_error(self, node_index, block):
100+
"""Try submitting a block to the given node.
101+
102+
Catch any exceptions that indicate the node has crashed.
103+
Returns true if the block was submitted successfully; false otherwise."""
104+
106105
try:
107106
self.nodes[node_index].submitblock(block)
108107
return True
109-
except (httplib.CannotSendRequest, httplib.RemoteDisconnected) as e:
108+
except http.client.BadStatusLine as e:
109+
# Prior to 3.5 BadStatusLine('') was raised for a remote disconnect error.
110+
if sys.version_info[0] == 3 and sys.version_info[1] < 5 and e.line == "''":
111+
self.log.debug("node %d submitblock raised exception: %s", node_index, e)
112+
return False
113+
else:
114+
raise
115+
except tuple(HTTP_DISCONNECT_ERRORS) as e:
110116
self.log.debug("node %d submitblock raised exception: %s", node_index, e)
111117
return False
112118
except OSError as e:
@@ -118,11 +124,13 @@ def submit_block_catch_error(self, node_index, block):
118124
# Unexpected exception, raise
119125
raise
120126

121-
# Use submitblock to sync node3's chain with the other nodes
122-
# If submitblock fails, restart the node and get the new utxo hash.
123127
def sync_node3blocks(self, block_hashes):
124-
# If any nodes crash while updating, we'll compare utxo hashes to
125-
# ensure recovery was successful.
128+
"""Use submitblock to sync node3's chain with the other nodes
129+
130+
If submitblock fails, restart the node and get the new utxo hash.
131+
If any nodes crash while updating, we'll compare utxo hashes to
132+
ensure recovery was successful."""
133+
126134
node3_utxo_hash = self.nodes[3].gettxoutsetinfo()['hash_serialized_2']
127135

128136
# Retrieve all the blocks from node3
@@ -161,9 +169,10 @@ def sync_node3blocks(self, block_hashes):
161169
self.log.debug("Checking txoutsetinfo matches for node %d", i)
162170
assert_equal(nodei_utxo_hash, node3_utxo_hash)
163171

164-
# Verify that the utxo hash of each node matches node3.
165-
# Restart any nodes that crash while querying.
166172
def verify_utxo_hash(self):
173+
"""Verify that the utxo hash of each node matches node3.
174+
175+
Restart any nodes that crash while querying."""
167176
node3_utxo_hash = self.nodes[3].gettxoutsetinfo()['hash_serialized_2']
168177
self.log.info("Verifying utxo hash matches for all nodes")
169178

@@ -175,9 +184,8 @@ def verify_utxo_hash(self):
175184
nodei_utxo_hash = self.restart_node(i, self.nodes[3].getbestblockhash())
176185
assert_equal(nodei_utxo_hash, node3_utxo_hash)
177186

178-
179187
def generate_small_transactions(self, node, count, utxo_list):
180-
FEE = 1000 # TODO: replace this with node relay fee based calculation
188+
FEE = 1000 # TODO: replace this with node relay fee based calculation
181189
num_transactions = 0
182190
random.shuffle(utxo_list)
183191
while len(utxo_list) >= 2 and num_transactions < count:
@@ -186,8 +194,8 @@ def generate_small_transactions(self, node, count, utxo_list):
186194
for i in range(2):
187195
utxo = utxo_list.pop()
188196
tx.vin.append(CTxIn(COutPoint(int(utxo['txid'], 16), utxo['vout'])))
189-
input_amount += int(utxo['amount']*COIN)
190-
output_amount = (input_amount - FEE)//3
197+
input_amount += int(utxo['amount'] * COIN)
198+
output_amount = (input_amount - FEE) // 3
191199

192200
if output_amount <= 0:
193201
# Sanity check -- if we chose inputs that are too small, skip
@@ -202,6 +210,9 @@ def generate_small_transactions(self, node, count, utxo_list):
202210
num_transactions += 1
203211

204212
def run_test(self):
213+
# Track test coverage statistics
214+
self.restart_counts = [0, 0, 0] # Track the restarts for nodes 0-2
215+
self.crashed_on_restart = 0 # Track count of crashes during recovery
205216

206217
# Start by creating a lot of utxos on node3
207218
initial_height = self.nodes[3].getblockcount()
@@ -210,7 +221,7 @@ def run_test(self):
210221

211222
# Sync these blocks with the other nodes
212223
block_hashes_to_sync = []
213-
for height in range(initial_height+1, self.nodes[3].getblockcount()+1):
224+
for height in range(initial_height + 1, self.nodes[3].getblockcount() + 1):
214225
block_hashes_to_sync.append(self.nodes[3].getblockhash(height))
215226

216227
self.log.debug("Syncing %d blocks with other nodes", len(block_hashes_to_sync))
@@ -233,13 +244,15 @@ def run_test(self):
233244
if random_height > starting_tip_height:
234245
# Randomly reorg from this point with some probability (1/4 for
235246
# tip, 1/5 for tip-1, ...)
236-
if random.random() < 1.0/(current_height + 4 - random_height):
247+
if random.random() < 1.0 / (current_height + 4 - random_height):
237248
self.log.debug("Invalidating block at height %d", random_height)
238249
self.nodes[3].invalidateblock(self.nodes[3].getblockhash(random_height))
239250

240251
# Now generate new blocks until we pass the old tip height
241252
self.log.debug("Mining longer tip")
242-
block_hashes = self.nodes[3].generate(current_height+1-self.nodes[3].getblockcount())
253+
block_hashes = []
254+
while current_height + 1 > self.nodes[3].getblockcount():
255+
block_hashes.extend(self.nodes[3].generate(min(10, current_height + 1 - self.nodes[3].getblockcount())))
243256
self.log.debug("Syncing %d new blocks...", len(block_hashes))
244257
self.sync_node3blocks(block_hashes)
245258
utxo_list = self.nodes[3].listunspent()

test/functional/test_framework/util.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,10 @@ def random_transaction(nodes, amount, min_fee, fee_increment, fee_variants):
412412
# Helper to create at least "count" utxos
413413
# Pass in a fee that is sufficient for relay and mining new transactions.
414414
def create_confirmed_utxos(fee, node, count):
415-
node.generate(int(0.5 * count) + 101)
415+
to_generate = int(0.5 * count) + 101
416+
while to_generate > 0:
417+
node.generate(min(25, to_generate))
418+
to_generate -= 25
416419
utxos = node.listunspent()
417420
iterations = count - len(utxos)
418421
addr1 = node.getnewaddress()

0 commit comments

Comments
 (0)