def parse_DID(did, name_type=None): """ Given a DID string, parse it into {'address': ..., 'index': ..., 'name_type'} Raise on invalid DID """ did_pattern = '^did:stack:v0:({}{{25,35}})-([0-9]+)$'.format(OP_BASE58CHECK_CLASS) m = re.match(did_pattern, did) assert m, 'Invalid DID: {}'.format(did) original_address = str(m.groups()[0]) name_index = int(m.groups()[1]) vb = keylib.b58check.b58check_version_byte(original_address) name_type = None if vb in [SUBDOMAIN_ADDRESS_VERSION_BYTE, SUBDOMAIN_ADDRESS_MULTISIG_VERSION_BYTE]: name_type = 'subdomain' # decode version if vb == SUBDOMAIN_ADDRESS_VERSION_BYTE: vb = bitcoin_blockchain.version_byte else: vb = bitcoin_blockchain.multisig_version_byte original_address = virtualchain.address_reencode(original_address, version_byte=vb) else: name_type = 'name' original_address = virtualchain.address_reencode(original_address) return {'address': original_address, 'index': name_index, 'name_type': name_type}
def check(state_engine): # not revealed, but ready ns = state_engine.get_namespace_reveal("test") if ns is not None: print "namespace reveal exists" return False ns = state_engine.get_namespace("test") if ns is None: print "no namespace" return False if ns['namespace_id'] != 'test': print "wrong namespace" return False # not preordered addr = virtualchain.address_reencode(virtualchain.get_privkey_address(pk)) addr2 = virtualchain.address_reencode( virtualchain.get_privkey_address(pk2)) preorder = state_engine.get_name_preorder( "foo.test", virtualchain.make_payment_script(addr), addr2) if preorder is not None: print "preorder exists" return False # registered name_rec = state_engine.get_name("foo.test") if name_rec is None: print "name does not exist" return False # owned by if name_rec['address'] != addr2 or name_rec[ 'sender'] != virtualchain.make_payment_script(addr2): print "sender is wrong" return False # paid with Stacks stacks_price = blockstack.lib.scripts.price_name_stacks( 'foo', ns, state_engine.lastblock) if name_rec['token_fee'] != stacks_price: print 'paid wrong token fee' print 'expected {} ({}), got {} ({})'.format( stacks_price, type(stacks_price), name_rec['token_fee'], type(name_rec['token_fee'])) return False if name_rec['op_fee'] > 5500: # dust minimum print 'paid in BTC ({})'.format(name_rec['op_fee']) return False # renewed on third attempt if name_rec['first_registered'] + 3 != name_rec['last_renewed']: print 'renewed at the wrong time: {} + 3 != {}'.format( name_rec['first_registered'], name_rec['last_renewed']) return False return True
def unlock_wallet(password=None, config_dir=CONFIG_DIR, wallet_path=None): """ Unlock the wallet, and store it to the running RPC daemon. This will only work if the wallet is in the latest supported state. Otherwise, the caller may need to migrate the wallet first. Return {'status': True} on success return {'error': ...} on error """ config_path = os.path.join(config_dir, CONFIG_FILENAME) wallet_path = os.path.join(config_dir, WALLET_FILENAME) if wallet_path is None else wallet_path if is_wallet_unlocked(config_dir): return {'status': True} try: if password is None: password = prompt_wallet_password() with open(wallet_path, "r") as f: data = f.read() data = json.loads(data) # decrypt... wallet_info = decrypt_wallet( data, password, config_path=config_path ) if 'error' in wallet_info: log.error('Failed to decrypt wallet: {}'.format(wallet_info['error'])) return wallet_info wallet = wallet_info['wallet'] if wallet_info['migrated']: # need to have the user migrate the wallet first return {'error': 'Wallet is in legacy format. Please migrate it with the `setup_wallet` command.', 'legacy': True} # save to RPC daemon try: res = save_keys_to_memory( wallet, config_path=config_path ) except KeyError as ke: if BLOCKSACK_DEBUG is not None: data = json.dumps(wallet, indent=4, sort_keys=True) log.error('data:\n{}\n'.format(data)) raise if 'error' in res: return res addresses = { 'payment_address': virtualchain.address_reencode(wallet['payment_addresses'][0]), 'owner_address': virtualchain.address_reencode(wallet['owner_addresses'][0]), 'data_pubkey': virtualchain.address_reencode(wallet['data_pubkeys'][0]) } return {'status': True, 'addresses': addresses} except KeyboardInterrupt: return {'error': 'Interrupted'}
def unlock_wallet(password=None, config_dir=CONFIG_DIR, wallet_path=None): """ Unlock the wallet, and store it to the running RPC daemon. This will only work if the wallet is in the latest supported state. Otherwise, the caller may need to migrate the wallet first. Return {'status': True} on success return {'error': ...} on error """ config_path = os.path.join(config_dir, CONFIG_FILENAME) wallet_path = os.path.join(config_dir, WALLET_FILENAME) if wallet_path is None else wallet_path if is_wallet_unlocked(config_dir): return {'status': True} try: if password is None: password = prompt_wallet_password() with open(wallet_path, "r") as f: data = f.read() data = json.loads(data) # decrypt... wallet_info = decrypt_wallet( data, password, config_path=config_path ) if 'error' in wallet_info: log.error('Failed to decrypt wallet: {}'.format(wallet_info['error'])) return wallet_info wallet = wallet_info['wallet'] if wallet_info['migrated']: # need to have the user migrate the wallet first return {'error': 'Wallet is in legacy format. Please migrate it with the `setup_wallet` command.', 'legacy': True} # save to RPC daemon try: res = save_keys_to_memory( wallet, config_path=config_path ) except KeyError as ke: if BLOCKSTACK_DEBUG is not None: data = json.dumps(wallet, indent=4, sort_keys=True) log.error('data:\n{}\n'.format(data)) raise if 'error' in res: return res addresses = { 'payment_address': virtualchain.address_reencode(wallet['payment_addresses'][0]), 'owner_address': virtualchain.address_reencode(wallet['owner_addresses'][0]), 'data_pubkey': virtualchain.address_reencode(wallet['data_pubkeys'][0]) } return {'status': True, 'addresses': addresses} except KeyboardInterrupt: return {'error': 'Interrupted'}
def check( state_engine ): # not revealed, but ready ns = state_engine.get_namespace_reveal( "test" ) if ns is not None: print "namespace reveal exists" return False ns = state_engine.get_namespace( "test" ) if ns is None: print "no namespace" return False if ns['namespace_id'] != 'test': print "wrong namespace" return False # not preordered addr = virtualchain.address_reencode(virtualchain.get_privkey_address(pk)) addr2 = virtualchain.address_reencode(virtualchain.get_privkey_address(pk2)) preorder = state_engine.get_name_preorder( "foo.test", virtualchain.make_payment_script(addr), wallets[3].addr ) if preorder is not None: print "preorder exists for foo.test" return False # not registered name_rec = state_engine.get_name( "foo.test" ) if name_rec is not None: print "name exists" return False # not preordered preorder = state_engine.get_name_preorder('bar.test', virtualchain.make_payment_script(addr2), wallets[3].addr) if preorder is not None: print 'preorder exists for bar.test' return False # registered name_rec = state_engine.get_name('bar.test') if name_rec is None: print 'bar.test does not exist' return False # owned if name_rec['address'] != wallets[3].addr or name_rec['sender'] != virtualchain.make_payment_script(wallets[3].addr): print 'bar.test not owned' return False # overpaid if name_rec['op_fee'] > 5500 or name_rec['token_fee'] != blockstack.lib.scripts.price_name_stacks('bar', ns, state_engine.lastblock) + 1: print 'bar.test improperly paid' return False return True
def scenario( wallets, **kw ): global pk, pk2 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, version_bits=blockstack.lib.config.NAMESPACE_VERSION_PAY_TO_CREATOR) testlib.next_block( **kw ) testlib.blockstack_namespace_ready( "test", wallets[1].privkey ) testlib.next_block( **kw ) # pay for a name in a v1 namespace with Stacks addr = virtualchain.address_reencode(virtualchain.get_privkey_address(pk)) addr2 = virtualchain.address_reencode(virtualchain.get_privkey_address(pk2)) # calculate the cost of doing so namespace = testlib.get_state_engine().get_namespace('test') stacks_price = blockstack.lib.scripts.price_name_stacks('foo', namespace, testlib.get_current_block(**kw)) btc_price = blockstack.lib.scripts.price_name('foo', namespace, testlib.get_current_block(**kw)) print '' print 'price of {} in Stacks is {}'.format('foo.test', stacks_price) print 'price of {} in BTC is {}'.format('foo.test', btc_price) print '' testlib.blockstack_send_tokens(addr, "STACKS", stacks_price + 2, wallets[0].privkey) testlib.blockstack_send_tokens(addr2, "STACKS", stacks_price + 1, wallets[0].privkey) testlib.send_funds(wallets[0].privkey, 3*btc_price, addr) testlib.send_funds(wallets[0].privkey, 3*btc_price, addr2) testlib.next_block(**kw) # preorder/register using Stacks testlib.blockstack_name_preorder( "foo.test", pk, addr2) testlib.blockstack_name_preorder( "bar.test", pk2, addr) testlib.next_block( **kw ) testlib.blockstack_name_register( "foo.test", pk, addr2 ) testlib.blockstack_name_register( "bar.test", pk2, addr ) testlib.next_block( **kw ) testlib.next_block( **kw ) testlib.next_block( **kw ) testlib.next_block( **kw ) # end of pay to namespace creator # renew using more stacks than we have (should fail) # bar.test should succeed testlib.blockstack_name_renew('foo.test', pk2, price={'units': 'STACKS', 'amount': stacks_price + 2}, expect_fail=True, safety_checks=False) testlib.blockstack_name_renew('bar.test', pk, price={'units': 'STACKS', 'amount': stacks_price + 1}) testlib.next_block(**kw) testlib.expect_snv_fail_at('foo.test', testlib.get_current_block(**kw))
def convert_funds_to_segwit(payment_key, tx_fee): # convert payment key to bech32 addr = virtualchain.address_reencode(virtualchain.get_privkey_address(payment_key)) pubk = virtualchain.lib.ecdsalib.ecdsa_private_key(payment_key, compressed=True).public_key().to_hex() addrhash = virtualchain.lib.hashing.bin_hash160(pubk.decode('hex')).encode('hex') segwit_addr = virtualchain.segwit_addr_encode(addrhash.decode('hex'), hrp='bcrt') # fund the segwit address, and then use the same payment key to send the transaction fund_inputs = testlib.get_utxos(addr) fund_outputs = [ {"script": '0014' + addrhash, "value": sum(inp['value'] for inp in fund_inputs) - tx_fee}, ] fund_prev_outputs = [{'out_script': inp['out_script'], 'value': inp['value']} for inp in fund_inputs] fund_serialized_tx = testlib.serialize_tx(fund_inputs, fund_outputs) fund_signed_tx = virtualchain.tx_sign_all_unsigned_inputs(payment_key, fund_prev_outputs, fund_serialized_tx) print fund_signed_tx res = testlib.broadcast_transaction(fund_signed_tx) assert 'error' not in res, res res.update({ 'utxos': fund_outputs }) return res
def _tx_pay_btc(txhex, privk, burn_price, burn_addr=blockstack.lib.config.BLOCKSTACK_BURN_ADDRESS): tx = virtualchain.btc_tx_deserialize(txhex) # up the burn amount tx['outs'][3]['script'] = virtualchain.btc_make_payment_script(burn_addr) tx['outs'][3]['value'] = burn_price tx['outs'][4]['value'] -= burn_price # re-sign for i in tx['ins']: i['script'] = '' txhex = virtualchain.btc_tx_serialize(tx) _addr = virtualchain.address_reencode(virtualchain.get_privkey_address(privk)) txhex_signed = virtualchain.tx_sign_all_unsigned_inputs(privk, testlib.get_utxos(_addr), txhex) # re-sign the last output with the payment key tx_signed = virtualchain.btc_tx_deserialize(txhex_signed) tx_signed['ins'][-1]['script'] = '' txhex_signed = virtualchain.tx_sign_all_unsigned_inputs(testlib.get_default_payment_wallet().privkey, testlib.get_utxos(testlib.get_default_payment_wallet().addr), virtualchain.btc_tx_serialize(tx_signed)) print txhex_signed res = testlib.broadcast_transaction(txhex_signed) assert 'error' not in res return res
def check( state_engine ): # not revealed, but ready ns = state_engine.get_namespace_reveal( "test" ) if ns is not None: print "namespace reveal exists" return False ns = state_engine.get_namespace( "test" ) if ns is None: print "no namespace" return False if ns['namespace_id'] != 'test': print "wrong namespace" return False # not preordered addr = virtualchain.address_reencode(virtualchain.get_privkey_address(pk)) preorder = state_engine.get_name_preorder( "foo.test", virtualchain.make_payment_script(addr), wallets[3].addr ) if preorder is not None: print "preorder exists" return False # not registered name_rec = state_engine.get_name( "foo.test" ) if name_rec is not None: print "name exists" return False return True
def get_master_address(self): if self.master_address is not None: return self.master_address hex_privkey = self.get_master_privkey() hex_pubkey = get_pubkey_hex(hex_privkey) return virtualchain.address_reencode(keylib.public_key_to_address(hex_pubkey))
def scenario( wallets, **kw ): global pk, pk2 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, version_bits=blockstack.lib.config.NAMESPACE_VERSION_PAY_TO_CREATOR) testlib.next_block( **kw ) testlib.blockstack_namespace_ready( "test", wallets[1].privkey ) testlib.next_block( **kw ) # pay for a name in a v1 namespace with Stacks addr = virtualchain.address_reencode(virtualchain.get_privkey_address(pk)) addr2 = virtualchain.address_reencode(virtualchain.get_privkey_address(pk2)) # calculate the cost of doing so namespace = testlib.get_state_engine().get_namespace('test') stacks_price = blockstack.lib.scripts.price_name_stacks('foo', namespace, testlib.get_current_block(**kw)) btc_price = blockstack.lib.scripts.price_name('foo', namespace, testlib.get_current_block(**kw)) print '' print 'price of {} in Stacks is {}'.format('foo.test', stacks_price) print '' testlib.blockstack_send_tokens(addr, "STACKS", stacks_price + 1, wallets[0].privkey) testlib.blockstack_send_tokens(addr2, "STACKS", stacks_price + 1, wallets[0].privkey) testlib.send_funds(wallets[0].privkey, btc_price - 5500 - 1, addr) # deliberately insufficient funds for ordering the name in BTC testlib.send_funds(wallets[0].privkey, btc_price - 5500 - 1, addr2) # deliberately insufficient funds for ordering the name in BTC testlib.next_block(**kw) testlib.next_block(**kw) testlib.next_block(**kw) testlib.next_block(**kw) # move beyond pay-to-creator requirement # preorder/register using Stacks, but overpay (foo.test fails, bar.test succeeds) testlib.blockstack_name_preorder( "foo.test", pk, wallets[3].addr, price={'units': 'STACKS', 'amount': stacks_price + 2}, expect_fail=True, safety_checks=False) testlib.blockstack_name_preorder( "bar.test", pk2, wallets[3].addr, price={'units': 'STACKS', 'amount': stacks_price + 1}) testlib.next_block( **kw ) testlib.expect_snv_fail_at('foo.test', testlib.get_current_block(**kw)) testlib.send_funds(wallets[0].privkey, btc_price - 5500 - 1, addr) # deliberately insufficient funds for ordering the name in BTC testlib.send_funds(wallets[0].privkey, btc_price - 5500 - 1, addr2) # deliberately insufficient funds for ordering the name in BTC testlib.blockstack_name_register( "foo.test", pk, wallets[3].addr ) # should fail testlib.blockstack_name_register( "bar.test", pk2, wallets[3].addr ) # should succeed testlib.next_block( **kw ) testlib.expect_snv_fail_at('foo.test', testlib.get_current_block(**kw))
def get_master_address(self): if self.master_address is not None: return self.master_address hex_privkey = self.get_master_privkey() hex_pubkey = get_pubkey_hex(hex_privkey) return virtualchain.address_reencode( keylib.public_key_to_address(hex_pubkey))
def send_as_segwit_bech32(txhex, payment_key): print 'txhex: {}'.format(txhex) # get op-return data tx = virtualchain.btc_tx_deserialize(txhex) payload = tx['outs'][0]['script'] print json.dumps(tx, indent=4, sort_keys=True) # convert payment key to bech32 addr = virtualchain.address_reencode(virtualchain.get_privkey_address(payment_key)) pubk = virtualchain.lib.ecdsalib.ecdsa_private_key(payment_key, compressed=True).public_key().to_hex() addrhash = virtualchain.lib.hashing.bin_hash160(pubk.decode('hex')).encode('hex') segwit_addr = virtualchain.segwit_addr_encode(addrhash.decode('hex'), hrp='bcrt') print 'privk = {}'.format(payment_key) print 'pubk = {}'.format(pubk) print 'addr = {}'.format(addr) print 'segwit addr = {}'.format(segwit_addr) print 'script = 00{}'.format(addrhash) tx_fee = 5500 res = convert_funds_to_segwit(payment_key, tx_fee) fund_outputs = res['utxos'] fund_txid = res['tx_hash'] new_tx = { 'locktime': 0, 'version': 2, 'ins': [ {'outpoint': {'hash': fund_txid, 'index': 0}, 'script': '', 'witness_script': '', 'sequence': 4294967295}, ], 'outs': [ {'script': tx['outs'][0]['script'], 'value': tx['outs'][0]['value']}, {'script': '0014' + addrhash, 'value': fund_outputs[0]['value'] - tx_fee * 2}, {'script': tx['outs'][2]['script'], 'value': tx['outs'][2]['value']} ] } unsigned_txhex = virtualchain.btc_tx_serialize(new_tx) print 'unsigned: {}'.format(unsigned_txhex) pk_segwit = virtualchain.make_segwit_info(payment_key) print json.dumps(pk_segwit, indent=4, sort_keys=True) signed_txhex = virtualchain.tx_sign_input(unsigned_txhex, 0, fund_outputs[0]['script'], fund_outputs[0]['value'], pk_segwit, segwit=True, scriptsig_type='p2wpkh') print 'signed: {}'.format(signed_txhex) res = testlib.broadcast_transaction(signed_txhex) assert 'error' not in res return res
def do_POST(self): content_type = self.headers.getheader('content-type') postvars = {} if content_type is not None: ctype, pdict = cgi.parse_header(content_type) if ctype == 'multipart/form-data': postvars = cgi.parse_multipart(self.rfile, pdict) elif ctype == 'application/x-www-form-urlencoded': length = int(self.headers.getheader('content-length')) postvars = cgi.parse_qs(self.rfile.read(length), keep_blank_values=1) if self.path == '/sendfunds': # fund an address addr = postvars.get('addr', [None]) value = postvars.get('value', [None]) if addr[0] is None or value[0] is None: log.error("Missing addr or value") self.send_response(400, "Invalid request: missing addr or value") self.end_headers() return try: value = int(value[0]) addr = virtualchain.address_reencode(addr[0]) except: log.error("Failed to read addr and/or value") log.error("postvars = {}".format(postvars)) self.send_response(400, "Invalid addr or value") self.end_headers() return # can't take more than 0.1 BTC per address # send funds res = testlib.send_funds( testlib.get_default_payment_wallet().privkey, value, addr) if 'error' in res: log.error("Failed to send {} from {} to {}: {}".format( value, testlib.get_default_payment_wallet().privkey, addr, res)) self.send_response(400, "Failed to send value") self.end_headers() return self.send_response(302) self.send_header('location', '/') self.end_headers() return else: log.error("Unsupported path {}".format(self.path)) self.send_response(400, "Only support /sendfunds at this time") self.end_headers() return
def scenario(wallets, **kw): 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, version_bits=blockstack.NAMESPACE_VERSION_PAY_WITH_STACKS) testlib.next_block(**kw) testlib.blockstack_namespace_ready("test", wallets[1].privkey) testlib.next_block(**kw) balances = testlib.get_wallet_balances(wallets[2]) assert balances[wallets[2].addr][STACKS] == 0 # should fail--not enough stacks testlib.blockstack_name_preorder("foo.test", wallets[2].privkey, wallets[3].addr, safety_checks=False, expect_fail=True) testlib.next_block(**kw) name_cost = testlib.blockstack_get_name_token_cost('foo.test') assert name_cost['units'] == STACKS assert name_cost['amount'] > 0 # send tokens and preorder multiple times in the block # should all succeed, BUT: force them to go in order through UTXO chaining for i in range(0, 5): name_recipient_privkey = wallets[-(i + 1)].privkey name_recipient_addr = virtualchain.address_reencode( virtualchain.get_privkey_address(name_recipient_privkey)) testlib.blockstack_send_tokens(name_recipient_addr, "STACKS", name_cost['amount'], wallets[0].privkey) testlib.send_funds(wallets[0].privkey, 1000000, name_recipient_addr) testlib.blockstack_name_preorder("foo_{}.test".format(i), name_recipient_privkey, wallets[3].addr, safety_checks=False) testlib.blockstack_name_register("foo_{}.test".format(i), name_recipient_privkey, wallets[3].addr, safety_checks=False) testlib.next_block(**kw)
def decrypt_wallet_legacy(data, key_defaults, password): """ Decrypt 0.14.1 and earlier wallets, given the wallet data, the default key values, and the password. Return {'status': True, 'wallet': wallet} on success Raise on error """ new_wallet = {} # NOTE: 'owner' must come before 'data', since we may use it to generate the data key keynames = ['payment', 'owner', 'data'] for keyname in keynames: # get the key's private key info and address keyname_privkey = '{}_privkey'.format(keyname) keyname_addresses = '{}_addresses'.format(keyname) encrypted_keyname = 'encrypted_{}_privkey'.format(keyname) if encrypted_keyname in data: # This key was explicitly defined in the wallet. # It is not guaranteed to be a child key of the # master private key. field = decrypt_private_key_info(data[encrypted_keyname], password) if 'error' in field: ret = { 'error': "Failed to decrypt {}: {}".format(encrypted_keyname, field['error']) } log.debug('Failed to decrypt {}: {}'.format( encrypted_keyname, field['error'])) return ret new_wallet[keyname_privkey] = field['private_key_info'] new_wallet[keyname_addresses] = [field['address']] else: # Legacy migration: this key is not defined in the wallet # use the appopriate default key assert keyname in key_defaults, 'BUG: no legacy private key for {}'.format( keyname) default_privkey = key_defaults[keyname] new_wallet[keyname_privkey] = default_privkey new_wallet[keyname_addresses] = [ virtualchain.address_reencode( keylib.ECPrivateKey( default_privkey, compressed=False).public_key().address()) ] return {'status': True, 'wallet': new_wallet}
def scenario( wallets, **kw ): global pk, pk2 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 ) # pay for a name in a v1 namespace with Stacks addr = virtualchain.address_reencode(virtualchain.get_privkey_address(pk)) addr2 = virtualchain.address_reencode(virtualchain.get_privkey_address(pk2)) # calculate the cost of doing so namespace = testlib.get_state_engine().get_namespace('test') stacks_price = blockstack.lib.scripts.price_name_stacks('foo', namespace, testlib.get_current_block(**kw)) btc_price = blockstack.lib.scripts.price_name('foo', namespace, testlib.get_current_block(**kw)) print '' print 'price of {} in Stacks is {}'.format('foo.test', stacks_price) print 'price of {} in BTC is {}'.format('foo.test', btc_price) print '' testlib.blockstack_send_tokens(addr, "STACKS", stacks_price + 1, wallets[0].privkey) testlib.blockstack_send_tokens(addr2, "STACKS", stacks_price + 1, wallets[0].privkey) testlib.send_funds(wallets[0].privkey, 2*btc_price, addr) testlib.send_funds(wallets[0].privkey, 2*btc_price, addr2) testlib.next_block(**kw) # preorder/register using Stacks testlib.blockstack_name_preorder( "foo.test", pk, addr2, price={'units': 'STACKS', 'amount': stacks_price + 1}) testlib.next_block( **kw ) testlib.blockstack_name_register( "foo.test", pk, addr2 ) testlib.next_block( **kw ) # renew using Stacks testlib.blockstack_name_renew('foo.test', pk2, price={'units': 'STACKS', 'amount': stacks_price + 1}) testlib.next_block(**kw)
def get_addresses_from_file(config_dir=CONFIG_DIR, wallet_path=None): """ Load up the set of addresses from the wallet Not all fields may be set in older wallets. """ data_pubkey = None payment_address = None owner_address = None if wallet_path is None: wallet_path = os.path.join(config_dir, WALLET_FILENAME) if not os.path.exists(wallet_path): log.error('No such file or directory: {}'.format(wallet_path)) return None, None, None with open(wallet_path, 'r') as f: data = f.read() try: data = json.loads(data) # best we can do is guarantee that this is a dict assert isinstance(data, dict) except: log.error('Invalid wallet data: not a JSON object (in {})'.format( wallet_path)) return None, None, None # extract addresses if data.has_key('payment_addresses'): payment_address = virtualchain.address_reencode( str(data['payment_addresses'][0])) if data.has_key('owner_addresses'): owner_address = virtualchain.address_reencode( str(data['owner_addresses'][0])) if data.has_key('data_pubkeys'): data_pubkey = str(data['data_pubkeys'][0]) return payment_address, owner_address, data_pubkey
def make_DID(name_type, address, index): """ Standard way of making a DID. name_type is "name" or "subdomain" """ if name_type not in ['name', 'subdomain']: raise ValueError("Require 'name' or 'subdomain' for name_type") if name_type == 'name': address = virtualchain.address_reencode(address) else: # what's the current version byte? vb = keylib.b58check.b58check_version_byte(address) if vb == bitcoin_blockchain.version_byte: # singlesig vb = SUBDOMAIN_ADDRESS_VERSION_BYTE else: vb = SUBDOMAIN_ADDRESS_MULTISIG_VERSION_BYTE address = virtualchain.address_reencode(address, version_byte=vb) return 'did:stack:v0:{}-{}'.format(address, index)
def get_addresses_from_file(config_dir=CONFIG_DIR, wallet_path=None): """ Load up the set of addresses from the wallet Not all fields may be set in older wallets. """ data_pubkey = None payment_address = None owner_address = None if wallet_path is None: wallet_path = os.path.join(config_dir, WALLET_FILENAME) if not os.path.exists(wallet_path): log.error('No such file or directory: {}'.format(wallet_path)) return None, None, None with open(wallet_path, 'r') as f: data = f.read() try: data = json.loads(data) # best we can do is guarantee that this is a dict assert isinstance(data, dict) except: log.error('Invalid wallet data: not a JSON object (in {})'.format(wallet_path)) return None, None, None # extract addresses if data.has_key('payment_addresses'): payment_address = virtualchain.address_reencode(str(data['payment_addresses'][0])) if data.has_key('owner_addresses'): owner_address = virtualchain.address_reencode(str(data['owner_addresses'][0])) if data.has_key('data_pubkeys'): data_pubkey = str(data['data_pubkeys'][0]) return payment_address, owner_address, data_pubkey
def get_child_address(self, index=0): """ @index is the child index Returns: child address for given @index """ if self.child_addresses is not None: return self.child_addresses[index] # force decompressed... hex_privkey = self.get_child_privkey(index) hex_pubkey = get_pubkey_hex(hex_privkey) return virtualchain.address_reencode(keylib.public_key_to_address(hex_pubkey))
def get_child_address(self, index=0): """ @index is the child index Returns: child address for given @index """ if self.child_addresses is not None: return self.child_addresses[index] # force decompressed... hex_privkey = self.get_child_privkey(index) hex_pubkey = get_pubkey_hex(hex_privkey) return virtualchain.address_reencode( keylib.public_key_to_address(hex_pubkey))
def decrypt_wallet_legacy(data, key_defaults, password): """ Decrypt 0.14.1 and earlier wallets, given the wallet data, the default key values, and the password. Return {'status': True, 'wallet': wallet} on success Raise on error """ new_wallet = {} # NOTE: 'owner' must come before 'data', since we may use it to generate the data key keynames = ['payment', 'owner', 'data'] for keyname in keynames: # get the key's private key info and address keyname_privkey = '{}_privkey'.format(keyname) keyname_addresses = '{}_addresses'.format(keyname) encrypted_keyname = 'encrypted_{}_privkey'.format(keyname) if encrypted_keyname in data: # This key was explicitly defined in the wallet. # It is not guaranteed to be a child key of the # master private key. field = decrypt_private_key_info(data[encrypted_keyname], password) if 'error' in field: ret = {'error': "Failed to decrypt {}: {}".format(encrypted_keyname, field['error'])} log.debug('Failed to decrypt {}: {}'.format(encrypted_keyname, field['error'])) return ret new_wallet[keyname_privkey] = field['private_key_info'] new_wallet[keyname_addresses] = [field['address']] else: # Legacy migration: this key is not defined in the wallet # use the appopriate default key assert keyname in key_defaults, 'BUG: no legacy private key for {}'.format(keyname) default_privkey = key_defaults[keyname] new_wallet[keyname_privkey] = default_privkey new_wallet[keyname_addresses] = [ virtualchain.address_reencode( keylib.ECPrivateKey(default_privkey, compressed=False).public_key().address() ) ] return {'status': True, 'wallet': new_wallet}
def scenario( wallets, **kw ): # try to spend tokens to ourselves testlib.blockstack_send_tokens(wallets[0].addr, "STACKS", 100000, wallets[0].privkey, safety_checks=False, expect_fail=True) testlib.next_block(**kw) # end of 689 assert virtualchain.lib.indexer.StateEngine.get_block_statistics(testlib.get_current_block(**kw))['num_processed_ops'] == 0 # try to spend more tokens than we have testlib.blockstack_send_tokens(wallets[1].addr, "STACKS", 600001, wallets[0].privkey, safety_checks=False, expect_fail=True) testlib.next_block(**kw) assert virtualchain.lib.indexer.StateEngine.get_block_statistics(testlib.get_current_block(**kw))['num_processed_ops'] == 0 # try to spend tokens that don't exist testlib.blockstack_send_tokens(wallets[1].addr, "noop", 600000, wallets[0].privkey, safety_checks=False, expect_fail=True) testlib.next_block(**kw) assert virtualchain.lib.indexer.StateEngine.get_block_statistics(testlib.get_current_block(**kw))['num_processed_ops'] == 0 # try to spend tokens with an invalid consensus hash (note that this is epoch 4) testlib.blockstack_send_tokens(wallets[1].addr, "STACKS", 600000, wallets[0].privkey, consensus_hash='00' * 16, safety_checks=False, expect_fail=True) testlib.next_block(**kw) assert virtualchain.lib.indexer.StateEngine.get_block_statistics(testlib.get_current_block(**kw))['num_processed_ops'] == 0 # try to spend tokens from an account that doesn't exist pk = virtualchain.lib.ecdsalib.ecdsa_private_key().to_hex() addr = virtualchain.address_reencode(virtualchain.get_privkey_address(pk)) testlib.send_funds(wallets[0].privkey, 10240000, addr) testlib.blockstack_send_tokens(wallets[1].addr, "STACKS", 100000, pk, safety_checks=False, expect_fail=True) testlib.next_block(**kw) assert virtualchain.lib.indexer.StateEngine.get_block_statistics(testlib.get_current_block(**kw))['num_processed_ops'] == 0 # try to send 0 tokens testlib.blockstack_send_tokens(wallets[1].addr, "STACKS", 0, wallets[0].privkey, safety_checks=False, expect_fail=True) testlib.next_block(**kw) assert virtualchain.lib.indexer.StateEngine.get_block_statistics(testlib.get_current_block(**kw))['num_processed_ops'] == 0
def replace_output_with_bech32(txhex, output_index, payment_key, addrhash): print 'txhex: {}'.format(txhex) tx = virtualchain.btc_tx_deserialize(txhex) new_tx = { 'locktime': 0, 'version': 1, 'ins': tx['ins'], 'outs': tx['outs'], } for inp in new_tx['ins']: inp['script'] = '' inp['witness_script'] = '' new_tx['outs'][output_index] = { 'script': '0014' + addrhash, 'value': tx['outs'][output_index]['value'] } unsigned_txhex = virtualchain.btc_tx_serialize(new_tx) print 'unsigned: {}'.format(unsigned_txhex) addr = virtualchain.address_reencode( virtualchain.get_privkey_address(payment_key)) utxos = testlib.get_utxos(addr) prev_outputs = [{ 'out_script': inp['out_script'], 'value': inp['value'] } for inp in utxos] signed_txhex = virtualchain.tx_sign_all_unsigned_inputs( payment_key, prev_outputs, unsigned_txhex) print 'signed: {}'.format(signed_txhex) res = testlib.broadcast_transaction(signed_txhex) assert 'error' not in res return res
def _tx_pay_btc(txhex, privk, btc_paid, burn_addr): tx = virtualchain.btc_tx_deserialize(txhex) # up the burn amount btc_price = blockstack.lib.scripts.price_name('baz', namespace, testlib.get_current_block(**kw)) tx['outs'][2]['script'] = virtualchain.btc_make_payment_script(burn_addr) tx['outs'][2]['value'] = btc_paid tx['outs'][1]['value'] -= btc_paid # re-sign for i in tx['ins']: i['script'] = '' txhex = virtualchain.btc_tx_serialize(tx) _addr = virtualchain.address_reencode(virtualchain.get_privkey_address(privk)) txhex_signed = virtualchain.tx_sign_all_unsigned_inputs(privk, testlib.get_utxos(_addr), txhex) print txhex_signed res = testlib.broadcast_transaction(txhex_signed) assert 'error' not in res, res['error'] return res
def make_subdomain_zone_file(domain_name, subdomain_pattern, subdomain_zonefile_pattern, sequence, iters, target_size, seed_privkey): zf_template = "$ORIGIN {}\n$TTL 3600\n{}" private_keychain = keychain.PrivateKeychain.from_private_key(seed_privkey) i = 0 txtrecs = [] prev_zf = None for i in range(0, iters): subdomain_privkey = private_keychain.child(i).private_key() subdomain_addr = virtualchain.address_reencode( virtualchain.ecdsalib.ecdsa_private_key( subdomain_privkey).public_key().address()) subdomain_name = subdomain_pattern.format(i) subdomain_zf = subdomain_zonefile_pattern.format(subdomain_name, i) i += 1 txtrec = subdomains.make_subdomain_txt(subdomain_name, domain_name, subdomain_addr, sequence, subdomain_zf, subdomain_privkey) txtrecs.append(txtrec) zf = zf_template.format(domain_name, '\n'.join(txtrecs)) if len(zf) > target_size: raise Exception( "Could not pack {} subdomains (at {}) into {} bytes".format( iters, len(txtrecs), target_size)) prev_zf = zf return prev_zf
def check( state_engine ): all_tests = {} # namespace preorder wire format # 0 2 3 23 39 # |-----|---|--------------------------------------|----------------| # magic op hash(ns_id,script_pubkey,reveal_addr) consensus hash namespace_preorders = { "valid": "%s%s" % ("11" * 20, "22" * 16), "too_short": "%s%s" % ("11" * 20, "33" * 15), "too_long": "%s%s00" % ("11" * 20, "22" * 16), } all_tests["*"] = compile_test( "*", namespace_preorders ) # namespace reveal wire format # 0 2 3 7 8 9 10 11 12 13 14 15 16 17 18 20 39 # |-----|---|--------|-----|-----|----|----|----|----|----|-----|-----|-----|--------|----------|-------------------------| # magic op life coeff. base 1-2 3-4 5-6 7-8 9-10 11-12 13-14 15-16 nonalpha version namespace ID # bucket exponents no-vowel # discounts namespace_reveals = { "valid": "%s%s%s%s%s%s%s%s%s%s%s%s%s%s" % ("11111111", "02", "03", "40", "41", "42", "43", "44", "45", "46", "47", "15", "0001", binascii.hexlify("hello")), "non-b38": "%s%s%s%s%s%s%s%s%s%s%s%s%s%s" % ("11111111", "02", "03", "40", "41", "42", "43", "44", "45", "46", "47", "15", "0001", binascii.hexlify("Hello")), "period2": "%s%s%s%s%s%s%s%s%s%s%s%s%s%s" % ("11111111", "02", "03", "40", "41", "42", "43", "44", "45", "46", "47", "15", "0001", binascii.hexlify("He.l.lo")), "period": "%s%s%s%s%s%s%s%s%s%s%s%s%s%s" % ("11111111", "02", "03", "40", "41", "42", "43", "44", "45", "46", "47", "15", "0001", binascii.hexlify(".")), "no-plus": "%s%s%s%s%s%s%s%s%s%s%s%s%s%s" % ("11111111", "02", "03", "40", "41", "42", "43", "44", "45", "46", "47", "15", "0001", binascii.hexlify("hel+lo")), "null_name": "%s%s%s%s%s%s%s%s%s%s%s%s%s%s" % ("11111111", "02", "03", "40", "41", "42", "43", "44", "45", "46", "47", "15", "0001", binascii.hexlify("")), "too_long": "%s%s%s%s%s%s%s%s%s%s%s%s%s%s" % ("11111111", "02", "03", "40", "41", "42", "43", "44", "45", "46", "47", "15", "0001", binascii.hexlify("hellohellohellohello")) } all_tests["&"] = compile_test( "&", namespace_reveals ) # namespace ready wire format # 0 2 3 4 23 # |-----|--|--|------------| # magic op . ns_id namespace_readys = { "valid": binascii.hexlify(".hello"), "non-b38": binascii.hexlify(".Hello"), "period": binascii.hexlify("."), "period2": binascii.hexlify(".hel.lo"), "no-plus": binascii.hexlify(".hel+lo"), "null_name": binascii.hexlify(""), "no-period": binascii.hexlify("hello"), "too_long": binascii.hexlify(".hellohellohellohello") } all_tests["!"] = compile_test( "!", namespace_readys ) # name preorder wire format # 0 2 3 23 39 # |-----|--|----------------------------------------------|--------------| # magic op hash(name.ns_id,script_pubkey,register_addr) consensus hash name_preorders = { "valid": "%s%s" % ("11" * 20, "22" * 16), "too_short": "%s%s" % ("11" * 20, "33" * 15), "too_long": "%s%s00" % ("11" * 20, "22" * 16), } all_tests["?"] = compile_test( "?", name_preorders ) # name register/renew wire format (pre F-day 2017) # 0 2 3 39 # |----|--|-----------------------------| # magic op name.ns_id (37 bytes) # name register/renew wire format (post F-day 2017) # 0 2 3 39 59 # |----|--|----------------------------------|-------------------| # magic op name.ns_id (37 bytes, 0-padded) value hash name_registrations = { "valid": binascii.hexlify("hello.test"), "null_name": binascii.hexlify(""), "non-b38": binascii.hexlify("Hello.test"), "no-namespace": binascii.hexlify("hello"), "null-namespace": binascii.hexlify("hello."), "2period": binascii.hexlify("hello.tes.t"), "no-plus": binascii.hexlify("hel+lo.test"), "too-long": binascii.hexlify("hellohellohellohellohellohellohel.test"), "valid_2": binascii.hexlify("hello.test" + "\x00" * 27 + "\x11" * 20), "null_name_2": binascii.hexlify("\x00" * 37 + "\x11" * 20), "non-b38_2": binascii.hexlify("Hello.test" + "\x00" * 27 + "\x11" * 20), "no-namespace_2": binascii.hexlify("hello" + "\x00" * 32 + "\x11" * 20), "null-namespace_2": binascii.hexlify("hello." + "\x00" * 31 + "\x11" * 20), "2period_2": binascii.hexlify("hello.tes.t" + "\x00" * 26 + "\x11" * 20), "no-plus_2": binascii.hexlify("hel+lo.test" + "\x00" * 26 + "\x11" * 20), "too-long_2": binascii.hexlify("hellohellohellohellohellohellohel.test" + "\x11" * 20), "no_hash": binascii.hexlify("hello.test" + "\x00" * 27), "hash_too_long": binascii.hexlify("hello.test" + "\x00" * 27 + "\x11" * 21), "padding_too_short": binascii.hexlify("hello.test" + "\x00" * 26 + "\x11" * 21), "op_too_short": binascii.hexlify("hello.test" + "\x00" * 26 + "\x11" * 20), } all_tests[":"] = compile_test( ":", name_registrations ) # name update wire format # 0 2 3 19 39 # |-----|--|-----------------------------------|-----------------------| # magic op hash128(name.ns_id,consensus hash) hash160(data) name_updates = { "valid": "%s%s" % ("11" * 16, "22" * 20), "too_short": "%s%s" % ("11" * 16, "33" * 19), "too_long": "%s%s00" % ("11" * 16, "22" * 20), } all_tests["+"] = compile_test( "+", name_updates ) # name transfer wire format # 0 2 3 4 20 36 # |-----|--|----|-------------------|---------------| # magic op keep hash128(name.ns_id) consensus hash # data? name_transfers = { "valid": "%s%s%s" % (binascii.hexlify(">"), "11" * 16, "22" * 16), "valid2": "%s%s%s" % (binascii.hexlify("~"), "11" * 16, "22" * 16), "too_short": "%s%s%s" % (binascii.hexlify(">"), "11" * 16, "33" * 15), "too_long": "%s%s%s00" % (binascii.hexlify(">"), "11" * 16, "22" * 16), "too_short2": "%s%s%s" % (binascii.hexlify("~"), "11" * 16, "33" * 15), "too_long2": "%s%s%s00" % (binascii.hexlify("~"), "11" * 16, "22" * 16), "invalid-opcode": "%s%s%s" % (binascii.hexlify("!"), "11" * 16, "22" * 16) } all_tests[">"] = compile_test( ">", name_transfers ) # name revoke wire format # 0 2 3 39 # |----|--|-----------------------------| # magic op name.ns_id (37 bytes) name_revokes = { "valid": binascii.hexlify("hello.test"), "null_name": binascii.hexlify(""), "non-b38": binascii.hexlify("Hello.test"), "no-namespace": binascii.hexlify("hello"), "null-namespace": binascii.hexlify("hello."), "2period": binascii.hexlify("hello.tes.t"), "no-plus": binascii.hexlify("hel+lo.test"), "too-long": binascii.hexlify("hellohellohellohellohellohellohel.test") } all_tests["~"] = compile_test( "~", name_revokes ) # name import wire format # 0 2 3 39 # |----|--|-----------------------------| # magic op name.ns_id (37 bytes) name_imports = { "valid": binascii.hexlify("hello.test"), "null_name": binascii.hexlify(""), "non-b38": binascii.hexlify("Hello.test"), "no-namespace": binascii.hexlify("hello"), "null-namespace": binascii.hexlify("hello."), "2period": binascii.hexlify("hello.tes.t"), "no-plus": binascii.hexlify("hel+lo.test"), "too-long": binascii.hexlify("hellohellohellohellohellohellohel.test") } all_tests[";"] = compile_test( ";", name_imports ) # announce wire format # 0 2 3 23 # |----|--|-----------------------------| # magic op message hash (160-bit) announces = { "valid": "11" * 20, "too-short": "11" * 19, "too-long": "11" * 21 } all_tests["#"] = compile_test( "#", announces ) fake_pubkey = wallets[0].pubkey_hex fake_sender = virtualchain.make_payment_script( wallets[0].addr ) fake_recipient = virtualchain.make_payment_script( wallets[1].addr ) fake_recipient_address = wallets[1].addr fake_import_update_hash = "44" * 20 fake_burn_address = virtualchain.address_reencode('1111111111111111111114oLvT2') fake_reveal_address = fake_burn_address # only 'valid' tests should return non-NULL # all other tests should return None for opcode, tests in all_tests.items(): print "test %s" % opcode for testname, testscript in tests.items(): bin_testscript = binascii.unhexlify(testscript)[3:] print 'script: {}'.format(bin_testscript) burn_addr = None reveal_addr = None import_hash = None if opcode in ['*', '?']: burn_addr = fake_burn_address elif opcode == ';': import_hash = fake_import_update_hash elif opcode == '&': reveal_addr = fake_reveal_address parsed_op = parse_nameop( opcode, bin_testscript, fake_pubkey, \ recipient=fake_recipient, recipient_address=fake_recipient_address, import_update_hash=import_hash, burn_address=burn_addr, reveal_address=reveal_addr ) if testname.startswith("valid"): # should work if parsed_op is None: print >> sys.stderr, "Failed to parse %s id%s%s (%s)" % (testname, opcode, bin_testscript, binascii.hexlify(bin_testscript)) return False else: # should fail if parsed_op is not None: print >> sys.stderr, "Parsed invalid test '%s' (id%s%s)" % (testname, opcode, bin_testscript) return False return True
def scenario(wallets, **kw): global wallet_keys, wallet_keys_2, error, index_file_data, resource_data wallet_keys = testlib.ysi_client_initialize_wallet("0123456789abcdef", wallets[5].privkey, wallets[3].privkey, wallets[4].privkey) test_proxy = testlib.TestAPIProxy() ysi_client.set_default_proxy(test_proxy) testlib.ysi_namespace_preorder("test", wallets[1].addr, wallets[0].privkey) testlib.next_block(**kw) testlib.ysi_namespace_reveal( "test", wallets[1].addr, 52595, 250, 4, [6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 10, 10, wallets[0].privkey) testlib.next_block(**kw) testlib.ysi_namespace_ready("test", wallets[1].privkey) testlib.next_block(**kw) testlib.ysi_name_preorder("foo.test", wallets[2].privkey, wallets[3].addr) testlib.next_block(**kw) testlib.ysi_name_register("foo.test", wallets[2].privkey, wallets[3].addr) testlib.next_block(**kw) # 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.ysi_cli_app_signin( "foo.test", datastore_pk, 'register.app', [ 'names', 'register', 'transfer', '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.ysi_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, 4): testlib.next_block(**kw) print 'Wait for update to be confirmed' time.sleep(10) res = testlib.ysi_REST_call("GET", "/v1/names/bar.test", ses) if 'error' in res or res['http_status'] != 200: res['test'] = 'Failed to get name bar.test' print json.dumps(res) return False zonefile_hash = res['response']['zonefile_hash'] # transfer it res = testlib.ysi_REST_call("PUT", "/v1/names/bar.test/owner", ses, data={'owner': wallets[7].addr}) if 'error' in res or res['http_status'] != 202: res['test'] = 'Failed to transfer name' print json.dumps(res) return False # wait for transfer to get confirmed for i in xrange(0, 6): testlib.next_block(**kw) res = testlib.verify_in_queue(ses, 'bar.test', 'transfer', None) if not res: return False for i in xrange(0, 4): testlib.next_block(**kw) print 'waiting for transfer to get confirmed' time.sleep(10) # poll res = testlib.ysi_REST_call("GET", "/v1/names/bar.test", ses) if 'error' in res or res['http_status'] != 200: res['test'] = 'Failed to query name' print json.dumps(res) error = True return False # new owner? if not res['response'].has_key('address'): res['test'] = 'No address given' print json.dumps(res) return False address_testnet = virtualchain.address_reencode(str( res['response']['address']), network='testnet') if address_testnet != wallets[7].addr: res['test'] = 'Failed to transfer name to new address {}'.format( wallets[7].addr) res['owner_address_testnet'] = address_testnet print json.dumps(res) return False # do we have the history for the name? res = testlib.ysi_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()) != 4: 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.ysi_REST_call( "GET", "/v1/names/bar.test/zonefile/{}".format(zonefile_hash), ses) if 'error' in res or res['http_status'] != 200: res['test'] = 'Failed to get name zonefile' print json.dumps(res) return False
def scenario(wallets, **kw): global pk testlib.blockstack_namespace_preorder("test", wallets[1].addr, wallets[0].privkey) testlib.next_block(**kw) # all names below are the same price testlib.blockstack_namespace_reveal( "test", wallets[1].addr, 52595, 250, 4, [6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 1, 1, wallets[0].privkey, version_bits=blockstack.lib.config.NAMESPACE_VERSION_PAY_TO_CREATOR) testlib.next_block(**kw) testlib.blockstack_namespace_ready("test", wallets[1].privkey) testlib.next_block(**kw) # pay for a name in a v1 namespace with Stacks addr = virtualchain.address_reencode(virtualchain.get_privkey_address(pk)) # calculate the cost of doing so namespace = testlib.get_state_engine().get_namespace('test') stacks_price = blockstack.lib.scripts.price_name_stacks( 'foo', namespace, testlib.get_current_block(**kw)) btc_price = blockstack.lib.scripts.price_name( 'foo', namespace, testlib.get_current_block(**kw)) print '' print 'price of {} in Stacks is {}'.format('foo.test', stacks_price) print '' testlib.blockstack_send_tokens(addr, "STACKS", stacks_price * 5, wallets[0].privkey) testlib.send_funds(wallets[0].privkey, btc_price * 10, addr) testlib.next_block(**kw) # preorder/register using Stacks (preorders should fail since we're using the wrong burn address for tokens) testlib.blockstack_name_preorder("foo.test", pk, wallets[3].addr, price={ 'units': 'STACKS', 'amount': stacks_price }, burn_addr=wallets[0].addr, safety_checks=False, expect_fail=True) testlib.blockstack_name_preorder("bar.test", pk, wallets[3].addr, price={ 'units': 'STACKS', 'amount': stacks_price - 1 }, burn_addr=wallets[0].addr, safety_checks=False, expect_fail=True) testlib.next_block(**kw) op_info = virtualchain.lib.indexer.StateEngine.get_block_statistics( testlib.get_current_block(**kw)) if op_info['num_processed_ops'] > 0: print 'handled ops this block' print op_info return False # preorder/register using Stacks (preorders should be accepted, but bar2 will fail to register) testlib.blockstack_name_preorder( "foo2.test", pk, wallets[2].addr, price={ 'units': 'STACKS', 'amount': stacks_price }, burn_addr=blockstack.lib.config.BLOCKSTACK_BURN_ADDRESS, safety_checks=False) testlib.blockstack_name_preorder( "bar2.test", pk, wallets[2].addr, price={ 'units': 'STACKS', 'amount': stacks_price - 1 }, burn_addr=blockstack.lib.config.BLOCKSTACK_BURN_ADDRESS, safety_checks=False) testlib.next_block(**kw) # preorder/register using Stacks (preorders should succeed now, but bar3.test will fail to register since we're not paying enough stacks) testlib.blockstack_name_register( "bar2.test", pk, wallets[2].addr ) # should fail at this point, since the preorder sent to the wrong burn address testlib.blockstack_name_register( "foo2.test", pk, wallets[2].addr ) # should fail at this point, since the preorder sent to the wrong burn address testlib.blockstack_name_preorder( "foo3.test", pk, wallets[2].addr, price={ 'units': 'STACKS', 'amount': stacks_price }, burn_addr=blockstack.lib.config.BLOCKSTACK_BURN_ADDRESS, safety_checks=False) testlib.blockstack_name_preorder( "bar3.test", pk, wallets[2].addr, price={ 'units': 'STACKS', 'amount': stacks_price - 1 }, burn_addr=blockstack.lib.config.BLOCKSTACK_BURN_ADDRESS, safety_checks=False) testlib.next_block(**kw) testlib.expect_snv_fail_at('bar2.test', testlib.get_current_block(**kw)) testlib.expect_snv_fail_at('foo2.test', testlib.get_current_block(**kw)) # tokens are not yet accepted if testlib.get_state_engine().get_name('bar2.test'): print 'registered bar2.test' return False if testlib.get_state_engine().get_name('foo2.test'): print 'registered foo2.test' return False # preorder/register using Stacks (should succeed without safety checks or overrides) testlib.blockstack_name_preorder("foo.test", pk, wallets[2].addr, price={ 'units': 'STACKS', 'amount': stacks_price }) testlib.next_block(**kw) testlib.blockstack_name_register("foo.test", pk, wallets[2].addr) # should succeed testlib.blockstack_name_register("bar2.test", pk, wallets[2].addr) # should fail testlib.blockstack_name_register("foo2.test", pk, wallets[2].addr) # should succeed testlib.blockstack_name_register("bar3.test", pk, wallets[2].addr) # should fail testlib.blockstack_name_register("foo3.test", pk, wallets[2].addr) # should succeed testlib.next_block(**kw) testlib.expect_snv_fail_at('bar2.test', testlib.get_current_block(**kw)) testlib.expect_snv_fail_at('bar3.test', testlib.get_current_block(**kw)) if testlib.get_state_engine().get_name('bar2.test'): print 'registered bar2.test' return False if testlib.get_state_engine().get_name('bar3.test'): print 'registered bar3.test' return False
def check(state_engine): # not revealed, but ready ns = state_engine.get_namespace_reveal("test") if ns is not None: print "namespace reveal exists" return False ns = state_engine.get_namespace("test") if ns is None: print "no namespace" return False if ns['namespace_id'] != 'test': print "wrong namespace" return False if ns['version'] != blockstack.lib.config.NAMESPACE_VERSION_PAY_TO_CREATOR: print 'wrong version' return False addr = virtualchain.address_reencode(virtualchain.get_privkey_address(pk)) for name in [ 'foo.test', 'bar2.test', 'foo2.test', 'bar3.test', 'foo3.test' ]: # not preordered, unless bar2.test stacks_price = blockstack.lib.scripts.price_name_stacks( name.split('.')[0], ns, state_engine.lastblock) preorder = state_engine.get_name_preorder( name, virtualchain.make_payment_script(addr), wallets[2].addr) if name == 'bar2.test' or name == 'bar3.test': if preorder is None: print 'missing {} preorder'.format(name) return False if preorder['token_fee'] != stacks_price - 1: print 'wrong token fee for {}'.format(name) return False if preorder['op_fee'] > 5500: print 'paid too much btc' return False continue else: if preorder is not None: print "preorder exists for {}".format(name) return False # registered name_rec = state_engine.get_name(name) if name_rec is None: print "name does not exist" return False # owned by if name_rec['address'] != wallets[2].addr or name_rec[ 'sender'] != virtualchain.make_payment_script(wallets[2].addr): print "sender is wrong" return False # paid with Stacks in all 3 cases, and not Bitcoin if name_rec['token_fee'] != stacks_price: print 'paid wrong token fee' print 'expected {} ({}), got {} ({})'.format( stacks_price, type(stacks_price), name_rec['token_fee'], type(name_rec['token_fee'])) return False if name_rec['op_fee'] > 5500: # dust minimum print 'paid in BTC ({})'.format(name_rec['op_fee']) return False return True
def scenario(wallets, **kw): 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("foo1.test", wallets[2].privkey, wallets[3].addr) testlib.blockstack_name_preorder("foo2.test", wallets[2].privkey, wallets[3].addr) testlib.blockstack_name_preorder("foo3.test", wallets[2].privkey, wallets[3].addr) testlib.blockstack_name_preorder("foo4.test", wallets[2].privkey, wallets[3].addr) testlib.blockstack_name_preorder("foo5.test", wallets[2].privkey, wallets[3].addr) testlib.blockstack_name_preorder("foo6.test", wallets[2].privkey, wallets[3].addr) testlib.blockstack_name_preorder("foo7.test", wallets[2].privkey, wallets[3].addr) testlib.next_block(**kw) zf_template = "$ORIGIN {}\n$TTL 3600\n{}" zf_default_url = '_https._tcp URI 10 1 "https://raw.githubusercontent.com/nobody/content/profile.md"' zonefiles = { 'foo1.test': zf_template.format( 'foo1.test', subdomains.make_subdomain_txt( 'bar.foo1.test', 'foo1.test', wallets[4].addr, 0, zf_template.format('bar.foo1.test', zf_default_url), wallets[4].privkey)), 'foo2.test': zf_template.format( 'foo2.test', subdomains.make_subdomain_txt( 'bar.foo2.test', 'foo2.test', wallets[4].addr, 0, zf_template.format('bar.foo2.test', zf_default_url), wallets[4].privkey)), 'foo3.test': zf_template.format( 'foo3.test', subdomains.make_subdomain_txt( 'bar.foo3.test', 'foo3.test', wallets[4].addr, 0, zf_template.format('bar.foo3.test', zf_default_url), wallets[4].privkey)), } testlib.blockstack_name_register( "foo1.test", wallets[2].privkey, wallets[3].addr, zonefile_hash=storage.get_zonefile_data_hash(zonefiles['foo1.test'])) testlib.blockstack_name_register( "foo2.test", wallets[2].privkey, wallets[3].addr, zonefile_hash=storage.get_zonefile_data_hash(zonefiles['foo2.test'])) testlib.blockstack_name_register( "foo3.test", wallets[2].privkey, wallets[3].addr, zonefile_hash=storage.get_zonefile_data_hash(zonefiles['foo3.test'])) testlib.blockstack_name_register("foo4.test", wallets[2].privkey, wallets[3].addr) testlib.blockstack_name_register("foo5.test", wallets[2].privkey, wallets[3].addr) testlib.blockstack_name_register("foo6.test", wallets[2].privkey, wallets[3].addr) testlib.blockstack_name_register("foo7.test", wallets[2].privkey, wallets[3].addr) testlib.next_block(**kw) assert testlib.blockstack_put_zonefile(zonefiles['foo1.test']) assert testlib.blockstack_put_zonefile(zonefiles['foo2.test']) assert testlib.blockstack_put_zonefile(zonefiles['foo3.test']) # kick off indexing and check testlib.next_block(**kw) def _query_subdomains(subdomain_names, expected_sequence, expected_owner, expect_pending): # query each subdomain. Should get the latest proxy = testlib.make_proxy() for fqn in subdomain_names: res = client.get_name_record(fqn, proxy=proxy) if 'error' in res: print res print 'failed to query {}'.format(fqn) return False # should have right sequence if res['sequence'] != expected_sequence: print 'wrong sequence; expected {}'.format(expected_sequence) print res return False # should have right owner if res['address'] != expected_owner: print 'wrong owner' print 'expected {}'.format(res['address']) print res return False # do we expect pending? if res['pending'] != expect_pending: print 'wrong pending (expected {})'.format(expect_pending) print res return False return True assert _query_subdomains( ['bar.foo1.test', 'bar.foo2.test', 'bar.foo3.test'], 0, wallets[4].addr, False) expected_owners_before = [wallets[4].addr] expected_owners_after = [wallets[4].addr] # update and transfer, but if i % 2 == 0, transfer to a different address # use a different domain name in each case. # verify that only transfers on the creator domain are valid. wallet_schedule = [ (4, 0), # not broadcast initially (4, 1), (0, 1), # not broadcast initially (1, 2), ] sequence_schedule = [ 1, 1, 2, 2, ] expected_zf_default_url = '_https._tcp URI 10 1 "https://test.com/?index={}"'.format( 4) expect_pending = False expect_sequence = 0 expect_owner = wallets[4].addr unsent_zonefiles = [] # send updates too, and transfer subdomains for i in range(0, 4): zf_template = "$ORIGIN {}\n$TTL 3600\n{}" zf_default_url = '_https._tcp URI 10 1 "https://test.com/?index={}"'.format( i + 1) names = [ 'foo1.test', 'foo2.test', 'foo3.test', ] k = wallet_schedule[i][0] k2 = wallet_schedule[i][1] s = sequence_schedule[i] zonefiles = { 'foo1.test': zf_template.format( names[0], subdomains.make_subdomain_txt( 'bar.foo1.test', names[0], wallets[k2].addr, s, zf_template.format('bar.foo1.test', zf_default_url), wallets[k].privkey)), 'foo2.test': zf_template.format( names[1], subdomains.make_subdomain_txt( 'bar.foo2.test', names[1], wallets[k2].addr, s, zf_template.format('bar.foo2.test', zf_default_url), wallets[k].privkey)), 'foo3.test': zf_template.format( names[2], subdomains.make_subdomain_txt( 'bar.foo3.test', names[2], wallets[k2].addr, s, zf_template.format('bar.foo3.test', zf_default_url), wallets[k].privkey)), } testlib.blockstack_name_update( names[0], storage.get_zonefile_data_hash(zonefiles['foo1.test']), wallets[3].privkey) testlib.blockstack_name_update( names[1], storage.get_zonefile_data_hash(zonefiles['foo2.test']), wallets[3].privkey) testlib.blockstack_name_update( names[2], storage.get_zonefile_data_hash(zonefiles['foo3.test']), wallets[3].privkey) testlib.next_block(**kw) if i % 2 == 1: # only broadcast periodically assert testlib.blockstack_put_zonefile(zonefiles['foo1.test']) assert testlib.blockstack_put_zonefile(zonefiles['foo2.test']) assert testlib.blockstack_put_zonefile(zonefiles['foo3.test']) expect_owner = wallets[k2].addr expect_sequence += 1 expected_owners_before.append(expect_owner) else: expect_pending = True unsent_zonefiles.append(zonefiles) expected_owners_after.append(wallets[k2].addr) # kick off subdomain indexing testlib.next_block(**kw) # verify history assert _query_subdomains( ['bar.foo1.test', 'bar.foo2.test', 'bar.foo3.test'], expect_sequence, expect_owner, expect_pending) # query subdomain history proxy = testlib.make_proxy() for subd in ['bar.foo1.test', 'bar.foo2.test', 'bar.foo3.test']: res = client.get_name_record(subd, include_history=True, proxy=proxy) if 'error' in res: print res return False if not res['pending']: print 'not pending, but it should be' print res return False # should be at 2 if res['sequence'] != 2: print 'wrong sequence' print res return False if virtualchain.address_reencode(str( res['address'])) != virtualchain.address_reencode( expect_owner): print 'wrong owner' print res return False for i, block_height in enumerate(sorted(res['history'])): if virtualchain.address_reencode( str(res['history'][block_height][0]['address']) ) != virtualchain.address_reencode(expected_owners_before[i]): print 'wrong owner at {}: expected {}'.format( block_height, expected_owners_before[i]) print json.dumps(res, indent=4, sort_keys=True) print expected_owners_before return False if res['history'][block_height][0]['sequence'] != i: print 'wrong sequence at {}: expected {}'.format( block_height, i) print json.dumps(res, indent=4, sort_keys=True) return False # send all missing subdomains. # should cause a cascading owner reorg. for zfbatch in unsent_zonefiles: for k in zfbatch: assert testlib.blockstack_put_zonefile(zfbatch[k]) testlib.next_block(**kw) # query subdomain history again. pending and owner should change proxy = testlib.make_proxy() for subd in ['bar.foo1.test', 'bar.foo2.test', 'bar.foo3.test']: res = client.get_name_record(subd, include_history=True, proxy=proxy) if 'error' in res: print res return False if res['pending']: print 'pending, but it should not be' print res return False if res['sequence'] != 2: print 'wrong sequence' print res return False if virtualchain.address_reencode(str( res['address'])) != virtualchain.address_reencode( wallets[1].addr): print 'wrong owner again' print res return False for i, block_height in enumerate(sorted(res['history'])): if virtualchain.address_reencode( str(res['history'][block_height][0]['address']) ) != virtualchain.address_reencode(str(expected_owners_after[i])): print 'wrong owner at {}: expected {}'.format( block_height, expected_owners_after[i]) print json.dumps(res, indent=4, sort_keys=True) print expected_owners_after print expected_owners_before print[wallets[i].addr for i in range(0, len(wallets))] return False if res['history'][block_height][0]['sequence'] != i: print 'wrong sequence at {}: expected {}'.format( block_height, i) print json.dumps(res, indent=4, sort_keys=True) return False # reindex assert testlib.check_subdomain_db(**kw)
def scenario(wallets, **kw): global pk, pk2 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, 6, 6, 6, 6, 6, 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) # pay for a name in a v1 namespace with Stacks addr = virtualchain.address_reencode(virtualchain.get_privkey_address(pk)) addr2 = virtualchain.address_reencode( virtualchain.get_privkey_address(pk2)) # calculate the cost of doing so namespace = testlib.get_state_engine().get_namespace('test') stacks_price = blockstack.lib.scripts.price_name_stacks( 'baz', namespace, testlib.get_current_block(**kw)) btc_price = blockstack.lib.scripts.price_name( 'baz', namespace, testlib.get_current_block(**kw)) print '' print 'price of {} in Stacks is {}'.format('baz.test', stacks_price) print 'price of {} in BTC is {}'.format('baz.test', btc_price) print '' testlib.blockstack_send_tokens(addr, "STACKS", stacks_price, wallets[0].privkey) testlib.blockstack_send_tokens(addr2, "STACKS", stacks_price * 2, wallets[0].privkey) testlib.send_funds(wallets[0].privkey, 10 * btc_price, addr) testlib.send_funds(wallets[0].privkey, 10 * btc_price, addr2) testlib.next_block(**kw) # preorder/register using Stacks testlib.blockstack_name_preorder("baz.test", wallets[2].privkey, addr2, price={ 'units': 'STACKS', 'amount': stacks_price }) testlib.blockstack_name_preorder("goo.test", wallets[2].privkey, addr2, price={ 'units': 'STACKS', 'amount': stacks_price }) testlib.blockstack_name_preorder("nop.test", wallets[2].privkey, addr2, price={ 'units': 'STACKS', 'amount': stacks_price }) testlib.next_block(**kw) testlib.blockstack_name_register("baz.test", wallets[2].privkey, addr2) testlib.blockstack_name_register("goo.test", wallets[2].privkey, addr2) testlib.blockstack_name_register("nop.test", wallets[2].privkey, addr2) testlib.next_block(**kw) balance_before = testlib.get_addr_balances(addr2)[addr2]['STACKS'] # pay with both Stacks and Bitcoin # should favor Stacks payment over Bitcoin payment if we pay enough stacks. # Stacks should have been burned, as well as BTC. res = testlib.blockstack_name_renew('baz.test', pk2, price={ 'units': 'STACKS', 'amount': stacks_price }, tx_only=True, expect_success=True) txhex = res['transaction'] tx = virtualchain.btc_tx_deserialize(txhex) # up the burn amount btc_price = blockstack.lib.scripts.price_name( 'baz', namespace, testlib.get_current_block(**kw)) tx['outs'][3]['script'] = virtualchain.btc_make_payment_script( blockstack.lib.config.BLOCKSTACK_BURN_ADDRESS) tx['outs'][3]['value'] = btc_price tx['outs'][4]['value'] -= btc_price # re-sign for i in tx['ins']: i['script'] = '' txhex = virtualchain.btc_tx_serialize(tx) txhex_signed = virtualchain.tx_sign_all_unsigned_inputs( pk2, testlib.get_utxos(addr2), txhex) # re-sign the last output with the payment key tx_signed = virtualchain.btc_tx_deserialize(txhex_signed) tx_signed['ins'][-1]['script'] = '' txhex_signed = virtualchain.tx_sign_all_unsigned_inputs( testlib.get_default_payment_wallet().privkey, testlib.get_utxos(testlib.get_default_payment_wallet().addr), virtualchain.btc_tx_serialize(tx_signed)) print txhex_signed res = testlib.broadcast_transaction(txhex_signed) if 'error' in res: print res return False testlib.next_block(**kw) # should have paid in Stacks balance_after = testlib.get_addr_balances(addr2)[addr2]['STACKS'] if balance_after != balance_before - stacks_price: print 'baz.test cost {}'.format(balance_before - balance_after) return False balance_before = testlib.get_addr_balances(addr2)[addr2]['STACKS'] # try to renew a name where we pay not enough stacks, but enough bitcoin. # should be rejected. res = testlib.blockstack_name_renew('goo.test', pk2, price={ 'units': 'STACKS', 'amount': stacks_price - 1 }, tx_only=True) txhex = res['transaction'] tx = virtualchain.btc_tx_deserialize(txhex) # up the burn amount to the name price btc_price = blockstack.lib.scripts.price_name( 'goo', namespace, testlib.get_current_block(**kw)) tx['outs'][3]['script'] = virtualchain.btc_make_payment_script( blockstack.lib.config.BLOCKSTACK_BURN_ADDRESS) tx['outs'][3]['value'] = btc_price tx['outs'][4]['value'] -= btc_price # re-sign for i in tx['ins']: i['script'] = '' txhex = virtualchain.btc_tx_serialize(tx) txhex_signed = virtualchain.tx_sign_all_unsigned_inputs( pk2, testlib.get_utxos(addr2), txhex) # re-sign the last output with the payment key tx_signed = virtualchain.btc_tx_deserialize(txhex_signed) tx_signed['ins'][-1]['script'] = '' txhex_signed = virtualchain.tx_sign_all_unsigned_inputs( testlib.get_default_payment_wallet().privkey, testlib.get_utxos(testlib.get_default_payment_wallet().addr), virtualchain.btc_tx_serialize(tx_signed)) print txhex_signed res = testlib.broadcast_transaction(txhex_signed) if 'error' in res: print res return False testlib.next_block(**kw) # should NOT have paid in Stacks balance_after = testlib.get_addr_balances(addr2)[addr2]['STACKS'] if balance_after != balance_before: print 'goo.test paid {}'.format(balance_before - balance_after) return False balance_before = testlib.get_addr_balances(addr2)[addr2]['STACKS'] # underpay in both Stacks and Bitcoin. # only bitcoin will be burned; transaction will not be processed res = testlib.blockstack_name_renew('nop.test', pk2, price={ 'units': 'STACKS', 'amount': stacks_price - 1 }, tx_only=True) txhex = res['transaction'] tx = virtualchain.btc_tx_deserialize(txhex) # up the burn amount to the name price btc_price = blockstack.lib.scripts.price_name( 'goo', namespace, testlib.get_current_block(**kw)) tx['outs'][3]['script'] = virtualchain.btc_make_payment_script( blockstack.lib.config.BLOCKSTACK_BURN_ADDRESS) tx['outs'][3]['value'] = btc_price - 1 tx['outs'][4]['value'] -= btc_price + 1 # re-sign for i in tx['ins']: i['script'] = '' txhex = virtualchain.btc_tx_serialize(tx) txhex_signed = virtualchain.tx_sign_all_unsigned_inputs( pk2, testlib.get_utxos(addr2), txhex) # re-sign the last output with the payment key tx_signed = virtualchain.btc_tx_deserialize(txhex_signed) tx_signed['ins'][-1]['script'] = '' txhex_signed = virtualchain.tx_sign_all_unsigned_inputs( testlib.get_default_payment_wallet().privkey, testlib.get_utxos(testlib.get_default_payment_wallet().addr), virtualchain.btc_tx_serialize(tx_signed)) print txhex_signed res = testlib.broadcast_transaction(txhex_signed) if 'error' in res: print res return False testlib.next_block(**kw) testlib.expect_snv_fail_at('nop.test', testlib.get_current_block(**kw)) balance_after = testlib.get_addr_balances(addr2)[addr2]['STACKS'] if balance_after != balance_before: print 'paid {} for nop.test'.format(balance_before - balance_after) return False
def check(state_engine): # not revealed, but ready ns = state_engine.get_namespace_reveal("test") if ns is not None: print "namespace reveal exists" return False ns = state_engine.get_namespace("test") if ns is None: print "no namespace" return False if ns['namespace_id'] != 'test': print "wrong namespace" return False # name should exist, but not be renewed addr2 = virtualchain.address_reencode( virtualchain.get_privkey_address(pk2)) name_rec = state_engine.get_name('nop.test') if name_rec is None: print 'name record does not exist for nop.test' return False if name_rec['first_registered'] != name_rec['last_renewed']: print 'name nop.test was renewed' return False # name should exist, but not be renewed addr2 = virtualchain.address_reencode( virtualchain.get_privkey_address(pk2)) name_rec = state_engine.get_name('goo.test') if name_rec is None: print 'name record does not exist for goo.test' return False if name_rec['first_registered'] != name_rec['last_renewed']: print 'name goo.test was renewed' return False for name in ['baz.test']: # not preordered preorder = state_engine.get_name_preorder( name, virtualchain.make_payment_script(wallets[2].addr), addr2) if preorder is not None: print "preorder exists" return False # registered name_rec = state_engine.get_name(name) if name_rec is None: print "name does not exist" return False # owned by if name_rec['address'] != addr2 or name_rec[ 'sender'] != virtualchain.make_payment_script(addr2): print "sender is wrong" return False # paid for baz.test with Stacks, but nevertheless burned Bitcoin # however, baz.test's burn output is equal to the bitcoin price for name in ['baz']: name_rec = state_engine.get_name(name + '.test') stacks_price = blockstack.lib.scripts.price_name_stacks( name, ns, state_engine.lastblock) if name_rec['token_fee'] != stacks_price: print 'paid wrong token fee for {}.test'.format(name) print 'expected {} ({}), got {} ({})'.format( stacks_price, type(stacks_price), name_rec['token_fee'], type(name_rec['token_fee'])) return False if name_rec['op_fee'] != blockstack.lib.scripts.price_name( name, ns, state_engine.lastblock): print 'paid wrong BTC for baz.test ({})'.format(name_rec['op_fee']) return False return True
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', 'transfer', '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, 6): testlib.next_block( **kw ) # wait for register to go through print 'Wait for register to be submitted' time.sleep(10) # wait for the register to get confirmed for i in xrange(0, 6): testlib.next_block( **kw ) res = testlib.verify_in_queue(ses, 'bar.test', 'register', None ) if not res: return False for i in xrange(0, 6): testlib.next_block( **kw ) print 'Wait for update to be submitted' time.sleep(10) # wait for update to get confirmed for i in xrange(0, 6): testlib.next_block( **kw ) res = testlib.verify_in_queue(ses, 'bar.test', 'update', None ) if not res: return False for i in xrange(0, 6): testlib.next_block( **kw ) print 'Wait for update to be confirmed' time.sleep(10) res = testlib.blockstack_REST_call("GET", "/v1/names/bar.test", ses) if 'error' in res or res['http_status'] != 200: res['test'] = 'Failed to get name bar.test' print json.dumps(res) return False zonefile_hash = res['response']['zonefile_hash'] # transfer it res = testlib.blockstack_REST_call("PUT", "/v1/names/bar.test/owner", ses, data={'owner': wallets[7].addr}) if 'error' in res or res['http_status'] != 202: res['test'] = 'Failed to transfer name' print json.dumps(res) return False # wait for transfer to get confirmed for i in xrange(0, 6): testlib.next_block( **kw ) res = testlib.verify_in_queue(ses, 'bar.test', 'transfer', None ) if not res: return False for i in xrange(0, 6): testlib.next_block( **kw ) print 'waiting for transfer to get confirmed' time.sleep(10) # poll res = testlib.blockstack_REST_call("GET", "/v1/names/bar.test", ses ) if 'error' in res or res['http_status'] != 200: res['test'] = 'Failed to query name' print json.dumps(res) error = True return False # new owner? if not res['response'].has_key('address'): res['test'] = 'No address given' print json.dumps(res) return False address_testnet = virtualchain.address_reencode(str(res['response']['address']), network='testnet') if address_testnet != wallets[7].addr: res['test'] = 'Failed to transfer name to new address {}'.format(wallets[7].addr) res['owner_address_testnet'] = address_testnet print json.dumps(res) return False # 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()) != 4: res['test'] = 'Failed to get update history' res['history'] = hist print json.dumps(res, indent=4, sort_keys=True) return False # get the zonefile res = testlib.blockstack_REST_call("GET", "/v1/names/bar.test/zonefile/{}".format(zonefile_hash), ses ) if 'error' in res or res['http_status'] != 200: res['test'] = 'Failed to get name zonefile' print json.dumps(res) return False
def scenario( wallets, **kw ): 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( "foo1.test", wallets[2].privkey, wallets[3].addr ) testlib.blockstack_name_preorder( "foo2.test", wallets[2].privkey, wallets[3].addr ) testlib.blockstack_name_preorder( "foo3.test", wallets[2].privkey, wallets[3].addr ) testlib.blockstack_name_preorder( "foo4.test", wallets[2].privkey, wallets[3].addr ) testlib.blockstack_name_preorder( "foo5.test", wallets[2].privkey, wallets[3].addr ) testlib.blockstack_name_preorder( "foo6.test", wallets[2].privkey, wallets[3].addr ) testlib.blockstack_name_preorder( "foo7.test", wallets[2].privkey, wallets[3].addr ) testlib.next_block( **kw ) zf_template = "$ORIGIN {}\n$TTL 3600\n{}" zf_default_url = '_https._tcp URI 10 1 "https://raw.githubusercontent.com/nobody/content/profile.md"' zonefiles = { 'foo1.test': zf_template.format('foo1.test', subdomains.make_subdomain_txt('bar.foo1.test', 'foo1.test', wallets[4].addr, 0, zf_template.format('bar.foo1.test', zf_default_url), wallets[4].privkey)), 'foo2.test': zf_template.format('foo2.test', subdomains.make_subdomain_txt('bar.foo2.test', 'foo2.test', wallets[4].addr, 0, zf_template.format('bar.foo2.test', zf_default_url), wallets[4].privkey)), 'foo3.test': zf_template.format('foo3.test', subdomains.make_subdomain_txt('bar.foo3.test', 'foo3.test', wallets[4].addr, 0, zf_template.format('bar.foo3.test', zf_default_url), wallets[4].privkey)), } testlib.blockstack_name_register( "foo1.test", wallets[2].privkey, wallets[3].addr, zonefile_hash=storage.get_zonefile_data_hash(zonefiles['foo1.test'])) testlib.blockstack_name_register( "foo2.test", wallets[2].privkey, wallets[3].addr, zonefile_hash=storage.get_zonefile_data_hash(zonefiles['foo2.test'])) testlib.blockstack_name_register( "foo3.test", wallets[2].privkey, wallets[3].addr, zonefile_hash=storage.get_zonefile_data_hash(zonefiles['foo3.test'])) testlib.blockstack_name_register( "foo4.test", wallets[2].privkey, wallets[3].addr, zonefile_hash='11' * 20) testlib.blockstack_name_register( "foo5.test", wallets[2].privkey, wallets[3].addr, zonefile_hash='11' * 20) testlib.blockstack_name_register( "foo6.test", wallets[2].privkey, wallets[3].addr, zonefile_hash='11' * 20) testlib.blockstack_name_register( "foo7.test", wallets[2].privkey, wallets[3].addr, zonefile_hash='11' * 20) testlib.next_block( **kw ) assert testlib.blockstack_put_zonefile(zonefiles['foo1.test']) assert testlib.blockstack_put_zonefile(zonefiles['foo2.test']) assert testlib.blockstack_put_zonefile(zonefiles['foo3.test']) # update and transfer, but if i % 2 == 0, transfer to a different address # use a different domain name in each case. # verify that only transfers on the creator domain are valid. wallet_schedule = [ (4, 0), # won't be accepted due to domain independence (4, 1), # will be accepted (1, 2), # won't be accepted due to domain independence (1, 3), # will be accepted ] sequence_schedule = [ 1, # won't be accepted due to domain independence 1, # will be accepted 2, # won't be accepted due to domain independence 2, # will be accepted ] expected_zf_default_url = '_https._tcp URI 10 1 "https://test.com/?index={}"'.format(4) for i in range(0, 4): zf_template = "$ORIGIN {}\n$TTL 3600\n{}" zf_default_url = '_https._tcp URI 10 1 "https://test.com/?index={}"'.format(i+1) if i % 2 == 0: names = [ 'foo{}.test'.format(i+4), 'foo{}.test'.format(i+4), 'foo{}.test'.format(i+4), ] else: names = [ 'foo1.test', 'foo2.test', 'foo3.test', ] k = wallet_schedule[i][0] k2 = wallet_schedule[i][1] s = sequence_schedule[i] zonefiles = { 'foo1.test': zf_template.format(names[0], subdomains.make_subdomain_txt('bar.foo1.test', names[0], wallets[k2].addr, s, zf_template.format('bar.foo1.test', zf_default_url), wallets[k].privkey)), 'foo2.test': zf_template.format(names[1], subdomains.make_subdomain_txt('bar.foo2.test', names[1], wallets[k2].addr, s, zf_template.format('bar.foo2.test', zf_default_url), wallets[k].privkey)), 'foo3.test': zf_template.format(names[2], subdomains.make_subdomain_txt('bar.foo3.test', names[2], wallets[k2].addr, s, zf_template.format('bar.foo3.test', zf_default_url), wallets[k].privkey)), } testlib.blockstack_name_update(names[0], storage.get_zonefile_data_hash(zonefiles['foo1.test']), wallets[3].privkey) testlib.blockstack_name_update(names[1], storage.get_zonefile_data_hash(zonefiles['foo2.test']), wallets[3].privkey) testlib.blockstack_name_update(names[2], storage.get_zonefile_data_hash(zonefiles['foo3.test']), wallets[3].privkey) testlib.next_block(**kw) assert testlib.blockstack_put_zonefile(zonefiles['foo1.test']) assert testlib.blockstack_put_zonefile(zonefiles['foo2.test']) assert testlib.blockstack_put_zonefile(zonefiles['foo3.test']) # kick off subdomain indexing testlib.next_block(**kw) # query each subdomain proxy = testlib.make_proxy() for i in xrange(1, 4): fqn = 'bar.foo{}.test'.format(i) res = client.get_name_record(fqn, proxy=proxy) if 'error' in res: print res return False if res['sequence'] != 2: print 'wrong sequence' print res return False if virtualchain.address_reencode(str(res['address'])) != virtualchain.address_reencode(wallets[3].addr): print 'wrong owner' print res return False expected_zonefile = zf_template.format(fqn, expected_zf_default_url) if base64.b64decode(res['zonefile']) != expected_zonefile: print 'zonefile mismatch' print 'expected\n{}'.format(expected_zonefile) print 'got\n{}'.format(base64.b64decode(res['zonefile'])) return False # should be in atlas as well zf = testlib.blockstack_get_zonefile(res['value_hash'], parse=False) if not zf: print 'no zone file {} in atlas'.format(res['value_hash']) return False if zf != expected_zonefile: print 'zonefile mismatch in atlas' print 'expected\n{}'.format(expected_zonefile) print 'got\n{}'.format(base64.b64decode(res['zonefile'])) return False # there should only be three history items per name hist = client.get_name_record(fqn, proxy=proxy, include_history=True) if 'error' in hist: print res return False if len(hist['history']) != 3: print 'wrong history length' print res return False # reindex assert testlib.check_subdomain_db(**kw)