cleaning up subdomain rest api + documentation

This commit is contained in:
Aaron Blankstein
2017-07-31 18:59:33 -04:00
parent 570c572103
commit 8105deda23
3 changed files with 151 additions and 37 deletions

View File

@@ -469,9 +469,6 @@ def subdomain_record_to_profile(my_rec):
if user_data_pubkey is None:
user_data_pubkey = owner_pubkey.to_hex()
print("Fetching profile from {} with pubkey {}".format(
urls, user_data_pubkey))
user_profile = storage.get_mutable_data(
None, user_data_pubkey, blockchain_id=None,
data_address=None, owner_address=None,

View File

@@ -66,43 +66,154 @@ At 4kb zonefile size, we can only fit around 20 updates per zonefile.
### Domain Operator Endpoint
We'll need to provide an API endpoint for sending operations to the
domain operator *and* an interface for sending commands to that
endpoint.
Operating a domain should be something that anyone running a Core node
should be able to do with a simple command:
The directory `subdomain_registrar/` contains our code for running a
subdomain registrar. It can be executed by running:
```
$ blockstack domain foo.id start
$ blockstack-subdomain-registrar start foo.id
```
Here, `foo.id` is the domain for which subdomains will be associated.
#### AddSubdomain Command
#### Configuration and Registration Files
Configuration of the subdomain registrar is done through `~/.blockstack_subdomains/config.ini`
The sqlite database which stores the registrations is located alongside the config `~/.blockstack_subdomains/registrar.db`.
You can change the location of the config file (and the database), by setting the environment variable `BLOCKSTACK_SUBDOMAIN_CONFIG`
#### Register Subdomain
Subdomain registrations can be submitted to this endpoint using a REST
API.
```
addSubdomain("foo", "bar.id", pubkey_hex, urls)
POST /register
```
This command adds a subdomain `foo` to a domain `bar.id`. This will:
The schema for registration is:
```
{
'type' : 'object',
'properties' : {
'subdomain' : {
'type': 'string',
'pattern': '([a-z0-9\-_+]{3,36})$'
},
'data_pubkey' : {
'type': 'string',
'pattern': r'^(pubkey:data:[0-9a-fA-F]+)$'
},
'uris' : {
'type': 'array',
'items':
{
'type': 'object',
'properties': {
'name': {
'type': 'string'
},
'priority': {
'type': 'integer',
'minimum': 0,
'maximum': 65535,
},
'weight': {
'type': 'integer',
'minimum': 0,
'maximum': 65535,
},
'target': {
'anyOf': [
{
'type': 'string',
'pattern': '^([a-z0-9+]+)://([a-zA-Z0-9\-_.~%#?&\\:/=]+)$'
},
{
'type': 'string',
'pattern': '^([a-zA-Z0-9\-_.~%#?&\\:/=]+)$'
},
],
},
'class': {
'type': 'string'
},
'_missing_class': {
'type': 'boolean'
},
},
'required': [
'name',
'priority',
'weight',
'target'
],
}
},
'zonefile_str' : {
'type' : 'string',
'maxLength' : 4096
}
}
'required': ['data_pubkey', 'subdomain']
}
```
The request supplies *either* a list of URIs for the subdomain,
or a raw zonefile for the subdomain.
The registrar will:
1. Check if the subdomain `foo` exists already on the domain.
2. Add a record to the zonefile.
3. Issue zonefile update.
2. Add the subdomain to the queue.
#### UpdateSubdomain Command
On success, this returns `202` and the message
```
updateSubdomain("foo", "bar.id", pubkey_hex, n, urls, signature)
{"status": "true", "message": "Subdomain registration queued."}
```
This command updates subdomain `foo` to a domain `bar.id`. This will:
When the registrar wakes up to prepare a transaction, it packs the queued
registrations together and issues an `UPDATE`.
1. Check if the subdomain `foo` exists already on the domain
2. Check that n = n' + 1
3. Check the signature
4. Issue zonefile update
#### Check subdomain registration status
A user can check on the registration status of their name via querying the
registrar.
This is an API call:
```
GET /status/{subdomain}
```
The registrar checks if the subdomain has propagated (i.e., the
registration is completed), in which case the following is returned:
```
{"status": "Subdomain already propagated"}
```
Or, if the subdomain has already been submitted in a transaction:
```
{"status": "Your subdomain was registered in transaction 09a40d6ea362608c68da6e1ebeb3210367abf7aa39ece5fd57fd63d269336399 -- it should propagate on the network once it has 6 confirmations."}
```
If the subdomain still hasn't been submitted yet:
```
{"status": "Subdomain is queued for update and should be announced within the next few blocks."}
```
If an error occurred trying to submit the `UPDATE` transaction, this endpoint will return an error
message in the `"error"` key of a JSON object.
#### Updating Entries
The subdomain registrar does not currently support updating subdomain entries.
### Resolver Behavior
@@ -126,7 +237,8 @@ all the current subdomain records.
1. Testing bad zonefile transitions / updates.
a. Wrong _n_ : this could be a rewrite, roll-back, whatever. [x]
b. Bad signature [x]
2. Caching resolver database [o]
3. Batching updates [o]
4. Web API [o]
5. Endpoint support for changing zonefiles/rotating keys
2. Caching resolver database [x]
3. Batching updates [x]
4. Web API [x]
5. Resolver database cache for holding *multiple* domains, instead of just one [o]
6. Endpoint support for changing zonefiles/rotating keys [o]

View File

@@ -356,40 +356,45 @@ class SubdomainRegistrarRPCWorker(threading.Thread):
self.server.serve_forever()
class SubdomainRegistrarRPCHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def send_message(self, code, message):
self.send_response(code)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(message + "\r\n")
def do_GET(self):
if not str(self.path).startswith("/status/"):
return self.send_response(404, json.dumps({"error" : "Unsupported API method"}))
return self.send_message(404, json.dumps({"error" : "Unsupported API method"}))
name = self.path[len("/status/"):]
if re.match(config.SUBDOMAIN_NAME_PATTERN, name) is None:
return self.send_response(404, json.dumps({"error" : "Invalid subdomain supplied"}))
return self.send_message(404, json.dumps({"error" : "Invalid subdomain supplied"}))
status = get_queued_name(name, self.server.domain_name)
if "error" in status:
status_code = status.get("status_code", 500)
return self.send_response(status_code, json.dumps({"error": status["error"]}))
return self.send_response(200, json.dumps(status))
return self.send_message(status_code, json.dumps({"error": status["error"]}))
return self.send_message(200, json.dumps(status))
def do_POST(self):
self.send_header("Content-Type", "application/json")
if str(self.path) != "/register":
return self.send_response(404, json.dumps({"error" : "Unsupported API method"}))
return self.send_message(404, json.dumps({"error" : "Unsupported API method"}))
length = int(self.headers.getheader('content-length'))
if length > 1024 * 1024:
return self.send_response(403, json.dumps({"error" : "Content length too long. Request Denied."}))
return self.send_message(403, json.dumps({"error" : "Content length too long. Request Denied."}))
try:
subdomain = parse_subdomain_request(self.rfile.read(length))
except Exception as e:
log.exception(e)
return self.send_response(401, json.dumps({"error" : "Problem parsing request"}))
return self.send_message(401, json.dumps({"error" : "Problem parsing request"}))
try:
queued_resp = queue_name_for_registration(subdomain, self.server.domain_name)
except subdomains.SubdomainAlreadyExists as e:
log.exception(e)
return self.send_response(403, json.dumps({"error" : "Subdomain already exists on this domain"}))
return self.send_message(403, json.dumps({"error" : "Subdomain already exists on this domain"}))
if "error" in queued_resp:
return self.send_response(500, json.dumps(queued_resp))
return self.send_response(202, json.dumps(queued_resp))
return self.send_message(500, json.dumps(queued_resp))
return self.send_message(202, json.dumps(queued_resp))
class SubdomainLock(object):
@staticmethod