mirror of
https://github.com/alexgo-io/stacks-puppet-node.git
synced 2026-04-05 16:50:52 +08:00
294 lines
9.3 KiB
Python
294 lines
9.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/>.
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
import sys
|
|
import urllib2
|
|
import stat
|
|
import time
|
|
|
|
from ..config import *
|
|
from ..nameset import *
|
|
from .auth import *
|
|
|
|
from ..scripts import is_name_valid
|
|
|
|
import blockstack_client
|
|
from blockstack_client import hash_zonefile, get_zonefile_data_hash, verify_zonefile
|
|
from blockstack_client.backend.queue import (
|
|
queue_append, queue_findall, queue_removeall, queue_findone
|
|
)
|
|
|
|
import blockstack_zones
|
|
|
|
import virtualchain
|
|
log = virtualchain.get_logger("blockstack-server")
|
|
|
|
def get_cached_zonefile_data( zonefile_hash, zonefile_dir=None ):
|
|
"""
|
|
Get a serialized cached zonefile from local disk
|
|
Return None if not found
|
|
"""
|
|
if zonefile_dir is None:
|
|
zonefile_dir = get_zonefile_dir()
|
|
|
|
zonefile_path_dir = cached_zonefile_dir( zonefile_dir, zonefile_hash )
|
|
zonefile_path = os.path.join( zonefile_path_dir, "zonefile.txt" )
|
|
if not os.path.exists( zonefile_path ):
|
|
log.debug("No zonefile at %s" % zonefile_path )
|
|
return None
|
|
|
|
with open(zonefile_path, "r") as f:
|
|
data = f.read()
|
|
|
|
# sanity check
|
|
if not verify_zonefile( data, zonefile_hash ):
|
|
log.debug("Corrupt zonefile '%s'" % zonefile_hash)
|
|
return None
|
|
|
|
return data
|
|
|
|
|
|
def get_cached_zonefile( zonefile_hash, zonefile_dir=None ):
|
|
"""
|
|
Get a cached zonefile dict from local disk
|
|
Return None if not found
|
|
"""
|
|
data = get_cached_zonefile_data( zonefile_hash, zonefile_dir=zonefile_dir )
|
|
if data is None:
|
|
log.debug("Not cached: %s" % zonefile_hash)
|
|
return None
|
|
|
|
try:
|
|
zonefile_dict = blockstack_zones.parse_zone_file( data )
|
|
assert blockstack_client.is_user_zonefile( zonefile_dict ), "Not a user zonefile: %s" % zonefile_hash
|
|
return zonefile_dict
|
|
except Exception, e:
|
|
log.error("Failed to parse zonefile")
|
|
return None
|
|
|
|
|
|
def get_zonefile_data_from_storage( name, zonefile_hash, drivers=None ):
|
|
"""
|
|
Get a serialized zonefile from our storage drivers.
|
|
Return the zonefile dict on success.
|
|
Raise on error
|
|
"""
|
|
zonefile_txt = blockstack_client.storage.get_immutable_data( zonefile_hash, hash_func=blockstack_client.get_blockchain_compat_hash, fqu=name, zonefile=True, drivers=drivers )
|
|
if zonefile_txt is None:
|
|
raise Exception("Failed to get valid zonefile data")
|
|
|
|
# verify
|
|
if get_zonefile_data_hash( zonefile_txt ) != zonefile_hash:
|
|
log.warn("Corrupted zonefile for '%s'" % name)
|
|
raise Exception("Corrupt zonefile")
|
|
|
|
return zonefile_txt
|
|
|
|
|
|
def cached_zonefile_dir( zonefile_dir, zonefile_hash ):
|
|
"""
|
|
Calculate the on-disk path to storing a zonefile's information, given the zonefile hash
|
|
"""
|
|
|
|
# split into directories, so we don't try to cram millions of files into one directory
|
|
zonefile_dir_parts = []
|
|
interval = 2
|
|
for i in xrange(0, len(zonefile_hash), interval):
|
|
zonefile_dir_parts.append( zonefile_hash[i:i+interval] )
|
|
|
|
zonefile_dir_path = os.path.join(zonefile_dir, "/".join(zonefile_dir_parts))
|
|
return zonefile_dir_path
|
|
|
|
|
|
def is_zonefile_cached( zonefile_hash, zonefile_dir=None, validate=False):
|
|
"""
|
|
Do we have the cached zonefile? It's okay if it's a non-standard zonefile.
|
|
if @validate is true, then check that the data in zonefile_dir_path/zonefile.txt matches zonefile_hash
|
|
Return True if so
|
|
Return False if not
|
|
"""
|
|
if zonefile_dir is None:
|
|
zonefile_dir = get_zonefile_dir()
|
|
|
|
zonefile_path_dir = cached_zonefile_dir( zonefile_dir, zonefile_hash )
|
|
zonefile_path = os.path.join(zonefile_path_dir, "zonefile.txt")
|
|
|
|
if not os.path.exists(zonefile_path):
|
|
return False
|
|
|
|
if validate:
|
|
zf = get_cached_zonefile_data( zonefile_hash, zonefile_dir=zonefile_dir )
|
|
if zf is None:
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def store_cached_zonefile_data( zonefile_data, zonefile_dir=None ):
|
|
"""
|
|
Store a validated zonefile.
|
|
zonefile_data should be a dict.
|
|
The caller should first authenticate the zonefile.
|
|
Return True on success
|
|
Return False on error
|
|
"""
|
|
if zonefile_dir is None:
|
|
zonefile_dir = get_zonefile_dir()
|
|
|
|
if not os.path.exists(zonefile_dir):
|
|
os.makedirs(zonefile_dir, 0700 )
|
|
|
|
zonefile_hash = get_zonefile_data_hash( zonefile_data )
|
|
zonefile_dir_path = cached_zonefile_dir( zonefile_dir, zonefile_hash )
|
|
if not os.path.exists(zonefile_dir_path):
|
|
os.makedirs(zonefile_dir_path)
|
|
|
|
zonefile_path = os.path.join(zonefile_dir_path, "zonefile.txt")
|
|
try:
|
|
with open( zonefile_path, "w" ) as f:
|
|
f.write(zonefile_data)
|
|
f.flush()
|
|
os.fsync(f.fileno())
|
|
except Exception, e:
|
|
log.exception(e)
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def store_cached_zonefile( zonefile_dict, zonefile_dir=None ):
|
|
"""
|
|
Store a validated zonefile.
|
|
zonefile_data should be a dict.
|
|
The caller should first authenticate the zonefile.
|
|
Return True on success
|
|
Return False on error
|
|
"""
|
|
try:
|
|
zonefile_data = blockstack_zones.make_zone_file( zonefile_dict )
|
|
except Exception, e:
|
|
log.exception(e)
|
|
log.error("Invalid zonefile dict")
|
|
return False
|
|
|
|
return store_cached_zonefile_data( zonefile_data, zonefile_dir=zonefile_dir )
|
|
|
|
|
|
def remove_cached_zonefile_data( zonefile_hash, zonefile_dir=None ):
|
|
"""
|
|
Remove a cached zonefile
|
|
"""
|
|
if zonefile_dir is None:
|
|
zonefile_dir = get_zonefile_dir()
|
|
|
|
if not os.path.exists(zonefile_dir):
|
|
return True
|
|
|
|
zonefile_hash = get_zonefile_data_hash( zonefile_data )
|
|
zonefile_dir_path = cached_zonefile_dir( zonefile_dir, zonefile_hash )
|
|
if not os.path.exists(zonefile_dir_path):
|
|
return True
|
|
|
|
zonefile_path = os.path.join(zonefile_dir_path, "zonefile.txt")
|
|
if not os.path.exists(zonefile_path):
|
|
return True
|
|
|
|
try:
|
|
os.unlink(zonefile_path)
|
|
except:
|
|
log.error("Failed to unlink zonefile %s (%s)" % (zonefile_hash, zonefile_path))
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def store_zonefile_data_to_storage( zonefile_text, txid, required=None, skip=None, cache=False, zonefile_dir=None, tx_required=True ):
|
|
"""
|
|
Upload a zonefile to our storage providers.
|
|
Return True if at least one provider got it.
|
|
Return False otherwise.
|
|
"""
|
|
if tx_required and txid is None:
|
|
log.error("No txid for zonefile hash '%s'" % (zonefile_hash))
|
|
return False
|
|
|
|
zonefile_hash = get_zonefile_data_hash( zonefile_text )
|
|
|
|
if cache:
|
|
rc = store_cached_zonefile_data( zonefile_text, zonefile_dir=zonefile_dir )
|
|
if not rc:
|
|
log.debug("Failed to cache zonefile %s" % zonefile_hash)
|
|
|
|
# NOTE: this can fail if one of the required drivers needs a non-null txid
|
|
res = blockstack_client.storage.put_immutable_data( None, txid, data_hash=zonefile_hash, data_text=zonefile_text, required=required, skip=skip )
|
|
if res is None:
|
|
log.error("Failed to store zonefile '%s' for '%s'" % (zonefile_hash, txid))
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def store_zonefile_to_storage( zonefile_dict, required=None, skip=None, cache=False, zonefile_dir=None ):
|
|
"""
|
|
Upload a zonefile to our storage providers.
|
|
Return True if at least one provider got it.
|
|
Return False otherwise.
|
|
"""
|
|
|
|
try:
|
|
zonefile_data = blockstack_zones.make_zone_file( zonefile_dict )
|
|
except Exception, e:
|
|
log.exception(e)
|
|
log.error("Invalid zonefile dict")
|
|
return False
|
|
|
|
name = zonefile_dict.get('$origin')
|
|
return store_zonefile_data_to_storage( zonefile_data, required=required, skip=skip, cache=cache, zonefile_dir=zonefile_dir, name=name )
|
|
|
|
|
|
def store_mutable_data_to_storage( blockchain_id, data_id, data_txt, required=None, skip=None ):
|
|
"""
|
|
Store the given mutable datum to storage providers.
|
|
Used by the storage gateway logic.
|
|
Return True on successful replication to all required drivers
|
|
Return False on error
|
|
"""
|
|
|
|
nocollide_data_id = '{}-{}'.format(blockchain_id, data_id)
|
|
res = blockstack_client.storage.put_mutable_data(nocollide_data_id, None, None, data_text=data_txt, required=required, skip=skip, blockchain_id=blockchain_id)
|
|
return res
|
|
|
|
|
|
def load_mutable_data_from_storage( blockchain_id, data_id, drivers=None ):
|
|
"""
|
|
Load mutable data from storage.
|
|
Used by the storage gateway logic.
|
|
"""
|
|
|
|
nocollide_data_id = '{}-{}'.format(blockchain_id, data_id)
|
|
res = blockstack_client.storage.get_mutable_data(data_id, None, blockchain_id=blockchain_id, drivers=drivers, decode=False)
|
|
return res
|
|
|