mirror of https://github.com/lukechilds/docs.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
315 lines
11 KiB
315 lines
11 KiB
6 years ago
|
---
|
||
|
layout: core
|
||
|
permalink: /:collection/:path.html
|
||
|
---
|
||
|
# Subdomain Design and Implementation
|
||
|
|
||
|
{:.no_toc}
|
||
|
|
||
|
Subdomains allow us to provide names to end users cheaply (and quickly). This
|
||
|
tutorial explains you how to create, register, and run a subdomain register, it
|
||
|
contains the following sections:
|
||
|
|
||
|
* TOC
|
||
|
{:toc}
|
||
|
|
||
|
|
||
|
## Strong subdomain ownership
|
||
|
|
||
|
For those who are new to this concept, it's a model where domains can
|
||
|
permanently, cryptographically delegate subdomains to particular keys,
|
||
|
relinquishing their ability to revoke the names or change the name
|
||
|
resolution details.
|
||
|
|
||
|
These names will be indicated with an `.`, e.g., `foo.bar.id`
|
||
|
|
||
|
## Overall Design
|
||
|
|
||
|
We can do this today with a special indexer & resolver endpoint and
|
||
|
without any changes to the core protocol.
|
||
|
|
||
|
We can do this by having a zone file record for each subdomain *i*
|
||
|
containing the following information:
|
||
|
|
||
|
1. An owner address *addr*
|
||
|
2. A sequence number *N*
|
||
|
3. A zonefile
|
||
|
4. A signature *S* of the above
|
||
|
|
||
|
The signature *S_i* must be verifiable with the address in the
|
||
|
*(N-1)*th entry for subdomain *i*.
|
||
|
|
||
|
## Zonefile Format
|
||
|
|
||
|
For now, the resolver will use an *TXT* record per subdomain to define
|
||
|
this information. The entry name will be `$(subdomain)`.
|
||
|
|
||
|
|
||
|
We'll use the format of [RFC 1464](https://tools.ietf.org/html/rfc1464)
|
||
|
for the TXT entry. We'll have the following strings with identifiers:
|
||
|
|
||
|
1. **parts** : this specifies the number of pieces that the
|
||
|
zonefile has been chopped into. TXT strings can only be 255 bytes,
|
||
|
so we chop up the zonefile.
|
||
|
2. **zf{n}**: part *n* of the zonefile, base64 encoded
|
||
|
3. **owner**: the owner address delegated to operate the subdomain
|
||
|
4. **seqn**: the sequence number
|
||
|
5. **sig**: signature of the above data.
|
||
|
|
||
|
```
|
||
|
$ORIGIN bar.id
|
||
|
$TTL 3600
|
||
|
pubkey TXT "pubkey:data:0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||
|
registrar URI 10 1 "bsreg://foo.com:8234"
|
||
|
aaron TXT "owner=33VvhhSQsYQyCVE2VzG3EHa9gfRCpboqHy" "seqn=0" "parts=1" "zf0=JE9SSUdJTiBhYXJvbgokVFRMIDM2MDAKbWFpbiBVUkkgMSAxICJwdWJrZXk6ZGF0YTowMzAyYWRlNTdlNjNiMzc1NDRmOGQ5Nzk4NjJhNDlkMDBkYmNlMDdmMjkzYmJlYjJhZWNmZTI5OTkxYTg3Mzk4YjgiCg=="
|
||
|
```
|
||
|
|
||
|
The `registrar` entry indicates how to contact the registrar service
|
||
|
for clients of the domain wishing to register or modify their entry.
|
||
|
|
||
|
### Operations per Zonefile
|
||
|
|
||
|
At 4kb zonefile size, we can only fit around 20 updates per zonefile.
|
||
|
|
||
|
## Domain Operator Endpoint
|
||
|
|
||
|
The directory `subdomain_registrar/` contains our code for running a
|
||
|
subdomain registrar. It can be executed by running:
|
||
|
|
||
|
```
|
||
|
$ blockstack-subdomain-registrar start foo.id
|
||
|
```
|
||
|
|
||
|
Here, `foo.id` is the domain for which subdomains will be associated.
|
||
|
|
||
|
### 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.
|
||
|
|
||
|
```
|
||
|
POST /register
|
||
|
```
|
||
|
|
||
|
The schema for registration is:
|
||
|
|
||
|
```
|
||
|
{
|
||
|
'type' : 'object',
|
||
|
'properties' : {
|
||
|
'name' : {
|
||
|
'type': 'string',
|
||
|
'pattern': '([a-z0-9\-_+]{3,36})$'
|
||
|
},
|
||
|
'owner_address' : {
|
||
|
'type': 'string',
|
||
|
'pattern': schemas.OP_ADDRESS_PATTERN
|
||
|
},
|
||
|
'zonefile' : {
|
||
|
'type' : 'string',
|
||
|
'maxLength' : blockstack_constants.RPC_MAX_ZONEFILE_LEN
|
||
|
}
|
||
|
},
|
||
|
'required':[
|
||
|
'name', 'owner_address', 'zonefile'
|
||
|
],
|
||
|
'additionalProperties' : True
|
||
|
}
|
||
|
```
|
||
|
|
||
|
The registrar will:
|
||
|
|
||
|
1. Check if the subdomain `foo` exists already on the domain.
|
||
|
2. Add the subdomain to the queue.
|
||
|
|
||
|
On success, this returns `202` and the message
|
||
|
|
||
|
```
|
||
|
{"status": "true", "message": "Subdomain registration queued."}
|
||
|
```
|
||
|
|
||
|
When the registrar wakes up to prepare a transaction, it packs the queued
|
||
|
registrations together and issues an `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
|
||
|
|
||
|
When a lookup like `foo.bar.id` hits the resolver, the resolver will need to:
|
||
|
|
||
|
1. Lookup the zonefile history of `bar.id`
|
||
|
2. Fetch all these zonefiles and filter by operations on `foo`
|
||
|
3. Verify that all `foo` operations are correct
|
||
|
4. Return the latest record for foo
|
||
|
5. Do a profile lookup for `foo.bar.id` by fetching the URLs in the entry.
|
||
|
*Note*, this spec does not define a priority order for fetching those URLs.
|
||
|
|
||
|
### Supported Core / Resolver Endpoints
|
||
|
|
||
|
Generally, domain endpoints are not aware of subdomains (only endpoints
|
||
|
aware of subdomains is `/v1/users/<foo.bar.tld>`,
|
||
|
`/v1/names/<foo.bar.tld>`, and `/v1/addresses/bitcoin/<foo.bar.tld>`)
|
||
|
The endpoints which *are* subdomain aware are marked as such in
|
||
|
[api-specs.md].
|
||
|
|
||
|
This means that search is *not* yet supported.
|
||
|
|
||
|
The lookups work just like normal -- it returns the user's
|
||
|
profile object:
|
||
|
|
||
|
```
|
||
|
$ curl -H "Authorization: bearer blockstack_integration_test_api_password" -H "Origin: http://localhost:3000" http://localhost:16268/v1/users/bar.foo.id -v -s | python -m json.tool
|
||
|
* Trying 127.0.0.1...
|
||
|
* Connected to localhost (127.0.0.1) port 16268 (#0)
|
||
|
> GET /v1/users/bar.foo.id HTTP/1.1
|
||
|
> Host: localhost:16268
|
||
|
> User-Agent: curl/7.50.1
|
||
|
> Accept: */*
|
||
|
> Authorization: bearer blockstack_integration_test_api_password
|
||
|
> Origin: http://localhost:3000
|
||
|
>
|
||
|
* HTTP 1.0, assume close after body
|
||
|
< HTTP/1.0 200 OK
|
||
|
< Server: SimpleHTTP/0.6 Python/2.7.12+
|
||
|
< Date: Thu, 03 Aug 2017 14:39:16 GMT
|
||
|
< content-type: application/json
|
||
|
< Access-Control-Allow-Origin: *
|
||
|
<
|
||
|
{ [66 bytes data]
|
||
|
* Closing connection 0
|
||
|
{
|
||
|
"bar": {
|
||
|
"@type": "Person",
|
||
|
"description": "Lorem Ipsum Bazorem"
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Name info lookups are also supported (this should enable authenticating logins
|
||
|
with `blockstack.js`, but I will need to double check).
|
||
|
|
||
|
```
|
||
|
$ curl -H "Authorization: bearer XXXX" -H "Origin: http://localhost:3000" http://localhost:6270/v1/names/created_equal.self_evident_truth.id -s | python -m json.tool
|
||
|
{
|
||
|
"address": "1AYddAnfHbw6bPNvnsQFFrEuUdhMhf2XG9",
|
||
|
"blockchain": "bitcoin",
|
||
|
"expire_block": -1,
|
||
|
"last_txid": "0bacfd5a3e0ec68723d5948d6c1a04ad0de1378c872d45fa2276ebbd7be230f7",
|
||
|
"satus": "registered_subdomain",
|
||
|
"zonefile_hash": "48fc1b351ce81cf0a9fd9b4eae7a3f80e93c0451",
|
||
|
"zonefile_txt": "$ORIGIN created_equal\n$TTL 3600\n_https._tcp URI 10 1 \"https://www.cs.princeton.edu/~ablankst/created_equal.json\"\n_file URI 10 1 \"file:///tmp/created_equal.json\"\n"
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### Subdomain Caching
|
||
|
|
||
|
A resolver *caches* a subdomain's state by keeping a database of all
|
||
|
the current subdomain records. This database is automatically updated
|
||
|
when a new zonefile for a particularly domain is seen by the resolver
|
||
|
(this is performed lazily).
|
||
|
|
||
|
### Testing Subdomain Registrar and Resolution
|
||
|
|
||
|
You can run a subdomain registrar and resolver with blockstack-core in
|
||
|
regtest mode as follows:
|
||
|
|
||
|
```bash
|
||
|
IMAGE=$(docker run -dt -p 3000:3000 -p 6270:6270 -p 16269:16269 -p 18332:18332 -e BLOCKSTACK_TEST_CLIENT_RPC_PORT=6270 -e BLOCKSTACK_TEST_CLIENT_BIND=0.0.0.0 -e BLOCKSTACK_TEST_BITCOIND_ALLOWIP=172.17.0.0/16 quay.io/blockstack/integrationtests:master blockstack-test-scenario --interactive 2 blockstack_integration_tests.scenarios.browser_env)
|
||
|
```
|
||
|
|
||
|
Once you see `Test finished; doing checks` in that container's logs, the
|
||
|
registrar has started and is ready to accept requests. (We recommend
|
||
|
following the docker instructions below for running this test in
|
||
|
Docker, as it will fetch the source code for the registrar and set the
|
||
|
correct environment variables for it to run).
|
||
|
|
||
|
Once this environment has started, you can issue a registration request from curl:
|
||
|
|
||
|
```
|
||
|
curl -X POST -H 'Content-Type: application/json' --data '{"zonefile": "$ORIGIN baz\n$TTL 3600\n_file URI 10 1 \"file:///tmp/baz.profile.json\"\n", "name": "baz", "owner_address": "14x2EMRz1gf16UzGbxZh2c6sJg4A8wcHLD"}' http://localhost:3000/register/
|
||
|
```
|
||
|
|
||
|
This registers `baz.foo.id` -- you can check the registrar's status with
|
||
|
|
||
|
```
|
||
|
curl http://localhost:3000/status/baz
|
||
|
```
|
||
|
|
||
|
The API endpoints `/v1/users/<foo.bar.tld>`,
|
||
|
`/v1/names/<foo.bar.tld>`, and `/v1/addresses/bitcoin/<foo.bar.tld>` all work, so if you query the core API, you'll get a response.
|
||
|
|
||
|
For example:
|
||
|
|
||
|
```
|
||
|
curl http://localhost:6270/v1/names/baz.foo.id | python -m json.tool
|
||
|
```
|
||
|
|
||
|
Will return:
|
||
|
```
|
||
|
{
|
||
|
"address": "1Nup2UcbVuVoDZeZCtR4vjSkrvTi8toTqc",
|
||
|
"blockchain": "bitcoin",
|
||
|
"expire_block": -1,
|
||
|
"last_txid": "43bbcbd8793cdc52f1b0bd2713ed136f4f104a683a9fd5c89911a57a8c4b28b6",
|
||
|
"satus": "registered_subdomain",
|
||
|
"zonefile_hash": "e7e3aada18c9ac5189f1c54089e987f58c0fa51e",
|
||
|
"zonefile_txt": "$ORIGIN bar\n$TTL 3600\n_file URI 10 1 \"file:///tmp/baz.profile.json\"\n"
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### Running an interactive testing environment with the Subdomain Registrar service
|
||
|
|
||
|
Follow the [instructions here](https://github.com/blockstack/blockstack-core/blob/master/integration_tests/README.md) to download the regtesting Docker image.
|
||
|
|
||
|
Since the subdomain registrar service runs on port 3000, we need to do two things to expose this endpoint to interact with it from the browser:
|
||
|
- Open port 3000 with `-p 3000:3000`
|
||
|
|
||
|
Here's the full command you'd run to start the interactive testing scenario:
|
||
|
|
||
|
```bash
|
||
|
IMAGE=$(docker run -dt -p 3000:3000 -p 6270:6270 -p 16269:16269 -p 18332:18332 -e BLOCKSTACK_TEST_CLIENT_RPC_PORT=6270 -e BLOCKSTACK_TEST_CLIENT_BIND=0.0.0.0 -e BLOCKSTACK_TEST_BITCOIND_ALLOWIP=172.17.0.0/16 quay.io/blockstack/integrationtests:master blockstack-test-scenario --interactive 2 blockstack_integration_tests.scenarios.browser_env)
|
||
|
```
|