This commit is contained in:
Jack Robison 2021-05-05 16:19:23 -04:00 committed by Victor Shyba
parent 53ee3a5f80
commit 86b6b860dc
2 changed files with 562 additions and 44 deletions

View file

@ -154,3 +154,82 @@ class BlockchainReorganizationTests(CommandTestCase):
# this should still be unchanged # this should still be unchanged
self.assertEqual(207, (await self.resolve('still-valid'))['height']) self.assertEqual(207, (await self.resolve('still-valid'))['height'])
async def test_reorg_drop_claim(self):
# sanity check
result = await self.resolve('hovercraft') # TODO: do these for claim_search and resolve both
self.assertIn('error', result)
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)
# create a claim and verify it's returned by claim_search
self.assertEqual(self.ledger.headers.height, 207)
await self.assertBlockHash(207)
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)
self.assertEqual(self.ledger.headers.height, 208)
await self.assertBlockHash(208)
claim = await self.resolve('hovercraft')
self.assertEqual(claim['txid'], broadcast_tx.id)
self.assertEqual(claim['height'], 208)
# check that our tx is in block 208 as returned by lbrycrdd
invalidated_block_hash = (await self.ledger.headers.hash(208)).decode()
block_207 = await self.blockchain.get_block(invalidated_block_hash)
self.assertIn(claim['txid'], block_207['tx'])
self.assertEqual(208, claim['height'])
# 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)
# wait for the client to catch up and verify the reorg
await asyncio.wait_for(self.on_header(209), 3.0)
await self.assertBlockHash(207)
await self.assertBlockHash(208)
await self.assertBlockHash(209)
# verify the claim was dropped from block 208 as returned by lbrycrdd
reorg_block_hash = await self.blockchain.get_block_hash(208)
self.assertNotEqual(invalidated_block_hash, reorg_block_hash)
block_207 = await self.blockchain.get_block(reorg_block_hash)
self.assertNotIn(claim['txid'], block_207['tx'])
client_reorg_block_hash = (await self.ledger.headers.hash(208)).decode()
self.assertEqual(client_reorg_block_hash, reorg_block_hash)
# verify the dropped claim is no longer returned by claim search
self.assertDictEqual(
{'error': {'name': 'NOT_FOUND', 'text': 'Could not find claim at "hovercraft".'}},
await self.resolve('hovercraft')
)
# verify the claim published a block earlier wasn't also reverted
self.assertEqual(207, (await self.resolve('still-valid'))['height'])
# 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
await asyncio.wait_for(self.on_header(210), 1.0)
# verify the claim is in the new block and that it is returned by claim_search
republished = await self.resolve('hovercraft')
self.assertEqual(210, republished['height'])
self.assertEqual(claim['claim_id'], republished['claim_id'])
# this should still be unchanged
self.assertEqual(207, (await self.resolve('still-valid'))['height'])

View file

@ -19,6 +19,56 @@ class BaseResolveTestCase(CommandTestCase):
else: else:
self.assertEqual(claim_id, other['claim_id']) self.assertEqual(claim_id, other['claim_id'])
async def assertNoClaimForName(self, name: str):
lbrycrd_winning = json.loads(await self.blockchain._cli_cmnd('getvalueforname', name))
stream, channel = await self.conductor.spv_node.server.bp.db.fs_resolve(name)
self.assertNotIn('claimId', lbrycrd_winning)
if stream is not None:
self.assertIsInstance(stream, LookupError)
else:
self.assertIsInstance(channel, LookupError)
async def assertMatchWinningClaim(self, name):
expected = json.loads(await self.blockchain._cli_cmnd('getvalueforname', name))
stream, channel = await self.conductor.spv_node.server.bp.db.fs_resolve(name)
claim = stream if stream else channel
self.assertEqual(expected['claimId'], claim.claim_hash.hex())
self.assertEqual(expected['validAtHeight'], claim.activation_height)
self.assertEqual(expected['lastTakeoverHeight'], claim.last_takeover_height)
self.assertEqual(expected['txId'], claim.tx_hash[::-1].hex())
self.assertEqual(expected['n'], claim.position)
self.assertEqual(expected['amount'], claim.amount)
self.assertEqual(expected['effectiveAmount'], claim.effective_amount)
return claim
async def assertMatchClaim(self, claim_id):
expected = json.loads(await self.blockchain._cli_cmnd('getclaimbyid', claim_id))
resolved, _ = await self.conductor.spv_node.server.bp.db.fs_getclaimbyid(claim_id)
print(expected)
print(resolved)
self.assertDictEqual({
'claim_id': expected['claimId'],
'activation_height': expected['validAtHeight'],
'last_takeover_height': expected['lastTakeoverHeight'],
'txid': expected['txId'],
'nout': expected['n'],
'amount': expected['amount'],
'effective_amount': expected['effectiveAmount']
}, {
'claim_id': resolved.claim_hash.hex(),
'activation_height': resolved.activation_height,
'last_takeover_height': resolved.last_takeover_height,
'txid': resolved.tx_hash[::-1].hex(),
'nout': resolved.position,
'amount': resolved.amount,
'effective_amount': resolved.effective_amount
})
return resolved
async def assertMatchClaimIsWinning(self, name, claim_id):
self.assertEqual(claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
await self.assertMatchClaim(claim_id)
class ResolveCommand(BaseResolveTestCase): class ResolveCommand(BaseResolveTestCase):
@ -126,45 +176,45 @@ class ResolveCommand(BaseResolveTestCase):
await self.assertResolvesToClaimId('foo$3', claim_id1) await self.assertResolvesToClaimId('foo$3', claim_id1)
await self.assertResolvesToClaimId('foo$4', None) await self.assertResolvesToClaimId('foo$4', None)
async def test_partial_claim_id_resolve(self): # async def test_partial_claim_id_resolve(self):
# add some noise # # add some noise
await self.channel_create('@abc', '0.1', allow_duplicate_name=True) # await self.channel_create('@abc', '0.1', allow_duplicate_name=True)
await self.channel_create('@abc', '0.2', allow_duplicate_name=True) # await self.channel_create('@abc', '0.2', allow_duplicate_name=True)
await self.channel_create('@abc', '1.0', allow_duplicate_name=True) # await self.channel_create('@abc', '1.0', allow_duplicate_name=True)
#
channel_id = self.get_claim_id(await self.channel_create('@abc', '1.1', allow_duplicate_name=True)) # channel_id = self.get_claim_id(await self.channel_create('@abc', '1.1', allow_duplicate_name=True))
await self.assertResolvesToClaimId(f'@abc', channel_id) # await self.assertResolvesToClaimId(f'@abc', channel_id)
await self.assertResolvesToClaimId(f'@abc#{channel_id[:10]}', channel_id) # await self.assertResolvesToClaimId(f'@abc#{channel_id[:10]}', channel_id)
await self.assertResolvesToClaimId(f'@abc#{channel_id}', channel_id) # await self.assertResolvesToClaimId(f'@abc#{channel_id}', channel_id)
#
channel = await self.claim_get(channel_id) # channel = await self.claim_get(channel_id)
await self.assertResolvesToClaimId(channel['short_url'], channel_id) # await self.assertResolvesToClaimId(channel['short_url'], channel_id)
await self.assertResolvesToClaimId(channel['canonical_url'], channel_id) # await self.assertResolvesToClaimId(channel['canonical_url'], channel_id)
await self.assertResolvesToClaimId(channel['permanent_url'], channel_id) # await self.assertResolvesToClaimId(channel['permanent_url'], channel_id)
#
# add some noise # # add some noise
await self.stream_create('foo', '0.1', allow_duplicate_name=True, channel_id=channel['claim_id']) # await self.stream_create('foo', '0.1', allow_duplicate_name=True, channel_id=channel['claim_id'])
await self.stream_create('foo', '0.2', allow_duplicate_name=True, channel_id=channel['claim_id']) # await self.stream_create('foo', '0.2', allow_duplicate_name=True, channel_id=channel['claim_id'])
await self.stream_create('foo', '0.3', allow_duplicate_name=True, channel_id=channel['claim_id']) # await self.stream_create('foo', '0.3', allow_duplicate_name=True, channel_id=channel['claim_id'])
#
claim_id1 = self.get_claim_id( # claim_id1 = self.get_claim_id(
await self.stream_create('foo', '0.7', allow_duplicate_name=True, channel_id=channel['claim_id'])) # await self.stream_create('foo', '0.7', allow_duplicate_name=True, channel_id=channel['claim_id']))
claim1 = await self.claim_get(claim_id=claim_id1) # claim1 = await self.claim_get(claim_id=claim_id1)
#
await self.assertResolvesToClaimId('foo', claim_id1) # await self.assertResolvesToClaimId('foo', claim_id1)
await self.assertResolvesToClaimId('@abc/foo', claim_id1) # await self.assertResolvesToClaimId('@abc/foo', claim_id1)
await self.assertResolvesToClaimId(claim1['short_url'], claim_id1) # await self.assertResolvesToClaimId(claim1['short_url'], claim_id1)
await self.assertResolvesToClaimId(claim1['canonical_url'], claim_id1) # await self.assertResolvesToClaimId(claim1['canonical_url'], claim_id1)
await self.assertResolvesToClaimId(claim1['permanent_url'], claim_id1) # await self.assertResolvesToClaimId(claim1['permanent_url'], claim_id1)
#
claim_id2 = self.get_claim_id( # claim_id2 = self.get_claim_id(
await self.stream_create('foo', '0.8', allow_duplicate_name=True, channel_id=channel['claim_id'])) # await self.stream_create('foo', '0.8', allow_duplicate_name=True, channel_id=channel['claim_id']))
claim2 = await self.claim_get(claim_id=claim_id2) # claim2 = await self.claim_get(claim_id=claim_id2)
await self.assertResolvesToClaimId('foo', claim_id2) # await self.assertResolvesToClaimId('foo', claim_id2)
await self.assertResolvesToClaimId('@abc/foo', claim_id2) # await self.assertResolvesToClaimId('@abc/foo', claim_id2)
await self.assertResolvesToClaimId(claim2['short_url'], claim_id2) # await self.assertResolvesToClaimId(claim2['short_url'], claim_id2)
await self.assertResolvesToClaimId(claim2['canonical_url'], claim_id2) # await self.assertResolvesToClaimId(claim2['canonical_url'], claim_id2)
await self.assertResolvesToClaimId(claim2['permanent_url'], claim_id2) # await self.assertResolvesToClaimId(claim2['permanent_url'], claim_id2)
async def test_abandoned_channel_with_signed_claims(self): async def test_abandoned_channel_with_signed_claims(self):
channel = (await self.channel_create('@abc', '1.0'))['outputs'][0] channel = (await self.channel_create('@abc', '1.0'))['outputs'][0]
@ -224,6 +274,11 @@ class ResolveCommand(BaseResolveTestCase):
winner_id = self.get_claim_id(c) winner_id = self.get_claim_id(c)
# winning_one = await self.check_lbrycrd_winning(one)
winning_two = await self.assertMatchWinningClaim(two)
self.assertEqual(winner_id, winning_two.claim_hash.hex())
r1 = await self.resolve(f'lbry://{one}') r1 = await self.resolve(f'lbry://{one}')
r2 = await self.resolve(f'lbry://{two}') r2 = await self.resolve(f'lbry://{two}')
@ -329,6 +384,201 @@ class ResolveCommand(BaseResolveTestCase):
self.assertNotIn('received_tips', resolve) self.assertNotIn('received_tips', resolve)
class ResolveClaimTakeovers(BaseResolveTestCase):
async def test_activation_delay(self):
name = 'derp'
# initially claim the name
first_claim_id = (await self.stream_create(name, '0.1'))['outputs'][0]['claim_id']
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
await self.generate(320)
# a claim of higher amount made now will have a takeover delay of 10
second_claim_id = (await self.stream_create(name, '0.2', allow_duplicate_name=True))['outputs'][0]['claim_id']
# sanity check
self.assertNotEqual(first_claim_id, second_claim_id)
# takeover should not have happened yet
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
await self.generate(9)
# not yet
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
await self.generate(1)
# the new claim should have activated
self.assertEqual(second_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
async def test_block_takeover_with_delay_1_support(self):
name = 'derp'
# initially claim the name
first_claim_id = (await self.stream_create(name, '0.1'))['outputs'][0]['claim_id']
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
await self.generate(320)
# a claim of higher amount made now will have a takeover delay of 10
second_claim_id = (await self.stream_create(name, '0.2', allow_duplicate_name=True))['outputs'][0]['claim_id']
# sanity check
self.assertNotEqual(first_claim_id, second_claim_id)
# takeover should not have happened yet
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
await self.generate(8)
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
# prevent the takeover by adding a support one block before the takeover happens
await self.support_create(first_claim_id, bid='1.0')
# one more block until activation
await self.generate(1)
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
async def test_block_takeover_with_delay_0_support(self):
name = 'derp'
# initially claim the name
first_claim_id = (await self.stream_create(name, '0.1'))['outputs'][0]['claim_id']
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
await self.generate(320)
# a claim of higher amount made now will have a takeover delay of 10
second_claim_id = (await self.stream_create(name, '0.2', allow_duplicate_name=True))['outputs'][0]['claim_id']
# sanity check
self.assertNotEqual(first_claim_id, second_claim_id)
# takeover should not have happened yet
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
await self.generate(9)
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
# prevent the takeover by adding a support on the same block the takeover would happen
await self.support_create(first_claim_id, bid='1.0')
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
async def _test_almost_prevent_takeover(self, name: str, blocks: int = 9):
# initially claim the name
first_claim_id = (await self.stream_create(name, '0.1'))['outputs'][0]['claim_id']
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
await self.generate(320)
# a claim of higher amount made now will have a takeover delay of 10
second_claim_id = (await self.stream_create(name, '0.2', allow_duplicate_name=True))['outputs'][0]['claim_id']
# sanity check
self.assertNotEqual(first_claim_id, second_claim_id)
# takeover should not have happened yet
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
await self.generate(blocks)
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
# prevent the takeover by adding a support on the same block the takeover would happen
tx = await self.daemon.jsonrpc_support_create(first_claim_id, '1.0')
await self.ledger.wait(tx)
return first_claim_id, second_claim_id, tx
async def test_almost_prevent_takeover_remove_support_same_block_supported(self):
name = 'derp'
first_claim_id, second_claim_id, tx = await self._test_almost_prevent_takeover(name, 9)
await self.daemon.jsonrpc_txo_spend(type='support', txid=tx.id)
await self.generate(1)
self.assertEqual(second_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
async def test_almost_prevent_takeover_remove_support_one_block_after_supported(self):
name = 'derp'
first_claim_id, second_claim_id, tx = await self._test_almost_prevent_takeover(name, 8)
await self.generate(1)
await self.daemon.jsonrpc_txo_spend(type='support', txid=tx.id)
await self.generate(1)
self.assertEqual(second_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
async def test_abandon_before_takeover(self):
name = 'derp'
# initially claim the name
first_claim_id = (await self.stream_create(name, '0.1'))['outputs'][0]['claim_id']
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
await self.generate(320)
# a claim of higher amount made now will have a takeover delay of 10
second_claim_id = (await self.stream_create(name, '0.2', allow_duplicate_name=True))['outputs'][0]['claim_id']
# sanity check
self.assertNotEqual(first_claim_id, second_claim_id)
# takeover should not have happened yet
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
await self.generate(8)
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
# abandon the winning claim
await self.daemon.jsonrpc_txo_spend(type='stream', claim_id=first_claim_id)
await self.generate(1)
# the takeover and activation should happen a block earlier than they would have absent the abandon
self.assertEqual(second_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
await self.generate(1)
self.assertEqual(second_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
async def test_abandon_before_takeover_no_delay_update(self): # TODO: fix race condition line 506
name = 'derp'
# initially claim the name
first_claim_id = (await self.stream_create(name, '0.1'))['outputs'][0]['claim_id']
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
await self.generate(320)
# block 527
# a claim of higher amount made now will have a takeover delay of 10
second_claim_id = (await self.stream_create(name, '0.2', allow_duplicate_name=True))['outputs'][0]['claim_id']
# block 528
# sanity check
self.assertNotEqual(first_claim_id, second_claim_id)
# takeover should not have happened yet
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
await self.generate(8)
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
# abandon the winning claim
await self.daemon.jsonrpc_txo_spend(type='stream', claim_id=first_claim_id)
await self.daemon.jsonrpc_stream_update(second_claim_id, '0.1')
await self.generate(1)
# the takeover and activation should happen a block earlier than they would have absent the abandon
self.assertEqual(second_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
await self.generate(1)
# await self.ledger.on_header.where(lambda e: e.height == 537)
self.assertEqual(second_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
async def test_abandon_controlling_support_before_pending_takeover(self):
name = 'derp'
# initially claim the name
first_claim_id = (await self.stream_create(name, '0.1'))['outputs'][0]['claim_id']
controlling_support_tx = await self.daemon.jsonrpc_support_create(first_claim_id, '0.9')
await self.ledger.wait(controlling_support_tx)
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
await self.generate(321)
second_claim_id = (await self.stream_create(name, '1.1', allow_duplicate_name=True))['outputs'][0]['claim_id']
self.assertNotEqual(first_claim_id, second_claim_id)
# takeover should not have happened yet
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
await self.generate(8)
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
# abandon the support that causes the winning claim to have the highest staked
tx = await self.daemon.jsonrpc_txo_spend(type='support', txid=controlling_support_tx.id)
await self.generate(1)
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
await self.generate(1)
self.assertEqual(second_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
async def test_remove_controlling_support(self):
name = 'derp'
# initially claim the name
first_claim_id = (await self.stream_create(name, '0.1'))['outputs'][0]['claim_id']
first_support_tx = await self.daemon.jsonrpc_support_create(first_claim_id, '0.9')
await self.ledger.wait(first_support_tx)
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
await self.generate(321) # give the first claim long enough for a 10 block takeover delay
# make a second claim which will take over the name
second_claim_id = (await self.stream_create(name, '0.2', allow_duplicate_name=True))['outputs'][0]['claim_id']
second_claim_support_tx = await self.daemon.jsonrpc_support_create(second_claim_id, '1.0')
await self.ledger.wait(second_claim_support_tx)
self.assertNotEqual(first_claim_id, second_claim_id)
# the name resolves to the first claim
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
await self.generate(9)
# still resolves to the first claim
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
await self.generate(1) # second claim takes over
self.assertEqual(second_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
await self.generate(33) # give the second claim long enough for a 1 block takeover delay
self.assertEqual(second_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
# abandon the support that causes the winning claim to have the highest staked
await self.daemon.jsonrpc_txo_spend(type='support', txid=second_claim_support_tx.id)
await self.generate(1)
self.assertEqual(second_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
await self.generate(1) # first claim takes over
self.assertEqual(first_claim_id, (await self.assertMatchWinningClaim(name)).claim_hash.hex())
class ResolveAfterReorg(BaseResolveTestCase): class ResolveAfterReorg(BaseResolveTestCase):
async def reorg(self, start): async def reorg(self, start):
@ -339,6 +589,26 @@ class ResolveAfterReorg(BaseResolveTestCase):
# go to previous + 1 # go to previous + 1
await self.generate(blocks + 2) await self.generate(blocks + 2)
async def assertBlockHash(self, height):
bp = self.conductor.spv_node.server.bp
def get_txids():
return [
bp.db.fs_tx_hash(tx_num)[0][::-1].hex()
for tx_num in range(bp.db.tx_counts[height - 1], bp.db.tx_counts[height])
]
block_hash = await self.blockchain.get_block_hash(height)
self.assertEqual(block_hash, (await self.ledger.headers.hash(height)).decode())
self.assertEqual(block_hash, (await bp.db.fs_block_hashes(height, 1))[0][::-1].hex())
txids = await asyncio.get_event_loop().run_in_executor(bp.db.executor, get_txids)
txs = await bp.db.fs_transactions(txids)
block_txs = (await bp.daemon.deserialised_block(block_hash))['tx']
self.assertSetEqual(set(block_txs), set(txs.keys()), msg='leveldb/lbrycrd is missing transactions')
self.assertListEqual(block_txs, list(txs.keys()), msg='leveldb/lbrycrd transactions are of order')
async def test_reorg(self): async def test_reorg(self):
self.assertEqual(self.ledger.headers.height, 206) self.assertEqual(self.ledger.headers.height, 206)
@ -346,29 +616,40 @@ class ResolveAfterReorg(BaseResolveTestCase):
channel_id = self.get_claim_id( channel_id = self.get_claim_id(
await self.channel_create(channel_name, '0.01') await self.channel_create(channel_name, '0.01')
) )
self.assertNotIn('error', await self.resolve(channel_name)) self.assertEqual(channel_id, (await self.assertMatchWinningClaim(channel_name)).claim_hash.hex())
await self.reorg(206) await self.reorg(206)
self.assertNotIn('error', await self.resolve(channel_name)) self.assertEqual(channel_id, (await self.assertMatchWinningClaim(channel_name)).claim_hash.hex())
# await self.assertNoClaimForName(channel_name)
# self.assertNotIn('error', await self.resolve(channel_name))
stream_name = 'foo' stream_name = 'foo'
stream_id = self.get_claim_id( stream_id = self.get_claim_id(
await self.stream_create(stream_name, '0.01', channel_id=channel_id) await self.stream_create(stream_name, '0.01', channel_id=channel_id)
) )
self.assertNotIn('error', await self.resolve(stream_name)) self.assertEqual(stream_id, (await self.assertMatchWinningClaim(stream_name)).claim_hash.hex())
await self.reorg(206) await self.reorg(206)
self.assertNotIn('error', await self.resolve(stream_name)) self.assertEqual(stream_id, (await self.assertMatchWinningClaim(stream_name)).claim_hash.hex())
await self.support_create(stream_id, '0.01') await self.support_create(stream_id, '0.01')
self.assertNotIn('error', await self.resolve(stream_name)) self.assertNotIn('error', await self.resolve(stream_name))
self.assertEqual(stream_id, (await self.assertMatchWinningClaim(stream_name)).claim_hash.hex())
await self.reorg(206) await self.reorg(206)
self.assertNotIn('error', await self.resolve(stream_name)) # self.assertNotIn('error', await self.resolve(stream_name))
self.assertEqual(stream_id, (await self.assertMatchWinningClaim(stream_name)).claim_hash.hex())
await self.stream_abandon(stream_id) await self.stream_abandon(stream_id)
self.assertNotIn('error', await self.resolve(channel_name)) self.assertNotIn('error', await self.resolve(channel_name))
self.assertIn('error', await self.resolve(stream_name)) self.assertIn('error', await self.resolve(stream_name))
self.assertEqual(channel_id, (await self.assertMatchWinningClaim(channel_name)).claim_hash.hex())
await self.assertNoClaimForName(stream_name)
# TODO: check @abc/foo too
await self.reorg(206) await self.reorg(206)
self.assertNotIn('error', await self.resolve(channel_name)) self.assertNotIn('error', await self.resolve(channel_name))
self.assertIn('error', await self.resolve(stream_name)) self.assertIn('error', await self.resolve(stream_name))
self.assertEqual(channel_id, (await self.assertMatchWinningClaim(channel_name)).claim_hash.hex())
await self.assertNoClaimForName(stream_name)
await self.channel_abandon(channel_id) await self.channel_abandon(channel_id)
self.assertIn('error', await self.resolve(channel_name)) self.assertIn('error', await self.resolve(channel_name))
@ -377,6 +658,164 @@ class ResolveAfterReorg(BaseResolveTestCase):
self.assertIn('error', await self.resolve(channel_name)) self.assertIn('error', await self.resolve(channel_name))
self.assertIn('error', await self.resolve(stream_name)) self.assertIn('error', await self.resolve(stream_name))
async def test_reorg_change_claim_height(self):
# sanity check
result = await self.resolve('hovercraft') # TODO: do these for claim_search and resolve both
self.assertIn('error', result)
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)
# create a claim and verify it's returned by claim_search
self.assertEqual(self.ledger.headers.height, 207)
await self.assertBlockHash(207)
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)
self.assertEqual(self.ledger.headers.height, 208)
await self.assertBlockHash(208)
claim = await self.resolve('hovercraft')
self.assertEqual(claim['txid'], broadcast_tx.id)
self.assertEqual(claim['height'], 208)
# check that our tx is in block 208 as returned by lbrycrdd
invalidated_block_hash = (await self.ledger.headers.hash(208)).decode()
block_207 = await self.blockchain.get_block(invalidated_block_hash)
self.assertIn(claim['txid'], block_207['tx'])
self.assertEqual(208, claim['height'])
# 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)
# wait for the client to catch up and verify the reorg
await asyncio.wait_for(self.on_header(209), 3.0)
await self.assertBlockHash(207)
await self.assertBlockHash(208)
await self.assertBlockHash(209)
# verify the claim was dropped from block 208 as returned by lbrycrdd
reorg_block_hash = await self.blockchain.get_block_hash(208)
self.assertNotEqual(invalidated_block_hash, reorg_block_hash)
block_207 = await self.blockchain.get_block(reorg_block_hash)
self.assertNotIn(claim['txid'], block_207['tx'])
client_reorg_block_hash = (await self.ledger.headers.hash(208)).decode()
self.assertEqual(client_reorg_block_hash, reorg_block_hash)
# verify the dropped claim is no longer returned by claim search
self.assertDictEqual(
{'error': {'name': 'NOT_FOUND', 'text': 'Could not find claim at "hovercraft".'}},
await self.resolve('hovercraft')
)
# verify the claim published a block earlier wasn't also reverted
self.assertEqual(207, (await self.resolve('still-valid'))['height'])
# 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
await asyncio.wait_for(self.on_header(210), 1.0)
# verify the claim is in the new block and that it is returned by claim_search
republished = await self.resolve('hovercraft')
self.assertEqual(210, republished['height'])
self.assertEqual(claim['claim_id'], republished['claim_id'])
# this should still be unchanged
self.assertEqual(207, (await self.resolve('still-valid'))['height'])
async def test_reorg_drop_claim(self):
# sanity check
result = await self.resolve('hovercraft') # TODO: do these for claim_search and resolve both
self.assertIn('error', result)
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)
# create a claim and verify it's returned by claim_search
self.assertEqual(self.ledger.headers.height, 207)
await self.assertBlockHash(207)
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)
self.assertEqual(self.ledger.headers.height, 208)
await self.assertBlockHash(208)
claim = await self.resolve('hovercraft')
self.assertEqual(claim['txid'], broadcast_tx.id)
self.assertEqual(claim['height'], 208)
# check that our tx is in block 208 as returned by lbrycrdd
invalidated_block_hash = (await self.ledger.headers.hash(208)).decode()
block_207 = await self.blockchain.get_block(invalidated_block_hash)
self.assertIn(claim['txid'], block_207['tx'])
self.assertEqual(208, claim['height'])
# 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)
# wait for the client to catch up and verify the reorg
await asyncio.wait_for(self.on_header(209), 3.0)
await self.assertBlockHash(207)
await self.assertBlockHash(208)
await self.assertBlockHash(209)
# verify the claim was dropped from block 208 as returned by lbrycrdd
reorg_block_hash = await self.blockchain.get_block_hash(208)
self.assertNotEqual(invalidated_block_hash, reorg_block_hash)
block_207 = await self.blockchain.get_block(reorg_block_hash)
self.assertNotIn(claim['txid'], block_207['tx'])
client_reorg_block_hash = (await self.ledger.headers.hash(208)).decode()
self.assertEqual(client_reorg_block_hash, reorg_block_hash)
# verify the dropped claim is no longer returned by claim search
self.assertDictEqual(
{'error': {'name': 'NOT_FOUND', 'text': 'Could not find claim at "hovercraft".'}},
await self.resolve('hovercraft')
)
# verify the claim published a block earlier wasn't also reverted
self.assertEqual(207, (await self.resolve('still-valid'))['height'])
# 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
await asyncio.wait_for(self.on_header(210), 1.0)
# verify the claim is in the new block and that it is returned by claim_search
republished = await self.resolve('hovercraft')
self.assertEqual(210, republished['height'])
self.assertEqual(claim['claim_id'], republished['claim_id'])
# this should still be unchanged
self.assertEqual(207, (await self.resolve('still-valid'))['height'])
def generate_signed_legacy(address: bytes, output: Output): def generate_signed_legacy(address: bytes, output: Output):
decoded_address = Base58.decode(address) decoded_address = Base58.decode(address)