forked from LBRYCommunity/lbry-sdk
further refactor resolve.py
This commit is contained in:
parent
ca17d8597d
commit
0b0fd64642
2 changed files with 73 additions and 160 deletions
|
@ -51,21 +51,20 @@ class MainNetLedger(BaseLedger):
|
|||
|
||||
@property
|
||||
def resolver(self):
|
||||
return Resolver(self.headers, self.transaction_class,
|
||||
hash160_to_address=self.hash160_to_address, network=self.network, ledger=self)
|
||||
return Resolver(self.headers, self.transaction_class, network=self.network, ledger=self)
|
||||
|
||||
def resolve(self, page, page_size, *uris):
|
||||
return self.resolver.resolve(page, page_size, *uris)
|
||||
|
||||
async def get_claim_by_claim_id(self, claim_id):
|
||||
result = (await self.network.get_claims_by_ids(claim_id)).pop(claim_id, {})
|
||||
return await self.resolver.get_certificate_and_validate_result(result)
|
||||
return await self.resolver.parse_and_validate_claim_result(result)
|
||||
|
||||
async def get_claim_by_outpoint(self, txid, nout):
|
||||
claims = (await self.network.get_claims_in_tx(txid)) or []
|
||||
for claim in claims:
|
||||
if claim['nout'] == nout:
|
||||
return await self.resolver.get_certificate_and_validate_result(claim)
|
||||
return await self.resolver.parse_and_validate_claim_result(claim)
|
||||
return 'claim not found'
|
||||
|
||||
async def start(self):
|
||||
|
|
|
@ -17,12 +17,11 @@ log = logging.getLogger(__name__)
|
|||
|
||||
class Resolver:
|
||||
|
||||
def __init__(self, headers, transaction_class, hash160_to_address, network, ledger):
|
||||
def __init__(self, headers, transaction_class, network, ledger):
|
||||
self.claim_trie_root = headers.claim_trie_root
|
||||
self.height = headers.height
|
||||
self.header_hash = headers.hash().decode()
|
||||
self.transaction_class = transaction_class
|
||||
self.hash160_to_address = hash160_to_address
|
||||
self.network = network
|
||||
self.ledger = ledger
|
||||
|
||||
|
@ -60,7 +59,7 @@ class Resolver:
|
|||
result = {}
|
||||
claim_trie_root = self.claim_trie_root
|
||||
parsed_uri = parse_lbry_uri(uri)
|
||||
certificate = None
|
||||
certificate_response = None
|
||||
# parse an included certificate
|
||||
if 'certificate' in resolution:
|
||||
certificate_response = resolution['certificate']['result']
|
||||
|
@ -69,37 +68,16 @@ class Resolver:
|
|||
if 'height' in certificate_response:
|
||||
height = certificate_response['height']
|
||||
depth = self.height - height
|
||||
certificate_result = _verify_proof(parsed_uri.name,
|
||||
certificate_response = _verify_proof(parsed_uri.name,
|
||||
claim_trie_root,
|
||||
certificate_response,
|
||||
height, depth,
|
||||
transaction_class=self.transaction_class,
|
||||
hash160_to_address=self.hash160_to_address)
|
||||
result['certificate'] = await self.parse_and_validate_claim_result(certificate_result,
|
||||
raw=raw)
|
||||
elif certificate_resolution_type == "claim_id":
|
||||
result['certificate'] = await self.parse_and_validate_claim_result(certificate_response,
|
||||
raw=raw)
|
||||
elif certificate_resolution_type == "sequence":
|
||||
result['certificate'] = await self.parse_and_validate_claim_result(certificate_response,
|
||||
raw=raw)
|
||||
else:
|
||||
log.error("unknown response type: %s", certificate_resolution_type)
|
||||
|
||||
if 'certificate' in result:
|
||||
certificate = result['certificate']
|
||||
if 'unverified_claims_in_channel' in resolution:
|
||||
max_results = len(resolution['unverified_claims_in_channel'])
|
||||
result['claims_in_channel'] = max_results
|
||||
else:
|
||||
result['claims_in_channel'] = 0
|
||||
else:
|
||||
result['error'] = "claim not found"
|
||||
result['success'] = False
|
||||
result['uri'] = str(parsed_uri)
|
||||
|
||||
else:
|
||||
certificate = None
|
||||
ledger=self.ledger)
|
||||
elif certificate_resolution_type not in ['winning', 'claim_id', 'sequence']:
|
||||
raise Exception("unknown response type: %s", certificate_resolution_type)
|
||||
result['certificate'] = await self.parse_and_validate_claim_result(certificate_response)
|
||||
result['claims_in_channel'] = len(resolution.get('unverified_claims_in_channel', []))
|
||||
|
||||
# if this was a resolution for a name, parse the result
|
||||
if 'claim' in resolution:
|
||||
|
@ -109,25 +87,16 @@ class Resolver:
|
|||
if 'height' in claim_response:
|
||||
height = claim_response['height']
|
||||
depth = self.height - height
|
||||
claim_result = _verify_proof(parsed_uri.name,
|
||||
claim_trie_root,
|
||||
claim_response,
|
||||
height, depth,
|
||||
transaction_class=self.transaction_class,
|
||||
hash160_to_address=self.hash160_to_address)
|
||||
result['claim'] = await self.parse_and_validate_claim_result(claim_result,
|
||||
certificate,
|
||||
raw)
|
||||
elif claim_resolution_type == "claim_id":
|
||||
result['claim'] = await self.parse_and_validate_claim_result(claim_response,
|
||||
certificate,
|
||||
raw)
|
||||
elif claim_resolution_type == "sequence":
|
||||
result['claim'] = await self.parse_and_validate_claim_result(claim_response,
|
||||
certificate,
|
||||
raw)
|
||||
else:
|
||||
log.error("unknown response type: %s", claim_resolution_type)
|
||||
claim_response = _verify_proof(parsed_uri.name,
|
||||
claim_trie_root,
|
||||
claim_response,
|
||||
height, depth,
|
||||
transaction_class=self.transaction_class,
|
||||
ledger=self.ledger)
|
||||
elif claim_resolution_type not in ["sequence", "winning", "claim_id"]:
|
||||
raise Exception("unknown response type: %s", claim_resolution_type)
|
||||
result['claim'] = await self.parse_and_validate_claim_result(claim_response,
|
||||
certificate_response)
|
||||
|
||||
# if this was a resolution for a name in a channel make sure there is only one valid
|
||||
# match
|
||||
|
@ -169,58 +138,40 @@ class Resolver:
|
|||
return {'error': 'claim not found', 'success': False, 'uri': str(parsed_uri)}
|
||||
return result
|
||||
|
||||
async def get_certificate_and_validate_result(self, claim_result):
|
||||
async def parse_and_validate_claim_result(self, claim_result, certificate=None):
|
||||
if not claim_result or 'value' not in claim_result:
|
||||
return claim_result
|
||||
certificate = None
|
||||
certificate_id = Claim.from_bytes(unhexlify(claim_result['value'])).signing_channel_id
|
||||
if certificate_id:
|
||||
certificate = await self.network.get_claims_by_ids(certificate_id)
|
||||
certificate = certificate.pop(certificate_id) if certificate else None
|
||||
return await self.parse_and_validate_claim_result(claim_result, certificate=certificate)
|
||||
claim_result = _decode_claim_result(claim_result)
|
||||
channel_id = None
|
||||
|
||||
async def parse_and_validate_claim_result(self, claim_result, certificate=None, raw=False):
|
||||
if not claim_result or 'value' not in claim_result:
|
||||
return claim_result
|
||||
|
||||
claim_result['decoded_claim'] = False
|
||||
decoded = None
|
||||
|
||||
if not raw:
|
||||
claim_value = claim_result['value']
|
||||
try:
|
||||
decoded = claim_result['value'] = Claim.from_bytes(unhexlify(claim_value))
|
||||
claim_result['decoded_claim'] = True
|
||||
except DecodeError:
|
||||
pass
|
||||
|
||||
if decoded:
|
||||
if claim_result['value']:
|
||||
claim_result['has_signature'] = False
|
||||
if decoded.is_signed:
|
||||
if claim_result['value'].is_signed:
|
||||
claim_result['has_signature'] = True
|
||||
claim_tx = await self.network.get_transaction(claim_result['txid'])
|
||||
if certificate is None:
|
||||
log.info("fetching certificate to check claim signature")
|
||||
certificate = await self.network.get_claims_by_ids(decoded.signing_channel_id)
|
||||
channel_id = claim_result['value'].signing_channel_id
|
||||
certificate = (await self.network.get_claims_by_ids(channel_id)).get(channel_id)
|
||||
if not certificate:
|
||||
log.warning('Certificate %s not found', decoded.signing_channel_id)
|
||||
log.warning('Certificate %s not found', channel_id)
|
||||
claim_result['channel_name'] = certificate['name'] if certificate else None
|
||||
cert_tx = await self.network.get_transaction(certificate['txid']) if certificate else None
|
||||
claim_result['has_signature'] = True
|
||||
claim_result['signature_is_valid'] = False
|
||||
validated, channel_name = validate_claim_signature_and_get_channel_name(
|
||||
claim_result['signature_is_valid'] = validate_claim_signature_and_get_channel_name(
|
||||
claim_result, certificate, self.ledger, claim_tx=claim_tx, cert_tx=cert_tx
|
||||
)
|
||||
claim_result['channel_name'] = channel_name
|
||||
if validated:
|
||||
claim_result['signature_is_valid'] = True
|
||||
|
||||
if 'height' in claim_result and claim_result['height'] is None:
|
||||
claim_result['height'] = -1
|
||||
|
||||
if 'amount' in claim_result:
|
||||
claim_result = format_amount_value(claim_result)
|
||||
claim_result['amount'] = dewies_to_lbc(claim_result['amount'])
|
||||
claim_result['effective_amount'] = dewies_to_lbc(claim_result['effective_amount'])
|
||||
claim_result['supports'] = [
|
||||
{'txid': txid, 'nout': nout, 'amount': dewies_to_lbc(amount)}
|
||||
for (txid, nout, amount) in claim_result['supports']
|
||||
]
|
||||
|
||||
claim_result['height'] = claim_result.get('height', -1) or -1
|
||||
claim_result['permanent_url'] = _get_permanent_url(
|
||||
claim_result, decoded.signing_channel_id if decoded else None)
|
||||
claim_result, channel_id)
|
||||
|
||||
return claim_result
|
||||
|
||||
|
@ -301,35 +252,16 @@ class Resolver:
|
|||
start_position = (page - 1) * page_size
|
||||
queries, names, claim_positions = self.prepare_claim_queries(start_position, page_size,
|
||||
channel_claim_infos)
|
||||
page_generator = await self.iter_channel_claims_pages(queries, claim_positions, names,
|
||||
certificate, page_size=page_size)
|
||||
upper_bound = len(claim_positions)
|
||||
if not page:
|
||||
return None, upper_bound
|
||||
if start_position > upper_bound:
|
||||
raise IndexError("claim %i greater than max %i" % (start_position, upper_bound))
|
||||
page_generator = await self.iter_channel_claims_pages(queries, claim_positions, names,
|
||||
certificate, page_size=page_size)
|
||||
return page_generator, upper_bound
|
||||
|
||||
|
||||
# Format amount to be decimal encoded string
|
||||
# Format value to be hex encoded string
|
||||
# TODO: refactor. Came from lbryum, there could be another part of torba doing it
|
||||
def format_amount_value(obj):
|
||||
if isinstance(obj, dict):
|
||||
for k, v in obj.items():
|
||||
if k in ('amount', 'effective_amount'):
|
||||
if not isinstance(obj[k], float):
|
||||
obj[k] = dewies_to_lbc(obj[k])
|
||||
elif k == 'supports' and isinstance(v, list):
|
||||
obj[k] = [{'txid': txid, 'nout': nout, 'amount': dewies_to_lbc(amount)}
|
||||
for (txid, nout, amount) in v]
|
||||
elif isinstance(v, (list, dict)):
|
||||
obj[k] = format_amount_value(v)
|
||||
elif isinstance(obj, list):
|
||||
obj = [format_amount_value(o) for o in obj]
|
||||
return obj
|
||||
|
||||
|
||||
def _get_permanent_url(claim_result, certificate_id):
|
||||
if certificate_id:
|
||||
return f"{claim_result['channel_name']}#{certificate_id}/{claim_result['name']}"
|
||||
|
@ -337,51 +269,39 @@ def _get_permanent_url(claim_result, certificate_id):
|
|||
return f"{claim_result['name']}#{claim_result['claim_id']}"
|
||||
|
||||
|
||||
def _verify_proof(name, claim_trie_root, result, height, depth, transaction_class, hash160_to_address):
|
||||
def _verify_proof(name, claim_trie_root, result, height, depth, transaction_class, ledger):
|
||||
"""
|
||||
Verify proof for name claim
|
||||
"""
|
||||
support_amount = sum([amt for (stxid, snout, amt) in result['supports']])
|
||||
|
||||
def _build_response(name, value, claim_id, txid, n, amount, effective_amount,
|
||||
claim_sequence, claim_address, supports):
|
||||
def _build_response(name, tx, nOut):
|
||||
output = tx.outputs[nOut]
|
||||
r = {
|
||||
'name': name,
|
||||
'value': hexlify(value),
|
||||
'claim_id': claim_id,
|
||||
'txid': txid,
|
||||
'nout': n,
|
||||
'amount': amount,
|
||||
'effective_amount': effective_amount,
|
||||
'value': hexlify(output.script.values['claim']),
|
||||
'claim_id': output.claim_id,
|
||||
'txid': tx.id,
|
||||
'nout': nOut,
|
||||
'amount': output.amount,
|
||||
'effective_amount': output.amount + support_amount,
|
||||
'height': height,
|
||||
'depth': depth,
|
||||
'claim_sequence': claim_sequence,
|
||||
'address': claim_address,
|
||||
'supports': supports
|
||||
'claim_sequence': result['claim_sequence'],
|
||||
'address': output.get_address(ledger),
|
||||
'supports': result['supports']
|
||||
}
|
||||
return r
|
||||
|
||||
def _parse_proof_result(name, result):
|
||||
support_amount = sum([amt for (stxid, snout, amt) in result['supports']])
|
||||
supports = result['supports']
|
||||
if 'txhash' in result['proof'] and 'nOut' in result['proof']:
|
||||
if 'transaction' in result:
|
||||
tx = transaction_class(raw=unhexlify(result['transaction']))
|
||||
nOut = result['proof']['nOut']
|
||||
if result['proof']['txhash'] == tx.id:
|
||||
if 0 <= nOut < len(tx.outputs):
|
||||
claim_output = tx.outputs[nOut]
|
||||
effective_amount = claim_output.amount + support_amount
|
||||
claim_address = hash160_to_address(claim_output.script.values['pubkey_hash'])
|
||||
claim_id = result['claim_id']
|
||||
claim_sequence = result['claim_sequence']
|
||||
claim_script = claim_output.script
|
||||
decoded_name = claim_script.values['claim_name'].decode()
|
||||
decoded_value = claim_script.values['claim']
|
||||
if decoded_name == name:
|
||||
return _build_response(name, decoded_value, claim_id,
|
||||
tx.id, nOut, claim_output.amount,
|
||||
effective_amount, claim_sequence,
|
||||
claim_address, supports)
|
||||
if tx.outputs[nOut].script.values['claim_name'].decode() == name:
|
||||
return _build_response(name, tx, nOut)
|
||||
return {'error': 'name in proof did not match requested name'}
|
||||
outputs = len(tx['outputs'])
|
||||
return {'error': 'invalid nOut: %d (let(outputs): %d' % (nOut, outputs)}
|
||||
|
@ -392,12 +312,8 @@ def _verify_proof(name, claim_trie_root, result, height, depth, transaction_clas
|
|||
return {'error': 'name is not claimed'}
|
||||
|
||||
if 'proof' in result:
|
||||
proof_name = name
|
||||
if 'name' in result:
|
||||
proof_name = result['name']
|
||||
name = result['name']
|
||||
if 'normalized_name' in result:
|
||||
proof_name = result['normalized_name']
|
||||
name = result.get('name', name)
|
||||
proof_name = result.get('normalized_name', name)
|
||||
try:
|
||||
verify_proof(result['proof'], claim_trie_root, proof_name)
|
||||
except InvalidProofError:
|
||||
|
@ -412,37 +328,39 @@ def validate_claim_signature_and_get_channel_name(claim_result, certificate_clai
|
|||
if cert_tx and certificate_claim and claim_tx and claim_result:
|
||||
tx = Transaction(unhexlify(claim_tx))
|
||||
cert_tx = Transaction(unhexlify(cert_tx))
|
||||
valid_signature = False
|
||||
try:
|
||||
is_signed = tx.outputs[claim_result['nout']].is_signed_by(
|
||||
valid_signature = tx.outputs[claim_result['nout']].is_signed_by(
|
||||
cert_tx.outputs[certificate_claim['nout']], ledger
|
||||
)
|
||||
except InvalidSignature:
|
||||
return False, None
|
||||
return is_signed, certificate_claim['name']
|
||||
return False, None
|
||||
pass
|
||||
if not valid_signature:
|
||||
log.warning("lbry://%s#%s has an invalid signature",
|
||||
claim_result['name'], claim_result['claim_id'])
|
||||
return valid_signature
|
||||
|
||||
|
||||
# TODO: The following came from code handling lbryum results. Now that it's all in one place a refactor should unify it.
|
||||
def _decode_claim_result(claim):
|
||||
if 'has_signature' in claim and claim['has_signature']:
|
||||
if not claim['signature_is_valid']:
|
||||
log.warning("lbry://%s#%s has an invalid signature",
|
||||
claim['name'], claim['claim_id'])
|
||||
if 'decoded_claim' in claim:
|
||||
return claim
|
||||
if 'value' not in claim:
|
||||
log.warning('Got an invalid claim while parsing, please report: %s', claim)
|
||||
claim['hex'] = None
|
||||
claim['value'] = None
|
||||
backend_message = ' SDK message: ' + claim['error'] if 'error' in claim else ''
|
||||
backend_message = ' SDK message: ' + claim.get('error', '')
|
||||
claim['error'] = "Failed to parse: missing value." + backend_message
|
||||
return claim
|
||||
try:
|
||||
if not isinstance(claim['value'], Claim):
|
||||
claim['value'] = Claim.from_bytes(unhexlify(claim['value']))
|
||||
claim['hex'] = hexlify(claim['value'].to_bytes())
|
||||
claim['decoded_claim'] = True
|
||||
except DecodeError:
|
||||
claim['decoded_claim'] = False
|
||||
claim['hex'] = claim['value']
|
||||
claim['value'] = None
|
||||
claim['error'] = "Failed to decode value"
|
||||
return claim
|
||||
|
||||
|
||||
|
@ -493,11 +411,7 @@ def pick_winner_from_channel_path_collision(claims_in_channel):
|
|||
# potentially triggering that.
|
||||
winner = None
|
||||
for claim in claims_in_channel:
|
||||
if not claim['signature_is_valid']:
|
||||
continue
|
||||
if winner is None:
|
||||
winner = claim
|
||||
elif claim['height'] < winner['height'] or \
|
||||
if winner is None or claim['height'] < winner['height'] or \
|
||||
(claim['height'] == winner['height'] and claim['nout'] < winner['nout']):
|
||||
winner = claim
|
||||
winner = claim if claim['signature_is_valid'] else winner
|
||||
return winner
|
||||
|
|
Loading…
Reference in a new issue