mirror of
https://github.com/alexgo-io/stacks-puppet-node.git
synced 2026-04-07 16:57:37 +08:00
315 lines
8.3 KiB
Python
315 lines
8.3 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
Blockstack
|
|
~~~~~
|
|
copyright: (c) 2014-2015 by Halfmoon Labs, Inc.
|
|
copyright: (c) 2016 by Blockstack.org
|
|
|
|
This file is part of Blockstack
|
|
|
|
Blockstack is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Blockstack is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
You should have received a copy of the GNU General Public License
|
|
along with Blockstack. If not, see <http://www.gnu.org/licenses/>.
|
|
"""
|
|
|
|
# this module contains a plugin that makes the DHT useful as ancillary storage
|
|
# TODO: find a way to initialize the DHT and local plugin state
|
|
|
|
import os
|
|
import sys
|
|
import traceback
|
|
|
|
# current_dir = os.path.abspath(os.path.dirname(__file__))
|
|
# sys.path.insert(0, current_dir)
|
|
|
|
from kademlia.network import Server
|
|
|
|
from twisted.python import log
|
|
from twisted.internet.error import ConnectionRefusedError
|
|
from twisted.internet import reactor
|
|
from txjsonrpc.netstring.jsonrpc import Proxy
|
|
|
|
from blockstack.storage import mutable_data_route, mutable_data_route_parse
|
|
from blcokstore.storage import mutable_data_parse, UnhandledURLException
|
|
|
|
from txjsonrpc.netstring import jsonrpc
|
|
from twisted.application import service, internet
|
|
|
|
from kademlia.network import Server
|
|
|
|
from blockstack.parsing import json_stable_serialize
|
|
|
|
import types
|
|
import re
|
|
import pybitcoin
|
|
|
|
|
|
DHT_SERVER_PORT = 6265 # blockstackd default to port 6264
|
|
|
|
DEFAULT_DHT_SERVERS = [('dht.openname.org', DHT_SERVER_PORT),
|
|
('dht.onename.com', DHT_SERVER_PORT),
|
|
('dht.halfmoonlabs.com', DHT_SERVER_PORT),
|
|
('127.0.0.1', DHT_SERVER_PORT)]
|
|
|
|
# 3 years
|
|
STORAGE_TTL = 3 * 60 * 60 * 24 * 365
|
|
|
|
NONE_VALUE = "(null)"
|
|
|
|
|
|
# client to the DHT
|
|
dht_server = None
|
|
dht_blockchain_index = None
|
|
|
|
|
|
def dht_data_hash(data):
|
|
"""
|
|
Calculate a key from the data.
|
|
"""
|
|
|
|
return pybitcoin.hash.hex_hash160(data)
|
|
|
|
|
|
def dht_url(data_hash):
|
|
"""
|
|
Generate a route URL for the DHT, given the data's key.
|
|
"""
|
|
return "dht:" + str(data_hash)
|
|
|
|
|
|
def dht_url_parse(data_url):
|
|
"""
|
|
Givne a DHT url, get the hash.
|
|
Raise an exception if it can't be parsed.
|
|
"""
|
|
# sanity check...
|
|
if type(data_url) not in [types.StringType, types.UnicodeType]:
|
|
raise Exception("Not a valid URL")
|
|
|
|
if not data_url.startswith("dht:"):
|
|
raise Exception("Not a valid URL: invalid scheme in '%s'" % data_url)
|
|
|
|
data_hash = data_url[4:]
|
|
if len(data_hash) == 0:
|
|
raise Exception("Not a valid URL: empty hash in '%s'" % data_url)
|
|
|
|
if re.match(r"^[a-fA-F0-9]+$", data_hash) is None:
|
|
raise Exception("Not a valid URL: invalid hash in '%s'" % data_url)
|
|
|
|
return data_hash
|
|
|
|
|
|
def dht_init(blockchain_index):
|
|
"""
|
|
Establish our connection to the DHT, and give
|
|
it the requisite state it needs (i.e. the index
|
|
over the blockchain).
|
|
"""
|
|
|
|
global dht_server
|
|
global dht_blockchain_index
|
|
|
|
dht_blockchain_index = blockchain_index
|
|
|
|
dht_server = Server(storage=BlockStorage())
|
|
dht_server.listen(DHT_SERVER_PORT)
|
|
bootstrap_servers = hostname_to_ip(DEFAULT_DHT_SERVERS)
|
|
dht_server.bootstrap(bootstrap_servers)
|
|
|
|
|
|
def dht_get_key(data_key):
|
|
"""
|
|
Given a key (a hash of data), go fetch the data.
|
|
"""
|
|
|
|
global dht_server
|
|
if dht_server is None:
|
|
raise Exception("DHT is not initialized")
|
|
|
|
value = dht_server.get(data_key)
|
|
|
|
if value == NONE_VALUE:
|
|
return None
|
|
else:
|
|
return value
|
|
|
|
|
|
def dht_put_data(data_key, data_value):
|
|
"""
|
|
Given a key and value, put it into the DHT.
|
|
"""
|
|
global dht_server
|
|
if dht_server is None:
|
|
raise Exceptoin("DHT is not initialized")
|
|
|
|
if data_value is None:
|
|
data_value = NONE_VALUE
|
|
|
|
return dht_server.set(data_key, data_value)
|
|
|
|
|
|
def dht_get_route(data_id):
|
|
"""
|
|
Given a data ID, go fetch the route to the data.
|
|
"""
|
|
|
|
global dht_server
|
|
if dht_server is None:
|
|
raise Exception("DHT is not initialized")
|
|
|
|
data_id_hash = dht_data_hash(data_id)
|
|
return dht_get_key(data_id_hash)
|
|
|
|
|
|
def dht_put_route(name, data_id, data, nonce, signature):
|
|
"""
|
|
Given the name, data ID, data, data nonce, and signature,
|
|
store a route.
|
|
"""
|
|
|
|
# in the DHT, there is no URL. Just a hash.
|
|
data_id_hash = dht_data_hash(data_id)
|
|
data_url = dht_url(data_id_hash)
|
|
|
|
route = mutable_data_route(data_id, [data_url])
|
|
route_json = json_stable_serialize(route)
|
|
return dht_put_data(data_id_hash, route_json)
|
|
|
|
|
|
# ---------------------------------------------------------
|
|
# Begin plugin implementation
|
|
# ---------------------------------------------------------
|
|
|
|
def get_immutable_handler(key):
|
|
"""
|
|
DHT implementation of the get_immutable_handler API call.
|
|
Given the hash of the data, return the data.
|
|
Return None if not found.
|
|
"""
|
|
return dht_get_key(key)
|
|
|
|
|
|
def get_route_handler(data_id):
|
|
"""
|
|
DHT implementation of the get_route_handler API call.
|
|
Given the unchanging ID of the data, return the route to the data.
|
|
Return None if not found.
|
|
"""
|
|
return dht_get_route(data_id)
|
|
|
|
|
|
def get_mutable_handler(data_url):
|
|
"""
|
|
DHT implementation of the get_mutable_handler API call.
|
|
Given a route URL to data, return the data itself.
|
|
If we can't handle this URL, raise UnhandledURLException.
|
|
Return the data if found.
|
|
Return None if not.
|
|
"""
|
|
try:
|
|
data_key = dht_url_parse(data_url)
|
|
except:
|
|
raise UnhandledURLException(data_url)
|
|
|
|
return dht_get_key(data_key)
|
|
|
|
|
|
def put_immutable_handler(name, key, data, signature):
|
|
"""
|
|
DHT implmentation of the put_immutable_handler API call.
|
|
Given the name of the user, the key to the data, the data itself,
|
|
and the writer's signature over the data, put the data into the
|
|
storage system.
|
|
Return True on success; False on failure.
|
|
"""
|
|
|
|
# TODO: the DHT in use at OneName should check to see that the user exists,
|
|
# and can write this data.
|
|
|
|
return dht_put_data(key, data)
|
|
|
|
|
|
def put_mutable_handler(name, data_id, data, nonce, signature):
|
|
"""
|
|
DHT implementation of the put_mutable_handler API call.
|
|
Given the name of the user, the unchanging ID for the data, the
|
|
data itself, a nonce representing this version of the data, and
|
|
the writer's signature over (data_id + data + nonce), put
|
|
the data into storage.
|
|
Return True on success; False on failure.
|
|
"""
|
|
|
|
# TODO: the DHT in use at OneName should check to see that the user exists,
|
|
# and can write this data.
|
|
|
|
# put a route, and then the data
|
|
|
|
try:
|
|
rc = dht_put_route(name, data_id, data, nonce, signature)
|
|
|
|
if not rc:
|
|
return False
|
|
|
|
except Exception, e:
|
|
traceback.print_exc()
|
|
return False
|
|
|
|
try:
|
|
rc = dht_put_data(dht_data_hash(data), data)
|
|
except Exception, e:
|
|
traceback.print_exc()
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def delete_immutable_handler(name, key, signature):
|
|
"""
|
|
DHT implementation of the delete_immutable_handler API call.
|
|
Given the name of the user, the key for the data, and the
|
|
writer's signature over the key, remove data from the
|
|
DHT.
|
|
Return True on success; False if not.
|
|
"""
|
|
|
|
# TODO: the DHT in use at OneName should check to see that the user exists,
|
|
# and can delete the data.
|
|
|
|
return dht_put_data(key, None)
|
|
|
|
|
|
def delete_mutable_handler(name, data_id, signature):
|
|
"""
|
|
DHT implementation of the delete_mutable_handler API call.
|
|
Given the name of the user, the unchanging data ID for the data,
|
|
and the writer's signature over the data_id, remove data from
|
|
the DHT.
|
|
Return True on success; False if not.
|
|
"""
|
|
|
|
# TODO: the DHT in use at OneName should check to see that the user exists,
|
|
# and can delete the data.
|
|
|
|
# put a null route to the data.
|
|
|
|
try:
|
|
rc = dht_put_route(name, data_id, None, None, signature)
|
|
|
|
if not rc:
|
|
return False
|
|
|
|
except Exception, e:
|
|
traceback.print_exc()
|
|
return False
|
|
|
|
return True
|