From 895b15a2d3a092955391bad9a54b052fa337c73d Mon Sep 17 00:00:00 2001 From: Aaron Blankstein Date: Thu, 20 Jul 2017 13:06:16 -0400 Subject: [PATCH] adding integration test for RESTful setting and fetching of raw zonefile, plus updating api-specs --- docs/api-specs.md | 21 +- .../scenarios/rest_register_update_zf.py | 292 ++++++++++++++++++ 2 files changed, 308 insertions(+), 5 deletions(-) create mode 100644 integration_tests/blockstack_integration_tests/scenarios/rest_register_update_zf.py diff --git a/docs/api-specs.md b/docs/api-specs.md index 93cfa77ae..ec80f501b 100644 --- a/docs/api-specs.md +++ b/docs/api-specs.md @@ -438,8 +438,9 @@ Sets the user's zonefile hash, and, if supplied, propagates the zonefile. If you supply the zonefile, the hash will be calculated from that. Ultimately, your requests should only supply one of `zonefile`, `zonefile_b64`, or `zonefile_hash`. - + Authorization: `update` ++ Parameters + + name: bar.test (string) - fully-qualified name + Request (application/json) + Schema @@ -448,20 +449,18 @@ that. Ultimately, your requests should only supply one of `zonefile`, 'properties': { "zonefile": { 'type': 'string', - 'maxLength': RPC_MAX_ZONEFILE_LEN, }, 'zonefile_b64': { 'type': 'string', - 'maxLength': (RPC_MAX_ZONEFILE_LEN * 4) / 3 + 1, }, 'zonefile_hash': { 'type': 'string', - 'pattern': OP_ZONEFILE_HASH_PATTERN, + 'pattern': '^([0-9a-fA-F]{20})$' }, 'tx_fee': { 'type': 'integer', 'minimum': 0, - 'maximum': TX_MAX_FEE + 'maximum': 500000 }, }, 'additionalProperties': False, @@ -472,6 +471,18 @@ that. Ultimately, your requests should only supply one of `zonefile`, {'transaction_hash' : '...'} +## Fetch zone file [GET /v1/names/{name}/zonefile] +Fetch a user's raw zonefile. ++ Parameters + + name: bar.test (string) - fully-qualified name ++ Response 200 (application/json) + + Body + + { + "zonefile": "$ORIGIN bar.test\n$TTL 3600\n_https._tcp URI 10 1 \"https://blockstack.s3.amazonaws.com/bar.test\"\n" + } + + # Group Name Querying This family of API endpoints deals with querying name information. diff --git a/integration_tests/blockstack_integration_tests/scenarios/rest_register_update_zf.py b/integration_tests/blockstack_integration_tests/scenarios/rest_register_update_zf.py new file mode 100644 index 000000000..a7358b0f0 --- /dev/null +++ b/integration_tests/blockstack_integration_tests/scenarios/rest_register_update_zf.py @@ -0,0 +1,292 @@ +#!/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 . +""" +import os +import testlib +import pybitcoin +import urllib2 +import json +import blockstack_client +import blockstack_profiles +import sys +import keylib +import time + +from keylib import ECPrivateKey, ECPublicKey + +wallets = [ + testlib.Wallet( "5JesPiN68qt44Hc2nT8qmyZ1JDwHebfoh9KQ52Lazb1m1LaKNj9", 100000000000 ), + testlib.Wallet( "5KHqsiU9qa77frZb6hQy9ocV7Sus9RWJcQGYYBJJBb2Efj1o77e", 100000000000 ), + testlib.Wallet( "5Kg5kJbQHvk1B64rJniEmgbD83FpZpbw2RjdAZEzTefs9ihN3Bz", 100000000000 ), + testlib.Wallet( "5JuVsoS9NauksSkqEjbUZxWwgGDQbMwPsEfoRBSpLpgDX1RtLX7", 100000000000 ), + testlib.Wallet( "5KEpiSRr1BrT8vRD7LKGCEmudokTh1iMHbiThMQpLdwBwhDJB1T", 100000000000 ), + testlib.Wallet( "5K5hDuynZ6EQrZ4efrchCwy6DLhdsEzuJtTDAf3hqdsCKbxfoeD", 100000000000 ), + testlib.Wallet( "5J39aXEeHh9LwfQ4Gy5Vieo7sbqiUMBXkPH7SaMHixJhSSBpAqz", 100000000000 ), + testlib.Wallet( "5K9LmMQskQ9jP1p7dyieLDAeB6vsAj4GK8dmGNJAXS1qHDqnWhP", 100000000000 ), + testlib.Wallet( "5KcNen67ERBuvz2f649t9F2o1ddTjC5pVUEqcMtbxNgHqgxG2gZ", 100000000000 ), + testlib.Wallet( "5KMbNjgZt29V6VNbcAmebaUT2CZMxqSridtM46jv4NkKTP8DHdV", 100000000000 ), +] + +consensus = "17ac43c1d8549c3181b200f1bf97eb7d" +wallet_keys = None +wallet_keys_2 = None +error = False + +index_file_data = "foo.test hello world" +resource_data = "hello world" + + +def scenario( wallets, **kw ): + + global wallet_keys, wallet_keys_2, error, index_file_data, resource_data + + wallet_keys = testlib.blockstack_client_initialize_wallet( "0123456789abcdef", wallets[5].privkey, wallets[3].privkey, wallets[4].privkey ) + test_proxy = testlib.TestAPIProxy() + blockstack_client.set_default_proxy( test_proxy ) + + 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 ) + + # migrate profiles + res = testlib.migrate_profile( "foo.test", proxy=test_proxy, wallet_keys=wallet_keys ) + if 'error' in res: + res['test'] = 'Failed to initialize foo.test profile' + print json.dumps(res, indent=4, sort_keys=True) + error = True + return + + # tell serialization-checker that value_hash can be ignored here + print "BLOCKSTACK_SERIALIZATION_CHECK_IGNORE value_hash" + sys.stdout.flush() + + testlib.next_block( **kw ) + + config_path = os.environ.get("BLOCKSTACK_CLIENT_CONFIG", None) + + # make a session + datastore_pk = keylib.ECPrivateKey(wallets[-1].privkey).to_hex() + res = testlib.blockstack_cli_app_signin("foo.test", datastore_pk, 'register.app', ['names', 'register', 'prices', 'zonefiles', 'blockchain', 'node_read', 'update']) + if 'error' in res: + print json.dumps(res, indent=4, sort_keys=True) + error = True + return + + ses = res['token'] + + # for funsies, get the price of .test + res = testlib.blockstack_REST_call('GET', '/v1/prices/namespaces/test', ses ) + if 'error' in res or res['http_status'] != 200: + res['test'] = 'Failed to get price of .test' + print json.dumps(res) + return False + + test_price = res['response']['satoshis'] + print '\n\n.test costed {} satoshis\n\n'.format(test_price) + + # get the price for bar.test + res = testlib.blockstack_REST_call('GET', '/v1/prices/names/bar.test', ses ) + if 'error' in res or res['http_status'] != 200: + res['test'] = 'Failed to get price of bar.test' + print json.dumps(res) + return False + + bar_price = res['response']['total_estimated_cost']['satoshis'] + print "\n\nbar.test will cost {} satoshis\n\n".format(bar_price) + + # register the name bar.test. autogenerate the rest + res = testlib.blockstack_REST_call('POST', '/v1/names', ses, data={'name': 'bar.test'} ) + if 'error' in res: + res['test'] = 'Failed to register user' + print json.dumps(res) + error = True + return False + + print res + tx_hash = res['response']['transaction_hash'] + + # wait for preorder to get confirmed... + for i in xrange(0, 6): + testlib.next_block( **kw ) + + res = testlib.verify_in_queue(ses, 'bar.test', 'preorder', tx_hash ) + if not res: + return False + + # wait for the preorder to get confirmed + for i in xrange(0, 6): + testlib.next_block( **kw ) + + # wait for register to go through + print 'Wait for register to be submitted' + time.sleep(10) + + # wait for the register to get confirmed + for i in xrange(0, 6): + testlib.next_block( **kw ) + + res = testlib.verify_in_queue(ses, 'bar.test', 'register', None ) + if not res: + return False + + for i in xrange(0, 6): + testlib.next_block( **kw ) + + print 'Wait for update to be submitted' + time.sleep(10) + + # wait for update to get confirmed + for i in xrange(0, 6): + testlib.next_block( **kw ) + + res = testlib.verify_in_queue(ses, 'bar.test', 'update', None ) + if not res: + return False + + for i in xrange(0, 12): + testlib.next_block( **kw ) + + print 'Wait for update to be confirmed' + time.sleep(10) + + res = testlib.blockstack_REST_call("GET", "/v1/names/bar.test", ses) + if 'error' in res or res['http_status'] != 200: + res['test'] = 'Failed to get name bar.test' + print json.dumps(res) + return False + + zonefile_hash = res['response']['zonefile_hash'] + + # do we have the history for the name? + res = testlib.blockstack_REST_call("GET", "/v1/names/bar.test/history", ses ) + if 'error' in res or res['http_status'] != 200: + res['test'] = "Failed to get name history for bar.test" + print json.dumps(res) + return False + + # valid history? + hist = res['response'] + if len(hist.keys()) != 3: + res['test'] = 'Failed to get update history' + res['history'] = hist + print json.dumps(res, indent=4, sort_keys=True) + return False + + # get the zonefile + res = testlib.blockstack_REST_call("GET", "/v1/names/bar.test/zonefile/{}".format(zonefile_hash), ses ) + if 'error' in res or res['http_status'] != 200: + res['test'] = 'Failed to get name zonefile' + print json.dumps(res) + return False + + # now, let's set a new zonefile + zf_str = "$ORIGIN bar.test\n$TTL 3600\nmy_vote TXT \"045a501e341fbf1b403ce3a6e66836a3a40a\"" + res = testlib.blockstack_REST_call("PUT", "/v1/names/bar.test/zonefile/", ses, + ses, data={'zonefile' : zf_str}) + + # wait for update to get confirmed + for i in xrange(0, 6): + testlib.next_block( **kw ) + + res = testlib.verify_in_queue(ses, 'bar.test', 'update', None ) + if not res: + return False + + for i in xrange(0, 6): + testlib.next_block( **kw ) + + res = testlib.blockstack_REST_call("GET", "/v1/names/bar.test/zonefile/", ses ) + if 'error' in res or res['http_status'] != 200: + res['test'] = 'Failed to get name zonefile' + print json.dumps(res) + return False + + if res['response']['zonefile'] != zf_str: + print "Zonefile wasn't updated." + print "Expected: {}".format(zf_str) + print "Received: {}".format(res['response']['zonefile']) + +def check( state_engine ): + + global wallet_keys, error, index_file_data, resource_data + + config_path = os.environ.get("BLOCKSTACK_CLIENT_CONFIG") + assert config_path + + if error: + print "Key operation failed." + return False + + # not revealed, but ready + ns = state_engine.get_namespace_reveal( "test" ) + if ns is not None: + print "namespace not ready" + 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 + + names = ['foo.test', 'bar.test'] + wallet_keys_list = [wallet_keys, wallet_keys] + test_proxy = testlib.TestAPIProxy() + + for i in xrange(0, len(names)): + name = names[i] + wallet_payer = 5 + wallet_owner = 3 + wallet_data_pubkey = 4 + + # not preordered + preorder = state_engine.get_name_preorder( name, pybitcoin.make_pay_to_address_script(wallets[wallet_payer].addr), wallets[wallet_owner].addr ) + if preorder is not None: + print "still have preorder" + return False + + # registered + name_rec = state_engine.get_name( name ) + if name_rec is None: + print "name does not exist" + return False + + # owned + if name_rec['address'] != wallets[wallet_owner].addr or name_rec['sender'] != pybitcoin.make_pay_to_address_script(wallets[wallet_owner].addr): + print "name {} has wrong owner".format(name) + return False + + return True