2018-11-04 06:55:50 +01:00
|
|
|
import logging
|
2020-04-01 19:52:14 +02:00
|
|
|
import asyncio
|
2020-04-01 21:26:35 +02:00
|
|
|
from binascii import hexlify
|
2020-03-31 16:30:36 +02:00
|
|
|
from lbry.testcase import CommandTestCase
|
2018-08-16 06:43:38 +02:00
|
|
|
|
|
|
|
|
2020-03-31 16:30:36 +02:00
|
|
|
class BlockchainReorganizationTests(CommandTestCase):
|
2018-08-16 06:43:38 +02:00
|
|
|
|
2018-11-04 06:55:50 +01:00
|
|
|
VERBOSITY = logging.WARN
|
2018-08-16 06:43:38 +02:00
|
|
|
|
2019-06-19 01:42:18 +02:00
|
|
|
async def assertBlockHash(self, height):
|
|
|
|
self.assertEqual(
|
2020-03-21 08:32:03 +01:00
|
|
|
(await self.ledger.headers.hash(height)).decode(),
|
2019-06-19 01:42:18 +02:00
|
|
|
await self.blockchain.get_block_hash(height)
|
|
|
|
)
|
|
|
|
|
2018-09-26 03:06:22 +02:00
|
|
|
async def test_reorg(self):
|
2020-04-24 03:17:44 +02:00
|
|
|
bp = self.conductor.spv_node.server.bp
|
|
|
|
bp.reorg_count_metric.set(0)
|
2019-06-18 23:50:41 +02:00
|
|
|
# invalidate current block, move forward 2
|
2020-03-31 16:30:36 +02:00
|
|
|
self.assertEqual(self.ledger.headers.height, 206)
|
|
|
|
await self.assertBlockHash(206)
|
|
|
|
await self.blockchain.invalidate_block((await self.ledger.headers.hash(206)).decode())
|
2019-06-18 23:50:41 +02:00
|
|
|
await self.blockchain.generate(2)
|
2020-03-31 16:30:36 +02:00
|
|
|
await self.ledger.on_header.where(lambda e: e.height == 207)
|
|
|
|
self.assertEqual(self.ledger.headers.height, 207)
|
|
|
|
await self.assertBlockHash(206)
|
|
|
|
await self.assertBlockHash(207)
|
2020-04-24 03:17:44 +02:00
|
|
|
self.assertEqual(1, bp.reorg_count_metric._samples()[0][2])
|
2018-09-26 03:06:22 +02:00
|
|
|
|
2019-06-18 23:50:41 +02:00
|
|
|
# invalidate current block, move forward 3
|
2020-03-31 16:30:36 +02:00
|
|
|
await self.blockchain.invalidate_block((await self.ledger.headers.hash(206)).decode())
|
2019-06-18 23:50:41 +02:00
|
|
|
await self.blockchain.generate(3)
|
2020-03-31 16:30:36 +02:00
|
|
|
await self.ledger.on_header.where(lambda e: e.height == 208)
|
|
|
|
self.assertEqual(self.ledger.headers.height, 208)
|
|
|
|
await self.assertBlockHash(206)
|
|
|
|
await self.assertBlockHash(207)
|
|
|
|
await self.assertBlockHash(208)
|
2020-04-24 03:17:44 +02:00
|
|
|
self.assertEqual(2, bp.reorg_count_metric._samples()[0][2])
|
2020-04-01 19:52:14 +02:00
|
|
|
|
2020-04-01 21:26:35 +02:00
|
|
|
async def test_reorg_change_claim_height(self):
|
2020-04-01 19:52:14 +02:00
|
|
|
# sanity check
|
2020-04-01 21:11:45 +02:00
|
|
|
txos, _, _, _ = await self.ledger.claim_search([], name='hovercraft')
|
|
|
|
self.assertListEqual(txos, [])
|
2020-04-01 19:52:14 +02:00
|
|
|
|
2020-04-05 22:58:36 +02:00
|
|
|
still_valid = await self.daemon.jsonrpc_stream_create(
|
|
|
|
'still-valid', '1.0', file_path=self.create_upload_file(data=b'hi!')
|
|
|
|
)
|
|
|
|
await self.ledger.wait(still_valid)
|
|
|
|
await self.generate(1)
|
|
|
|
|
2020-04-01 21:11:45 +02:00
|
|
|
# create a claim and verify it's returned by claim_search
|
2020-04-05 22:58:36 +02:00
|
|
|
self.assertEqual(self.ledger.headers.height, 207)
|
2020-04-01 21:11:45 +02:00
|
|
|
broadcast_tx = await self.daemon.jsonrpc_stream_create(
|
|
|
|
'hovercraft', '1.0', file_path=self.create_upload_file(data=b'hi!')
|
|
|
|
)
|
|
|
|
await self.ledger.wait(broadcast_tx)
|
|
|
|
await self.generate(1)
|
|
|
|
await self.ledger.wait(broadcast_tx, self.blockchain.block_expected)
|
2020-04-05 22:58:36 +02:00
|
|
|
self.assertEqual(self.ledger.headers.height, 208)
|
2020-04-01 21:11:45 +02:00
|
|
|
txos, _, _, _ = await self.ledger.claim_search([], name='hovercraft')
|
|
|
|
self.assertEqual(1, len(txos))
|
|
|
|
txo = txos[0]
|
|
|
|
self.assertEqual(txo.tx_ref.id, broadcast_tx.id)
|
2020-04-05 22:58:36 +02:00
|
|
|
self.assertEqual(txo.tx_ref.height, 208)
|
2020-04-01 19:52:14 +02:00
|
|
|
|
2020-04-05 22:58:36 +02:00
|
|
|
# check that our tx is in block 208 as returned by lbrycrdd
|
|
|
|
invalidated_block_hash = (await self.ledger.headers.hash(208)).decode()
|
2020-04-01 19:52:14 +02:00
|
|
|
block_207 = await self.blockchain.get_block(invalidated_block_hash)
|
2020-04-01 21:11:45 +02:00
|
|
|
self.assertIn(txo.tx_ref.id, block_207['tx'])
|
2020-04-05 22:58:36 +02:00
|
|
|
self.assertEqual(208, txos[0].tx_ref.height)
|
2020-04-01 19:52:14 +02:00
|
|
|
|
|
|
|
# reorg the last block dropping our claim tx
|
|
|
|
await self.blockchain.invalidate_block(invalidated_block_hash)
|
|
|
|
await self.blockchain.clear_mempool()
|
|
|
|
await self.blockchain.generate(2)
|
|
|
|
|
2020-04-05 22:58:36 +02:00
|
|
|
# verify the claim was dropped from block 208 as returned by lbrycrdd
|
|
|
|
reorg_block_hash = await self.blockchain.get_block_hash(208)
|
2020-04-01 19:52:14 +02:00
|
|
|
self.assertNotEqual(invalidated_block_hash, reorg_block_hash)
|
|
|
|
block_207 = await self.blockchain.get_block(reorg_block_hash)
|
2020-04-01 21:11:45 +02:00
|
|
|
self.assertNotIn(txo.tx_ref.id, block_207['tx'])
|
2020-04-01 19:52:14 +02:00
|
|
|
|
|
|
|
# wait for the client to catch up and verify the reorg
|
2020-04-05 22:58:36 +02:00
|
|
|
await asyncio.wait_for(self.on_header(209), 3.0)
|
2020-04-01 19:52:14 +02:00
|
|
|
await self.assertBlockHash(207)
|
|
|
|
await self.assertBlockHash(208)
|
2020-04-05 22:58:36 +02:00
|
|
|
await self.assertBlockHash(209)
|
|
|
|
client_reorg_block_hash = (await self.ledger.headers.hash(208)).decode()
|
2020-04-01 19:52:14 +02:00
|
|
|
self.assertEqual(client_reorg_block_hash, reorg_block_hash)
|
|
|
|
|
2020-04-01 21:11:45 +02:00
|
|
|
# verify the dropped claim is no longer returned by claim search
|
|
|
|
txos, _, _, _ = await self.ledger.claim_search([], name='hovercraft')
|
|
|
|
self.assertListEqual(txos, [])
|
2020-04-01 21:26:35 +02:00
|
|
|
|
2020-04-05 22:58:36 +02:00
|
|
|
# verify the claim published a block earlier wasn't also reverted
|
|
|
|
txos, _, _, _ = await self.ledger.claim_search([], name='still-valid')
|
|
|
|
self.assertEqual(1, len(txos))
|
|
|
|
self.assertEqual(207, txos[0].tx_ref.height)
|
|
|
|
|
2020-04-01 21:26:35 +02:00
|
|
|
# broadcast the claim in a different block
|
|
|
|
new_txid = await self.blockchain.sendrawtransaction(hexlify(broadcast_tx.raw).decode())
|
|
|
|
self.assertEqual(broadcast_tx.id, new_txid)
|
|
|
|
await self.blockchain.generate(1)
|
|
|
|
|
|
|
|
# wait for the client to catch up
|
2020-04-05 22:58:36 +02:00
|
|
|
await asyncio.wait_for(self.on_header(210), 1.0)
|
2020-04-01 21:26:35 +02:00
|
|
|
|
|
|
|
# verify the claim is in the new block and that it is returned by claim_search
|
2020-04-05 22:58:36 +02:00
|
|
|
block_210 = await self.blockchain.get_block((await self.ledger.headers.hash(210)).decode())
|
|
|
|
self.assertIn(txo.tx_ref.id, block_210['tx'])
|
2020-04-01 21:26:35 +02:00
|
|
|
txos, _, _, _ = await self.ledger.claim_search([], name='hovercraft')
|
|
|
|
self.assertEqual(1, len(txos))
|
|
|
|
self.assertEqual(txos[0].tx_ref.id, new_txid)
|
2020-04-05 22:58:36 +02:00
|
|
|
self.assertEqual(210, txos[0].tx_ref.height)
|
|
|
|
|
|
|
|
# this should still be unchanged
|
|
|
|
txos, _, _, _ = await self.ledger.claim_search([], name='still-valid')
|
|
|
|
self.assertEqual(1, len(txos))
|
|
|
|
self.assertEqual(207, txos[0].tx_ref.height)
|