Changeset View
Changeset View
Standalone View
Standalone View
test/functional/mempool_persist.py
Show All 34 Lines | - Verify that savemempool throws when the RPC is called if | ||||
node1 can't write to disk. | node1 can't write to disk. | ||||
""" | """ | ||||
from decimal import Decimal | from decimal import Decimal | ||||
import os | import os | ||||
import time | import time | ||||
from test_framework.test_framework import BitcoinTestFramework | from test_framework.test_framework import BitcoinTestFramework | ||||
from test_framework.mininode import P2PTxInvStore | |||||
from test_framework.util import ( | from test_framework.util import ( | ||||
assert_equal, | assert_equal, | ||||
assert_greater_than_or_equal, | assert_greater_than_or_equal, | ||||
assert_raises_rpc_error, | assert_raises_rpc_error, | ||||
connect_nodes, | |||||
disconnect_nodes, | |||||
wait_until, | |||||
) | ) | ||||
class MempoolPersistTest(BitcoinTestFramework): | class MempoolPersistTest(BitcoinTestFramework): | ||||
def set_test_params(self): | def set_test_params(self): | ||||
self.num_nodes = 3 | self.num_nodes = 3 | ||||
self.extra_args = [[], ["-persistmempool=0"], []] | self.extra_args = [[], ["-persistmempool=0"], []] | ||||
Show All 22 Lines | def run_test(self): | ||||
fees = self.nodes[0].getmempoolentry(txid=last_txid)['fees'] | fees = self.nodes[0].getmempoolentry(txid=last_txid)['fees'] | ||||
assert_equal(fees['base'] + Decimal('0.00001000'), fees['modified']) | assert_equal(fees['base'] + Decimal('0.00001000'), fees['modified']) | ||||
tx_creation_time = self.nodes[0].getmempoolentry(txid=last_txid)[ | tx_creation_time = self.nodes[0].getmempoolentry(txid=last_txid)[ | ||||
'time'] | 'time'] | ||||
assert_greater_than_or_equal(tx_creation_time, tx_creation_time_lower) | assert_greater_than_or_equal(tx_creation_time, tx_creation_time_lower) | ||||
assert_greater_than_or_equal(tx_creation_time_higher, tx_creation_time) | assert_greater_than_or_equal(tx_creation_time_higher, tx_creation_time) | ||||
# disconnect nodes & make a txn that remains in the unbroadcast set. | |||||
disconnect_nodes(self.nodes[0], self.nodes[2]) | |||||
self.nodes[0].sendtoaddress( | |||||
self.nodes[2].getnewaddress(), Decimal("12")) | |||||
connect_nodes(self.nodes[0], self.nodes[2]) | |||||
self.log.debug("Stop-start the nodes. Verify that node0 has the " | self.log.debug("Stop-start the nodes. Verify that node0 has the " | ||||
"transactions in its mempool and node1 does not. " | "transactions in its mempool and node1 does not. " | ||||
"Verify that node2 calculates its balance correctly " | "Verify that node2 calculates its balance correctly " | ||||
"after loading wallet transactions.") | "after loading wallet transactions.") | ||||
self.stop_nodes() | self.stop_nodes() | ||||
# Give this one a head-start, so we can be "extra-sure" that it didn't | # Give this one a head-start, so we can be "extra-sure" that it didn't | ||||
# load anything later | # load anything later | ||||
# Also don't store the mempool, to keep the datadir clean | # Also don't store the mempool, to keep the datadir clean | ||||
self.start_node(1, extra_args=["-persistmempool=0"]) | self.start_node(1, extra_args=["-persistmempool=0"]) | ||||
self.start_node(0) | self.start_node(0) | ||||
self.start_node(2) | self.start_node(2) | ||||
# start_node is blocking on the mempool being loaded | # start_node is blocking on the mempool being loaded | ||||
assert self.nodes[0].getmempoolinfo()["loaded"] | assert self.nodes[0].getmempoolinfo()["loaded"] | ||||
assert self.nodes[2].getmempoolinfo()["loaded"] | assert self.nodes[2].getmempoolinfo()["loaded"] | ||||
assert_equal(len(self.nodes[0].getrawmempool()), 5) | assert_equal(len(self.nodes[0].getrawmempool()), 6) | ||||
assert_equal(len(self.nodes[2].getrawmempool()), 5) | assert_equal(len(self.nodes[2].getrawmempool()), 5) | ||||
# The others have loaded their mempool. If node_1 loaded anything, we'd | # The others have loaded their mempool. If node_1 loaded anything, we'd | ||||
# probably notice by now: | # probably notice by now: | ||||
assert_equal(len(self.nodes[1].getrawmempool()), 0) | assert_equal(len(self.nodes[1].getrawmempool()), 0) | ||||
self.log.debug('Verify prioritization is loaded correctly') | self.log.debug('Verify prioritization is loaded correctly') | ||||
fees = self.nodes[0].getmempoolentry(txid=last_txid)['fees'] | fees = self.nodes[0].getmempoolentry(txid=last_txid)['fees'] | ||||
assert_equal(fees['base'] + Decimal('0.00001000'), fees['modified']) | assert_equal(fees['base'] + Decimal('0.00001000'), fees['modified']) | ||||
self.log.debug('Verify time is loaded correctly') | self.log.debug('Verify time is loaded correctly') | ||||
assert_equal( | assert_equal( | ||||
tx_creation_time, | tx_creation_time, | ||||
self.nodes[0].getmempoolentry( | self.nodes[0].getmempoolentry( | ||||
txid=last_txid)['time']) | txid=last_txid)['time']) | ||||
# Verify accounting of mempool transactions after restart is correct | # Verify accounting of mempool transactions after restart is correct | ||||
# Flush mempool to wallet | # Flush mempool to wallet | ||||
self.nodes[2].syncwithvalidationinterfacequeue() | self.nodes[2].syncwithvalidationinterfacequeue() | ||||
assert_equal(node2_balance, self.nodes[2].getbalance()) | assert_equal(node2_balance, self.nodes[2].getbalance()) | ||||
# start node0 with wallet disabled so wallet transactions don't get | |||||
# resubmitted | |||||
self.log.debug( | self.log.debug( | ||||
"Stop-start node0 with -persistmempool=0. Verify that it doesn't load its mempool.dat file.") | "Stop-start node0 with -persistmempool=0. Verify that it doesn't load its mempool.dat file.") | ||||
self.stop_nodes() | self.stop_nodes() | ||||
self.start_node(0, extra_args=["-persistmempool=0"]) | self.start_node(0, extra_args=["-persistmempool=0", "-disablewallet"]) | ||||
assert self.nodes[0].getmempoolinfo()["loaded"] | assert self.nodes[0].getmempoolinfo()["loaded"] | ||||
assert_equal(len(self.nodes[0].getrawmempool()), 0) | assert_equal(len(self.nodes[0].getrawmempool()), 0) | ||||
self.log.debug( | self.log.debug( | ||||
"Stop-start node0. Verify that it has the transactions in its mempool.") | "Stop-start node0. Verify that it has the transactions in its mempool.") | ||||
self.stop_nodes() | self.stop_nodes() | ||||
self.start_node(0) | self.start_node(0) | ||||
assert self.nodes[0].getmempoolinfo()["loaded"] | assert self.nodes[0].getmempoolinfo()["loaded"] | ||||
assert_equal(len(self.nodes[0].getrawmempool()), 5) | assert_equal(len(self.nodes[0].getrawmempool()), 6) | ||||
mempooldat0 = os.path.join( | mempooldat0 = os.path.join( | ||||
self.nodes[0].datadir, self.chain, 'mempool.dat') | self.nodes[0].datadir, self.chain, 'mempool.dat') | ||||
mempooldat1 = os.path.join( | mempooldat1 = os.path.join( | ||||
self.nodes[1].datadir, self.chain, 'mempool.dat') | self.nodes[1].datadir, self.chain, 'mempool.dat') | ||||
self.log.debug( | self.log.debug( | ||||
"Remove the mempool.dat file. Verify that savemempool to disk via RPC re-creates it") | "Remove the mempool.dat file. Verify that savemempool to disk via RPC re-creates it") | ||||
os.remove(mempooldat0) | os.remove(mempooldat0) | ||||
self.nodes[0].savemempool() | self.nodes[0].savemempool() | ||||
assert os.path.isfile(mempooldat0) | assert os.path.isfile(mempooldat0) | ||||
self.log.debug( | self.log.debug( | ||||
"Stop nodes, make node1 use mempool.dat from node0. Verify it has 5 transactions") | "Stop nodes, make node1 use mempool.dat from node0. Verify it has 6 transactions") | ||||
os.rename(mempooldat0, mempooldat1) | os.rename(mempooldat0, mempooldat1) | ||||
self.stop_nodes() | self.stop_nodes() | ||||
self.start_node(1, extra_args=[]) | self.start_node(1, extra_args=[]) | ||||
assert self.nodes[1].getmempoolinfo()["loaded"] | assert self.nodes[1].getmempoolinfo()["loaded"] | ||||
assert_equal(len(self.nodes[1].getrawmempool()), 5) | assert_equal(len(self.nodes[1].getrawmempool()), 6) | ||||
self.log.debug( | self.log.debug( | ||||
"Prevent bitcoind from writing mempool.dat to disk. Verify that `savemempool` fails") | "Prevent bitcoind from writing mempool.dat to disk. Verify that `savemempool` fails") | ||||
# to test the exception we are creating a tmp folder called mempool.dat.new | # to test the exception we are creating a tmp folder called mempool.dat.new | ||||
# which is an implementation detail that could change and break this | # which is an implementation detail that could change and break this | ||||
# test | # test | ||||
mempooldotnew1 = mempooldat1 + '.new' | mempooldotnew1 = mempooldat1 + '.new' | ||||
os.mkdir(mempooldotnew1) | os.mkdir(mempooldotnew1) | ||||
assert_raises_rpc_error(-1, "Unable to dump mempool to disk", | assert_raises_rpc_error(-1, "Unable to dump mempool to disk", | ||||
self.nodes[1].savemempool) | self.nodes[1].savemempool) | ||||
os.rmdir(mempooldotnew1) | os.rmdir(mempooldotnew1) | ||||
self.test_persist_unbroadcast() | |||||
def test_persist_unbroadcast(self): | |||||
node0 = self.nodes[0] | |||||
self.start_node(0) | |||||
# clear out mempool | |||||
node0.generate(1) | |||||
# disconnect nodes to make a txn that remains in the unbroadcast set. | |||||
disconnect_nodes(node0, self.nodes[1]) | |||||
node0.sendtoaddress(self.nodes[1].getnewaddress(), Decimal("12")) | |||||
# shutdown, then startup with wallet disabled | |||||
self.stop_nodes() | |||||
self.start_node(0, extra_args=["-disablewallet"]) | |||||
# check that txn gets broadcast due to unbroadcast logic | |||||
conn = node0.add_p2p_connection(P2PTxInvStore()) | |||||
# 15 min + 1 for buffer | |||||
node0.mockscheduler(16 * 60) | |||||
wait_until(lambda: len(conn.get_invs()) == 1) | |||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
MempoolPersistTest().main() | MempoolPersistTest().main() |