コード例 #1
0
def scenario( wallets, **kw ):

    global put_result, wallet_keys, legacy_profile, zonefile_hash, zonefile_hash_2, error

    wallet_keys = testlib.ysi_client_initialize_wallet( "0123456789abcdef", wallets[8].privkey, wallets[3].privkey, wallets[4].privkey )

    test_proxy = testlib.TestAPIProxy()
    ysi_client.set_default_proxy( test_proxy )


    testlib.ysi_namespace_preorder( "test", wallets[1].addr, wallets[0].privkey )
    testlib.next_block( **kw )

    testlib.ysi_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.ysi_namespace_ready( "test", wallets[1].privkey )
    testlib.next_block( **kw )

    testlib.ysi_name_preorder( "foo.test", wallets[2].privkey, wallets[3].addr )
    testlib.next_block( **kw )
    
    testlib.ysi_name_register( "foo.test", wallets[2].privkey, wallets[3].addr )
    testlib.next_block( **kw )

    # set up legacy profile hash
    legacy_txt = json.dumps(legacy_profile,sort_keys=True)
    legacy_hash = virtualchain.lib.hashing.hex_hash160( legacy_txt )
    
    result_1 = testlib.ysi_cli_update("foo.test", legacy_txt, '0123456789abcdef') 
    if 'error' in result_1:
        print json.dumps(result_1, indent=4, sort_keys=True)
        return False

    # wait for it to go through...
    for i in xrange(0, 12):
        testlib.next_block(**kw)

    print "wait 10 seconds for update to go through"
    time.sleep(10)

    rc = ysi_client.storage.put_immutable_data( legacy_txt, result_1['transaction_hash'], data_hash=legacy_hash )
    assert rc is not None

    testlib.next_block( **kw )

    # migrate profiles to standard zonefiles
    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)
        return False 

    testlib.next_block( **kw )

    # give foo.test a nonstandard zonefile (as something that serializes to JSON)
    nonstandard_zonefile_json = {'nonstandard': 'true', 'error': 'nonstandard'}
    nonstandard_zonefile_txt = json.dumps(nonstandard_zonefile_json, sort_keys=True)
    nonstandard_zonefile_raw = binascii.unhexlify( "".join(["%02x" % i for i in xrange(0, 256)]))

    zf_data = [nonstandard_zonefile_txt, nonstandard_zonefile_raw]
    for zi in xrange(0, len(zf_data)):
        nonstandard_zonefile = zf_data[zi]

        resp = testlib.ysi_cli_update( "foo.test", nonstandard_zonefile, "0123456789abcdef", nonstandard=True )
        if 'error' in resp:
            print "failed to put nonstandard zonefile '%s'" % nonstandard_zonefile
            print json.dumps(resp, indent=4, sort_keys=True)
            return False

        testlib.expect_atlas_zonefile(resp['zonefile_hash'])

        # wait for it to take effect
        for i in xrange(0, 12):
            testlib.next_block( **kw )

        time.sleep(10)

        # getting zonefile should still work...
        resp = testlib.ysi_cli_get_name_zonefile( "foo.test", json=True, raw=False)
        if 'error' in resp:
            print "failed to get zonefile %s" % zi
            print json.dumps(resp, indent=4, sort_keys=True)
            return False 

        if resp['zonefile'] != nonstandard_zonefile:
            print "failed to load nonstandard zonefile json"
            print "expected:\n%s\n\ngot:\n%s" % (nonstandard_zonefile, resp['zonefile'])
            return False

        # the following should all fail
        dataplane_funcs = [
            ("lookup",        lambda: testlib.ysi_cli_lookup( "foo.test" )),
            ("put_immutable", lambda: testlib.ysi_cli_put_immutable( "foo.test", "fail", '{"Fail": "Yes"}', password='******' )),
            ("get_immutable", lambda: testlib.ysi_cli_get_immutable( "foo.test", "fail" )),
            ("put_mutable",   lambda: testlib.ysi_cli_put_mutable( "foo.test", "fail", '{"fail": "yes"}', password='******' )),
            ("get_mutable",   lambda: testlib.ysi_cli_get_mutable( "foo.test", "fail" )),
            ("delete_immutable", lambda: testlib.ysi_cli_delete_immutable( "foo.test", "00" * 32, password='******' )),
            ("delete_mutable", lambda: testlib.ysi_cli_delete_mutable( "foo.test", "fail", password='******' ))
        ]

        for data_func_name, data_func in dataplane_funcs:
            resp = data_func()
            if 'error' not in resp:
                if data_func_name != 'lookup':
                    print "%s succeeded when it should not have:\n%s" % (data_func_name, json.dumps(resp, indent=4, sort_keys=True))
                    return False
                elif 'error' not in resp['profile']:
                    print "%s succeeded when it should not have:\n%s" % (data_func_name, json.dumps(resp, indent=4, sort_keys=True))
                    return False
      
        # this should succeed
        zf_hist = testlib.ysi_cli_list_zonefile_history( "foo.test" )
        if len(zf_hist) != 2*(zi+1)+1:
            print "missing zonefile history: %s (expected %s items, got %s)" % (zf_hist, zi+3, len(zf_hist))
            return False

        update_hist = testlib.ysi_cli_list_update_history( "foo.test" )
        if len(update_hist) != 2*(zi+1)+1:
            print 'missing zonefile history: %s (expected %s items, got %s)' % (zf_hist, zi+3, len(zf_hist))
            return False

        name_hist = testlib.ysi_cli_get_name_blockchain_history("foo.test")
        
        if zf_hist[-1] != nonstandard_zonefile:
            print "invalid zonefile: expected\n%s\ngot\n%s\n" % (nonstandard_zonefile, zf_hist[-1])
            return False

        # this should work, but with "non-standard zonefiles"
        hist = testlib.ysi_cli_list_immutable_data_history("foo.test", "fail")
        if len(hist) != 2*(zi+1)+1:
            print "missing immutable data history: %s (expected %s items, got %s)" % (hist, zi+3, len(hist))
            return False

        if hist[-1] != 'non-standard zonefile':
            print "not a non-standard zonefile: %s" % hist[-1]
            return False 

        # verify that we can migrate it back
        resp = testlib.ysi_cli_migrate( "foo.test", "0123456789abcdef", force=True, interactive=False )
        if 'error' in resp:
            print "failed to migrate"
            print json.dumps(resp, indent=4, sort_keys=True)
            return False

        zonefile_hash = resp['zonefile_hash']

        # wait for it to take effect
        for i in xrange(0, 12):
            testlib.next_block( **kw )

        time.sleep(10)

    # see that put_immutable works
    put_result = testlib.ysi_cli_put_immutable("foo.test", "hello_world_immutable", json.dumps({'hello': 'world'}), password='******')
    if 'error' in put_result:
        print json.dumps(put_result, indent=4, sort_keys=True )
        return False

    testlib.expect_atlas_zonefile(put_result['zonefile_hash'])

    # tell serialization-checker that value_hash can be ignored here
    print "BLOCKSTACK_SERIALIZATION_CHECK_IGNORE value_hash"
    sys.stdout.flush()
    
    # wait for confirmation
    for i in xrange(0, 12):
        testlib.next_block( **kw )

    print "waiting for confirmation"
    time.sleep(10)

    # see that put_mutable works
    put_result = testlib.ysi_cli_put_mutable("foo.test", "hello_world_mutable", json.dumps({'hello': 'world'}), password='******')
    if 'error' in put_result:
        print json.dumps(put_result, indent=4, sort_keys=True )
    
    testlib.next_block( **kw )
def check( state_engine ):

    global wallet_keys, wallet_keys_2, datasets, zonefile_hash, zonefile_hash_2


    # 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 

    # not preordered
    names = ['foo.test', 'bar.test']
    wallet_keys_list = [wallet_keys, wallet_keys_2]
    zonefile_hashes = [zonefile_hash[:], zonefile_hash_2[:]]

    for i in xrange(0, len(names)):
        name = names[i]
        wallet_payer = 3 * (i+1) - 1
        wallet_owner = 3 * (i+1)
        wallet_data_pubkey = 3 * (i+1)  # same as owner key
        wallet_keys = wallet_keys_list[i]
        zonefile_hash = zonefile_hashes[i]

        preorder = state_engine.get_name_preorder( name, virtualchain.make_payment_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'] != virtualchain.make_payment_script(wallets[wallet_owner].addr):
            print "name has wrong owner"
            return False 

        # zonefile is NOT legacy 
        user_zonefile = ysi_client.zonefile.load_name_zonefile( name, zonefile_hash )
        if 'error' in user_zonefile:
            print json.dumps(user_zonefile, indent=4, sort_keys=True)
            return False 

        if ysi_profiles.is_profile_in_legacy_format( user_zonefile ):
            print "legacy still"
            print json.dumps(user_zonefile, indent=4, sort_keys=True)
            return False

        # zonefile has no key 
        zonefile_key = ysi_client.user.user_zonefile_data_pubkey(user_zonefile)
        if zonefile_key is not None:
            print 'still have zonefile key'
            print json.dumps(user_zonefile, indent=4, sort_keys=True)
            return False

        # still have all the right info 
        user_profile = ysi_client.profile.get_profile( name, user_zonefile=user_zonefile )
        if user_profile is None or 'error' in user_profile:
            if user_profile is not None:
                print json.dumps(user_profile, indent=4, sort_keys=True)
            else:
                print "\n\nprofile is None\n\n"

            return False

    # can get mutable data with owner address
    res = testlib.ysi_cli_get_mutable( "bar.test", "hello_world_mutable" )

    if 'error' in res:
        print json.dumps(res, indent=4, sort_keys=True)
        return False
    
    if json.loads(res['data']) != {'hello': 'world'}:
        print 'invalid data: {}'.format(res['data'])
        return False

    # can get mutable data with explicit public key
    res = testlib.ysi_cli_get_mutable( "bar.test", "hello_world_mutable", public_key=keylib.ECPrivateKey(wallet_keys_2['data_privkey']).public_key().to_hex() )
    print 'mutable: {}'.format(res)

    if 'error' in res:
        print json.dumps(res, indent=4, sort_keys=True)
        return False
    
    if json.loads(res['data']) != {'hello': 'world'}:
        print 'invalid data: {}'.format(res['data'])
        return False

    # can get immutable data by name
    res = testlib.ysi_cli_get_immutable( 'foo.test', 'hello_world_immutable' )
    print 'immutable by name: {}'.format(res)

    if 'error' in res:
        return res

    if json.loads(res['data']) != {'hello': 'world_immutable'}:
        print 'invalid immutable data: {}'.format(res['data'])
        return False

    # can get immutable data by hash
    hsh = res['hash']
    res = testlib.ysi_cli_get_immutable( 'foo.test', hsh )
    print 'immutable: {}'.format(res)

    if 'error' in res:
        return res

    if json.loads(res['data']) != {'hello': 'world_immutable'}:
        print 'invalid immutable data by hash: {}'.format(res['data'])
        return False

    return True
コード例 #3
0
def check(state_engine):

    global error

    # 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

    # not preordered
    preorder = state_engine.get_name_preorder(
        "foo.test", virtualchain.make_payment_script(wallets[2].addr),
        wallets[3].addr)
    if preorder is not None:
        print "still have preorder"
        return False

    # registered
    name_rec = state_engine.get_name("foo.test")
    if name_rec is None:
        print "name does not exist"
        return False

    # owned
    if name_rec['address'] != wallets[3].addr or name_rec[
            'sender'] != virtualchain.make_payment_script(wallets[3].addr):
        print "name has wrong owner"
        return False

    # have right data
    test_proxy = testlib.TestAPIProxy()
    ysi_client.set_default_proxy(test_proxy)

    # should always work
    for i in xrange(1, len(datasets)):
        immutable_data = testlib.ysi_cli_get_immutable(
            'foo.test', 'hello_world_{}_immutable'.format(i + 1))
        if immutable_data is None:
            print "No data received for immutable dataset %s" % i
            return False

        if 'error' in immutable_data:
            print "No data received for immutable dataset %s: %s" % (
                i, immutable_data['error'])
            return False

        if not immutable_data.has_key('data'):
            print "Missing data\n%s" % json.dumps(
                immutable_data, indent=4, sort_keys=True)
            return False

        data_json = json.loads(immutable_data['data'])
        if data_json != datasets[i]:
            print "did not get dataset %s\ngot %s\nexpected %s" % (
                i, data_json, datasets[i])
            return False

        mutable_data = testlib.ysi_cli_get_mutable(
            'foo.test', 'hello_world_{}_mutable'.format(i + 1))
        if mutable_data is None:
            print "No data received for mutable dataset %s" % (i + 1)
            return False

        if 'error' in mutable_data:
            print "No data received for mutable dataset %s: %s" % (
                i + 1, mutable_data['error'])
            return False

        data_json = json.loads(mutable_data['data'])
        if data_json != datasets[i]:
            print "did not get dataset %s\ngot %s\nexpected %s" % (
                i + 1, data_json, datasets[i])
            return False

    # should fail
    immutable_data = testlib.ysi_cli_get_immutable('foo.test',
                                                   'hello_world_1_immutable')
    if immutable_data is not None and 'error' not in immutable_data:
        print "Got deleted immutable dataset"
        return False

    mutable_data = testlib.ysi_cli_get_mutable("foo.test",
                                               "hello_world_1_mutable")
    if mutable_data is not None and 'error' not in mutable_data:
        print "Got deleted mutable dataset"
        return False

    return True
コード例 #4
0
def check( state_engine ):

    global wallet_keys, dataset_1, data_hash_1, dataset_2, data_hash_2

    # 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 

    # not preordered
    preorder = state_engine.get_name_preorder( "foo.test", virtualchain.make_payment_script(wallets[2].addr), wallets[3].addr )
    if preorder is not None:
        print "still have preorder"
        return False
    
    # registered 
    name_rec = state_engine.get_name( "foo.test" )
    if name_rec is None:
        print "name does not exist"
        return False 

    # owned 
    if name_rec['address'] != wallets[3].addr or name_rec['sender'] != virtualchain.make_payment_script(wallets[3].addr):
        print "name has wrong owner"
        return False 

    # have right hash 
    if name_rec['value_hash'] != last_hash:
        print "Invalid zonefile hash (%s != %s)" % (name_rec['value_hash'], last_hash)
        return False 

    # have right data 
    test_proxy = testlib.TestAPIProxy()
    ysi_client.set_default_proxy( test_proxy )

    for i in xrange(0, len(datasets)):
        immutable_data = testlib.ysi_cli_get_immutable("foo.test", immutable_data_hashes[i] )
        if immutable_data is None:
            print "No data received for dataset %s" % i
            return False 

        if 'error' in immutable_data:
            print "No data received for dataset %s" % i
            return False

        if not immutable_data.has_key('data'):
            print "Missing data\n%s" % json.dumps(immutable_data, indent=4, sort_keys=True)
            return False 

        data_json = json.loads(immutable_data['data'])
        if data_json != datasets[i]:
            print "did not get dataset %s\ngot %s\nexpected %s" % (i, data_json, datasets[i])
            return False 

        immutable_data_by_name = testlib.ysi_cli_get_immutable( "foo.test", "hello_world_%s" % (i+1) )
        if immutable_data_by_name is None:
            print "No data received by name for dataset %s" % i
            return False 

        if 'error' in immutable_data_by_name:
            print "No data received for dataset hello_world_%s" % (i+1)
            return False 

        if not immutable_data_by_name.has_key('data'):
            print "Misisng data\n%s" % json.dumps(immutable_data_by_name, indent=4, sort_keys=True)
            return False 

        data_json = json.loads(immutable_data_by_name['data'])
        if data_json != datasets[i]:
            print "did not get dataset hello_world_%s\ngot %s\nexpected %s" % (i+1, data_json, datasets[i])
            return False 

    return True
コード例 #5
0
def check( state_engine ):

    global preorder_info, register_info, update_info, balance_before, balance_after, names_owned_before, names_owned_after, whois, blockchain_record, deposit_info, price_info
    global blockchain_history, zonefile_info, all_names_info, namespace_names_info, wallet_info, lookup_info, update_history, zonefile_history, names_info

    # 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 
    
    # registered 
    name_rec = state_engine.get_name( "foo.test" )
    if name_rec is None:
        print "name does not exist"
        return False 

    # owned by the right address 
    owner_address = wallets[3].addr
    if name_rec['address'] != owner_address or name_rec['sender'] != virtualchain.make_payment_script(owner_address):
        print "sender is wrong"
        return False 

    # all queues are drained 
    queue_info = testlib.ysi_client_queue_state()
    if len(queue_info) > 0:
        print "Still in queue:\n%s" % json.dumps(queue_info, indent=4, sort_keys=True)
        return False

    # have an update hash 
    if 'value_hash' not in name_rec or name_rec.get('value_hash', None) is None:
        print "No value hash"
        return False 

    # have a zonefile 
    zonefile = testlib.ysi_get_zonefile( name_rec['value_hash'] )
    if zonefile is None or 'error' in zonefile:
        if zonefile is not None:
            print "zonefile lookup error: %s" % zonefile['error']
        else:
            print "no zonefile returned"
        return False

    # hashes to this zonefile 
    if ysi_client.hash_zonefile( zonefile ) != name_rec['value_hash']:
        print "wrong zonefile: %s != %s" % (ysi_client.hash_zonefile(zonefile), name_rec['value_hash'])
        return False

    # verify that the profile is there 
    profile = testlib.ysi_get_profile( "foo.test" )
    if profile is None or 'error' in profile:
        if profile is None:
            print "no profile returned"
        else:
            print "profile lookup error: %s" % profile['error']

        return False

    # check queue operations 
    for queue_type, queue_state in [("preorder", preorder_info), ("register", register_info), ("update", update_info)]:
        if not queue_state.has_key('queues'):
            print "missing queues:\n%s" % json.dumps(queue_state, indent=4, sort_keys=True)
            return False

        for k in ['name', 'confirmations', 'tx_hash']:
            for q in queue_state['queues'][queue_type]:
                if not q.has_key(k):
                    print "missing key %s\n%s" % (k, json.dumps(queue_state, indent=4, sort_keys=True))
                    return False
            
                if q['name'] != 'foo.test':
                    print "wrong name: %s" % queue_state['name']
                    return False

    # check price
    for k in ['preorder_tx_fee', 'register_tx_fee', 'total_estimated_cost', 'name_price']:
        if not price_info.has_key(k):
            print "bad price info (missing %s):\n%s" % (k, json.dumps(price_info, indent=4, sort_keys=True))
            return False

    
    # deposit info 
    if not deposit_info.has_key('address') or deposit_info['address'] != wallets[2].addr:
        print "bad deposit info:\n%s" % json.dumps(deposit_info, indent=4, sort_keys=True)
        return False

    # whois info
    for k in ['block_preordered_at', 'block_renewed_at', 'last_transaction_id', 'owner_address', 'owner_script', 'expire_block', 'has_zonefile', 'zonefile_hash']:
        if not whois.has_key(k):
            print "bad whois: missing %s\n%s" % (k, json.dumps(whois, indent=4, sort_keys=True))
            return False
    
    # balance 
    for balance_info in [balance_before, balance_after]:
        for k in ['total_balance', 'addresses']:
            if not balance_info.has_key(k):
                print "missing '%s'\n%s" % (k, json.dumps(balance_info, indent=4, sort_keys=True))
                return False

    # name listing
    if len(names_owned_before) != 0:
        print "owned before: %s" % names_owned_before
        return False

    if len(names_owned_after) != 1 or names_owned_after[0] != 'foo.test':
        print "owned after: %s" % names_owned_after
        return False

    # blockchain record 
    for k in ['name', 'op', 'op_fee', 'opcode', 'vtxindex', 'txid', 'value_hash', 'sender', 'address', 'history']:
        if not blockchain_record.has_key(k):
            print "missing %s\n%s" % (k, json.dumps(blockchain_record, indent=4, sort_keys=True))
            return False

    # blockchain history (should have a preorder, register, and 2 updates)
    if len(blockchain_history) != 4:
        print "invalid history\n%s\n" % json.dumps(blockchain_history, indent=4, sort_keys=True)
        return False

    block_heights = blockchain_history.keys()
    block_heights.sort()
    expected_opcodes = ['NAME_PREORDER', 'NAME_REGISTRATION', 'NAME_UPDATE', 'NAME_UPDATE']
    for bh, opcode in zip(block_heights, expected_opcodes):
        if len(blockchain_history[bh]) != 1:
            print "invalid history: multiple ops at %s\n%s" % (bh, json.dumps(blockchain_history, indent=4, sort_keys=True))
            return False

        if blockchain_history[bh][0]['opcode'] != opcode:
            print "invalid history: expected %s at %s\n%s" % (opcode, bh, json.dumps(blockchain_history, indent=4, sort_keys=True))
            return False

    # zonefile info
    if zonefile_info is None or type(zonefile_info) != str:
        print "invalid zonefile\n%s\n" % zonefile_info
        return False

    # name query
    if type(all_names_info) == dict and 'error' in all_names_info:
        print "error in all_names: %s" % all_names_info
        return False

    all_names = all_names_info
    if len(all_names) != 1 or all_names != ['foo.test']:
        print "all names: %s" % all_names
        return False

    # namespace query
    if type(namespace_names_info) == dict and 'error' in namespace_names_info:
        print "error in namesace_names: %s" % namespace_names_info
        return False

    namespace_names = namespace_names_info
    if len(namespace_names) != 1 or namespace_names != ['foo.test']:
        print "all namespace names: %s" % namespace_names
        return False

    # wallet info
    for k in ['payment_privkey', 'owner_privkey', 'data_privkey', 'payment_address', 'owner_address', 'data_pubkey']:
        if not wallet_info.has_key(k):
            print "missing %s\n%s" % (k, json.dumps(wallet_info, indent=4, sort_keys=True))
            return False

    # profile info
    for k in ['profile', 'zonefile']:
        if not lookup_info.has_key(k):
            print "missing '%s'\n%s" % (k, json.dumps(lookup_info, indent=4, sort_keys=True))
            return False

    if lookup_info['zonefile'] != zonefile_info:
        print "unequal zonefiles:\n%s\n%s" % (json.dumps(lookup_info['zonefile'], indent=4, sort_keys=True), json.dumps(zonefile_info, indent=4, sort_keys=True))
        return False

    # update history (2 items)
    if len(update_history) != 2 or update_history[1] != blockchain_record['value_hash']:
        print "invalid update history\n%s" % json.dumps(update_history, indent=4, sort_keys=True)
        return False

    # zonefile history (expect 2 items)
    if len(zonefile_history) != 2 or zonefile_history[1] != zonefile_info:
        print "invalid zonefile history\n%s" % json.dumps(zonefile_history, indent=4, sort_keys=True)
        print "zonefile current:\n%s" % json.dumps(zonefile_info, indent=4, sort_keys=True)
        return False

    # names info
    if type(names_info) != dict:
        print "invalid names info: %s" % names_info
        return False
        
    for k in ['names_owned', 'addresses']:
        if not names_info.has_key(k):
            print "invalid names info (missing %s): %s" % (k, names_info)
            return False

    if len(names_info['addresses']) != 1:
        print "invalid names info (addresses): %s" % names_info
        return False

    if names_info['addresses'][0]['names_owned'] != ['foo.test']:
        print "invalid names info (names_owned): %s" % names_info
        return False

    if names_info['addresses'][0]['address'] != wallets[3].addr:
        print "invalid names info (addresses.address): %s" % names_info
        return False

    # immutable data 
    immutable_data = testlib.ysi_cli_get_immutable( "foo.test", "hello_world" )
    if 'error' in immutable_data:
        print "Failed to get immutable data 'hello_world'"
        print json.dumps(immutable_data, indent=4, sort_keys=True)
        return False

    if 'data' not in immutable_data:
        print "invalid immutable_data: %s" % immutable_data
        return False 

    if json.loads(immutable_data['data']) != {'hello': 'world'}:
        print "failed to get immutable data"
        print 'exected %s, got %s' % ({'hello': 'world'}, immutable_data['data'])
        return False

    return True