add method for resolving a blockstack DID to a blockchain record

This commit is contained in:
Jude Nelson
2017-10-27 19:25:57 -04:00
parent b122674893
commit 17fb989a31

View File

@@ -28,6 +28,7 @@ import json
import traceback
import os
import random
import re
from xmlrpclib import ServerProxy, Transport
from defusedxml import xmlrpc
import httplib
@@ -38,7 +39,7 @@ from utils import url_to_host_port
from .constants import (
MAX_RPC_LEN, CONFIG_PATH, BLOCKSTACK_TEST, DEFAULT_TIMEOUT,
BLOCKSTACK_DEBUG
BLOCKSTACK_DEBUG, NAME_REVOKE
)
# prevent the usual XML attacks
@@ -57,6 +58,8 @@ from .operations import (
from .schemas import (
OP_NAMESPACE_PATTERN,
OP_BASE58CHECK_CLASS,
OP_NAME_OR_SUBDOMAIN_PATTERN,
OP_CONSENSUS_HASH_PATTERN,
OP_CONSENSUS_HASH_PATTERN,
NAMEOP_SCHEMA_PROPERTIES,
@@ -1013,6 +1016,224 @@ def get_names_owned_by_address(address, proxy=None):
return resp['names']
def get_num_historic_names_by_address(address, proxy=None):
"""
Get the number of names historically created by an address
Returns the number of names on success
Returns {'error': ...} on error
"""
num_names_schema = {
'type': 'object',
'properties': {
'count': {
'type': 'integer',
'minimum': 0,
},
},
'required': [
'count',
],
}
schema = json_response_schema( num_names_schema )
if proxy is None:
proxy = get_default_proxy()
resp = {}
try:
resp = proxy.get_num_historic_names_by_address(address)
resp = json_validate(schema, resp)
if json_is_error(resp):
return resp
except ValidationError as e:
if BLOCKSTACK_DEBUG:
log.exception(e)
log.error("Caught exception while connecting to Blockstack node: {}".format(ee))
resp = json_traceback(resp.get('error'))
return resp
except Exception as ee:
if BLOCKSTACK_DEBUG:
log.exception(ee)
log.error("Caught exception while connecting to Blockstack node: {}".format(ee))
resp = {'error': 'Failed to contact Blockstack node. Try again with `--debug`.'}
return resp
return resp['count']
def get_historic_names_by_address_page(address, offset, count, proxy=None):
"""
Get the list of names historically created by an address
Returns the list of names on success
Returns {'error': ...} on error
"""
names_schema = {
'type': 'object',
'properties': {
'names': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'name': {
'type': 'string',
'pattern': OP_NAME_OR_SUBDOMAIN_PATTERN,
},
'block_id': {
'type': 'integer',
'minimum': 0,
},
'vtxindex': {
'type': 'integer',
'minimum': 0,
},
},
'required': [
'name',
'block_id',
'vtxindex',
],
},
},
},
'required': [
'names'
],
}
schema = json_response_schema( names_schema )
proxy = get_default_proxy() if proxy is None else proxy
assert count <= 100, "Page too big"
resp = {}
try:
resp = proxy.get_historic_names_by_address(address, offset, count)
resp = json_validate(schema, resp)
if json_is_error(resp):
return resp
# names must be valid
for n in resp['names']:
assert scripts.is_name_valid(str(n['name'])), ('Invalid name "{}"'.format(str(n['name'])))
except (ValidationError, AssertionError) as e:
if BLOCKSTACK_DEBUG:
log.exception(e)
log.error("Caught exception while connecting to Blockstack node: {}".format(e))
resp = json_traceback(resp.get('error'))
return resp
except Exception as ee:
if BLOCKSTACK_DEBUG:
log.exception(ee)
log.error("Caught exception while connecting to Blockstack node: {}".format(ee))
resp = {'error': 'Failed to contact Blockstack node. Try again with `--debug`.'}
return resp
return resp['names']
def get_historic_names_by_address(address, offset=None, count=None, proxy=None):
"""
Get the list of names created by an address throughout history
Returns the list of names on success
Returns {'error': ...} on failure
"""
proxy = get_default_proxy() if proxy is None else proxy
offset = 0 if offset is None else offset
if count is None:
# get all names owned by this address
count = get_num_historic_names_by_address(address, proxy=proxy)
if json_is_error(count):
return count
count -= offset
page_size = 100
all_names = []
while len(all_names) < count:
request_size = page_size
if count - len(all_names) < request_size:
request_size = count - len(all_names)
page = get_historic_names_by_address_page(address, offset + len(all_names), request_size, proxy=proxy)
if json_is_error(page):
# error
return page
if len(page) > request_size:
# error
error_str = 'server replied too much data'
return {'error': error_str}
elif len(page) == 0:
# end-of-table
break
all_names += page
return all_names[:count]
def get_DID_blockchain_record(did, proxy=None):
"""
Resolve a Blockstack decentralized identifier (DID) to its blockchain record.
DID format: did:stack:v0:${address}-${name_index}, where:
* address is the address that created the name this DID references
* name_index is the nth name ever created by this address.
Follow the sequence of NAME_TRANSFERs and NAME_RENEWALs to find the current
address, and then look up the public key.
"""
proxy = get_default_proxy() if proxy is None else proxy
did_pattern = '^did:stack:v0:({}{{25,34}})-([0-9]+)$'.format(OP_BASE58CHECK_CLASS)
m = re.match(did_pattern, did)
assert m, 'Invalid DID: {}'.format(did)
address = m.groups()[0]
name_index = int(m.groups()[1])
addr_names = get_historic_names_by_address(address, proxy=proxy)
if json_is_error(addr_names):
return addr_names
if len(addr_names) <= name_index:
return {'error': 'Invalid DID: index {} exceeds number of names ({}) created by {}'.format(name_index, len(addr_names), address)}
# order by blockchain and tx
addr_names.sort(lambda n1,n2: -1 if n1['block_id'] < n2['block_id'] or (n1['block_id'] == n2['block_id'] and n1['vtxindex'] < n2['vtxindex']) else 1)
name = addr_names[name_index]['name']
start_block = addr_names[name_index]['block_id']
end_block = 100000000 # TODO: update if this gets too small
# verify that the name hasn't been revoked since this DID was created.
# however, it can have expired, transferred, or been re-registered.
name_history = get_name_blockchain_history(name, start_block, end_block)
final_name_state = None
for history_block in sorted(name_history.keys()):
for history_state in name_history[history_block]:
if history_state['op'] == NAME_REVOKE:
# end of the line
return {'error': 'The name for this DID has been revoked'}
final_name_state = history_state
return final_name_state
def get_consensus_at(block_height, proxy=None, hostport=None):
"""
Get consensus at a block
@@ -1486,6 +1707,7 @@ def get_op_history_rows(name, proxy=None):
return history_rows
def get_zonefiles_by_block(from_block, to_block, proxy=None):
"""
Get zonefile information for zonefiles announced in [@from_block, @to_block]
@@ -1538,6 +1760,7 @@ def get_zonefiles_by_block(from_block, to_block, proxy=None):
return { 'last_block' : last_server_block,
'zonefile_info' : output_zonefiles }
def get_nameops_affected_at(block_id, proxy=None):
"""
Get the *current* states of the name records that were
@@ -1641,6 +1864,7 @@ def get_nameops_affected_at(block_id, proxy=None):
return all_nameops
def get_nameops_at(block_id, proxy=None):
"""
Get all the name operation that happened at a given block,