mirror of
https://github.com/dashpay/dash.git
synced 2024-12-27 04:52:59 +01:00
7d601cfa85
c055f6b216659b844c8dcd4ff2a977f181099678 test: Remove false coinstatsindex test (Fabian Jahr) Pull request description: This test never actually tested the behavior that it describes in the comments. This was discovered in #21590 which seems to speed up muhash which lead to the test failing. I can vaguely remember that the described behavior was desired by some reviewers of `coinstatsindex`: That `coinstatsindex` should be aware of stale blocks and able to return statistics on them as well. The index actually does this for blocks that it sees while the index is active, i.e. while running `coinstatsindex` all blocks will be indexed and even when they become stale the index (via `gettxoutsetinfo`) will still return a result for them when given the right hash. But this currently does not work for blocks that the node saw and that became stale _before_ the node activated `coinstatsindex`. While the index syncs initially everything but the active chain is ignored and I don't see any indication that this ever worked differently in the past. Introducing this behavior seems non-trivial at first glance so, while I will give this a shot, I think the test should be removed so it does not confuse users and does not block #21590. Top commit has no ACKs. Tree-SHA512: b05f5dfeea3e453c8bb7c761501d0d896d4412a3f0c08037955951fae9fe388c63402da401792591e18da8fb67734f47f1a297d573cdb66e0ced451698718067
310 lines
12 KiB
Python
Executable File
310 lines
12 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Copyright (c) 2020 The Bitcoin Core developers
|
|
# Distributed under the MIT software license, see the accompanying
|
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
"""Test coinstatsindex across nodes.
|
|
|
|
Test that the values returned by gettxoutsetinfo are consistent
|
|
between a node running the coinstatsindex and a node without
|
|
the index.
|
|
"""
|
|
|
|
import struct
|
|
|
|
from decimal import Decimal
|
|
|
|
from test_framework.blocktools import (
|
|
create_block,
|
|
create_coinbase,
|
|
)
|
|
from test_framework.messages import (
|
|
COIN,
|
|
COutPoint,
|
|
CTransaction,
|
|
CTxIn,
|
|
CTxOut,
|
|
ToHex,
|
|
)
|
|
from test_framework.script import (
|
|
CScript,
|
|
OP_FALSE,
|
|
OP_RETURN,
|
|
)
|
|
from test_framework.test_framework import BitcoinTestFramework
|
|
from test_framework.util import (
|
|
assert_equal,
|
|
assert_raises_rpc_error,
|
|
)
|
|
|
|
class CoinStatsIndexTest(BitcoinTestFramework):
|
|
def set_test_params(self):
|
|
self.setup_clean_chain = True
|
|
self.num_nodes = 2
|
|
self.supports_cli = False
|
|
self.extra_args = [
|
|
[],
|
|
["-coinstatsindex"]
|
|
]
|
|
|
|
def skip_test_if_missing_module(self):
|
|
self.skip_if_no_wallet()
|
|
|
|
def run_test(self):
|
|
self._test_coin_stats_index()
|
|
self._test_use_index_option()
|
|
self._test_reorg_index()
|
|
self._test_index_rejects_hash_serialized()
|
|
|
|
def block_subsidy(self, prev_bits):
|
|
# Subsidy calculations valid till block 4500
|
|
diff = 0x0000ffff / (prev_bits & 0x00ffffff)
|
|
subsidy = (1111.0 / (pow((diff+1.0),2.0)))
|
|
return min(500, max(1, subsidy))
|
|
|
|
def get_block_subsidy(self, prev_height):
|
|
prev_block = self.nodes[0].getblockheader(self.nodes[0].getblockhash(prev_height), True)
|
|
return self.block_subsidy(struct.unpack('!I', bytes.fromhex(prev_block['bits']))[0])
|
|
|
|
def block_sanity_check(self, block_info, prev_height):
|
|
if prev_height != -1:
|
|
block_subsidy = self.get_block_subsidy(prev_height)
|
|
else:
|
|
block_subsidy = 50 # see chainparams.cpp
|
|
assert_equal(
|
|
block_info['prevout_spent'] + block_subsidy,
|
|
block_info['new_outputs_ex_coinbase'] + block_info['coinbase'] + block_info['unspendable']
|
|
)
|
|
|
|
def _test_coin_stats_index(self):
|
|
node = self.nodes[0]
|
|
index_node = self.nodes[1]
|
|
# Both none and muhash options allow the usage of the index
|
|
index_hash_options = ['none', 'muhash']
|
|
|
|
# Generate a normal transaction and mine it
|
|
node.generate(101)
|
|
address = self.nodes[0].get_deterministic_priv_key().address
|
|
node.sendtoaddress(address=address, amount=10, subtractfeefromamount=True)
|
|
node.generate(1)
|
|
|
|
self.sync_blocks(timeout=120)
|
|
|
|
self.log.info("Test that gettxoutsetinfo() output is consistent with or without coinstatsindex option")
|
|
res0 = node.gettxoutsetinfo('none')
|
|
|
|
# The fields 'disk_size' and 'transactions' do not exist on the index
|
|
del res0['disk_size'], res0['transactions']
|
|
|
|
for hash_option in index_hash_options:
|
|
res1 = index_node.gettxoutsetinfo(hash_option)
|
|
# The fields 'block_info' and 'total_unspendable_amount' only exist on the index
|
|
del res1['block_info'], res1['total_unspendable_amount']
|
|
res1.pop('muhash', None)
|
|
|
|
# Everything left should be the same
|
|
assert_equal(res1, res0)
|
|
|
|
self.log.info("Test that gettxoutsetinfo() can get fetch data on specific heights with index")
|
|
|
|
# Generate a new tip
|
|
node.generate(5)
|
|
|
|
for hash_option in index_hash_options:
|
|
# Fetch old stats by height
|
|
res2 = index_node.gettxoutsetinfo(hash_option, 102)
|
|
del res2['block_info'], res2['total_unspendable_amount']
|
|
res2.pop('muhash', None)
|
|
assert_equal(res0, res2)
|
|
|
|
# Fetch old stats by hash
|
|
res3 = index_node.gettxoutsetinfo(hash_option, res0['bestblock'])
|
|
del res3['block_info'], res3['total_unspendable_amount']
|
|
res3.pop('muhash', None)
|
|
assert_equal(res0, res3)
|
|
|
|
# It does not work without coinstatsindex
|
|
assert_raises_rpc_error(-8, "Querying specific block heights requires coinstatsindex", node.gettxoutsetinfo, hash_option, 102)
|
|
|
|
self.log.info("Test gettxoutsetinfo() with index and verbose flag")
|
|
|
|
for hash_option in index_hash_options:
|
|
# Genesis block is unspendable
|
|
res4 = index_node.gettxoutsetinfo(hash_option, 0)
|
|
assert_equal(res4['total_unspendable_amount'], 50)
|
|
assert_equal(res4['block_info'], {
|
|
'unspendable': 50,
|
|
'prevout_spent': 0,
|
|
'new_outputs_ex_coinbase': 0,
|
|
'coinbase': 0,
|
|
'unspendables': {
|
|
'genesis_block': 50,
|
|
'bip30': 0,
|
|
'scripts': 0,
|
|
'unclaimed_rewards': 0
|
|
}
|
|
})
|
|
self.block_sanity_check(res4['block_info'], -1)
|
|
|
|
# Test an older block height that included a normal tx
|
|
res5 = index_node.gettxoutsetinfo(hash_option, 102)
|
|
assert_equal(res5['total_unspendable_amount'], 50)
|
|
assert_equal(res5['block_info'], {
|
|
'unspendable': 0,
|
|
'prevout_spent': 500,
|
|
'new_outputs_ex_coinbase': Decimal('499.99999775'),
|
|
'coinbase': Decimal('500.00000225'),
|
|
'unspendables': {
|
|
'genesis_block': 0,
|
|
'bip30': 0,
|
|
'scripts': 0,
|
|
'unclaimed_rewards': 0
|
|
}
|
|
})
|
|
self.block_sanity_check(res5['block_info'], 101)
|
|
|
|
# Generate and send a normal tx with two outputs
|
|
tx1_inputs = []
|
|
tx1_outputs = {self.nodes[0].getnewaddress(): 21, self.nodes[0].getnewaddress(): 42}
|
|
raw_tx1 = self.nodes[0].createrawtransaction(tx1_inputs, tx1_outputs)
|
|
funded_tx1 = self.nodes[0].fundrawtransaction(raw_tx1)
|
|
signed_tx1 = self.nodes[0].signrawtransactionwithwallet(funded_tx1['hex'])
|
|
tx1_txid = self.nodes[0].sendrawtransaction(signed_tx1['hex'])
|
|
|
|
# Find the right position of the 21 BTC output
|
|
tx1_final = self.nodes[0].gettransaction(tx1_txid)
|
|
for output in tx1_final['details']:
|
|
if output['amount'] == Decimal('21.00000000') and output['category'] == 'receive':
|
|
n = output['vout']
|
|
|
|
# Generate and send another tx with an OP_RETURN output (which is unspendable)
|
|
tx2 = CTransaction()
|
|
tx2.vin.append(CTxIn(COutPoint(int(tx1_txid, 16), n), b''))
|
|
tx2.vout.append(CTxOut(int(20.99 * COIN), CScript([OP_RETURN] + [OP_FALSE]*30)))
|
|
tx2_hex = self.nodes[0].signrawtransactionwithwallet(ToHex(tx2))['hex']
|
|
self.nodes[0].sendrawtransaction(tx2_hex)
|
|
|
|
# Include both txs in a block
|
|
self.nodes[0].generate(1)
|
|
self.sync_all()
|
|
|
|
for hash_option in index_hash_options:
|
|
# Check all amounts were registered correctly
|
|
res6 = index_node.gettxoutsetinfo(hash_option, 108)
|
|
assert_equal(res6['total_unspendable_amount'], Decimal('70.98999999'))
|
|
assert_equal(res6['block_info'], {
|
|
'unspendable': Decimal('20.98999999'),
|
|
'prevout_spent': 511,
|
|
'new_outputs_ex_coinbase': Decimal('489.99999741'),
|
|
'coinbase': Decimal('500.01000260'),
|
|
'unspendables': {
|
|
'genesis_block': 0,
|
|
'bip30': 0,
|
|
'scripts': Decimal('20.98999999'),
|
|
'unclaimed_rewards': 0
|
|
}
|
|
})
|
|
self.block_sanity_check(res6['block_info'], 107)
|
|
|
|
# Create a coinbase that does not claim full subsidy and also
|
|
# has two outputs
|
|
cb = create_coinbase(109, nValue=35)
|
|
cb.vout.append(CTxOut(5 * COIN, CScript([OP_FALSE])))
|
|
cb.rehash()
|
|
|
|
# Generate a block that includes previous coinbase
|
|
tip = self.nodes[0].getbestblockhash()
|
|
block_time = self.nodes[0].getblock(tip)['time'] + 1
|
|
block = create_block(int(tip, 16), cb, block_time)
|
|
block.solve()
|
|
self.nodes[0].submitblock(ToHex(block))
|
|
self.sync_all()
|
|
|
|
for hash_option in index_hash_options:
|
|
res7 = index_node.gettxoutsetinfo(hash_option, 109)
|
|
assert_equal(res7['total_unspendable_amount'], Decimal('530.98999999'))
|
|
assert_equal(res7['block_info'], {
|
|
'unspendable': 460,
|
|
'prevout_spent': 0,
|
|
'new_outputs_ex_coinbase': 0,
|
|
'coinbase': 40,
|
|
'unspendables': {
|
|
'genesis_block': 0,
|
|
'bip30': 0,
|
|
'scripts': 0,
|
|
'unclaimed_rewards': 460
|
|
}
|
|
})
|
|
self.block_sanity_check(res7['block_info'], 108)
|
|
|
|
self.log.info("Test that the index is robust across restarts")
|
|
|
|
res8 = index_node.gettxoutsetinfo('muhash')
|
|
self.restart_node(1, extra_args=self.extra_args[1])
|
|
res9 = index_node.gettxoutsetinfo('muhash')
|
|
assert_equal(res8, res9)
|
|
|
|
index_node.generate(1)
|
|
res10 = index_node.gettxoutsetinfo('muhash')
|
|
assert(res8['txouts'] < res10['txouts'])
|
|
|
|
def _test_use_index_option(self):
|
|
self.log.info("Test use_index option for nodes running the index")
|
|
|
|
self.connect_nodes(0, 1)
|
|
self.nodes[0].waitforblockheight(110)
|
|
res = self.nodes[0].gettxoutsetinfo('muhash')
|
|
option_res = self.nodes[1].gettxoutsetinfo(hash_type='muhash', hash_or_height=None, use_index=False)
|
|
del res['disk_size'], option_res['disk_size']
|
|
assert_equal(res, option_res)
|
|
|
|
def _test_reorg_index(self):
|
|
self.log.info("Test that index can handle reorgs")
|
|
|
|
# Generate two block, let the index catch up, then invalidate the blocks
|
|
index_node = self.nodes[1]
|
|
reorg_blocks = index_node.generatetoaddress(2, index_node.getnewaddress())
|
|
reorg_block = reorg_blocks[1]
|
|
res_invalid = index_node.gettxoutsetinfo('muhash')
|
|
index_node.invalidateblock(reorg_blocks[0])
|
|
assert_equal(index_node.gettxoutsetinfo('muhash')['height'], 110)
|
|
|
|
# Add two new blocks
|
|
block = index_node.generate(2)[1]
|
|
res = index_node.gettxoutsetinfo(hash_type='muhash', hash_or_height=None, use_index=False)
|
|
|
|
# Test that the result of the reorged block is not returned for its old block height
|
|
res2 = index_node.gettxoutsetinfo(hash_type='muhash', hash_or_height=112)
|
|
assert_equal(res["bestblock"], block)
|
|
assert_equal(res["muhash"], res2["muhash"])
|
|
assert(res["muhash"] != res_invalid["muhash"])
|
|
|
|
# Test that requesting reorged out block by hash is still returning correct results
|
|
res_invalid2 = index_node.gettxoutsetinfo(hash_type='muhash', hash_or_height=reorg_block)
|
|
assert_equal(res_invalid2["muhash"], res_invalid["muhash"])
|
|
assert(res["muhash"] != res_invalid2["muhash"])
|
|
|
|
# Add another block, so we don't depend on reconsiderblock remembering which
|
|
# blocks were touched by invalidateblock
|
|
index_node.generate(1)
|
|
self.sync_all()
|
|
|
|
# Ensure that removing and re-adding blocks yields consistent results
|
|
block = index_node.getblockhash(99)
|
|
index_node.invalidateblock(block)
|
|
index_node.reconsiderblock(block)
|
|
res3 = index_node.gettxoutsetinfo(hash_type='muhash', hash_or_height=112)
|
|
assert_equal(res2, res3)
|
|
|
|
def _test_index_rejects_hash_serialized(self):
|
|
self.log.info("Test that the rpc raises if the legacy hash is passed with the index")
|
|
|
|
msg = "hash_serialized_2 hash type cannot be queried for a specific block"
|
|
assert_raises_rpc_error(-8, msg, self.nodes[1].gettxoutsetinfo, hash_type='hash_serialized_2', hash_or_height=111)
|
|
|
|
for use_index in {True, False, None}:
|
|
assert_raises_rpc_error(-8, msg, self.nodes[1].gettxoutsetinfo, hash_type='hash_serialized_2', hash_or_height=111, use_index=use_index)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
CoinStatsIndexTest().main()
|