Merge branch 'master' into feature/token-v1

This commit is contained in:
Jude Nelson
2018-08-07 14:39:29 -04:00
11 changed files with 529 additions and 33 deletions

View File

@@ -48,9 +48,6 @@ import xmlrpclib
from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler
import SocketServer
# stop common XML attacks
from defusedxml import xmlrpc
xmlrpc.monkey_patch()
import virtualchain
from virtualchain.lib.hashing import *
@@ -76,6 +73,11 @@ from lib.subdomains import (subdomains_init, SubdomainNotFound, get_subdomain_in
import lib.nameset.virtualchain_hooks as virtualchain_hooks
import lib.config as config
# stop common XML attacks
from defusedxml import xmlrpc
xmlrpc.MAX_DATA = MAX_RPC_LEN
xmlrpc.monkey_patch()
# global variables, for use with the RPC server
bitcoind = None
rpc_server = None

View File

@@ -3029,6 +3029,174 @@ def get_name_and_history(name, include_expired=False, include_grace=True, hostpo
return {'status': True, 'record': rec, 'lastblock': hist['lastblock'], 'indexing': hist['indexing']}
def get_name_history_page(name, page, hostport=None, proxy=None):
"""
Get a page of the name's history
Returns {'status': True, 'history': ..., 'indexing': ..., 'lastblock': ...} on success
Returns {'error': ...} on error
"""
assert hostport or proxy, 'Need hostport or proxy'
if proxy is None:
proxy = connect_hostport(hostport)
hist_schema = {
'type': 'object',
'patternProperties': {
'^[0-9]+$': {
'type': 'array',
'items': {
'type': 'object',
'properties': OP_HISTORY_SCHEMA['properties'],
'required': [
'op',
'opcode',
'txid',
'vtxindex',
],
},
},
},
}
hist_resp_schema = {
'type': 'object',
'properties': {
'history': hist_schema,
},
'required': [ 'history' ],
}
resp_schema = json_response_schema(hist_resp_schema)
resp = {}
lastblock = None
indexin = None
try:
_resp = proxy.get_name_history_page(name, page)
resp = json_validate(resp_schema, _resp)
if json_is_error(resp):
return resp
lastblock = _resp['lastblock']
indexing = _resp['indexing']
except ValidationError as 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 {'status': True, 'history': resp['history'], 'lastblock': lastblock, 'indexing': indexing}
def name_history_merge(h1, h2):
"""
Given two name histories (grouped by block), merge them.
"""
ret = {}
blocks_1 = [int(b) for b in h1.keys()]
blocks_2 = [int(b) for b in h2.keys()]
# find overlapping blocks
overlap = list(set(blocks_1).intersection(set(blocks_2)))
if len(overlap) > 0:
for b in overlap:
h = h1[str(b)] + h2[str(b)]
h.sort(lambda v1, v2: -1 if v1['vtxindex'] < v2['vtxindex'] else 1)
uniq = []
last_vtxindex = None
for i in range(0, len(h)):
if h[i]['vtxindex'] != last_vtxindex:
uniq.append(h[i])
last_vtxindex = h[i]['vtxindex']
ret[str(b)] = uniq
all_blocks = list(set(blocks_1 + blocks_2))
for b in all_blocks:
if b in overlap:
continue
if b in blocks_1:
ret[str(b)] = h1[str(b)]
else:
ret[str(b)] = h2[str(b)]
return ret
def get_name_history(name, hostport=None, proxy=None, history_page=None):
"""
Get the full history of a name
Returns {'status': True, 'history': ...} on success, where history is grouped by block
Returns {'error': ...} on error
"""
assert hostport or proxy, 'Need hostport or proxy'
if proxy is None:
proxy = connect_hostport(hostport)
hist = {}
indexing = None
lastblock = None
if history_page != None:
resp = get_name_history_page(name, history_page, proxy=proxy)
if 'error' in resp:
return resp
indexing = resp['indexing']
lastblock = resp['lastblock']
return {'status': True, 'history': resp['history'], 'indexing': indexing, 'lastblock': lastblock}
for i in range(0, 10000): # this is obviously too big
resp = get_name_history_page(name, i, proxy=proxy)
if 'error' in resp:
return resp
indexing = resp['indexing']
lastblock = resp['lastblock']
if len(resp['history']) == 0:
# caught up
break
hist = name_history_merge(hist, resp['history'])
return {'status': True, 'history': hist, 'indexing': indexing, 'lastblock': lastblock}
def get_name_and_history(name, include_expired=False, include_grace=True, hostport=None, proxy=None, history_page=None):
"""
Get the current name record and its history
(this is a replacement for proxy.get_name_blockchain_record())
Return {'status': True, 'record': ...} on success, where .record.history is defined as {block_height: [{history}, {history}, ...], ...}
Return {'error': ...} on error
"""
assert hostport or proxy, 'Need hostport or proxy'
if proxy is None:
proxy = connect_hostport(hostport)
hist = get_name_history(name, proxy=proxy, history_page=history_page)
if 'error' in hist:
return hist
# just the name
rec = get_name_record(name, include_history=False, include_expired=include_expired, include_grace=include_grace, proxy=proxy)
if 'error' in rec:
return rec
rec['history'] = hist['history']
return {'status': True, 'record': rec, 'lastblock': hist['lastblock'], 'indexing': hist['indexing']}
def get_name_at(name, block_id, include_expired=False, hostport=None, proxy=None):
"""
Get the name as it was at a particular height.

View File

@@ -189,6 +189,7 @@ RPC_MAX_ZONEFILE_LEN = 40960 # 40KB
RPC_MAX_INDEXING_DELAY = 2 * 3600 # 2 hours; maximum amount of time before the absence of new blocks causes the node to stop responding
MAX_RPC_LEN = RPC_MAX_ZONEFILE_LEN * 150 # maximum blockstackd RPC length (100 40K zone files, plus base64 encoding overhead and XMLRPC padding)
if os.environ.get("BLOCKSTACK_TEST_MAX_RPC_LEN"):
MAX_RPC_LEN = int(os.environ.get("BLOCKSTACK_TEST_MAX_RPC_LEN"))
print("Overriding MAX_RPC_LEN to {}".format(MAX_RPC_LEN))

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
Blockstack
~~~~~
copyright: (c) 2014-2015 by Halfmoon Labs, Inc.
copyright: (c) 2016-2018 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 is a place-holder
GENESIS_BLOCK = {
'history': [],
'rows': [],
}

View File

@@ -935,7 +935,7 @@ class BlockstackDB(virtualchain.StateEngine):
cur = self.db.cursor()
name_hist = namedb_get_history( cur, name, offset=offset, count=count, reverse=reverse )
return name_hist
def get_all_nameops_at( self, block_number, offset=None, count=None, include_history=None, restore_history=None ):
"""
@@ -1994,10 +1994,4 @@ class BlockstackDB(virtualchain.StateEngine):
self.vesting[block_height] = True
return True
def get_block_ops_hash( self, block_id ):
"""
Get the block's operations hash
"""
return self.get_ops_hash_at(block_id)

View File

@@ -1226,33 +1226,61 @@ Get a name's owner, zone file, and blockchain-based information.
"zonefile_hash": "b100a68235244b012854a95f9114695679002af9"
}
+ Response 400 (application/json)
+ Body
{
'error': 'Invalid address'
}
+ Schema
{
'type': 'object',
'properties': {
'error': {
'type': 'string',
},
'type': 'object',
'properties': {
'address': {
'type': 'string',
'pattern': r"^([123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+)$",
},
'required': [
'error'
],
'blockchain': {
'type': 'string',
'pattern': '^bitcoin$',
},
'expire_block': {
'type': 'integer',
'minimum': 0,
},
'last_txid': {
'type': 'string',
'pattern': '^[0-9a-fA-F]+$',
},
'status': {
'type': 'string',
'pattern': '^(registered|revoked)$',
},
'zonefile': {
'anyOf': [
{
'type': 'string',
},
{
'type': 'object',
'properties': {
'error': {
'type': 'string',
},
},
},
],
},
'zonefile_hash': {
'type': 'string',
'pattern': '^[0-9a-fA-F]{20}$`,
},
},
{ 'required':
[
'address', 'blockchain', 'last_txid',
'status', 'zonefile', 'zonefile_hash'
]
}
}
+ Response 400 (application/json)
+ Body
{
'error': 'Invalid token type'
'error': 'Invalid name'
}
+ Schema

View File

@@ -30,6 +30,7 @@ import json
import tempfile
import subprocess
import fcntl
import blockstack
import virtualchain
from .version import __version__, __version_major__, __version_minor__, __version_patch__
@@ -297,10 +298,10 @@ BLOCKSTACK_BURN_ADDRESS = virtualchain.hex_hash160_to_address(BLOCKSTACK_BURN_PU
# never changes, so safe to duplicate to avoid gratuitous imports
MAXIMUM_NAMES_PER_ADDRESS = 25
RPC_MAX_ZONEFILE_LEN = 4096 # 4KB
RPC_MAX_ZONEFILE_LEN = blockstack.lib.config.RPC_MAX_ZONEFILE_LEN
RPC_MAX_PROFILE_LEN = 1024000 # 1MB
MAX_RPC_LEN = RPC_MAX_ZONEFILE_LEN * 110 # maximum blockstackd RPC length--100 zonefiles with overhead
MAX_RPC_LEN = blockstack.lib.config.MAX_RPC_LEN
if os.environ.get("BLOCKSTACK_TEST_MAX_RPC_LEN"):
MAX_RPC_LEN = int(os.environ.get("BLOCKSTACK_TEST_MAX_RPC_LEN"))
print("Overriding MAX_RPC_LEN to {}".format(MAX_RPC_LEN))

View File

@@ -888,12 +888,26 @@ class BlockstackAPIEndpointHandler(SimpleHTTPRequestHandler):
return {'error': 'Not found', 'http_status': 404}
if 'zonefile' in domain_rec:
<<<<<<< HEAD:integration_tests/blockstack_integration_tests/scenarios/blockstack_client/rpc.py
return {'status': True, 'zonefile': domain_rec['zonefile']}
=======
try:
zf_txt = base64.b64decode(domain_rec['zonefile'])
return {'status': True, 'zonefile': zf_txt}
except:
log.error("Failed to parse zonefile returned by blockstackd: contents return: {}"
.format(domain_rec['zonefile']))
return {'error': 'Failed to parse zonefile', 'http_status': 502}
>>>>>>> master:blockstack_client/rpc.py
last_zonefile_hash = None
page = 0
while attempts > 0:
<<<<<<< HEAD:integration_tests/blockstack_integration_tests/scenarios/blockstack_client/rpc.py
# paginate back through the name's history to find a zone file
=======
# paginate back through the name's history to find a zone file
>>>>>>> master:blockstack_client/rpc.py
res = blockstackd_client.get_name_history_page(name, page, hostport=blockstackd_url)
if 'error' in res:
log.error("Failed to get name history page {} for {}: {}".format(page, name, res['error']))
@@ -1011,7 +1025,11 @@ class BlockstackAPIEndpointHandler(SimpleHTTPRequestHandler):
else:
return self._reply_json(
{'error': res['error']}, status_code=res['http_status'])
<<<<<<< HEAD:integration_tests/blockstack_integration_tests/scenarios/blockstack_client/rpc.py
=======
>>>>>>> master:blockstack_client/rpc.py
domain_zf_txt = res['zonefile']
domain_zf_json = zonefile.decode_name_zonefile(domain_name, domain_zf_txt, allow_legacy=False)
matching_uris = [ x['target'] for x in domain_zf_json['uri'] if x['name'] == '_resolver' ]
@@ -1106,6 +1124,11 @@ class BlockstackAPIEndpointHandler(SimpleHTTPRequestHandler):
return
blockstackd_url = get_blockstackd_url(self.server.config_path)
<<<<<<< HEAD:integration_tests/blockstack_integration_tests/scenarios/blockstack_client/rpc.py
=======
# res = proxy.get_name_blockchain_history(name, start_block, end_block)
>>>>>>> master:blockstack_client/rpc.py
res = blockstackd_client.get_name_record(name, include_history=True,
hostport=blockstackd_url, history_page=page)
if json_is_error(res):

View File

@@ -24,4 +24,8 @@
__version_major__ = '0'
__version_minor__ = '19'
__version_patch__ = '0'
<<<<<<< HEAD:integration_tests/blockstack_integration_tests/scenarios/blockstack_client/version.py
__version__ = '{}.{}.{}.0'.format(__version_major__, __version_minor__, __version_patch__)
=======
__version__ = '{}.{}.{}.9'.format(__version_major__, __version_minor__, __version_patch__)
>>>>>>> master:blockstack_client/version.py

View File

@@ -0,0 +1,95 @@
#!/usr/bin/env python2
# -*- 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 testlib
import json
import blockstack
import blockstack_client
import virtualchain
import binascii
import socket
import base64
import xmlrpclib
import StringIO
import gzip
wallets = [
testlib.Wallet( "5JesPiN68qt44Hc2nT8qmyZ1JDwHebfoh9KQ52Lazb1m1LaKNj9", 100000000000 ),
testlib.Wallet( "5KHqsiU9qa77frZb6hQy9ocV7Sus9RWJcQGYYBJJBb2Efj1o77e", 100000000000 ),
testlib.Wallet( "5Kg5kJbQHvk1B64rJniEmgbD83FpZpbw2RjdAZEzTefs9ihN3Bz", 100000000000 ),
testlib.Wallet( "5JuVsoS9NauksSkqEjbUZxWwgGDQbMwPsEfoRBSpLpgDX1RtLX7", 100000000000 ),
testlib.Wallet( "5KEpiSRr1BrT8vRD7LKGCEmudokTh1iMHbiThMQpLdwBwhDJB1T", 100000000000 )
]
consensus = "17ac43c1d8549c3181b200f1bf97eb7d"
def make_big_zonefile(filler):
return '{:02x}'.format(filler) * 20480
def scenario( wallets, **kw ):
# make some zonefiles
testlib.blockstack_namespace_preorder( "test", wallets[1].addr, wallets[0].privkey )
testlib.next_block( **kw )
testlib.blockstack_namespace_reveal( "test", wallets[1].addr, 52595, 250, 4, [6,5,4,3,2,1,0,0,0,0,0,0,0,0,0,0], 10, 10, wallets[0].privkey )
testlib.next_block( **kw )
testlib.blockstack_namespace_ready( "test", wallets[1].privkey )
testlib.next_block( **kw )
testlib.blockstack_name_preorder( "foo.test", wallets[2].privkey, wallets[3].addr )
testlib.next_block( **kw )
testlib.blockstack_name_register( "foo.test", wallets[2].privkey, wallets[3].addr )
testlib.next_block( **kw )
zfhashes = []
for j in range(0, 5):
for i in range(0, 20):
big_zonefile = make_big_zonefile(i + 20*j)
zonefile_hash = blockstack.lib.storage.get_zonefile_data_hash(big_zonefile)
testlib.blockstack_name_update("foo.test", zonefile_hash, wallets[3].privkey)
zfhashes.append(zonefile_hash)
testlib.next_block(**kw)
for i in range(0, 20):
big_zonefile = make_big_zonefile(i + 20*j)
res = blockstack.lib.client.put_zonefiles('http://localhost:16264', [base64.b64encode(big_zonefile)])
assert res['saved'][0] == 1
print '\n\ntest with {} zone files\n\n'.format(20 * j + i)
res = blockstack.lib.client.get_zonefiles('http://localhost:16264', zfhashes)
assert 'error' not in res, res
for zfh in zfhashes:
assert zfh in res['zonefiles'], 'missing {}, got {}'.format(zfh, res['zonefiles'].keys())
def check( state_engine ):
return True

View File

@@ -0,0 +1,151 @@
#!/usr/bin/env python2
# -*- 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/>.
"""
# activate STACKS Phase 1
"""
TEST ENV BLOCKSTACK_EPOCH_1_END_BLOCK 682
TEST ENV BLOCKSTACK_EPOCH_2_END_BLOCK 683
"""
import testlib
import virtualchain
import time
import json
import sys
import os
import BaseHTTPServer
import stun
import urlparse
import atexit
import subprocess
import socket
import threading
import traceback
import virtualchain
import cgi
import blockstack
import requests
log = virtualchain.get_logger('testnet')
wallets = [
testlib.Wallet(virtualchain.lib.ecdsalib.ecdsa_private_key().to_wif(), 100000000000 ),
testlib.Wallet(virtualchain.lib.ecdsalib.ecdsa_private_key().to_wif(), 100000000000 ),
testlib.Wallet(virtualchain.lib.ecdsalib.ecdsa_private_key().to_wif(), 100000000000 ),
testlib.Wallet(virtualchain.lib.ecdsalib.ecdsa_private_key().to_wif(), 1000000000000000000 ),
testlib.Wallet(virtualchain.lib.ecdsalib.ecdsa_private_key().to_wif(), 100000000000 ),
]
consensus = "17ac43c1d8549c3181b200f1bf97eb7d"
GAIA_READ_URL = None
GAIA_WRITE_URL = None
GAIA_READ_PORT = None
GAIA_WRITE_PORT = None
owner_privkey = None
owner_address = None
def scenario( wallets, **kw ):
global GAIA_READ_URL
global GAIA_READ_PORT
global GAIA_WRITE_PORT
global GAIA_WRITE_URL
global owner_privkey
global owner_address
# get gaia hub info
with open(os.path.join(os.environ['BLOCKSTACK_WORKING_DIR'], 'gaia.conf'), 'r') as f:
GAIA_CONF = json.loads(f.read().strip())
try:
GAIA_READ_PORT = urlparse.urlparse(GAIA_CONF['readURL']).netloc.split(':')[-1]
GAIA_READ_PORT = int(GAIA_READ_PORT)
except:
GAIA_READ_PORT = 80
if os.environ.get('BLOCKSTACK_PUBLIC_TESTNET_GAIA_READ_PORT'):
GAIA_READ_PORT = int(os.environ['BLOCKSTACK_PUBLIC_TESTNET_GAIA_READ_PORT'])
read_urlinfo = urlparse.urlparse(GAIA_CONF['readURL'])
GAIA_READ_URL = 'http://{}:{}'.format(read_urlinfo.netloc.split(':')[0], GAIA_READ_PORT)
GAIA_WRITE_PORT = GAIA_CONF['port']
if os.environ.get('BLOCKSTACK_PUBLIC_TESTNET_GAIA_WRITE_PORT'):
GAIA_WRITE_PORT = int(os.environ['BLOCKSTACK_PUBLIC_TESTNET_GAIA_WRITE_PORT'])
GAIA_WRITE_URL = 'http://{}:{}'.format(GAIA_CONF['servername'], GAIA_WRITE_PORT)
# fill in URL
tb_conf_path = os.path.join(os.environ['BLOCKSTACK_WORKING_DIR'], 'transaction-broadcaster.conf')
with open(tb_conf_path, 'r') as f:
tb_conf = json.loads(f.read().strip())
testlib.blockstack_namespace_preorder( "test", wallets[1].addr, wallets[0].privkey )
testlib.next_block( **kw )
testlib.blockstack_namespace_reveal( "test", wallets[1].addr, -1, 250, 4, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], 10, 10, wallets[0].privkey, version_bits=1)
testlib.next_block( **kw )
testlib.blockstack_namespace_ready( "test", wallets[1].privkey )
testlib.next_block( **kw )
# register this user under a keychain
res = testlib.nodejs_cli('make_keychain')
res = json.loads(res)
mnemonic = res['mnemonic']
owner_privkey = res['ownerKeyInfo']['privateKey']
owner_address = res['ownerKeyInfo']['idAddress'][3:]
profile = {
'type': '@Person',
'account': [],
'name': 'Testy McTestface',
}
testlib.blockstack_register_user('foo.test', wallets[3].privkey, owner_privkey, profile=profile, **kw)
print ""
print "mnemnic: {}".format(mnemonic)
print "hub url: {}".format(GAIA_WRITE_URL)
print ""
def check( state_engine ):
# not revealed, but ready
ns = state_engine.get_namespace_reveal( "test" )
if ns is not None:
print "namespace reveal exists"
return False
ns = state_engine.get_namespace( "test" )
if ns is None:
print "no namespace"
return False
if ns['namespace_id'] != 'test':
print "wrong namespace"
return False
return True