Merge pull request #2871 from lbryio/wallet_txo_data_leak
fix to prevent transaction cache from leaking some information about outputs between unrelated wallets
This commit is contained in:
commit
e24c78be32
4 changed files with 43 additions and 24 deletions
|
@ -2155,11 +2155,11 @@ class Daemon(metaclass=JSONRPCServerType):
|
||||||
accounts = wallet.get_accounts_or_all(funding_account_ids)
|
accounts = wallet.get_accounts_or_all(funding_account_ids)
|
||||||
txo = None
|
txo = None
|
||||||
if claim_id:
|
if claim_id:
|
||||||
txo = await self.ledger.get_claim_by_claim_id(accounts, claim_id)
|
txo = await self.ledger.get_claim_by_claim_id(accounts, claim_id, include_purchase_receipt=True)
|
||||||
if not isinstance(txo, Output) or not txo.is_claim:
|
if not isinstance(txo, Output) or not txo.is_claim:
|
||||||
raise Exception(f"Could not find claim with claim_id '{claim_id}'. ")
|
raise Exception(f"Could not find claim with claim_id '{claim_id}'. ")
|
||||||
elif url:
|
elif url:
|
||||||
txo = (await self.ledger.resolve(accounts, [url]))[url]
|
txo = (await self.ledger.resolve(accounts, [url], include_purchase_receipt=True))[url]
|
||||||
if not isinstance(txo, Output) or not txo.is_claim:
|
if not isinstance(txo, Output) or not txo.is_claim:
|
||||||
raise Exception(f"Could not find claim with url '{url}'. ")
|
raise Exception(f"Could not find claim with url '{url}'. ")
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import os
|
import os
|
||||||
import zlib
|
import zlib
|
||||||
|
import copy
|
||||||
import base64
|
import base64
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
@ -660,12 +661,21 @@ class Ledger(metaclass=LedgerRegistry):
|
||||||
txs: List[Transaction] = await asyncio.gather(*(
|
txs: List[Transaction] = await asyncio.gather(*(
|
||||||
self.cache_transaction(*tx) for tx in outputs.txs
|
self.cache_transaction(*tx) for tx in outputs.txs
|
||||||
))
|
))
|
||||||
if include_purchase_receipt and accounts:
|
|
||||||
|
txos, blocked = outputs.inflate(txs)
|
||||||
|
|
||||||
|
includes = (
|
||||||
|
include_purchase_receipt, include_is_my_output,
|
||||||
|
include_sent_supports, include_sent_tips
|
||||||
|
)
|
||||||
|
if accounts and any(includes):
|
||||||
|
copies = []
|
||||||
|
receipts = {}
|
||||||
|
if include_purchase_receipt:
|
||||||
priced_claims = []
|
priced_claims = []
|
||||||
for tx in txs:
|
for txo in txos:
|
||||||
for txo in tx.outputs:
|
if isinstance(txo, Output) and txo.has_price:
|
||||||
if txo.has_price:
|
priced_claims.append(txo)
|
||||||
priced_claims.append(txo)
|
|
||||||
if priced_claims:
|
if priced_claims:
|
||||||
receipts = {
|
receipts = {
|
||||||
txo.purchased_claim_id: txo for txo in
|
txo.purchased_claim_id: txo for txo in
|
||||||
|
@ -674,46 +684,48 @@ class Ledger(metaclass=LedgerRegistry):
|
||||||
purchased_claim_id__in=[c.claim_id for c in priced_claims]
|
purchased_claim_id__in=[c.claim_id for c in priced_claims]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
for txo in priced_claims:
|
|
||||||
txo.purchase_receipt = receipts.get(txo.claim_id)
|
|
||||||
txos, blocked = outputs.inflate(txs)
|
|
||||||
if any((include_is_my_output, include_sent_supports, include_sent_tips)):
|
|
||||||
for txo in txos:
|
for txo in txos:
|
||||||
if isinstance(txo, Output) and txo.can_decode_claim:
|
if isinstance(txo, Output) and txo.can_decode_claim:
|
||||||
|
# transactions and outputs are cached and shared between wallets
|
||||||
|
# we don't want to leak informaion between wallet so we add the
|
||||||
|
# wallet specific metadata on throw away copies of the txos
|
||||||
|
txo_copy = copy.copy(txo)
|
||||||
|
copies.append(txo_copy)
|
||||||
|
if include_purchase_receipt:
|
||||||
|
txo_copy.purchase_receipt = receipts.get(txo.claim_id)
|
||||||
if include_is_my_output:
|
if include_is_my_output:
|
||||||
mine = await self.db.get_txo_count(
|
mine = await self.db.get_txo_count(
|
||||||
claim_id=txo.claim_id, txo_type__in=CLAIM_TYPES, is_my_output=True,
|
claim_id=txo.claim_id, txo_type__in=CLAIM_TYPES, is_my_output=True,
|
||||||
unspent=True, accounts=accounts
|
unspent=True, accounts=accounts
|
||||||
)
|
)
|
||||||
if mine:
|
if mine:
|
||||||
txo.is_my_output = True
|
txo_copy.is_my_output = True
|
||||||
else:
|
else:
|
||||||
txo.is_my_output = False
|
txo_copy.is_my_output = False
|
||||||
if include_sent_supports:
|
if include_sent_supports:
|
||||||
supports = await self.db.get_txo_sum(
|
supports = await self.db.get_txo_sum(
|
||||||
claim_id=txo.claim_id, txo_type=TXO_TYPES['support'],
|
claim_id=txo.claim_id, txo_type=TXO_TYPES['support'],
|
||||||
is_my_input=True, is_my_output=True,
|
is_my_input=True, is_my_output=True,
|
||||||
unspent=True, accounts=accounts
|
unspent=True, accounts=accounts
|
||||||
)
|
)
|
||||||
txo.sent_supports = supports
|
txo_copy.sent_supports = supports
|
||||||
if include_sent_tips:
|
if include_sent_tips:
|
||||||
tips = await self.db.get_txo_sum(
|
tips = await self.db.get_txo_sum(
|
||||||
claim_id=txo.claim_id, txo_type=TXO_TYPES['support'],
|
claim_id=txo.claim_id, txo_type=TXO_TYPES['support'],
|
||||||
is_my_input=True, is_my_output=False,
|
is_my_input=True, is_my_output=False,
|
||||||
accounts=accounts
|
accounts=accounts
|
||||||
)
|
)
|
||||||
txo.sent_tips = tips
|
txo_copy.sent_tips = tips
|
||||||
if include_received_tips:
|
if include_received_tips:
|
||||||
tips = await self.db.get_txo_sum(
|
tips = await self.db.get_txo_sum(
|
||||||
claim_id=txo.claim_id, txo_type=TXO_TYPES['support'],
|
claim_id=txo.claim_id, txo_type=TXO_TYPES['support'],
|
||||||
is_my_input=False, is_my_output=True,
|
is_my_input=False, is_my_output=True,
|
||||||
accounts=accounts
|
accounts=accounts
|
||||||
)
|
)
|
||||||
txo.received_tips = tips
|
txo_copy.received_tips = tips
|
||||||
if not include_purchase_receipt:
|
else:
|
||||||
# txo's are cached across wallets, this prevents
|
copies.append(txo)
|
||||||
# leaking receipts between wallets
|
txos = copies
|
||||||
txo.purchase_receipt = None
|
|
||||||
return txos, blocked, outputs.offset, outputs.total
|
return txos, blocked, outputs.offset, outputs.total
|
||||||
|
|
||||||
async def resolve(self, accounts, urls, **kwargs):
|
async def resolve(self, accounts, urls, **kwargs):
|
||||||
|
@ -740,8 +752,8 @@ class Ledger(metaclass=LedgerRegistry):
|
||||||
include_is_my_output=include_is_my_output
|
include_is_my_output=include_is_my_output
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_claim_by_claim_id(self, accounts, claim_id) -> Output:
|
async def get_claim_by_claim_id(self, accounts, claim_id, **kwargs) -> Output:
|
||||||
for claim in (await self.claim_search(accounts, claim_id=claim_id))[0]:
|
for claim in (await self.claim_search(accounts, claim_id=claim_id, **kwargs))[0]:
|
||||||
return claim
|
return claim
|
||||||
|
|
||||||
async def _report_state(self):
|
async def _report_state(self):
|
||||||
|
|
|
@ -147,7 +147,7 @@ class PurchaseCommandTests(CommandTestCase):
|
||||||
self.assertEqual(result[1]['claim_id'], result[1]['purchase_receipt']['claim_id'])
|
self.assertEqual(result[1]['claim_id'], result[1]['purchase_receipt']['claim_id'])
|
||||||
|
|
||||||
url = result[0]['canonical_url']
|
url = result[0]['canonical_url']
|
||||||
resolve = await self.resolve(url)
|
resolve = await self.resolve(url, include_purchase_receipt=True)
|
||||||
self.assertEqual(result[0]['claim_id'], resolve['purchase_receipt']['claim_id'])
|
self.assertEqual(result[0]['claim_id'], resolve['purchase_receipt']['claim_id'])
|
||||||
|
|
||||||
self.assertItemCount(await self.daemon.jsonrpc_file_list(), 0)
|
self.assertItemCount(await self.daemon.jsonrpc_file_list(), 0)
|
||||||
|
|
|
@ -321,7 +321,14 @@ class ResolveCommand(BaseResolveTestCase):
|
||||||
self.assertEqual('0.0', resolve['sent_tips'])
|
self.assertEqual('0.0', resolve['sent_tips'])
|
||||||
self.assertEqual('0.9', resolve['received_tips'])
|
self.assertEqual('0.9', resolve['received_tips'])
|
||||||
self.assertEqual('1.4', resolve['meta']['support_amount'])
|
self.assertEqual('1.4', resolve['meta']['support_amount'])
|
||||||
self.assertNotIn('purchase_receipt', resolve) # prevent leaking cached receipts
|
|
||||||
|
# make sure nothing is leaked between wallets through cached tx/txos
|
||||||
|
resolve = await self.resolve('priced')
|
||||||
|
self.assertNotIn('is_my_output', resolve)
|
||||||
|
self.assertNotIn('purchase_receipt', resolve)
|
||||||
|
self.assertNotIn('sent_supports', resolve)
|
||||||
|
self.assertNotIn('sent_tips', resolve)
|
||||||
|
self.assertNotIn('received_tips', resolve)
|
||||||
|
|
||||||
|
|
||||||
class ResolveAfterReorg(BaseResolveTestCase):
|
class ResolveAfterReorg(BaseResolveTestCase):
|
||||||
|
|
Loading…
Reference in a new issue