def setup_env(session, blockchain_id):
    
    # export to environment 
    blockstack_client.set_secret("BLOCKSTACK_API_SESSION", session)
    res = testlib.blockstack_test_setenv("TEST_BLOCKSTACK_TEST_INDEXED_STORAGE", "1")
    if 'error' in res:
        print json.dumps(res, indent=4, sort_keys=True)
        return False

    res = testlib.blockstack_test_setenv("TEST_BLOCKSTACK_TEST_DISK_ROOT", "/tmp/blockstack-integration-test-storage-{}".format(blockchain_id))
    if 'error' in res:
        print json.dumps(res, indent=4, sort_keys=True)
        return False

    return True
def target_datastore(blockchain_id):
    # point to a particular user's datastore
    if blockchain_id is None:
        blockchain_id = ''
    else:
        blockchain_id = '-{}'.format(blockchain_id)

    res = testlib.blockstack_test_setenv("TEST_BLOCKSTACK_TEST_DISK_ROOT", "/tmp/blockstack-integration-test-storage{}".format(blockchain_id))
    if 'error' in res:
        print json.dumps(res, indent=4, sort_keys=True)
        return False
    
    os.environ['TEST_BLOCKSTACK_TEST_DISK_ROOT'] = '/tmp/blockstack-integration-test-storage{}'.format(blockchain_id)
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[3].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, but no data key in the zone file
    res = testlib.migrate_profile("foo.test",
                                  zonefile_has_data_key=False,
                                  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', 'user_read'
        ])
    if 'error' in res:
        print json.dumps(res, indent=4, sort_keys=True)
        error = True
        return

    ses = res['token']

    # register the name bar.test. autogenerate the rest
    old_user_zonefile = blockstack_client.zonefile.make_empty_zonefile(
        'bar.test', None)
    old_user_zonefile_txt = blockstack_zones.make_zone_file(old_user_zonefile)

    res = testlib.blockstack_REST_call('POST',
                                       '/v1/names',
                                       ses,
                                       data={
                                           'name': 'bar.test',
                                           'zonefile': old_user_zonefile_txt,
                                           'make_profile': True
                                       })
    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, 4):
        testlib.next_block(**kw)

    # wait for register to go through
    print 'Wait for register to be submitted'
    time.sleep(10)

    # wait for the register/update 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, 3):
        testlib.next_block(**kw)

    # should have nine confirmations now
    res = testlib.get_queue(ses, 'register')
    if 'error' in res:
        print res
        return False

    if len(res) != 1:
        print res
        return False

    reg = res[0]
    confs = blockstack_client.get_tx_confirmations(reg['tx_hash'])
    if confs != 9:
        print 'wrong number of confs for {} (expected 9): {}'.format(
            reg['tx_hash'], confs)
        return False

    # stop the API server
    testlib.stop_api()

    # advance blockchain
    testlib.next_block(**kw)
    testlib.next_block(**kw)

    confs = blockstack_client.get_tx_confirmations(reg['tx_hash'])
    if confs != 11:
        print 'wrong number of confs for {} (expected 11): {}'.format(
            reg['tx_hash'], confs)
        return False

    # make sure the registrar does not process reg/up zonefile replication
    # (i.e. we want to make sure that the zonefile gets processed even if the blockchain goes too fast)
    os.environ[
        'BLOCKSTACK_TEST_REGISTRAR_FAULT_INJECTION_SKIP_REGUP_REPLICATION'] = '1'
    testlib.start_api("0123456789abcdef")

    print 'Wait to verify that we do not remove the zone file just because the tx is confirmed'
    time.sleep(10)

    # verify that this is still in the queue
    res = testlib.get_queue(ses, 'register')
    if 'error' in res:
        print res
        return False

    if len(res) != 1:
        print res
        return False

    # clear the fault
    print 'Clearing regup replication fault'
    testlib.blockstack_test_setenv(
        "BLOCKSTACK_TEST_REGISTRAR_FAULT_INJECTION_SKIP_REGUP_REPLICATION",
        "0")

    # wait for register to go through
    print 'Wait for zonefile to replicate'
    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

    old_expire_block = res['response']['expire_block']

    # get the zonefile
    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

    # zonefile must not have a public key listed
    zonefile_txt = res['response']['zonefile']
    print zonefile_txt

    parsed_zonefile = blockstack_zones.parse_zone_file(zonefile_txt)
    if parsed_zonefile.has_key('txt'):
        print 'have txt records'
        print parsed_zonefile
        return False

    # renew it, but put the *current* owner key as the zonefile's *new* public key
    new_user_zonefile = blockstack_client.zonefile.make_empty_zonefile(
        'bar.test', wallets[3].pubkey_hex)
    new_user_zonefile_txt = blockstack_zones.make_zone_file(new_user_zonefile)

    res = testlib.blockstack_REST_call("POST",
                                       "/v1/names",
                                       ses,
                                       data={
                                           'name': 'bar.test',
                                           'zonefile': new_user_zonefile_txt
                                       })
    if 'error' in res or res['http_status'] != 202:
        res['test'] = 'Failed to renew name'
        print json.dumps(res)
        return False

    # verify in renew queue
    for i in xrange(0, 6):
        testlib.next_block(**kw)

    res = testlib.verify_in_queue(ses, 'bar.test', 'renew', None)
    if not res:
        return False

    for i in xrange(0, 3):
        testlib.next_block(**kw)

    # should have nine confirmations now
    res = testlib.get_queue(ses, 'renew')
    if 'error' in res:
        print res
        return False

    if len(res) != 1:
        print res
        return False

    reg = res[0]
    confs = blockstack_client.get_tx_confirmations(reg['tx_hash'])
    if confs != 9:
        print 'wrong number of confs for {} (expected 9): {}'.format(
            reg['tx_hash'], confs)
        return False

    # stop the API server
    testlib.stop_api()

    # advance blockchain
    testlib.next_block(**kw)
    testlib.next_block(**kw)

    confs = blockstack_client.get_tx_confirmations(reg['tx_hash'])
    if confs != 11:
        print 'wrong number of confs for {} (expected 11): {}'.format(
            reg['tx_hash'], confs)
        return False

    # make the registrar skip the first few steps, so the only thing it does is clear out confirmed updates
    # (i.e. we want to make sure that the renewal's zonefile gets processed even if the blockchain goes too fast)
    os.environ[
        'BLOCKSTACK_TEST_REGISTRAR_FAULT_INJECTION_SKIP_RENEWAL_REPLICATION'] = '1'
    testlib.start_api("0123456789abcdef")

    # wait a while
    print 'Wait to verify that clearing out confirmed transactions does NOT remove zonefiles'
    time.sleep(10)

    # verify that this is still in the queue
    res = testlib.get_queue(ses, 'renew')
    if 'error' in res:
        print res
        return False

    if len(res) != 1:
        print res
        return False

    # clear the fault
    print 'Clearing renewal replication fault'
    testlib.blockstack_test_setenv(
        "BLOCKSTACK_TEST_REGISTRAR_FAULT_INJECTION_SKIP_RENEWAL_REPLICATION",
        "0")

    # now the renewal zonefile should replicate
    print 'Wait for renewal zonefile to replicate'
    time.sleep(10)

    # new expire block
    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

    new_expire_block = res['response']['expire_block']

    # 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",
                                       ses)
    if 'error' in res or res['http_status'] != 200:
        res['test'] = 'Failed to get name zonefile'
        print json.dumps(res)
        return False

    # zonefile must have old owner key
    zonefile_txt = res['response']['zonefile']
    parsed_zonefile = blockstack_zones.parse_zone_file(zonefile_txt)
    if not parsed_zonefile.has_key('txt'):
        print 'missing txt'
        print parsed_zonefile
        return False

    found = False
    for txtrec in parsed_zonefile['txt']:
        if txtrec['name'] == 'pubkey' and txtrec[
                'txt'] == 'pubkey:data:{}'.format(wallets[3].pubkey_hex):
            found = True

    if not found:
        print 'missing public key {}'.format(wallets[3].pubkey_hex)
        return False

    # profile lookup must work
    res = testlib.blockstack_REST_call("GET", "/v1/users/bar.test", ses)
    if 'error' in res or res['http_status'] != 200:
        res['text'] = 'failed to get profile for bar.test'
        print json.dumps(res)
        return False

    print ''
    print json.dumps(res['response'], indent=4, sort_keys=True)
    print ''

    # verify pushed back
    if old_expire_block + 10 > new_expire_block:
        # didn't go through
        print >> sys.stderr, "Renewal didn't work: %s --> %s" % (
            old_expire_block, new_expire_block)
        return False
def scenario( wallets, **kw ):

    global wallet_keys, wallet_keys_2, wallet_keychain, error, index_file_data, resource_data, sessions

    test_proxy = testlib.TestAPIProxy()
    blockstack_client.set_default_proxy( test_proxy )
    
    wallet_keys_2 = blockstack_client.make_wallet_keys( owner_privkey=wallets[4].privkey, data_privkey=wallets[5].privkey, payment_privkey=wallets[3].privkey )
    wallet_keys = blockstack_client.make_wallet_keys( owner_privkey=wallets[3].privkey, data_privkey=wallets[4].privkey, payment_privkey=wallets[5].privkey )

    wallet_keychain = {
        'foo.test': wallet_keys,
        'bar.test': wallet_keys_2,
    }

    # install wallet_keys
    testlib.blockstack_client_set_wallet( "0123456789abcdef", wallet_keys['payment_privkey'], wallet_keys['owner_privkey'], wallet_keys['data_privkey'] )

    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.blockstack_name_preorder( "bar.test", wallets[3].privkey, wallets[4].addr )
    testlib.next_block( **kw )
    
    testlib.blockstack_name_register( "foo.test", wallets[2].privkey, wallets[3].addr )
    testlib.blockstack_name_register( "bar.test", wallets[3].privkey, wallets[4].addr )
    testlib.next_block( **kw )
   
    setup_storage_dirs(['foo.test', 'bar.test'])

    # migrate profiles 
    # BUT! make sure we store the profile for foo.test into both foo.test's and bar.test's storage directories!
    os.environ['TEST_BLOCKSTACK_TEST_DISK_ROOT'] = '/tmp/blockstack-integration-test-storage-foo.test'

    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

    shutil.copy("/tmp/blockstack-integration-test-storage-foo.test/mutable/foo.test", "/tmp/blockstack-integration-test-storage-bar.test/mutable/foo.test")
    
    # tell serialization-checker that value_hash can be ignored here
    print "BLOCKSTACK_SERIALIZATION_CHECK_IGNORE value_hash"
    sys.stdout.flush()
    testlib.next_block(**kw)

    # store zonefile
    res = blockstack_client.proxy.put_zonefiles("localhost:16264", [base64.b64encode(res['zonefile_txt'])])
    if 'error' in res:
        print 'failed to store zonefile for foo.test: {}'.format(res)
        return False

    # BUT! make sure we store the profile for bar.test into both foo.test's and bar.test's storage directories!
    os.environ['TEST_BLOCKSTACK_TEST_DISK_ROOT'] = '/tmp/blockstack-integration-test-storage-bar.test'

    res = testlib.migrate_profile( "bar.test", proxy=test_proxy, wallet_keys=wallet_keys_2 )
    if 'error' in res:
        res['test'] = 'Failed to initialize bar.test profile'
        print json.dumps(res, indent=4, sort_keys=True)
        return False

    shutil.copy("/tmp/blockstack-integration-test-storage-bar.test/mutable/bar.test", "/tmp/blockstack-integration-test-storage-foo.test/mutable/bar.test")

    # tell serialization-checker that value_hash can be ignored here
    print "BLOCKSTACK_SERIALIZATION_CHECK_IGNORE value_hash"
    sys.stdout.flush()
    testlib.next_block(**kw)

    # store zonefile
    res = blockstack_client.proxy.put_zonefiles("localhost:16264", [base64.b64encode(res['zonefile_txt'])])
    if 'error' in res:
        print 'failed to store zonefile for bar.test: {}'.format(res)
        return False

    res = testlib.start_api("0123456789abcdef")
    if 'error' in res:
        print 'failed to start API for foo.test: {}'.format(res)
        return False

    target_datastore('foo.test')

    # instantiate foo.test's test driver
    res = testlib.blockstack_REST_call('POST', '/v1/node/drivers/storage/test?index=1&force=1', None, api_pass='******')
    if 'error' in res or res['http_status'] != 200:
        print json.dumps(res, indent=4, sort_keys=True)
        return False

    res = testlib.blockstack_cli_put_account("foo.test", "test", 'storage', "test:///index/index.manifest?diskroot=/tmp/blockstack-integration-test-storage-foo.test",
                                             None, wallet_keys=wallet_keys )
    if 'error' in res:
        res['test'] = 'Failed to create foo.test account'
        print json.dumps(res, indent=4, sort_keys=True)
        return False

    os.makedirs('/tmp/blockstack-integration-test-storage/mutable')
    os.makedirs('/tmp/blockstack-integration-test-storage/immutable')

    shutil.copy("/tmp/blockstack-integration-test-storage-foo.test/mutable/foo.test", "/tmp/blockstack-integration-test-storage-bar.test/mutable/foo.test")
    shutil.copy("/tmp/blockstack-integration-test-storage-foo.test/mutable/foo.test", "/tmp/blockstack-integration-test-storage/mutable/foo.test")

    # link test account for bar.test
    # BUT! make sure we store the profile for bar.test into foo.test's and bar.test's storage directories!
    target_datastore('bar.test')

    # instantiate bar.test's test driver 
    res = testlib.blockstack_REST_call('POST', '/v1/node/drivers/storage/test?index=1&force=1', None, api_pass='******')
    if 'error' in res or res['http_status'] != 200:
        print json.dumps(res, indent=4, sort_keys=True)
        return False

    res = testlib.blockstack_cli_put_account("bar.test", "test", 'storage', "test:///index/index.manifest?diskroot=/tmp/blockstack-integration-test-storage-bar.test",
                                             None, wallet_keys=wallet_keys_2 )
    if 'error' in res:
        res['test'] = 'Failed to create bar.test account'
        print json.dumps(res, indent=4, sort_keys=True)
        return False

    shutil.copy("/tmp/blockstack-integration-test-storage-bar.test/mutable/bar.test", "/tmp/blockstack-integration-test-storage-foo.test/mutable/bar.test")
    shutil.copy("/tmp/blockstack-integration-test-storage-bar.test/mutable/bar.test", "/tmp/blockstack-integration-test-storage/mutable/bar.test")

    # restore...we'll set up foo.test next
    res = testlib.blockstack_test_setenv("TEST_BLOCKSTACK_TEST_DISK_ROOT", "/tmp/blockstack-integration-test-storage-foo.test")
    if 'error' in res:
        print json.dumps(res, indent=4, sort_keys=True)
        return False

    # get datastore keys...
    foo_datastore_pk = keylib.ECPrivateKey(wallets[-1].privkey).to_hex()
    datastore_id_res = testlib.blockstack_cli_datastore_get_id( foo_datastore_pk )
    foo_datastore_id = datastore_id_res['datastore_id']

    bar_datastore_pk = keylib.ECPrivateKey(wallets[-2].privkey).to_hex()
    datastore_id_res = testlib.blockstack_cli_datastore_get_id( bar_datastore_pk )
    bar_datastore_id = datastore_id_res['datastore_id']

    # activate foo.test 
    res = activate_account("foo.test", foo_datastore_pk)
    if not res:
        print 'failed to start API for bar.test: {}'.format(res)
        return False

    # set up foo.test's datastore 
    res = setup_datastore(wallets[-1].privkey, "foo.test", 1)
    if not res:
        print 'failed to setup foo.test datastore'
        return False

    # activate bar.test 
    res = activate_account("bar.test", bar_datastore_pk)
    if not res:
        print 'failed to start API for bar.test: {}'.format(res)
        return False

    # make *absolutely certain* that the test driver does not load data from
    # foo.test's or bar.test's storage directories.  We want to verify that we can look up
    # the index manifest URLs from the profile
    target_datastore(None)

    print "\n\nbar.test tries to read foo.test's datastore {}\n\n".format(foo_datastore_id)
    res = read_datastore(foo_datastore_id, "foo.test", 1)
    if not res:
        print 'failed to read foo.test datastore {}'.format(foo_datastore_id)
        return False

    # set up bar.test's files 
    res = setup_datastore(wallets[-2].privkey, 'bar.test', 2)
    if not res:
        print 'failed to setup bar.test datastore'
        return False

    # activate foo.test
    res = activate_account("foo.test", foo_datastore_pk)
    if not res:
        print 'failed to start API for foo.test: {}'.format(res)
        return False

    # make *absolutely certain* that the test driver does not load data from
    # foo.test's or bar.test's storage directories.  We want to verify that we can look up
    # the index manifest URLs from the profile
    target_datastore(None)

    # try to read all of bar.test's files
    print "\n\nfoo.test tries to read bar.test's datastore {}\n\n".format(bar_datastore_id)
    res = read_datastore(bar_datastore_id, 'bar.test', 2)
    if not res:
        print 'failed to read bar.test datastore {}'.format(bar_datastore_id)
        return False

    # re-target foo.test's datastore
    target_datastore('foo.test')

    # have foo.test write new files 
    print '\n\nupdate foo.test datastore\n\n'
    res = write_datastore('foo.test', foo_datastore_pk, 3)
    if not res:
        print 'failed to update foo.test datastore {}'.format(foo_datastore_id)
        return False

    # activate bar.test
    res = activate_account("bar.test", bar_datastore_pk)
    if not res:
        print 'failed to start API for bar.test: {}'.format(res)
        return False

    # make *absolutely certain* that the test driver does not load data from
    # foo.test's or bar.test's storage directories.  We want to verify that we can look up
    # the index manifest URLs from the profile
    target_datastore(None)

    # get foo.test's new files 
    res = read_datastore(foo_datastore_id, 'foo.test', 3)
    if not res:
        print 'failed to read new files from foo.test'
        return False

    # re-target bar.test's datastore
    target_datastore('bar.test')

    # have bar write some new files 
    print '\n\nupdate bar.test datastore\n\n'
    res = write_datastore('bar.test', bar_datastore_pk, 4)
    if not res:
        print 'failed ot update bar.test datastore {}'.format(bar_datastore_id)
        return False

    # activate foo.test
    res = activate_account("foo.test", foo_datastore_pk)
    if not res:
        print 'failed to start API for foo.test: {}'.format(res)
        return False

    # delete foo's files 
    print '\n\ndelete foo.test files\n\n'
    res = clear_datastore_files('foo.test', foo_datastore_pk)
    if not res:
        print 'failed to clear datastore {} for foo.test'.format(foo_datastore_id)
        return False

    # activate bar.test
    res = activate_account("bar.test", bar_datastore_pk)
    if not res:
        print 'failed to start API for bar.test: {}'.format(res)
        return False

    # make *absolutely certain* that the test driver does not load data from
    # foo.test's or bar.test's storage directories.  We want to verify that we can look up
    # the index manifest URLs from the profile
    target_datastore(None)

    # verify that foo's files are gone 
    res = check_datastore_files_absent(foo_datastore_id, 'foo.test')
    if not res:
        print 'failed to verify that foo.test datastore {} is devoid of files'.format(foo_datastore_id)
        return False

    # re-target bar.test's datastore
    target_datastore('bar.test')

    # clear bar.test's files
    print '\n\ndelete bar.test files\n\n'
    res = clear_datastore_files('bar.test', bar_datastore_pk)
    if not res:
        print 'failed to clear datastore {} for bar.test'.format(bar_datastore_id)
        return False

    # activate foo.test
    res = activate_account("foo.test", foo_datastore_pk)
    if not res:
        print 'failed to start API for foo.test: {}'.format(res)
        return False

    # make *absolutely certain* that the test driver does not load data from
    # foo.test's or bar.test's storage directories.  We want to verify that we can look up
    # the index manifest URLs from the profile
    target_datastore(None)

    # verify that bar's files are gone 
    res = check_datastore_files_absent(bar_datastore_id, 'bar.test')
    if not res:
        print 'failed to verify that bar.test datastore {} is devoid of files'.format(bar_datastore_id)
        return False

    # re-target foo.test's datastore
    target_datastore("foo.test")

    # clear foo's directories 
    res = clear_datastore_directories('foo.test', foo_datastore_pk)
    if not res:
        print 'failed to clear foo.test datastore {} of directories'.format(foo_datastore_id)
        return False

    # activate bar.test
    res = activate_account("bar.test", bar_datastore_pk)
    if not res:
        print 'failed to start API for bar.test: {}'.format(res)
        return False

    # make *absolutely certain* that the test driver does not load data from
    # foo.test's or bar.test's storage directories.  We want to verify that we can look up
    # the index manifest URLs from the profile
    target_datastore(None)

    # verify that foo's directories are gone 
    res = check_datastore_directories_absent(foo_datastore_id, "foo.test")
    if not res:
        print 'failed to verify that foo.test datastore {} is devoid of directories'.format(foo_datastore_id)
        return False

    # re-target bar.test's datastore
    target_datastore('bar.test')

    # clear bar's directories
    res = clear_datastore_directories('bar.test', bar_datastore_pk)
    if not res:
        print 'failed to clear bar.test datastore {} of directories'.format(bar_datastore_id)
        return False

    # activate foo.test
    res = activate_account('foo.test', foo_datastore_pk)
    if not res:
        print 'failed to start API for foo.test: {}'.format(res)
        return False

    # make *absolutely certain* that the test driver does not load data from
    # foo.test's or bar.test's storage directories.  We want to verify that we can look up
    # the index manifest URLs from the profile
    target_datastore(None)

    # verify that bar's directories are gone
    res = check_datastore_directories_absent(bar_datastore_id, "bar.test")
    if not res:
        print 'failed to verify that bar.test datastore {} is devoid of directories'.format(bar_datastore_id)
        return False

    # root should be empty in both cases
    print 'listdir {} (bar.test)'.format('/')
    res = testlib.blockstack_cli_datastore_listdir( 'bar.test', bar_datastore_id, '/', data_pubkeys=get_data_pubkeys('bar.test'))
    if 'error' in res:
        print 'failed to listdir / on bar.test: {}'.format(res['error'])
        return False

    if len(res['children'].keys()) > 0:
        print 'root still has children: {}'.format(res['children'].keys())
        return False

    # activate bar.test
    res = activate_account('bar.test', bar_datastore_pk)
    if not res:
        print 'failed to start API for foo.test: {}'.format(res)
        return False

    print 'listdir {} (foo.test)'.format('/')
    res = testlib.blockstack_cli_datastore_listdir( 'foo.test', foo_datastore_id, '/', data_pubkeys=get_data_pubkeys('foo.test'))
    if 'error' in res:
        print 'failed to listdir / on foo.test: {}'.format(res['error'])
        return False

    if len(res['children'].keys()) > 0:
        print 'root still has children: {}'.format(res['children'].keys())
        return False

    testlib.next_block( **kw )
Exemplo n.º 5
0
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)

    data_pk = wallets[-1].privkey
    data_pub = wallets[-1].pubkey_hex

    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', 'update', 'prices', 'zonefiles', 'blockchain',
            'node_read'
        ])
    if 'error' in res:
        print json.dumps(res, indent=4, sort_keys=True)
        error = True
        return

    ses = res['token']

    # register the name bar.test
    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, 4):
        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, 4):
        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, 3):
        testlib.next_block(**kw)

    # should have 9 confirmations
    res = testlib.get_queue(ses, 'update')
    if 'error' in res:
        print res
        return False

    if len(res) != 1:
        print res
        return False

    reg = res[0]
    confs = blockstack_client.get_tx_confirmations(reg['tx_hash'])
    if confs != 9:
        print 'wrong number of confs for {} (expected 9): {}'.format(
            reg['tx_hash'], confs)
        return False

    # stop the API server
    testlib.stop_api()

    # advance blockchain
    testlib.next_block(**kw)
    testlib.next_block(**kw)

    confs = blockstack_client.get_tx_confirmations(reg['tx_hash'])
    if confs != 11:
        print 'wrong number of confs for {} (expected 11): {}'.format(
            reg['tx_hash'], confs)
        return False

    # make the registrar skip the first few steps, so the only thing it does is clear out confirmed updates
    # (i.e. we want to make sure that the renewal's zonefile gets processed even if the blockchain goes too fast)
    os.environ[
        'BLOCKSTACK_TEST_REGISTRAR_FAULT_INJECTION_SKIP_UPDATE_REPLICATION'] = '1'
    testlib.start_api("0123456789abcdef")

    # wait a while
    print 'Wait to verify that clearing out confirmed transactions does NOT remove zonefiles'
    time.sleep(10)

    # verify that this is still in the queue
    res = testlib.get_queue(ses, 'update')
    if 'error' in res:
        print res
        return False

    if len(res) != 1:
        print res
        return False

    # clear the fault
    print 'Clearing update replication fault'
    testlib.blockstack_test_setenv(
        "BLOCKSTACK_TEST_REGISTRAR_FAULT_INJECTION_SKIP_UPDATE_REPLICATION",
        "0")

    # now the renewal zonefile should replicate
    print 'Wait for first zonefile to replicate'
    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

    first_zonefile_hash = res['response']['zonefile_hash']

    # update
    res = testlib.blockstack_REST_call("PUT",
                                       "/v1/names/bar.test/zonefile",
                                       ses,
                                       data={'zonefile_hash': '11' * 20})
    if 'error' in res or res['http_status'] != 202:
        res['test'] = 'failed to update zonefile hash'
        print json.dumps(res)
        return False

    # 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, 3):
        testlib.next_block(**kw)

    # should have 9 confirmations
    res = testlib.get_queue(ses, 'update')
    if 'error' in res:
        print res
        return False

    if len(res) != 1:
        print res
        return False

    reg = res[0]
    confs = blockstack_client.get_tx_confirmations(reg['tx_hash'])
    if confs != 9:
        print 'wrong number of confs for {} (expected 9): {}'.format(
            reg['tx_hash'], confs)
        return False

    # stop the API server
    testlib.stop_api()

    # advance blockchain
    testlib.next_block(**kw)
    testlib.next_block(**kw)

    confs = blockstack_client.get_tx_confirmations(reg['tx_hash'])
    if confs != 11:
        print 'wrong number of confs for {} (expected 11): {}'.format(
            reg['tx_hash'], confs)
        return False

    # make the registrar skip the first few steps, so the only thing it does is clear out confirmed updates
    # (i.e. we want to make sure that the renewal's zonefile gets processed even if the blockchain goes too fast)
    os.environ[
        'BLOCKSTACK_TEST_REGISTRAR_FAULT_INJECTION_SKIP_UPDATE_REPLICATION'] = '1'
    testlib.start_api("0123456789abcdef")

    # wait a while
    print 'Wait to verify that clearing out confirmed transactions does NOT remove zonefiles'
    time.sleep(10)

    # verify that it is NOT in the queue, since there is no zone file
    res = testlib.get_queue(ses, 'update')
    if 'error' not in res:
        print res
        return False

    # clear the fault
    print 'Clearing update replication fault'
    testlib.blockstack_test_setenv(
        "BLOCKSTACK_TEST_REGISTRAR_FAULT_INJECTION_SKIP_UPDATE_REPLICATION",
        "0")

    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

    # update set?
    if res['response']['zonefile_hash'] != '11' * 20:
        res['test'] = 'failed to set zonefile hash'
        print json.dumps(res)
        return False

    # update with nonstandard zonefile
    res = testlib.blockstack_REST_call("PUT",
                                       "/v1/names/bar.test/zonefile",
                                       ses,
                                       data={'zonefile': 'hello world'})
    if 'error' in res or res['http_status'] != 202:
        res['test'] = 'failed to update zonefile hash'
        print json.dumps(res)
        return False

    # wait for third 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, 3):
        testlib.next_block(**kw)

    # should have 9 confirmations
    res = testlib.get_queue(ses, 'update')
    if 'error' in res:
        print res
        return False

    if len(res) != 1:
        print res
        return False

    reg = res[0]
    confs = blockstack_client.get_tx_confirmations(reg['tx_hash'])
    if confs != 9:
        print 'wrong number of confs for {} (expected 9): {}'.format(
            reg['tx_hash'], confs)
        return False

    # stop the API server
    testlib.stop_api()

    # advance blockchain
    testlib.next_block(**kw)
    testlib.next_block(**kw)

    confs = blockstack_client.get_tx_confirmations(reg['tx_hash'])
    if confs != 11:
        print 'wrong number of confs for {} (expected 11): {}'.format(
            reg['tx_hash'], confs)
        return False

    # make the registrar skip the first few steps, so the only thing it does is clear out confirmed updates
    # (i.e. we want to make sure that the renewal's zonefile gets processed even if the blockchain goes too fast)
    os.environ[
        'BLOCKSTACK_TEST_REGISTRAR_FAULT_INJECTION_SKIP_UPDATE_REPLICATION'] = '1'
    testlib.start_api("0123456789abcdef")

    # wait a while
    print 'Wait to verify that clearing out confirmed transactions does NOT remove zonefiles'
    time.sleep(10)

    # verify that this is still in the queue
    res = testlib.get_queue(ses, 'update')
    if 'error' in res:
        print res
        return False

    if len(res) != 1:
        print res
        return False

    # clear the fault
    print 'Clearing update replication fault'
    testlib.blockstack_test_setenv(
        "BLOCKSTACK_TEST_REGISTRAR_FAULT_INJECTION_SKIP_UPDATE_REPLICATION",
        "0")

    print 'Wait for third 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

    last_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 foo.test"
        print json.dumps(res)
        return False

    # valid history?
    hist = res['response']
    if len(hist.keys()) != 5:
        res['test'] = 'Failed to get update history'
        res['history'] = hist
        print json.dumps(res, indent=4, sort_keys=True)
        return False

    # get the historic zonefile
    res = testlib.blockstack_REST_call(
        "GET", "/v1/names/bar.test/zonefile/{}".format(first_zonefile_hash),
        ses)
    if 'error' in res or res['http_status'] != 200:
        res['test'] = 'Failed to get first zonefile'
        print json.dumps(res)
        return False

    # get the latest zonefile
    res = testlib.blockstack_REST_call(
        "GET",
        "/v1/names/bar.test/zonefile/{}?raw=1".format(last_zonefile_hash), ses)
    if 'error' in res or res['http_status'] != 200:
        res['test'] = 'Failed to get last zonefile'
        print json.dumps(res)
        return False

    if res['raw'] != 'hello world':
        res['test'] = 'Failed to set zonefile data'
        print json.dumps(res)
        return False
Exemplo n.º 6
0
def scenario(wallets, **kw):

    global wallet_keys, error, index_file_data, resource_data

    test_proxy = testlib.TestAPIProxy()
    blockstack_client.set_default_proxy(test_proxy)
    wallet_keys = blockstack_client.make_wallet_keys(
        owner_privkey=wallets[3].privkey,
        data_privkey=wallets[4].privkey,
        payment_privkey=wallets[5].privkey)
    testlib.blockstack_client_set_wallet("0123456789abcdef",
                                         wallet_keys['payment_privkey'],
                                         wallet_keys['owner_privkey'],
                                         wallet_keys['data_privkey'])

    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)

    res = testlib.start_api("0123456789abcdef")
    if 'error' in res:
        print 'failed to start API: {}'.format(res)
        return False

    # sign in and make a token
    datastore_pk = keylib.ECPrivateKey(wallets[-1].privkey).to_hex()
    res = testlib.blockstack_cli_app_signin(
        "foo.test", datastore_pk, 'http://localhost:8888',
        ['store_read', 'store_write', 'store_admin'])
    if 'error' in res:
        print json.dumps(res, indent=4, sort_keys=True)
        error = True
        return

    # export to environment
    blockstack_client.set_secret("BLOCKSTACK_API_SESSION", res['token'])
    ses = res['token']

    datastore_id_res = testlib.blockstack_cli_datastore_get_id(datastore_pk)
    datastore_id = datastore_id_res['datastore_id']

    # use random data for file
    file_data = None
    with open('/dev/urandom', 'r') as f:
        file_data = f.read(16384)

    # make datastore with two storage drivers
    res = testlib.blockstack_cli_create_datastore('foo.test', datastore_pk,
                                                  ['disk', 'test'], ses)
    if 'error' in res:
        print "failed to create datastore: {}".format(res['error'])
        return False

    # simulate a failure in the 'test' driver
    res = testlib.blockstack_test_setenv(
        'BLOCKSTACK_INTEGRATION_TEST_STORAGE_FAILURE', '1')
    if 'error' in res:
        print 'failed to setenv: {}'.format(res)
        return False

    # make directories (should all fail, since 'test' is offline; however, 'disk' will have worked)
    for dpath in ['/dir1', '/dir2', '/dir1/dir3', '/dir1/dir3/dir4']:
        print 'mkdir {} (expect failure)'.format(dpath)
        res = testlib.blockstack_cli_datastore_mkdir('foo.test', datastore_pk,
                                                     dpath, ses)
        if 'error' not in res:
            print 'accidentally succeeded to mkdir {}: {}'.format(dpath, res)
            return False

    # stat directories (should all fail locally due to ENOENT, since the parent directory will not have been updated)
    for dpath in ['/dir1/dir3/dir4', '/dir1/dir3', '/dir2', '/dir1']:
        print 'stat {} (expect failure)'.format(dpath)
        res = testlib.blockstack_cli_datastore_stat('foo.test', datastore_id,
                                                    dpath, ses)
        if 'error' not in res or 'errno' not in res:
            print 'accidentally succeeded to stat {}: {}'.format(dpath, res)
            return False

        if res['errno'] != errno.ENOENT:
            print 'wrong errno: {}'.format(res)
            return False

    # list directories (all the ones we tried to create should fail due to ENOENT)
    for dpath, expected in [('/dir1', ['dir3']), ('/dir1/dir3', ['dir4']),
                            ('/dir1/dir3/dir4', [])]:
        print 'listdir {} (expect failure)'.format(dpath)
        res = testlib.blockstack_cli_datastore_listdir('foo.test',
                                                       datastore_id, dpath,
                                                       ses)
        if 'error' not in res or 'errno' not in res:
            print 'accidentally succeeded to list {}: {}'.format(dpath, res)
            return False

        if res['errno'] != errno.ENOENT:
            print 'wrong errno: {}'.format(res)
            return False

    # restore driver
    res = testlib.blockstack_test_setenv(
        'BLOCKSTACK_INTEGRATION_TEST_STORAGE_FAILURE', '0')
    if 'error' in res:
        print 'failed to setenv: {}'.format(res)
        return False

    # make directories (should succeed)
    for dpath in ['/dir1', '/dir2', '/dir1/dir3', '/dir1/dir3/dir4']:
        print 'mkdir {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_mkdir('foo.test', datastore_pk,
                                                     dpath, ses)
        if 'error' in res:
            print 'failed to mkdir {}: {}'.format(dpath, res['error'])
            return False

    # make directories (should fail with EEXIST)
    for dpath in ['/dir1', '/dir2', '/dir1/dir3', '/dir1/dir3/dir4']:
        print 'mkdir {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_mkdir('foo.test', datastore_pk,
                                                     dpath, ses)
        if 'error' not in res:
            print 'accidentally succeeded to mkdir {}: {}'.format(dpath, res)
            return False

        if not res.has_key('errno'):
            print 'no errno in error {}'.format(res)
            return False

        if res['errno'] != errno.EEXIST:
            print 'wrong errno in error {}'.format(res)
            return False

    # stat directories (should succeed)
    for dpath in ['/dir1', '/dir2', '/dir1/dir3', '/dir1/dir3/dir4']:
        print 'stat {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_stat('foo.test', datastore_id,
                                                    dpath, ses)
        if 'error' in res:
            print 'failed to stat {}: {}'.format(dpath, res['error'])
            return False

        if res['type'] != blockstack_client.schemas.MUTABLE_DATUM_DIR_TYPE:
            print 'not a directory: {}, {}'.format(dpath, res)
            return False

    # list directories (should succeed)
    for dpath, expected in [('/', ['dir1', 'dir2']), ('/dir1', ['dir3']),
                            ('/dir1/dir3', ['dir4']), ('/dir1/dir3/dir4', [])]:
        print 'listdir {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_listdir('foo.test',
                                                       datastore_id, dpath,
                                                       ses)
        if 'error' in res:
            print 'failed to listdir {}: {}'.format(dpath, res['error'])
            return False

        if len(res['children'].keys()) != len(expected):
            print 'invalid directory: expected:\n{}\ngot:\n{}\n'.format(
                expected, res)
            return False

        for child in expected:
            if not res['children'].has_key(child):
                print 'invalid directory: missing {} in {}'.format(child, res)
                return False

    # put files
    for dpath in [
            '/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4',
            '/dir1/dir3/dir4/file5'
    ]:
        print 'putfile {}'.format(dpath)
        data = '{} hello {}'.format(file_data, dpath)
        res = testlib.blockstack_cli_datastore_putfile('foo.test',
                                                       datastore_pk, dpath,
                                                       data, ses)
        if 'error' in res:
            print 'failed to putfile {}: {}'.format(dpath, res['error'])
            return False

    # simulate a failure in the 'test' driver
    res = testlib.blockstack_test_setenv(
        'BLOCKSTACK_INTEGRATION_TEST_STORAGE_FAILURE', '1')
    if 'error' in res:
        print 'failed to setenv: {}'.format(res)
        return False

    # stat files (should succeed)
    for dpath in [
            '/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4',
            '/dir1/dir3/dir4/file5'
    ]:
        print 'stat {} (should still work)'.format(dpath)
        res = testlib.blockstack_cli_datastore_stat('foo.test', datastore_id,
                                                    dpath, ses)
        if 'error' in res:
            print 'failed to stat {}: {}'.format(dpath, res['error'])
            return False

        if res['type'] != blockstack_client.schemas.MUTABLE_DATUM_FILE_TYPE:
            print 'not a file: {}, {}'.format(dpath, res)
            return False

    # list directories again (should succeed)
    for dpath, expected in [('/', ['dir1', 'dir2', 'file1', 'file2']),
                            ('/dir1', ['dir3', 'file3']),
                            ('/dir1/dir3', ['dir4', 'file4']),
                            ('/dir1/dir3/dir4', ['file5'])]:
        print 'listdir {} (should still work)'.format(dpath)
        res = testlib.blockstack_cli_datastore_listdir('foo.test',
                                                       datastore_id, dpath,
                                                       ses)
        if 'error' in res:
            print 'failed to listdir {}: {}'.format(dpath, res['error'])
            return False

        if len(res['children'].keys()) != len(expected):
            print 'invalid directory: expected:\n{}\ngot:\n{}\n'.format(
                expected, res)
            return False

        for child in expected:
            if not res['children'].has_key(child):
                print 'invalid directory: missing {} in {}'.format(child, res)
                return False

    # get files (should succeed and return latest data)
    for dpath in [
            '/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4',
            '/dir1/dir3/dir4/file5'
    ]:
        print 'getfile {} (should still work)'.format(dpath)
        res = testlib.blockstack_cli_datastore_getfile('foo.test',
                                                       datastore_id, dpath,
                                                       ses)
        if 'error' in res:
            print 'failed to getfile {}: {}'.format(dpath, res['error'])
            return False

        if res != '{} hello {}'.format(file_data, dpath):
            print 'failed to read {}'.format(dpath)
            return False

    # put files (should succeed with 'disk', but fail overall since we have a failed driver)
    for dpath in [
            '/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4',
            '/dir1/dir3/dir4/file5'
    ]:
        print 'putfile {} (expect failure)'.format(dpath)
        data = '{} hello 2 {}'.format(file_data, dpath)
        res = testlib.blockstack_cli_datastore_putfile('foo.test',
                                                       datastore_pk, dpath,
                                                       data, ses)
        if 'error' not in res or 'errno' not in res:
            print 'accidentally succeeded to putfile {}: {}'.format(
                dpath, res['error'])
            return False

    # get files (should get the new data, despite the failed service)
    for dpath in [
            '/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4',
            '/dir1/dir3/dir4/file5'
    ]:
        print 'getfile {} (should still work)'.format(dpath)
        res = testlib.blockstack_cli_datastore_getfile('foo.test',
                                                       datastore_id, dpath,
                                                       ses)
        if 'error' in res:
            print 'failed to getfile {}: {}'.format(dpath, res['error'])
            return False

        if res != '{} hello 2 {}'.format(file_data, dpath):
            print 'failed to read {}'.format(dpath)
            return False

    # restore test driver
    res = testlib.blockstack_test_setenv(
        'BLOCKSTACK_INTEGRATION_TEST_STORAGE_FAILURE', '0')
    if 'error' in res:
        print 'failed to setenv: {}'.format(res)
        return False

    # put files (should succeed now)
    for dpath in [
            '/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4',
            '/dir1/dir3/dir4/file5'
    ]:
        print 'putfile {}'.format(dpath)
        data = '{} hello 2 {}'.format(file_data, dpath)
        res = testlib.blockstack_cli_datastore_putfile('foo.test',
                                                       datastore_pk, dpath,
                                                       data, ses)
        if 'error' in res:
            print 'failed to putfile {}: {}'.format(dpath, res['error'])
            return False

    # get files again! should see results of last put
    for dpath in [
            '/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4',
            '/dir1/dir3/dir4/file5'
    ]:
        print 'getfile {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_getfile('foo.test',
                                                       datastore_id, dpath,
                                                       ses)
        if 'error' in res:
            print 'failed to getfile {}: {}'.format(dpath, res['error'])
            return False

        if res != '{} hello 2 {}'.format(file_data, dpath):
            print 'failed to read {}'.format(dpath)
            return False

    # simulate a failure in the 'test' driver
    res = testlib.blockstack_test_setenv(
        'BLOCKSTACK_INTEGRATION_TEST_STORAGE_FAILURE', '1')
    if 'error' in res:
        print 'failed to setenv: {}'.format(res)
        return False

    # remove files (should fail since 'test' driver is disabled)
    for dpath in [
            '/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4',
            '/dir1/dir3/dir4/file5'
    ]:
        print 'deletefile {} (expect failure)'.format(dpath)
        res = testlib.blockstack_cli_datastore_deletefile(
            'foo.test', datastore_pk, dpath, ses)
        if 'error' not in res:
            print 'accidentally failed to deletefile {}: {}'.format(
                dpath, res['error'])
            return False

    # get files (should fail, since the idata was removed from 'disk')
    for dpath in [
            '/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4',
            '/dir1/dir3/dir4/file5'
    ]:
        print 'getfile {} (should fail)'.format(dpath)
        res = testlib.blockstack_cli_datastore_getfile('foo.test',
                                                       datastore_id, dpath,
                                                       ses)
        if 'error' not in res:
            print 'accidentally got {}: {}'.format(dpath, res)
            return False

        if res['errno'] != errno.EREMOTEIO:
            print 'wrong errno: {}'.format(res)
            return False

    # stat files (should still work)
    for dpath in [
            '/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4',
            '/dir1/dir3/dir4/file5'
    ]:
        print 'stat {} (should still work)'.format(dpath)
        res = testlib.blockstack_cli_datastore_stat('foo.test', datastore_id,
                                                    dpath, ses)
        if 'error' in res:
            print 'failed to stat {}: {}'.format(path, res)
            return False

    # restore test driver
    res = testlib.blockstack_test_setenv(
        'BLOCKSTACK_INTEGRATION_TEST_STORAGE_FAILURE', '0')
    if 'error' in res:
        print 'failed to setenv: {}'.format(res)
        return False

    # stat files (should still work)
    for dpath in [
            '/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4',
            '/dir1/dir3/dir4/file5'
    ]:
        print 'stat {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_stat('foo.test', datastore_id,
                                                    dpath, ses)
        if 'error' in res:
            print 'failed to stat {}: {}'.format(path, res)
            return False

    # remove files (should work now)
    for dpath in [
            '/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4',
            '/dir1/dir3/dir4/file5'
    ]:
        print 'deletefile {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_deletefile(
            'foo.test', datastore_pk, dpath, ses)
        if 'error' in res:
            print 'failed to deletefile {}: {}'.format(dpath, res)
            return False

    # put file data (should succeed on both 'disk' and 'test')
    for dpath in [
            '/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4',
            '/dir1/dir3/dir4/file5'
    ]:
        print 'putfile {}'.format(dpath)
        data = '{} hello 3 {}'.format(file_data, dpath)
        res = testlib.blockstack_cli_datastore_putfile('foo.test',
                                                       datastore_pk, dpath,
                                                       data, ses)
        if 'error' in res:
            print 'failed to putfile {}: {}'.format(dpath, res['error'])
            return False

    # get files again! should see results of last put
    for dpath in [
            '/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4',
            '/dir1/dir3/dir4/file5'
    ]:
        print 'getfile {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_getfile('foo.test',
                                                       datastore_id, dpath,
                                                       ses)
        if 'error' in res:
            print 'failed to getfile {}: {}'.format(dpath, res['error'])
            return False

        if res != '{} hello 3 {}'.format(file_data, dpath):
            print 'failed to read {}'.format(dpath)
            return False

    # cause 'test' to fail
    res = testlib.blockstack_test_setenv(
        'BLOCKSTACK_INTEGRATION_TEST_STORAGE_FAILURE', '1')
    if 'error' in res:
        print 'failed to setenv: {}'.format(res)
        return False

    # put file data (should fail, but succeed on 'disk')
    for dpath in [
            '/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4',
            '/dir1/dir3/dir4/file5'
    ]:
        print 'putfile {} (expect failure)'.format(dpath)
        data = '{} hello 4 {}'.format(file_data, dpath)
        res = testlib.blockstack_cli_datastore_putfile('foo.test',
                                                       datastore_pk, dpath,
                                                       data, ses)
        if 'error' not in res:
            print 'accidentally succeeded to putfile {}: {}'.format(dpath, res)
            return False

        if res['errno'] != errno.EREMOTEIO:
            print 'wrong errno: {}'.format(res)
            return False

    # get files again! should see results of last put
    for dpath in [
            '/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4',
            '/dir1/dir3/dir4/file5'
    ]:
        print 'getfile {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_getfile('foo.test',
                                                       datastore_id, dpath,
                                                       ses)
        if 'error' in res:
            print 'failed to getfile {}: {}'.format(dpath, res['error'])
            return False

        if res != '{} hello 4 {}'.format(file_data, dpath):
            print 'failed to read {}'.format(dpath)
            return False

    # restore 'test'
    res = testlib.blockstack_test_setenv(
        'BLOCKSTACK_INTEGRATION_TEST_STORAGE_FAILURE', '0')
    if 'error' in res:
        print 'failed to setenv: {}'.format(res)
        return False

    # get files again! should see results of last put
    for dpath in [
            '/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4',
            '/dir1/dir3/dir4/file5'
    ]:
        print 'getfile {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_getfile('foo.test',
                                                       datastore_id, dpath,
                                                       ses)
        if 'error' in res:
            print 'failed to getfile {}: {}'.format(dpath, res['error'])
            return False

        if res != '{} hello 4 {}'.format(file_data, dpath):
            print 'failed to read {}'.format(dpath)
            return False

    # remove files (should succeed)
    for dpath in [
            '/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4',
            '/dir1/dir3/dir4/file5'
    ]:
        print 'deletefile {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_deletefile(
            'foo.test', datastore_pk, dpath, ses)
        if 'error' in res:
            print 'failed to deletefile {}: {}'.format(dpath, res['error'])
            return False

    # stat files (should all fail)
    for dpath in [
            '/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4',
            '/dir1/dir3/dir4/file5'
    ]:
        print 'stat {} (expect failure)'.format(dpath)
        res = testlib.blockstack_cli_datastore_stat('foo.test', datastore_id,
                                                    dpath, ses)
        if 'error' not in res or 'errno' not in res:
            print 'accidentally succeeded to stat {}: {}'.format(dpath, res)
            return False

        if res['errno'] != errno.ENOENT:
            print 'wrong errno: {}'.format(res)
            return False

    # get files (should all fail)
    for dpath in [
            '/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4',
            '/dir1/dir3/dir4/file5'
    ]:
        print 'getfile {} (expect failure)'.format(dpath)
        res = testlib.blockstack_cli_datastore_getfile('foo.test',
                                                       datastore_id, dpath,
                                                       ses)
        if 'error' not in res or 'errno' not in res:
            print 'accidentally succeeded to get {}: {}'.format(dpath, res)
            return False

        if res['errno'] != errno.ENOENT:
            print 'wrong errno: {}'.format(res)
            return False

    # list directories
    for dpath, expected in [('/', ['dir1', 'dir2']), ('/dir1', ['dir3']),
                            ('/dir2', []), ('/dir1/dir3', ['dir4']),
                            ('/dir1/dir3/dir4', [])]:
        print 'listdir {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_listdir('foo.test',
                                                       datastore_id, dpath,
                                                       ses)
        if 'error' in res:
            print 'failed to listdir {}: {}'.format(dpath, res['error'])
            return False

        if len(res['children'].keys()) != len(expected):
            print 'invalid directory: expected:\n{}\ngot:\n{}\n'.format(
                expected, res)
            return False

        for child in expected:
            if not res['children'].has_key(child):
                print 'invalid directory: missing {} in {}'.format(child, res)
                return False

    # break 'test'
    res = testlib.blockstack_test_setenv(
        'BLOCKSTACK_INTEGRATION_TEST_STORAGE_FAILURE', '1')
    if 'error' in res:
        print 'failed to setenv: {}'.format(res)
        return False

    # stat files (should all fail)
    for dpath in [
            '/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4',
            '/dir1/dir3/dir4/file5'
    ]:
        print 'stat {} (expect failure)'.format(dpath)
        res = testlib.blockstack_cli_datastore_stat('foo.test', datastore_id,
                                                    dpath, ses)
        if 'error' not in res or 'errno' not in res:
            print 'accidentally succeeded to stat {}: {}'.format(dpath, res)
            return False

        if res['errno'] != errno.ENOENT:
            print 'wrong errno: {}'.format(res)
            return False

    # get files (should all fail)
    for dpath in [
            '/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4',
            '/dir1/dir3/dir4/file5'
    ]:
        print 'getfile {} (expect failure)'.format(dpath)
        res = testlib.blockstack_cli_datastore_getfile('foo.test',
                                                       datastore_id, dpath,
                                                       ses)
        if 'error' not in res or 'errno' not in res:
            print 'accidentally succeeded to get {}: {}'.format(dpath, res)
            return False

        if res['errno'] != errno.ENOENT:
            print 'wrong errno: {}'.format(res)
            return False

    # list directories
    for dpath, expected in [('/', ['dir1', 'dir2']), ('/dir1', ['dir3']),
                            ('/dir2', []), ('/dir1/dir3', ['dir4']),
                            ('/dir1/dir3/dir4', [])]:
        print 'listdir {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_listdir('foo.test',
                                                       datastore_id, dpath,
                                                       ses)
        if 'error' in res:
            print 'failed to listdir {}: {}'.format(dpath, res['error'])
            return False

        if len(res['children'].keys()) != len(expected):
            print 'invalid directory: expected:\n{}\ngot:\n{}\n'.format(
                expected, res)
            return False

        for child in expected:
            if not res['children'].has_key(child):
                print 'invalid directory: missing {} in {}'.format(child, res)
                return False

    # remove directories (should fail, but '/dir2' and '/dir1/dir3/dir4''s idata should be gone)
    for dpath in ['/dir1/dir3/dir4', '/dir1/dir3', '/dir2', '/dir1']:
        print 'rmdir {} (expect failure)'.format(dpath)
        res = testlib.blockstack_cli_datastore_rmdir('foo.test', datastore_pk,
                                                     dpath, ses)
        if 'error' not in res:
            print 'accidentally succeeded to rmdir {}: {}'.format(dpath, res)
            return False

        if dpath not in ['/dir1/dir3/dir4', '/dir2']:
            if res.get('errno') != errno.ENOTEMPTY:
                print 'wrong errno for deleting {}'.format(res)
                return False

    # list directories (should fail for /dir1/dir3/dir4 and /dir2 since its idata got deleted, but it should still work for everyone else)
    for dpath, expected in [('/dir2', []), ('/dir1/dir3/dir4', [])]:
        print 'listdir {} (expect failure)'.format(dpath)
        res = testlib.blockstack_cli_datastore_listdir('foo.test',
                                                       datastore_id, dpath,
                                                       ses)
        if 'error' not in res or 'errno' not in res:
            print 'accidentally succeeded to list {}: {}'.format(dpath, res)
            return False

        if res['errno'] != errno.EREMOTEIO:
            print 'wrong errno: {}'.format(res)
            return False

    # these should still work
    for dpath, expected in [('/', ['dir1', 'dir2']), ('/dir1', ['dir3']),
                            ('/dir1/dir3', ['dir4'])]:
        print 'listdir {} (should still work)'.format(dpath)
        res = testlib.blockstack_cli_datastore_listdir('foo.test',
                                                       datastore_id, dpath,
                                                       ses)
        if 'error' in res:
            print 'failed to listdir {}: {}'.format(dpath, res['error'])
            return False

        if len(res['children'].keys()) != len(expected):
            print 'invalid directory: expected:\n{}\ngot:\n{}\n'.format(
                expected, res)
            return False

        for child in expected:
            if not res['children'].has_key(child):
                print 'invalid directory: missing {} in {}'.format(child, res)
                return False

    # restore service
    res = testlib.blockstack_test_setenv(
        'BLOCKSTACK_INTEGRATION_TEST_STORAGE_FAILURE', '0')
    if 'error' in res:
        print 'failed to setenv: {}'.format(res)
        return False

    # remove directories (should succeed)
    for dpath in ['/dir1/dir3/dir4', '/dir1/dir3', '/dir2', '/dir1']:
        print 'rmdir {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_rmdir('foo.test', datastore_pk,
                                                     dpath, ses)
        if 'error' in res:
            print 'failed to rmdir {}: {}'.format(dpath, res['error'])
            return False

    # stat directories (should all fail)
    for dpath in ['/dir1/dir3/dir4', '/dir1/dir3', '/dir2', '/dir1']:
        print 'stat {} (expect failure)'.format(dpath)
        res = testlib.blockstack_cli_datastore_stat('foo.test', datastore_id,
                                                    dpath, ses)
        if 'error' not in res or 'errno' not in res:
            print 'accidentally succeeded to stat {}: {}'.format(dpath, res)
            return False

        if res['errno'] != errno.ENOENT:
            print 'wrong errno: {}'.format(res)
            return False

    # list directories (should all fail)
    for dpath, expected in [('/dir2', []), ('/dir1', ['dir3']),
                            ('/dir1/dir3', ['dir4']), ('/dir1/dir3/dir4', [])]:
        print 'listdir {} (expect failure)'.format(dpath)
        res = testlib.blockstack_cli_datastore_listdir('foo.test',
                                                       datastore_id, dpath,
                                                       ses)
        if 'error' not in res or 'errno' not in res:
            print 'accidentally succeeded to list {}: {}'.format(dpath, res)
            return False

        if res['errno'] != errno.ENOENT:
            print 'wrong errno: {}'.format(res)
            return False

    # root should be empty
    print 'listdir {}'.format('/')
    res = testlib.blockstack_cli_datastore_listdir('foo.test', datastore_id,
                                                   '/', ses)
    if 'error' in res:
        print 'failed to listdir /: {}'.format(res['error'])
        return False

    if len(res['children'].keys()) > 0:
        print 'root still has children: {}'.format(res['children'].keys())
        return False

    # simulate a failure in the 'test' driver
    res = testlib.blockstack_test_setenv(
        'BLOCKSTACK_INTEGRATION_TEST_STORAGE_FAILURE', '1')
    if 'error' in res:
        print 'failed to setenv: {}'.format(res)
        return False

    # delete datastore (should fail)
    print 'delete datastore (expect failure)'
    res = testlib.blockstack_cli_delete_datastore('foo.test', datastore_pk,
                                                  ses)
    if 'error' not in res:
        print 'accidentally succeeded to delete datastore'
        print json.dumps(res)
        return False

    # restore service
    res = testlib.blockstack_test_setenv(
        'BLOCKSTACK_INTEGRATION_TEST_STORAGE_FAILURE', '0')
    if 'error' in res:
        print 'failed to setenv: {}'.format(res)
        return False

    print 'delete datastore'
    res = testlib.blockstack_cli_delete_datastore('foo.test', datastore_pk,
                                                  ses)
    if 'error' in res:
        print 'failed to delete foo-app.com datastore'
        print json.dumps(res)
        return False

    # no more data in test-disk driver
    names = os.listdir("/tmp/blockstack-integration-test-storage/mutable")
    if names != ['foo.test']:
        print 'improper cleanup on test'
        return False

    # due to our failed mkdir of /dir1 and /dir2, these
    # will have leaked. Expect 5 entries (including foo.test):
    # an idata and inode header for both dirs
    names = os.listdir("/tmp/blockstack-disk/mutable")
    if len(names) != 5:
        print 'imporper cleanup on disk'
        return False

    if 'foo.test' not in names:
        print 'missing foo.test'
        return False

    testlib.next_block(**kw)
def scenario( wallets, **kw ):

    global wallet_keys, error, index_file_data, resource_data

    test_proxy = testlib.TestAPIProxy()
    blockstack_client.set_default_proxy( test_proxy )
    wallet_keys = blockstack_client.make_wallet_keys( owner_privkey=wallets[3].privkey, data_privkey=wallets[4].privkey, payment_privkey=wallets[5].privkey )
    testlib.blockstack_client_set_wallet( "0123456789abcdef", wallet_keys['payment_privkey'], wallet_keys['owner_privkey'], wallet_keys['data_privkey'] )

    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 )
    
    res = testlib.start_api("0123456789abcdef")
    if 'error' in res:
        print 'failed to start API: {}'.format(res)
        return False

    # sign in and make a token 
    datastore_pk = keylib.ECPrivateKey(wallets[-1].privkey).to_hex()
    res = testlib.blockstack_cli_app_signin("foo.test", datastore_pk, 'http://localhost:8888', ['store_read', 'store_write', 'store_admin'])
    if 'error' in res:
        print json.dumps(res, indent=4, sort_keys=True)
        error = True
        return 

    # export to environment 
    blockstack_client.set_secret("BLOCKSTACK_API_SESSION", res['token'])
    ses = res['token']

    datastore_id_res = testlib.blockstack_cli_datastore_get_id( datastore_pk )
    datastore_id = datastore_id_res['datastore_id']

    # use random data for file 
    file_data = None
    with open('/dev/urandom', 'r') as f:
        file_data = f.read(16384)

    # make datastore with two storage drivers
    res = testlib.blockstack_cli_create_datastore( 'foo.test', datastore_pk, ['disk', 'test'], ses )
    if 'error' in res:
        print "failed to create datastore: {}".format(res['error'])
        return False

    # simulate a failure in the 'test' driver
    res = testlib.blockstack_test_setenv('BLOCKSTACK_INTEGRATION_TEST_STORAGE_FAILURE', '1')
    if 'error' in res:
        print 'failed to setenv: {}'.format(res)
        return False

    # make directories (should all fail, since 'test' is offline; however, 'disk' will have worked)
    for dpath in ['/dir1', '/dir2', '/dir1/dir3', '/dir1/dir3/dir4']:
        print 'mkdir {} (expect failure)'.format(dpath)
        res = testlib.blockstack_cli_datastore_mkdir( 'foo.test', datastore_pk, dpath, ses )
        if 'error' not in res:
            print 'accidentally succeeded to mkdir {}: {}'.format(dpath, res)
            return False

    # stat directories (should all fail locally due to ENOENT, since the parent directory will not have been updated)
    for dpath in ['/dir1/dir3/dir4', '/dir1/dir3', '/dir2', '/dir1']:
        print 'stat {} (expect failure)'.format(dpath)
        res = testlib.blockstack_cli_datastore_stat( 'foo.test', datastore_id, dpath, ses )
        if 'error' not in res or 'errno' not in res:
            print 'accidentally succeeded to stat {}: {}'.format(dpath, res)
            return False

        if res['errno'] != errno.ENOENT:
            print 'wrong errno: {}'.format(res)
            return False

    # list directories (all the ones we tried to create should fail due to ENOENT)
    for dpath, expected in [('/dir1', ['dir3']), ('/dir1/dir3', ['dir4']), ('/dir1/dir3/dir4', [])]:
        print 'listdir {} (expect failure)'.format(dpath)
        res = testlib.blockstack_cli_datastore_listdir( 'foo.test', datastore_id, dpath, ses )
        if 'error' not in res or 'errno' not in res:
            print 'accidentally succeeded to list {}: {}'.format(dpath, res)
            return False

        if res['errno'] != errno.ENOENT:
            print 'wrong errno: {}'.format(res)
            return False

    # restore driver
    res = testlib.blockstack_test_setenv('BLOCKSTACK_INTEGRATION_TEST_STORAGE_FAILURE', '0')
    if 'error' in res:
        print 'failed to setenv: {}'.format(res)
        return False

    # make directories (should succeed)
    for dpath in ['/dir1', '/dir2', '/dir1/dir3', '/dir1/dir3/dir4']:
        print 'mkdir {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_mkdir( 'foo.test', datastore_pk, dpath, ses )
        if 'error' in res:
            print 'failed to mkdir {}: {}'.format(dpath, res['error'])
            return False

    # make directories (should fail with EEXIST)
    for dpath in ['/dir1', '/dir2', '/dir1/dir3', '/dir1/dir3/dir4']:
        print 'mkdir {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_mkdir( 'foo.test', datastore_pk, dpath, ses )
        if 'error' not in res:
            print 'accidentally succeeded to mkdir {}: {}'.format(dpath, res)
            return False

        if not res.has_key('errno'):
            print 'no errno in error {}'.format(res)
            return False

        if res['errno'] != errno.EEXIST:
            print 'wrong errno in error {}'.format(res)
            return False

    # stat directories (should succeed)
    for dpath in ['/dir1', '/dir2', '/dir1/dir3', '/dir1/dir3/dir4']:
        print 'stat {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_stat( 'foo.test', datastore_id, dpath, ses )
        if 'error' in res:
            print 'failed to stat {}: {}'.format(dpath, res['error'])
            return False

        if res['type'] != blockstack_client.schemas.MUTABLE_DATUM_DIR_TYPE:
            print 'not a directory: {}, {}'.format(dpath, res)
            return False

    # list directories (should succeed)
    for dpath, expected in [('/', ['dir1', 'dir2']), ('/dir1', ['dir3']), ('/dir1/dir3', ['dir4']), ('/dir1/dir3/dir4', [])]:
        print 'listdir {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_listdir( 'foo.test', datastore_id, dpath, ses )
        if 'error' in res:
            print 'failed to listdir {}: {}'.format(dpath, res['error'])
            return False

        if len(res['children'].keys()) != len(expected):
            print 'invalid directory: expected:\n{}\ngot:\n{}\n'.format(expected, res)
            return False

        for child in expected: 
            if not res['children'].has_key(child):
                print 'invalid directory: missing {} in {}'.format(child, res)
                return False

    # put files 
    for dpath in ['/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4', '/dir1/dir3/dir4/file5']:
        print 'putfile {}'.format(dpath)
        data = '{} hello {}'.format(file_data, dpath)
        res = testlib.blockstack_cli_datastore_putfile( 'foo.test', datastore_pk, dpath, data, ses )
        if 'error' in res:
            print 'failed to putfile {}: {}'.format(dpath, res['error'])
            return False

    # simulate a failure in the 'test' driver 
    res = testlib.blockstack_test_setenv('BLOCKSTACK_INTEGRATION_TEST_STORAGE_FAILURE', '1')
    if 'error' in res:
        print 'failed to setenv: {}'.format(res)
        return False

    # stat files (should succeed)
    for dpath in ['/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4', '/dir1/dir3/dir4/file5']:
        print 'stat {} (should still work)'.format(dpath)
        res = testlib.blockstack_cli_datastore_stat( 'foo.test', datastore_id, dpath, ses )
        if 'error' in res:
            print 'failed to stat {}: {}'.format(dpath, res['error'])
            return False

        if res['type'] != blockstack_client.schemas.MUTABLE_DATUM_FILE_TYPE:
            print 'not a file: {}, {}'.format(dpath, res)
            return False

    # list directories again (should succeed)
    for dpath, expected in [('/', ['dir1', 'dir2', 'file1', 'file2']), ('/dir1', ['dir3', 'file3']), ('/dir1/dir3', ['dir4', 'file4']), ('/dir1/dir3/dir4', ['file5'])]:
        print 'listdir {} (should still work)'.format(dpath)
        res = testlib.blockstack_cli_datastore_listdir( 'foo.test', datastore_id, dpath, ses )
        if 'error' in res:
            print 'failed to listdir {}: {}'.format(dpath, res['error'])
            return False

        if len(res['children'].keys()) != len(expected):
            print 'invalid directory: expected:\n{}\ngot:\n{}\n'.format(expected, res)
            return False

        for child in expected: 
            if not res['children'].has_key(child):
                print 'invalid directory: missing {} in {}'.format(child, res)
                return False
     
    # get files (should succeed and return latest data)
    for dpath in ['/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4', '/dir1/dir3/dir4/file5']:
        print 'getfile {} (should still work)'.format(dpath)
        res = testlib.blockstack_cli_datastore_getfile( 'foo.test', datastore_id, dpath, ses )
        if 'error' in res:
            print 'failed to getfile {}: {}'.format(dpath, res['error'])
            return False

        if res != '{} hello {}'.format(file_data, dpath):
            print 'failed to read {}'.format(dpath)
            return False

    # put files (should succeed with 'disk', but fail overall since we have a failed driver)
    for dpath in ['/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4', '/dir1/dir3/dir4/file5']:
        print 'putfile {} (expect failure)'.format(dpath)
        data = '{} hello 2 {}'.format(file_data, dpath)
        res = testlib.blockstack_cli_datastore_putfile( 'foo.test', datastore_pk, dpath, data, ses )
        if 'error' not in res or 'errno' not in res:
            print 'accidentally succeeded to putfile {}: {}'.format(dpath, res['error'])
            return False

    # get files (should get the new data, despite the failed service)
    for dpath in ['/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4', '/dir1/dir3/dir4/file5']:
        print 'getfile {} (should still work)'.format(dpath)
        res = testlib.blockstack_cli_datastore_getfile( 'foo.test', datastore_id, dpath, ses )
        if 'error' in res:
            print 'failed to getfile {}: {}'.format(dpath, res['error'])
            return False

        if res != '{} hello 2 {}'.format(file_data, dpath):
            print 'failed to read {}'.format(dpath)
            return False

    # restore test driver 
    res = testlib.blockstack_test_setenv('BLOCKSTACK_INTEGRATION_TEST_STORAGE_FAILURE', '0')
    if 'error' in res:
        print 'failed to setenv: {}'.format(res)
        return False

    # put files (should succeed now)
    for dpath in ['/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4', '/dir1/dir3/dir4/file5']:
        print 'putfile {}'.format(dpath)
        data = '{} hello 2 {}'.format(file_data, dpath)
        res = testlib.blockstack_cli_datastore_putfile( 'foo.test', datastore_pk, dpath, data, ses )
        if 'error' in res:
            print 'failed to putfile {}: {}'.format(dpath, res['error'])
            return False

    # get files again! should see results of last put
    for dpath in ['/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4', '/dir1/dir3/dir4/file5']:
        print 'getfile {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_getfile( 'foo.test', datastore_id, dpath, ses )
        if 'error' in res:
            print 'failed to getfile {}: {}'.format(dpath, res['error'])
            return False

        if res != '{} hello 2 {}'.format(file_data, dpath):
            print 'failed to read {}'.format(dpath)
            return False

    # simulate a failure in the 'test' driver 
    res = testlib.blockstack_test_setenv('BLOCKSTACK_INTEGRATION_TEST_STORAGE_FAILURE', '1')
    if 'error' in res:
        print 'failed to setenv: {}'.format(res)
        return False

    # remove files (should fail since 'test' driver is disabled)
    for dpath in ['/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4', '/dir1/dir3/dir4/file5']:
        print 'deletefile {} (expect failure)'.format(dpath)
        res = testlib.blockstack_cli_datastore_deletefile( 'foo.test', datastore_pk, dpath, ses )
        if 'error' not in res:
            print 'accidentally failed to deletefile {}: {}'.format(dpath, res['error'])
            return False

    # get files (should fail, since the idata was removed from 'disk')
    for dpath in ['/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4', '/dir1/dir3/dir4/file5']:
        print 'getfile {} (should fail)'.format(dpath)
        res = testlib.blockstack_cli_datastore_getfile( 'foo.test', datastore_id, dpath, ses )
        if 'error' not in res:
            print 'accidentally got {}: {}'.format(dpath, res)
            return False

        if res['errno'] != errno.EREMOTEIO:
            print 'wrong errno: {}'.format(res)
            return False

    # stat files (should still work)
    for dpath in ['/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4', '/dir1/dir3/dir4/file5']:
        print 'stat {} (should still work)'.format(dpath)
        res = testlib.blockstack_cli_datastore_stat( 'foo.test', datastore_id, dpath, ses )
        if 'error' in res:
            print 'failed to stat {}: {}'.format(path, res)
            return False

    # restore test driver
    res = testlib.blockstack_test_setenv('BLOCKSTACK_INTEGRATION_TEST_STORAGE_FAILURE', '0')
    if 'error' in res:
        print 'failed to setenv: {}'.format(res)
        return False

    # stat files (should still work)
    for dpath in ['/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4', '/dir1/dir3/dir4/file5']:
        print 'stat {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_stat( 'foo.test', datastore_id, dpath, ses )
        if 'error' in res:
            print 'failed to stat {}: {}'.format(path, res)
            return False

    # remove files (should work now)
    for dpath in ['/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4', '/dir1/dir3/dir4/file5']:
        print 'deletefile {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_deletefile( 'foo.test', datastore_pk, dpath, ses )
        if 'error' in res:
            print 'failed to deletefile {}: {}'.format(dpath, res)
            return False

    # put file data (should succeed on both 'disk' and 'test')
    for dpath in ['/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4', '/dir1/dir3/dir4/file5']:
        print 'putfile {}'.format(dpath)
        data = '{} hello 3 {}'.format(file_data, dpath)
        res = testlib.blockstack_cli_datastore_putfile( 'foo.test', datastore_pk, dpath, data, ses )
        if 'error' in res:
            print 'failed to putfile {}: {}'.format(dpath, res['error'])
            return False

    # get files again! should see results of last put
    for dpath in ['/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4', '/dir1/dir3/dir4/file5']:
        print 'getfile {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_getfile( 'foo.test', datastore_id, dpath, ses )
        if 'error' in res:
            print 'failed to getfile {}: {}'.format(dpath, res['error'])
            return False

        if res != '{} hello 3 {}'.format(file_data, dpath):
            print 'failed to read {}'.format(dpath)
            return False

    # cause 'test' to fail
    res = testlib.blockstack_test_setenv('BLOCKSTACK_INTEGRATION_TEST_STORAGE_FAILURE', '1')
    if 'error' in res:
        print 'failed to setenv: {}'.format(res)
        return False

    # put file data (should fail, but succeed on 'disk')
    for dpath in ['/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4', '/dir1/dir3/dir4/file5']:
        print 'putfile {} (expect failure)'.format(dpath)
        data = '{} hello 4 {}'.format(file_data, dpath)
        res = testlib.blockstack_cli_datastore_putfile( 'foo.test', datastore_pk, dpath, data, ses )
        if 'error' not in res:
            print 'accidentally succeeded to putfile {}: {}'.format(dpath, res)
            return False

        if res['errno'] != errno.EREMOTEIO:
            print 'wrong errno: {}'.format(res)
            return False

    # get files again! should see results of last put
    for dpath in ['/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4', '/dir1/dir3/dir4/file5']:
        print 'getfile {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_getfile( 'foo.test', datastore_id, dpath, ses )
        if 'error' in res:
            print 'failed to getfile {}: {}'.format(dpath, res['error'])
            return False

        if res != '{} hello 4 {}'.format(file_data, dpath):
            print 'failed to read {}'.format(dpath)
            return False

    # restore 'test'
    res = testlib.blockstack_test_setenv('BLOCKSTACK_INTEGRATION_TEST_STORAGE_FAILURE', '0')
    if 'error' in res:
        print 'failed to setenv: {}'.format(res)
        return False

    # get files again! should see results of last put
    for dpath in ['/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4', '/dir1/dir3/dir4/file5']:
        print 'getfile {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_getfile( 'foo.test', datastore_id, dpath, ses )
        if 'error' in res:
            print 'failed to getfile {}: {}'.format(dpath, res['error'])
            return False

        if res != '{} hello 4 {}'.format(file_data, dpath):
            print 'failed to read {}'.format(dpath)
            return False

    # remove files (should succeed)
    for dpath in ['/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4', '/dir1/dir3/dir4/file5']:
        print 'deletefile {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_deletefile( 'foo.test', datastore_pk, dpath, ses )
        if 'error' in res:
            print 'failed to deletefile {}: {}'.format(dpath, res['error'])
            return False

    # stat files (should all fail)
    for dpath in ['/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4', '/dir1/dir3/dir4/file5']:
        print 'stat {} (expect failure)'.format(dpath)
        res = testlib.blockstack_cli_datastore_stat( 'foo.test', datastore_id, dpath, ses )
        if 'error' not in res or 'errno' not in res:
            print 'accidentally succeeded to stat {}: {}'.format(dpath, res)
            return False

        if res['errno'] != errno.ENOENT:
            print 'wrong errno: {}'.format(res)
            return False
 
    # get files (should all fail)
    for dpath in ['/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4', '/dir1/dir3/dir4/file5']:
        print 'getfile {} (expect failure)'.format(dpath)
        res = testlib.blockstack_cli_datastore_getfile( 'foo.test', datastore_id, dpath, ses )
        if 'error' not in res or 'errno' not in res:
            print 'accidentally succeeded to get {}: {}'.format(dpath, res)
            return False

        if res['errno'] != errno.ENOENT:
            print 'wrong errno: {}'.format(res)
            return False

    # list directories 
    for dpath, expected in [('/', ['dir1', 'dir2']), ('/dir1', ['dir3']), ('/dir2', []), ('/dir1/dir3', ['dir4']), ('/dir1/dir3/dir4', [])]:
        print 'listdir {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_listdir( 'foo.test', datastore_id, dpath, ses )
        if 'error' in res:
            print 'failed to listdir {}: {}'.format(dpath, res['error'])
            return False

        if len(res['children'].keys()) != len(expected):
            print 'invalid directory: expected:\n{}\ngot:\n{}\n'.format(expected, res)
            return False

        for child in expected: 
            if not res['children'].has_key(child):
                print 'invalid directory: missing {} in {}'.format(child, res)
                return False

    # break 'test'
    res = testlib.blockstack_test_setenv('BLOCKSTACK_INTEGRATION_TEST_STORAGE_FAILURE', '1')
    if 'error' in res:
        print 'failed to setenv: {}'.format(res)
        return False

    # stat files (should all fail)
    for dpath in ['/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4', '/dir1/dir3/dir4/file5']:
        print 'stat {} (expect failure)'.format(dpath)
        res = testlib.blockstack_cli_datastore_stat( 'foo.test', datastore_id, dpath, ses )
        if 'error' not in res or 'errno' not in res:
            print 'accidentally succeeded to stat {}: {}'.format(dpath, res)
            return False

        if res['errno'] != errno.ENOENT:
            print 'wrong errno: {}'.format(res)
            return False

    # get files (should all fail)
    for dpath in ['/file1', '/file2', '/dir1/file3', '/dir1/dir3/file4', '/dir1/dir3/dir4/file5']:
        print 'getfile {} (expect failure)'.format(dpath)
        res = testlib.blockstack_cli_datastore_getfile( 'foo.test', datastore_id, dpath, ses )
        if 'error' not in res or 'errno' not in res:
            print 'accidentally succeeded to get {}: {}'.format(dpath, res)
            return False

        if res['errno'] != errno.ENOENT:
            print 'wrong errno: {}'.format(res)
            return False

    # list directories 
    for dpath, expected in [('/', ['dir1', 'dir2']), ('/dir1', ['dir3']), ('/dir2', []), ('/dir1/dir3', ['dir4']), ('/dir1/dir3/dir4', [])]:
        print 'listdir {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_listdir( 'foo.test', datastore_id, dpath, ses )
        if 'error' in res:
            print 'failed to listdir {}: {}'.format(dpath, res['error'])
            return False

        if len(res['children'].keys()) != len(expected):
            print 'invalid directory: expected:\n{}\ngot:\n{}\n'.format(expected, res)
            return False

        for child in expected: 
            if not res['children'].has_key(child):
                print 'invalid directory: missing {} in {}'.format(child, res)
                return False

    # remove directories (should fail, but '/dir2' and '/dir1/dir3/dir4''s idata should be gone)
    for dpath in ['/dir1/dir3/dir4', '/dir1/dir3', '/dir2', '/dir1']:
        print 'rmdir {} (expect failure)'.format(dpath)
        res = testlib.blockstack_cli_datastore_rmdir( 'foo.test', datastore_pk, dpath, ses )
        if 'error' not in res:
            print 'accidentally succeeded to rmdir {}: {}'.format(dpath, res)
            return False

        if dpath not in ['/dir1/dir3/dir4', '/dir2']:
            if res.get('errno') != errno.ENOTEMPTY:
                print 'wrong errno for deleting {}'.format(res)
                return False

    # list directories (should fail for /dir1/dir3/dir4 and /dir2 since its idata got deleted, but it should still work for everyone else)
    for dpath, expected in [('/dir2', []), ('/dir1/dir3/dir4', [])]:
        print 'listdir {} (expect failure)'.format(dpath)
        res = testlib.blockstack_cli_datastore_listdir( 'foo.test', datastore_id, dpath, ses )
        if 'error' not in res or 'errno' not in res:
            print 'accidentally succeeded to list {}: {}'.format(dpath, res)
            return False

        if res['errno'] != errno.EREMOTEIO:
            print 'wrong errno: {}'.format(res)
            return False

    # these should still work
    for dpath, expected in [('/', ['dir1', 'dir2']), ('/dir1', ['dir3']), ('/dir1/dir3', ['dir4'])]:
        print 'listdir {} (should still work)'.format(dpath)
        res = testlib.blockstack_cli_datastore_listdir( 'foo.test', datastore_id, dpath, ses )
        if 'error' in res:
            print 'failed to listdir {}: {}'.format(dpath, res['error'])
            return False

        if len(res['children'].keys()) != len(expected):
            print 'invalid directory: expected:\n{}\ngot:\n{}\n'.format(expected, res)
            return False

        for child in expected: 
            if not res['children'].has_key(child):
                print 'invalid directory: missing {} in {}'.format(child, res)
                return False

    # restore service
    res = testlib.blockstack_test_setenv('BLOCKSTACK_INTEGRATION_TEST_STORAGE_FAILURE', '0')
    if 'error' in res:
        print 'failed to setenv: {}'.format(res)
        return False

    # remove directories (should succeed)
    for dpath in ['/dir1/dir3/dir4', '/dir1/dir3', '/dir2', '/dir1']:
        print 'rmdir {}'.format(dpath)
        res = testlib.blockstack_cli_datastore_rmdir( 'foo.test', datastore_pk, dpath, ses )
        if 'error' in res:
            print 'failed to rmdir {}: {}'.format(dpath, res['error'])
            return False

    # stat directories (should all fail)
    for dpath in ['/dir1/dir3/dir4', '/dir1/dir3', '/dir2', '/dir1']:
        print 'stat {} (expect failure)'.format(dpath)
        res = testlib.blockstack_cli_datastore_stat( 'foo.test', datastore_id, dpath, ses )
        if 'error' not in res or 'errno' not in res:
            print 'accidentally succeeded to stat {}: {}'.format(dpath, res)
            return False

        if res['errno'] != errno.ENOENT:
            print 'wrong errno: {}'.format(res)
            return False

    # list directories (should all fail) 
    for dpath, expected in [('/dir2', []), ('/dir1', ['dir3']), ('/dir1/dir3', ['dir4']), ('/dir1/dir3/dir4', [])]:
        print 'listdir {} (expect failure)'.format(dpath)
        res = testlib.blockstack_cli_datastore_listdir( 'foo.test', datastore_id, dpath, ses )
        if 'error' not in res or 'errno' not in res:
            print 'accidentally succeeded to list {}: {}'.format(dpath, res)
            return False

        if res['errno'] != errno.ENOENT:
            print 'wrong errno: {}'.format(res)
            return False

    # root should be empty 
    print 'listdir {}'.format('/')
    res = testlib.blockstack_cli_datastore_listdir( 'foo.test', datastore_id, '/', ses )
    if 'error' in res:
        print 'failed to listdir /: {}'.format(res['error'])
        return False

    if len(res['children'].keys()) > 0:
        print 'root still has children: {}'.format(res['children'].keys())
        return False

    # simulate a failure in the 'test' driver 
    res = testlib.blockstack_test_setenv('BLOCKSTACK_INTEGRATION_TEST_STORAGE_FAILURE', '1')
    if 'error' in res:
        print 'failed to setenv: {}'.format(res)
        return False


    # delete datastore (should fail) 
    print 'delete datastore (expect failure)'
    res = testlib.blockstack_cli_delete_datastore( 'foo.test', datastore_pk, ses )
    if 'error' not in res:
        print 'accidentally succeeded to delete datastore'
        print json.dumps(res)
        return False

    # restore service
    res = testlib.blockstack_test_setenv('BLOCKSTACK_INTEGRATION_TEST_STORAGE_FAILURE', '0')
    if 'error' in res:
        print 'failed to setenv: {}'.format(res)
        return False

    print 'delete datastore'
    res = testlib.blockstack_cli_delete_datastore( 'foo.test', datastore_pk, ses )
    if 'error' in res:
        print 'failed to delete foo-app.com datastore'
        print json.dumps(res)
        return False

    # no more data in test-disk driver 
    names = os.listdir("/tmp/blockstack-integration-test-storage/mutable")
    if names != ['foo.test']:
        print 'improper cleanup on test'
        return False

    # due to our failed mkdir of /dir1 and /dir2, these
    # will have leaked. Expect 5 entries (including foo.test):
    # an idata and inode header for both dirs
    names = os.listdir("/tmp/blockstack-disk/mutable")
    if len(names) != 5:
        print 'imporper cleanup on disk'
        return False

    if 'foo.test' not in names:
        print 'missing foo.test'
        return False

    testlib.next_block( **kw )