diff --git a/lbry/lbry/testcase.py b/lbry/lbry/testcase.py index 6179bd681..45fb870a4 100644 --- a/lbry/lbry/testcase.py +++ b/lbry/lbry/testcase.py @@ -214,7 +214,9 @@ class CommandTestCase(IntegrationTestCase): self.assertEqual(claim['outputs'][0]['name'], name) if confirm: await self.on_transaction_dict(claim) + height = self.ledger.headers.height await self.generate(1) + await self.on_header(height + 1) await self.on_transaction_dict(claim) return claim diff --git a/lbry/lbry/wallet/__init__.py b/lbry/lbry/wallet/__init__.py index ae3591f9d..0b2a3815c 100644 --- a/lbry/lbry/wallet/__init__.py +++ b/lbry/lbry/wallet/__init__.py @@ -2,7 +2,7 @@ __node_daemon__ = 'lbrycrdd' __node_cli__ = 'lbrycrd-cli' __node_bin__ = '' __node_url__ = ( - 'https://github.com/lbryio/lbrycrd/releases/download/v0.12.4.1/lbrycrd-linux.zip' + 'https://github.com/lbryio/lbrycrd/releases/download/v0.17.2.0/lbrycrd-linux.zip' ) __spvserver__ = 'lbry.wallet.server.coin.LBCRegTest' diff --git a/lbry/lbry/wallet/ledger.py b/lbry/lbry/wallet/ledger.py index 6a964c15d..70404b762 100644 --- a/lbry/lbry/wallet/ledger.py +++ b/lbry/lbry/wallet/ledger.py @@ -3,10 +3,11 @@ import logging from binascii import unhexlify from typing import Tuple, List, Dict +from lbry.wallet.claim_proofs import verify_proof, InvalidProofError from torba.client.baseledger import BaseLedger from torba.client.baseaccount import SingleKey from lbry.schema.result import Outputs -from lbry.schema.url import URL +from lbry.schema.url import URL, normalize_name from lbry.wallet.dewies import dewies_to_lbc from lbry.wallet.resolve import Resolver from lbry.wallet.account import Account @@ -72,7 +73,50 @@ class MainNetLedger(BaseLedger): result[url] = txo else: result[url] = {'error': f'{url} did not resolve to a claim'} - return result + return await self.validate_resolutions(result) + + async def validate_resolutions(self, resolutions: dict): + provables = {} + for url, txo in resolutions.items(): + if isinstance(txo, dict) and 'error' in txo: + continue + parsed_url = URL.parse(url) + # cases we check: names without sequence, claim id or any modifier + # except: @channel/name as the signature is enough, instead we check '@channel' + if parsed_url.has_channel: + if not parsed_url.has_stream_in_channel and parsed_url.channel.to_dict().keys() == {'name'}: + provables.setdefault(normalize_name(parsed_url.channel.name), []).append((url, txo.id)) + elif parsed_url.has_stream and parsed_url.stream.to_dict().keys() == {'name'}: + provables.setdefault(normalize_name(parsed_url.stream.name), []).append((url, txo.id)) + if not provables: + return resolutions + root_hash = self.headers.claim_trie_root.decode() + names = list(provables.keys()) + proofs = await self.network.get_name_proofs(self.headers.hash().decode(), *names) + for proof, name in zip(proofs, names): + all_failed = True + for url, txid in provables[name]: + if not proof.get('txhash'): + resolutions[url] = {'error': f"Proof mismatch on {url}: no claims under {name} but we got {txid}"} + continue + expected_txid = f"{proof['txhash']}:{proof['nOut']}" + if txid != expected_txid: + resolutions[url] = { + 'error': f"Proof mismatch: {url} resolved to {txid} instead of {expected_txid} on {name}" + } + else: + all_failed = False + if not all_failed: + try: + verify_proof(proof, root_hash, name) + continue + except InvalidProofError as error: + for url, _ in provables[name]: + resolution = resolutions[url] + if isinstance(resolution, dict) and 'error' in resolution: + continue + resolutions[url] = {'error': f'Invalid proof for {url}: {str(error)}'} + return resolutions async def claim_search(self, **kwargs) -> Tuple[List, int, int]: return await self._inflate_outputs(self.network.claim_search(**kwargs)) diff --git a/lbry/lbry/wallet/network.py b/lbry/lbry/wallet/network.py index df1d2c757..24740b1aa 100644 --- a/lbry/lbry/wallet/network.py +++ b/lbry/lbry/wallet/network.py @@ -9,5 +9,8 @@ class Network(BaseNetwork): def resolve(self, urls): return self.rpc('blockchain.claimtrie.resolve', urls) + def get_name_proofs(self, block_hash, *names): + return self.rpc('blockchain.claimtrie.getnameproofs', (block_hash, *names)) + def claim_search(self, **kwargs): return self.rpc('blockchain.claimtrie.search', kwargs) diff --git a/lbry/lbry/wallet/server/session.py b/lbry/lbry/wallet/server/session.py index 7b861c934..c5b63d7e6 100644 --- a/lbry/lbry/wallet/server/session.py +++ b/lbry/lbry/wallet/server/session.py @@ -104,6 +104,7 @@ class LBRYElectrumX(ElectrumX): 'blockchain.transaction.get_height': self.transaction_get_height, 'blockchain.claimtrie.search': self.claimtrie_search, 'blockchain.claimtrie.resolve': self.claimtrie_resolve, + 'blockchain.claimtrie.getnameproofs': self.claimtrie_getnameproofs, 'blockchain.claimtrie.getclaimsbyids': self.claimtrie_getclaimsbyids, 'blockchain.block.get_server_height': self.get_server_height, } @@ -165,6 +166,9 @@ class LBRYElectrumX(ElectrumX): async def get_server_height(self): return self.bp.height + def claimtrie_getnameproofs(self, block_hash, *names): + return self.daemon._send_vector('getnameproof', iter((name, block_hash) for name in names)) + async def transaction_get_height(self, tx_hash): self.assert_tx_hash(tx_hash) transaction_info = await self.daemon.getrawtransaction(tx_hash, True) diff --git a/torba/torba/orchstr8/node.py b/torba/torba/orchstr8/node.py index b0a38ecc6..880a49c16 100644 --- a/torba/torba/orchstr8/node.py +++ b/torba/torba/orchstr8/node.py @@ -266,6 +266,7 @@ class BlockchainNode: self.data_path = None self.protocol = None self.transport = None + self.blockchain_address = None self._block_expected = 0 self.hostname = 'localhost' self.peerport = 9246 + 2 # avoid conflict with default peer port @@ -329,7 +330,7 @@ class BlockchainNode: self.daemon_bin, f'-datadir={self.data_path}', '-printtoconsole', '-regtest', '-server', '-txindex', f'-rpcuser={self.rpcuser}', f'-rpcpassword={self.rpcpassword}', f'-rpcport={self.rpcport}', - f'-port={self.peerport}' + f'-port={self.peerport}', '-addresstype=legacy', '-vbparams=segwit:0:999999999999' ) self.log.info(' '.join(command)) self.transport, self.protocol = await loop.subprocess_exec( @@ -364,9 +365,10 @@ class BlockchainNode: self.log.info(out.decode().strip()) return out.decode().strip() - def generate(self, blocks): + async def generate(self, blocks, address=None): self._block_expected += blocks - return self._cli_cmnd('generate', str(blocks)) + address = await self.get_raw_change_address() + return await self._cli_cmnd('generatetoaddress', str(blocks), str(address)) def invalidate_block(self, blockhash): return self._cli_cmnd('invalidateblock', blockhash) @@ -375,7 +377,7 @@ class BlockchainNode: return self._cli_cmnd('getblockhash', str(block)) def get_raw_change_address(self): - return self._cli_cmnd('getrawchangeaddress') + return self._cli_cmnd('getrawchangeaddress', 'legacy') async def get_balance(self): return float(await self._cli_cmnd('getbalance'))