mirror of
https://github.com/LBRYFoundation/lbry-sdk.git
synced 2025-08-31 09:21:29 +00:00
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
|
@property
|
||||||
def resolver(self):
|
def resolver(self):
|
||||||
return Resolver(self.headers, self.transaction_class,
|
return Resolver(self.headers, self.transaction_class, network=self.network, ledger=self)
|
||||||
hash160_to_address=self.hash160_to_address, network=self.network, ledger=self)
|
|
||||||
|
|
||||||
def resolve(self, page, page_size, *uris):
|
def resolve(self, page, page_size, *uris):
|
||||||
return self.resolver.resolve(page, page_size, *uris)
|
return self.resolver.resolve(page, page_size, *uris)
|
||||||
|
|
||||||
async def get_claim_by_claim_id(self, claim_id):
|
async def get_claim_by_claim_id(self, claim_id):
|
||||||
result = (await self.network.get_claims_by_ids(claim_id)).pop(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):
|
async def get_claim_by_outpoint(self, txid, nout):
|
||||||
claims = (await self.network.get_claims_in_tx(txid)) or []
|
claims = (await self.network.get_claims_in_tx(txid)) or []
|
||||||
for claim in claims:
|
for claim in claims:
|
||||||
if claim['nout'] == nout:
|
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'
|
return 'claim not found'
|
||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
|
|
|
@ -17,12 +17,11 @@ log = logging.getLogger(__name__)
|
||||||
|
|
||||||
class Resolver:
|
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.claim_trie_root = headers.claim_trie_root
|
||||||
self.height = headers.height
|
self.height = headers.height
|
||||||
self.header_hash = headers.hash().decode()
|
self.header_hash = headers.hash().decode()
|
||||||
self.transaction_class = transaction_class
|
self.transaction_class = transaction_class
|
||||||
self.hash160_to_address = hash160_to_address
|
|
||||||
self.network = network
|
self.network = network
|
||||||
self.ledger = ledger
|
self.ledger = ledger
|
||||||
|
|
||||||
|
@ -60,7 +59,7 @@ class Resolver:
|
||||||
result = {}
|
result = {}
|
||||||
claim_trie_root = self.claim_trie_root
|
claim_trie_root = self.claim_trie_root
|
||||||
parsed_uri = parse_lbry_uri(uri)
|
parsed_uri = parse_lbry_uri(uri)
|
||||||
certificate = None
|
certificate_response = None
|
||||||
# parse an included certificate
|
# parse an included certificate
|
||||||
if 'certificate' in resolution:
|
if 'certificate' in resolution:
|
||||||
certificate_response = resolution['certificate']['result']
|
certificate_response = resolution['certificate']['result']
|
||||||
|
@ -69,37 +68,16 @@ class Resolver:
|
||||||
if 'height' in certificate_response:
|
if 'height' in certificate_response:
|
||||||
height = certificate_response['height']
|
height = certificate_response['height']
|
||||||
depth = self.height - height
|
depth = self.height - height
|
||||||
certificate_result = _verify_proof(parsed_uri.name,
|
certificate_response = _verify_proof(parsed_uri.name,
|
||||||
claim_trie_root,
|
claim_trie_root,
|
||||||
certificate_response,
|
certificate_response,
|
||||||
height, depth,
|
height, depth,
|
||||||
transaction_class=self.transaction_class,
|
transaction_class=self.transaction_class,
|
||||||
hash160_to_address=self.hash160_to_address)
|
ledger=self.ledger)
|
||||||
result['certificate'] = await self.parse_and_validate_claim_result(certificate_result,
|
elif certificate_resolution_type not in ['winning', 'claim_id', 'sequence']:
|
||||||
raw=raw)
|
raise Exception("unknown response type: %s", certificate_resolution_type)
|
||||||
elif certificate_resolution_type == "claim_id":
|
result['certificate'] = await self.parse_and_validate_claim_result(certificate_response)
|
||||||
result['certificate'] = await self.parse_and_validate_claim_result(certificate_response,
|
result['claims_in_channel'] = len(resolution.get('unverified_claims_in_channel', []))
|
||||||
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
|
|
||||||
|
|
||||||
# if this was a resolution for a name, parse the result
|
# if this was a resolution for a name, parse the result
|
||||||
if 'claim' in resolution:
|
if 'claim' in resolution:
|
||||||
|
@ -109,25 +87,16 @@ class Resolver:
|
||||||
if 'height' in claim_response:
|
if 'height' in claim_response:
|
||||||
height = claim_response['height']
|
height = claim_response['height']
|
||||||
depth = self.height - height
|
depth = self.height - height
|
||||||
claim_result = _verify_proof(parsed_uri.name,
|
claim_response = _verify_proof(parsed_uri.name,
|
||||||
claim_trie_root,
|
claim_trie_root,
|
||||||
claim_response,
|
claim_response,
|
||||||
height, depth,
|
height, depth,
|
||||||
transaction_class=self.transaction_class,
|
transaction_class=self.transaction_class,
|
||||||
hash160_to_address=self.hash160_to_address)
|
ledger=self.ledger)
|
||||||
result['claim'] = await self.parse_and_validate_claim_result(claim_result,
|
elif claim_resolution_type not in ["sequence", "winning", "claim_id"]:
|
||||||
certificate,
|
raise Exception("unknown response type: %s", claim_resolution_type)
|
||||||
raw)
|
result['claim'] = await self.parse_and_validate_claim_result(claim_response,
|
||||||
elif claim_resolution_type == "claim_id":
|
certificate_response)
|
||||||
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)
|
|
||||||
|
|
||||||
# if this was a resolution for a name in a channel make sure there is only one valid
|
# if this was a resolution for a name in a channel make sure there is only one valid
|
||||||
# match
|
# match
|
||||||
|
@ -169,58 +138,40 @@ class Resolver:
|
||||||
return {'error': 'claim not found', 'success': False, 'uri': str(parsed_uri)}
|
return {'error': 'claim not found', 'success': False, 'uri': str(parsed_uri)}
|
||||||
return result
|
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:
|
if not claim_result or 'value' not in claim_result:
|
||||||
return claim_result
|
return claim_result
|
||||||
certificate = None
|
claim_result = _decode_claim_result(claim_result)
|
||||||
certificate_id = Claim.from_bytes(unhexlify(claim_result['value'])).signing_channel_id
|
channel_id = None
|
||||||
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)
|
|
||||||
|
|
||||||
async def parse_and_validate_claim_result(self, claim_result, certificate=None, raw=False):
|
if claim_result['value']:
|
||||||
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:
|
|
||||||
claim_result['has_signature'] = False
|
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'])
|
claim_tx = await self.network.get_transaction(claim_result['txid'])
|
||||||
if certificate is None:
|
if certificate is None:
|
||||||
log.info("fetching certificate to check claim signature")
|
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:
|
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
|
cert_tx = await self.network.get_transaction(certificate['txid']) if certificate else None
|
||||||
claim_result['has_signature'] = True
|
claim_result['signature_is_valid'] = validate_claim_signature_and_get_channel_name(
|
||||||
claim_result['signature_is_valid'] = False
|
|
||||||
validated, channel_name = validate_claim_signature_and_get_channel_name(
|
|
||||||
claim_result, certificate, self.ledger, claim_tx=claim_tx, cert_tx=cert_tx
|
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:
|
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['permanent_url'] = _get_permanent_url(
|
||||||
claim_result, decoded.signing_channel_id if decoded else None)
|
claim_result, channel_id)
|
||||||
|
|
||||||
return claim_result
|
return claim_result
|
||||||
|
|
||||||
|
@ -301,35 +252,16 @@ class Resolver:
|
||||||
start_position = (page - 1) * page_size
|
start_position = (page - 1) * page_size
|
||||||
queries, names, claim_positions = self.prepare_claim_queries(start_position, page_size,
|
queries, names, claim_positions = self.prepare_claim_queries(start_position, page_size,
|
||||||
channel_claim_infos)
|
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)
|
upper_bound = len(claim_positions)
|
||||||
if not page:
|
if not page:
|
||||||
return None, upper_bound
|
return None, upper_bound
|
||||||
if start_position > upper_bound:
|
if start_position > upper_bound:
|
||||||
raise IndexError("claim %i greater than max %i" % (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
|
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):
|
def _get_permanent_url(claim_result, certificate_id):
|
||||||
if certificate_id:
|
if certificate_id:
|
||||||
return f"{claim_result['channel_name']}#{certificate_id}/{claim_result['name']}"
|
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']}"
|
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
|
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,
|
def _build_response(name, tx, nOut):
|
||||||
claim_sequence, claim_address, supports):
|
output = tx.outputs[nOut]
|
||||||
r = {
|
r = {
|
||||||
'name': name,
|
'name': name,
|
||||||
'value': hexlify(value),
|
'value': hexlify(output.script.values['claim']),
|
||||||
'claim_id': claim_id,
|
'claim_id': output.claim_id,
|
||||||
'txid': txid,
|
'txid': tx.id,
|
||||||
'nout': n,
|
'nout': nOut,
|
||||||
'amount': amount,
|
'amount': output.amount,
|
||||||
'effective_amount': effective_amount,
|
'effective_amount': output.amount + support_amount,
|
||||||
'height': height,
|
'height': height,
|
||||||
'depth': depth,
|
'depth': depth,
|
||||||
'claim_sequence': claim_sequence,
|
'claim_sequence': result['claim_sequence'],
|
||||||
'address': claim_address,
|
'address': output.get_address(ledger),
|
||||||
'supports': supports
|
'supports': result['supports']
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
|
|
||||||
def _parse_proof_result(name, result):
|
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 'txhash' in result['proof'] and 'nOut' in result['proof']:
|
||||||
if 'transaction' in result:
|
if 'transaction' in result:
|
||||||
tx = transaction_class(raw=unhexlify(result['transaction']))
|
tx = transaction_class(raw=unhexlify(result['transaction']))
|
||||||
nOut = result['proof']['nOut']
|
nOut = result['proof']['nOut']
|
||||||
if result['proof']['txhash'] == tx.id:
|
if result['proof']['txhash'] == tx.id:
|
||||||
if 0 <= nOut < len(tx.outputs):
|
if 0 <= nOut < len(tx.outputs):
|
||||||
claim_output = tx.outputs[nOut]
|
if tx.outputs[nOut].script.values['claim_name'].decode() == name:
|
||||||
effective_amount = claim_output.amount + support_amount
|
return _build_response(name, tx, nOut)
|
||||||
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)
|
|
||||||
return {'error': 'name in proof did not match requested name'}
|
return {'error': 'name in proof did not match requested name'}
|
||||||
outputs = len(tx['outputs'])
|
outputs = len(tx['outputs'])
|
||||||
return {'error': 'invalid nOut: %d (let(outputs): %d' % (nOut, 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'}
|
return {'error': 'name is not claimed'}
|
||||||
|
|
||||||
if 'proof' in result:
|
if 'proof' in result:
|
||||||
proof_name = name
|
name = result.get('name', name)
|
||||||
if 'name' in result:
|
proof_name = result.get('normalized_name', name)
|
||||||
proof_name = result['name']
|
|
||||||
name = result['name']
|
|
||||||
if 'normalized_name' in result:
|
|
||||||
proof_name = result['normalized_name']
|
|
||||||
try:
|
try:
|
||||||
verify_proof(result['proof'], claim_trie_root, proof_name)
|
verify_proof(result['proof'], claim_trie_root, proof_name)
|
||||||
except InvalidProofError:
|
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:
|
if cert_tx and certificate_claim and claim_tx and claim_result:
|
||||||
tx = Transaction(unhexlify(claim_tx))
|
tx = Transaction(unhexlify(claim_tx))
|
||||||
cert_tx = Transaction(unhexlify(cert_tx))
|
cert_tx = Transaction(unhexlify(cert_tx))
|
||||||
|
valid_signature = False
|
||||||
try:
|
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
|
cert_tx.outputs[certificate_claim['nout']], ledger
|
||||||
)
|
)
|
||||||
except InvalidSignature:
|
except InvalidSignature:
|
||||||
return False, None
|
pass
|
||||||
return is_signed, certificate_claim['name']
|
if not valid_signature:
|
||||||
return False, None
|
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.
|
# 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):
|
def _decode_claim_result(claim):
|
||||||
if 'has_signature' in claim and claim['has_signature']:
|
if 'decoded_claim' in claim:
|
||||||
if not claim['signature_is_valid']:
|
return claim
|
||||||
log.warning("lbry://%s#%s has an invalid signature",
|
|
||||||
claim['name'], claim['claim_id'])
|
|
||||||
if 'value' not in claim:
|
if 'value' not in claim:
|
||||||
log.warning('Got an invalid claim while parsing, please report: %s', claim)
|
log.warning('Got an invalid claim while parsing, please report: %s', claim)
|
||||||
claim['hex'] = None
|
claim['hex'] = None
|
||||||
claim['value'] = 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
|
claim['error'] = "Failed to parse: missing value." + backend_message
|
||||||
return claim
|
return claim
|
||||||
try:
|
try:
|
||||||
if not isinstance(claim['value'], Claim):
|
if not isinstance(claim['value'], Claim):
|
||||||
claim['value'] = Claim.from_bytes(unhexlify(claim['value']))
|
claim['value'] = Claim.from_bytes(unhexlify(claim['value']))
|
||||||
claim['hex'] = hexlify(claim['value'].to_bytes())
|
claim['hex'] = hexlify(claim['value'].to_bytes())
|
||||||
|
claim['decoded_claim'] = True
|
||||||
except DecodeError:
|
except DecodeError:
|
||||||
|
claim['decoded_claim'] = False
|
||||||
claim['hex'] = claim['value']
|
claim['hex'] = claim['value']
|
||||||
claim['value'] = None
|
claim['value'] = None
|
||||||
claim['error'] = "Failed to decode value"
|
|
||||||
return claim
|
return claim
|
||||||
|
|
||||||
|
|
||||||
|
@ -493,11 +411,7 @@ def pick_winner_from_channel_path_collision(claims_in_channel):
|
||||||
# potentially triggering that.
|
# potentially triggering that.
|
||||||
winner = None
|
winner = None
|
||||||
for claim in claims_in_channel:
|
for claim in claims_in_channel:
|
||||||
if not claim['signature_is_valid']:
|
if winner is None or claim['height'] < winner['height'] or \
|
||||||
continue
|
|
||||||
if winner is None:
|
|
||||||
winner = claim
|
|
||||||
elif claim['height'] < winner['height'] or \
|
|
||||||
(claim['height'] == winner['height'] and claim['nout'] < winner['nout']):
|
(claim['height'] == winner['height'] and claim['nout'] < winner['nout']):
|
||||||
winner = claim
|
winner = claim if claim['signature_is_valid'] else winner
|
||||||
return winner
|
return winner
|
||||||
|
|
Loading…
Add table
Reference in a new issue