def test_exercise_2(self): block_hash = bytes.fromhex( '0000000053787814ed9dd8c029d0a0a9af4ab8ec0591dc31bdc4ab31fae88ce9') passphrase = b'Jimmy Song Programming Blockchain' # FILL THIS IN secret = little_endian_to_int(hash256(passphrase)) private_key = PrivateKey(secret=secret) addr = private_key.point.address(testnet=True) filter_size = 30 filter_num_functions = 5 filter_tweak = 90210 # FILL THIS IN h160 = decode_base58(addr) bf = BloomFilter(filter_size, filter_num_functions, filter_tweak) bf.add(h160) node = SimpleNode('tbtc.programmingblockchain.com', testnet=True, logging=False) node.handshake() node.send(bf.filterload()) getdata = GetDataMessage() getdata.add_data(FILTERED_BLOCK_DATA_TYPE, block_hash) node.send(getdata) mb = node.wait_for(MerkleBlock) tx = node.wait_for(Tx) self.assertEqual( tx.serialize().hex(), '0100000002a663815ab2b2ba5f53e442f9a2ea6cc11bbcd98fb1585e48a134bd870dbfbd6a000000006a47304402202151107dc2367cf5a9e2429cde0641c252374501214ce52069fbca1320180aa602201a43b5d4f91e48514c00c01521dc04f02c57f15305adc4eaad01c418f6e7a1180121031dbe3aff7b9ad64e2612b8b15e9f5e4a3130663a526df91abfb7b1bd16de5d6effffffff618b00a343488bd62751cf21f368ce3be76e3a0323fdc594a0d24f27a1155cd2000000006a473044022024c4dd043ab8637c019528b549e0b10333b2dfa83e7ca66776e401ad3fc31b6702207d4d1d73ac8940c59c57c0b7daf084953324154811c10d06d0563947a88f99b20121031dbe3aff7b9ad64e2612b8b15e9f5e4a3130663a526df91abfb7b1bd16de5d6effffffff0280969800000000001976a914ad346f8eb57dee9a37981716e498120ae80e44f788aca0ce6594000000001976a9146e13971913b9aa89659a9f53d327baa8826f2d7588ac00000000' )
def test_example_1(self): node = SimpleNode('tbtc.programmingblockchain.com', testnet=True, logging=False) version = VersionMessage() node.send(version) verack = node.wait_for(VerAckMessage) self.assertEqual(verack.command, b'verack')
def test_exercise_10(self): block_hex = '0000000000044b01a9440b34f582fe171c7b8642fedd0ebfccf8fdf6a1810900' block_hash = bytes.fromhex(block_hex) node = SimpleNode('tbtc.programmingblockchain.com', testnet=True) node.handshake() getdata = GetDataMessage() getdata.add_data(BLOCK_DATA_TYPE, block_hash) node.send(getdata) b = node.wait_for(Block) self.assertTrue(b.check_pow()) self.assertTrue(b.validate_merkle_root())
def test_exercise_10(self): block_hex = '0000000000044b01a9440b34f582fe171c7b8642fedd0ebfccf8fdf6a1810900' block_hash = bytes.fromhex(block_hex) node = SimpleNode('tbtc.programmingblockchain.com', testnet=True) node.handshake() getdata = GetDataMessage() getdata.add_data(BLOCK_DATA_TYPE, block_hash) node.send(getdata.command, getdata.serialize()) block_envelope = node.wait_for_commands([b'block']) stream = block_envelope.stream() b = Block.parse(stream) self.assertTrue(b.check_pow()) num_txs = read_varint(stream) tx_hashes = [] for _ in range(num_txs): t = Tx.parse(stream) tx_hashes.append(t.hash()) b.tx_hashes = tx_hashes self.assertTrue(b.validate_merkle_root())
def test_exercise_4(self): last_block_hex = '000000000d65610b5af03d73ed67704713c9b734d87cf4b970d39a0416dd80f9' last_block = bytes.fromhex(last_block_hex) secret = little_endian_to_int( hash256(b'Jimmy Song Programming Blockchain')) private_key = PrivateKey(secret=secret) addr = private_key.point.address(testnet=True) h160 = decode_base58(addr) target_address = 'mwJn1YPMq7y5F8J3LkC5Hxg9PHyZ5K4cFv' self.assertEqual(addr, target_address) filter_size = 30 filter_num_functions = 5 filter_tweak = 90210 # FILL THIS IN target_h160 = decode_base58(target_address) target_script = p2pkh_script(target_h160) fee = 5000 # fee in satoshis node = SimpleNode('tbtc.programmingblockchain.com', testnet=True, logging=False) bf = BloomFilter(filter_size, filter_num_functions, filter_tweak) bf.add(h160) node.handshake() node.send(b'filterload', bf.filterload()) getheaders_message = GetHeadersMessage(start_block=last_block) node.send(getheaders_message.command, getheaders_message.serialize()) headers_envelope = node.wait_for_commands([HeadersMessage.command]) stream = headers_envelope.stream() headers = HeadersMessage.parse(stream) get_data_message = GetDataMessage() for block in headers.blocks: self.assertTrue(block.check_pow()) if last_block is not None: self.assertEqual(block.prev_block, last_block) last_block = block.hash() get_data_message.add_data(FILTERED_BLOCK_DATA_TYPE, last_block) node.send(get_data_message.command, get_data_message.serialize()) prev_tx = None while prev_tx is None: envelope = node.wait_for_commands([b'merkleblock', b'tx']) stream = envelope.stream() if envelope.command == b'merkleblock': mb = MerkleBlock.parse(stream) self.assertTrue(mb.is_valid()) else: prev = Tx.parse(stream, testnet=True) for i, tx_out in enumerate(prev.tx_outs): if tx_out.script_pubkey.address(testnet=True) == addr: prev_tx = prev.hash() prev_index = i prev_amount = tx_out.amount break tx_in = TxIn(prev_tx, prev_index) output_amount = prev_amount - fee tx_out = TxOut(output_amount, target_script) tx_obj = Tx(1, [tx_in], [tx_out], 0, testnet=True) tx_obj.sign_input(0, private_key) self.assertEqual( tx_obj.serialize().hex(), '010000000194e631abb9e1079ec72a1616a3aa0111c614e65b96a6a4420e2cc6af9e6cc96e000000006a47304402203cc8c56abe1c0dd043afa9eb125dafbebdde2dd4cd7abf0fb1aae0667a22006e02203c95b74d0f0735bbf1b261d36e077515b6939fc088b9d7c1b7030a5e494596330121021cdd761c7eb1c90c0af0a5963e94bf0203176b4662778d32bd6d7ab5d8628b32ffffffff01f8829800000000001976a914ad346f8eb57dee9a37981716e498120ae80e44f788ac00000000' )
def test_exercise_5(self): expected = [ '00000000864b744c5025331036aa4a16e9ed1cbb362908c625272150fa059b29', '000000002e9ccffc999166ccf8d72129e1b2e9c754f6c90ad2f77cab0d9fb4c7', '0000000009b9f0436a9c733e2c9a9d9c8fe3475d383bdc1beb7bfa995f90be70', '000000000a9c9c79f246042b9e2819822287f2be7cd6487aecf7afab6a88bed5', '000000003a7002e1247b0008cba36cd46f57cd7ce56ac9d9dc5644265064df09', '00000000061e01e82afff6e7aaea4eb841b78cc0eed3af11f6706b14471fa9c8', '000000003911e011ae2459e44d4581ac69ba703fb26e1421529bd326c538f12d', '000000000a5984d6c73396fe40de392935f5fc2a8e48eedf38034ce0a3178a60', '000000000786bdc642fa54c0a791d58b732ed5676516fffaeca04492be97c243', '000000001359c49f9618f3ee69afbd1b3196f1832acc47557d42256fcc6b7f48', '00000000270dde98d582af35dff5aed02087dad8529dc5c808c67573d6dabaf4', '00000000425c160908c215c4adf998771a2d1c472051bc58320696f3a5eb0644', '0000000006a5976471986377805d4a148d8822bb7f458138c83f167d197817c9', '000000000318394ea17038ef369f3cccc79b3d7dfda957af6c8cd4a471ffa814', '000000000ad4f9d0b8e86871478cc849f7bc42fb108ebec50e4a795afc284926', '000000000207e63e68f2a7a4c067135883d726fd65e3620142fb9bdf50cce1f6', '00000000003b426d2c12ee66b2eedb4dcc05d5e158685b222240d31e43687762', '00000000017cf6ee86e3d483f9a978ded72be1fa5af37d287a71c5dfb87cdd83', '00000000004b1d9fe16fc0c72cfa0395c98a3e460cd2affb8640e28bca295a4a' ] node = SimpleNode('tbtc.programmingblockchain.com', testnet=True) node.handshake() last_block_hash = TESTNET_GENESIS_BLOCK_HASH count = 1 while count <= 40000: getheaders = GetHeadersMessage(start_block=last_block_hash) node.send(getheaders.command, getheaders.serialize()) headers_envelope = node.wait_for_commands([b'headers']) headers_message = HeadersMessage.parse(headers_envelope.stream()) for b in headers_message.blocks: self.assertTrue(b.check_pow()) if last_block_hash != TESTNET_GENESIS_BLOCK_HASH: print(count) self.assertEqual(b.prev_block, last_block_hash) count += 1 last_block_hash = b.hash() if count % 2016 == 0: self.assertEqual(b.id(), expected.pop(0))
def test_exercise_6(self): last_block_hex = '000000000d65610b5af03d73ed67704713c9b734d87cf4b970d39a0416dd80f9' secret = little_endian_to_int( hash256(b'Jimmy Song Programming Blockchain')) private_key = PrivateKey(secret=secret) addr = private_key.point.address(testnet=True) h160 = decode_base58(addr) target_address = 'mwJn1YPMq7y5F8J3LkC5Hxg9PHyZ5K4cFv' self.assertEqual(addr, target_address) target_h160 = decode_base58(target_address) target_script = p2pkh_script(target_h160) fee = 5000 node = SimpleNode('tbtc.programmingblockchain.com', testnet=True) bf = BloomFilter(30, 5, 90210) bf.add(h160) node.handshake() node.send(bf.filterload()) start_block = bytes.fromhex(last_block_hex) getheaders = GetHeadersMessage(start_block=start_block) node.send(getheaders) headers = node.wait_for(HeadersMessage) last_block = None getdata = GetDataMessage() for b in headers.blocks: if not b.check_pow(): raise RuntimeError('proof of work is invalid') if last_block is not None and b.prev_block != last_block: raise RuntimeError('chain broken') getdata.add_data(FILTERED_BLOCK_DATA_TYPE, b.hash()) last_block = b.hash() node.send(getdata) prev_tx, prev_index, prev_tx_obj = None, None, None while prev_tx is None: message = node.wait_for(MerkleBlock, Tx) if message.command == b'merkleblock': if not message.is_valid(): raise RuntimeError('invalid merkle proof') else: message.testnet = True for i, tx_out in enumerate(message.tx_outs): if tx_out.script_pubkey.address(testnet=True) == addr: prev_tx = message.hash() prev_index = i prev_amount = tx_out.amount self.assertEqual( message.id(), '6ec96c9eafc62c0e42a4a6965be614c61101aaa316162ac79e07e1b9ab31e694' ) self.assertEqual(i, 0) break tx_in = TxIn(prev_tx, prev_index) output_amount = prev_amount - fee tx_out = TxOut(output_amount, target_script) tx_obj = Tx(1, [tx_in], [tx_out], 0, testnet=True) tx_obj.sign_input(0, private_key) self.assertEqual( tx_obj.serialize().hex(), '010000000194e631abb9e1079ec72a1616a3aa0111c614e65b96a6a4420e2cc6af9e6cc96e000000006a47304402203cc8c56abe1c0dd043afa9eb125dafbebdde2dd4cd7abf0fb1aae0667a22006e02203c95b74d0f0735bbf1b261d36e077515b6939fc088b9d7c1b7030a5e494596330121021cdd761c7eb1c90c0af0a5963e94bf0203176b4662778d32bd6d7ab5d8628b32ffffffff01f8829800000000001976a914ad346f8eb57dee9a37981716e498120ae80e44f788ac00000000' )
def test_example_2(self): expected = [ '00000000693067b0e6b440bc51450b9f3850561b07f6d3c021c54fbd6abb9763', '00000000f037ad09d0b05ee66b8c1da83030abaf909d2b1bf519c3c7d2cd3fdf', '000000006ce8b5f16fcedde13acbc9641baa1c67734f177d770a4069c06c9de8', '00000000563298de120522b5ae17da21aaae02eee2d7fcb5be65d9224dbd601c', '000000009b0a4b2833b4a0aa61171ee75b8eb301ac45a18713795a72e461a946', '00000000fa8a7363e8f6fdc88ec55edf264c9c7b31268c26e497a4587c750584', '000000008ac55b5cd76a5c176f2457f0e9df5ff1c719d939f1022712b1ba2092', '000000007f0c796631f00f542c0b402d638d3518bc208f8c9e5d29d2f169c084', '00000000ffb062296c9d4eb5f87bbf905d30669d26eab6bced341bd3f1dba5fd', '0000000074c108842c3ec2252bba62db4050bf0dddfee3ddaa5f847076b8822f', '0000000067dc2f84a73fbf5d3c70678ce4a1496ef3a62c557bc79cbdd1d49f22', '00000000dbf06f47c0624262ecb197bccf6bdaaabc2d973708ac401ac8955acc', '000000009260fe30ec89ef367122f429dcc59f61735760f2b2288f2e854f04ac', '00000000f9f1a700898c4e0671af6efd441eaf339ba075a5c5c7b0949473c80b', '000000005107662c86452e7365f32f8ffdc70d8d87aa6f78630a79f7d77fbfe6', '00000000984f962134a7291e3693075ae03e521f0ee33378ec30a334d860034b', '000000005e36047e39452a7beaaa6721048ac408a3e75bb60a8b0008713653ce', '00000000128d789579ffbec00203a371cbb39cee27df35d951fd66e62ed59258', '000000008dde642fb80481bb5e1671cb04c6716de5b7f783aa3388456d5c8a85' ] node = SimpleNode('btc.programmingblockchain.com', testnet=False) node.handshake() last_block_hash = GENESIS_BLOCK_HASH count = 1 for _ in range(20): getheaders = GetHeadersMessage(start_block=last_block_hash) node.send(getheaders.command, getheaders.serialize()) headers_envelope = node.wait_for_commands([b'headers']) headers_message = HeadersMessage.parse(headers_envelope.stream()) for b in headers_message.blocks: self.assertTrue(b.check_pow()) if last_block_hash != GENESIS_BLOCK_HASH: self.assertEqual(b.prev_block, last_block_hash) count += 1 last_block_hash = b.hash() if count % 2016 == 0: self.assertEqual(b.id(), expected.pop(0))
def test_example_1(self): node = SimpleNode('btc.programmingblockchain.com', testnet=False) node.handshake() last_block_hash = GENESIS_BLOCK_HASH first_epoch_block = None expected_bits = None count = 1 want = ('ffff001d', 'ffff001d', 'ffff001d', 'ffff001d', 'ffff001d', 'ffff001d', 'ffff001d', 'ffff001d', 'ffff001d', 'ffff001d', 'ffff001d', 'ffff001d', 'ffff001d', 'ffff001d', 'ffff001d', 'ffff001d', '6ad8001d', '28c4001d') for bits in want: getheaders = GetHeadersMessage(start_block=last_block_hash) node.send(getheaders.command, getheaders.serialize()) headers_envelope = node.wait_for_commands([b'headers']) headers_message = HeadersMessage.parse(headers_envelope.stream()) for block in headers_message.blocks: if not block.check_pow(): raise RuntimeError( 'bad proof of work at block {}'.format(count)) if last_block_hash != GENESIS_BLOCK_HASH and block.prev_block != last_block_hash: raise RuntimeError( 'discontinuous block at {}'.format(count)) if expected_bits and block.bits != expected_bits: raise RuntimeError('bad bits at block {} {} vs {}'.format( count, block.bits.hex(), expected_bits.hex())) if first_epoch_block and count % 2016 == 2015: expected_bits = calculate_new_bits( expected_bits, block.timestamp - first_epoch_block.timestamp) self.assertEqual(expected_bits.hex(), bits) elif first_epoch_block is None: expected_bits = block.bits if count % 2016 == 0 or not first_epoch_block: first_epoch_block = block count += 1 last_block_hash = block.hash()
def test_example_5(self): last_block_hex = '00000000000538d5c2246336644f9a4956551afb44ba47278759ec55ea912e19' address = 'mwJn1YPMq7y5F8J3LkC5Hxg9PHyZ5K4cFv' h160 = decode_base58(address) node = SimpleNode('tbtc.programmingblockchain.com', testnet=True, logging=False) bf = BloomFilter(30, 5, 90210) bf.add(h160) node.handshake() node.send(b'filterload', bf.filterload()) start_block = bytes.fromhex(last_block_hex) getheaders_message = GetHeadersMessage(start_block=start_block) node.send(b'getheaders', getheaders_message.serialize()) headers_envelope = node.wait_for_commands({b'headers'}) stream = headers_envelope.stream() headers = HeadersMessage.parse(stream) get_data_message = GetDataMessage() for b in headers.blocks: if not b.check_pow(): raise RuntimeError('proof of work is invalid') get_data_message.add_data(FILTERED_BLOCK_DATA_TYPE, b.hash()) node.send(b'getdata', get_data_message.serialize()) found = False while not found: envelope = node.wait_for_commands({b'merkleblock', b'tx'}) stream = envelope.stream() if envelope.command == b'merkleblock': mb = MerkleBlock.parse(stream) if not mb.is_valid(): raise RuntimeError('invalid merkle proof') else: prev_tx_obj = Tx.parse(stream, testnet=True) for i, tx_out in enumerate(prev_tx_obj.tx_outs): if tx_out.script_pubkey.address(testnet=True) == address: self.assertEqual( prev_tx_obj.id(), 'e3930e1e566ca9b75d53b0eb9acb7607f547e1182d1d22bd4b661cfe18dcddf1' ) self.assertEqual(i, 0) found = True break
# if not message.is_valid(): # raise RuntimeError('invalid merkle proof') # else: # for i, tx_out in enumerate(message.tx_outs): # if tx_out.script_pubkey.address(testnet=True) == utxo_address: # print('found: {}:{}'.format(message.id(), i)) # found = True # break node.handshake() getdata = GetDataMessage() t = '1ca6b09df876b19f37548986f331a5360a472fc8a3604b19877dda83a6bfe326' t_bytes = bytes.fromhex(t) #getdata.add_data(BLOCK_DATA_TYPE, bytes.fromhex(block_hash_hex)) getdata.add_data(TX_DATA_TYPE, t_bytes) node.send(getdata) received_tx = node.wait_for(Tx) print(received_tx.id() == t_bytes.hex()) # print("Given: ", block_hash_hex) # while not found: # message = node.wait_for(Block) # if message.command == b'block': # received_block_hash = message.hash().hex() # print("Received: ", received_block_hash) # print(received_block_hash == block_hash_hex) # print(message.tx_hashes) # found = True # break
# our test #last_block_hex = '0000000017e6fbd8931bce659d45d92040a4674950f2ae5416d0bf1a239641f9' last_block_hex = '00000000970369111c044804ec0319792c9e1aa29f59a622c5d14b3544ae4eba' #0000000017e6fbd8931bce659d45d92040a4674950f2ae5416d0bf1a239641f9 #last_block_hex = '0000000000000004fea90996fdf40772e2c2c76205a1fb57fae465194fdaffb9' address = 'mvEg6eZ3sUApodedYQrkpEPMMALsr1K1k1' h160 = decode_base58(address) node = SimpleNode('testnet.programmingbitcoin.com', testnet=True, logging=False) bf = BloomFilter(size=30, function_count=5, tweak=90210) bf.add(h160) node.handshake() node.send(bf.filterload()) start_block = bytes.fromhex(last_block_hex) getheaders = GetHeadersMessage(start_block=start_block) node.send(getheaders) print('ok2') headers = node.wait_for(HeadersMessage) print('ok3') getdata = GetDataMessage() for b in headers.blocks: if not b.check_pow(): raise RuntimeError('proof of work is invalid') getdata.add_data(FILTERED_BLOCK_DATA_TYPE, b.hash()) node.send(getdata) found = False while not found: print('ok1')
def test_exercise_6(self): last_block_hex = '00000000000538d5c2246336644f9a4956551afb44ba47278759ec55ea912e19' secret = little_endian_to_int( hash256(b'Jimmy Song Programming Blockchain')) private_key = PrivateKey(secret=secret) addr = private_key.point.address(testnet=True) h160 = decode_base58(addr) target_address = 'mwJn1YPMq7y5F8J3LkC5Hxg9PHyZ5K4cFv' self.assertEqual(addr, target_address) target_h160 = decode_base58(target_address) target_script = p2pkh_script(target_h160) fee = 5000 node = SimpleNode('tbtc.programmingblockchain.com', testnet=True) bf = BloomFilter(30, 5, 90210) bf.add(h160) node.handshake() node.send(b'filterload', bf.filterload()) start_block = bytes.fromhex(last_block_hex) getheaders_message = GetHeadersMessage(start_block=start_block) node.send(getheaders_message.command, getheaders_message.serialize()) headers_envelope = node.wait_for_commands({HeadersMessage.command}) stream = headers_envelope.stream() headers = HeadersMessage.parse(stream) last_block = None get_data_message = GetDataMessage() for b in headers.blocks: if not b.check_pow(): raise RuntimeError('proof of work is invalid') if last_block is not None and b.prev_block != last_block: raise RuntimeError('chain broken') get_data_message.add_data(FILTERED_BLOCK_DATA_TYPE, b.hash()) last_block = b.hash() node.send(get_data_message.command, get_data_message.serialize()) prev_tx, prev_index, prev_tx_obj = None, None, None while prev_tx is None: envelope = node.wait_for_commands({b'merkleblock', b'tx'}) stream = envelope.stream() if envelope.command == b'merkleblock': mb = MerkleBlock.parse(stream) if not mb.is_valid(): raise RuntimeError('invalid merkle proof') else: prev_tx_obj = Tx.parse(stream, testnet=True) for i, tx_out in enumerate(prev_tx_obj.tx_outs): if tx_out.script_pubkey.address(testnet=True) == addr: prev_tx = prev_tx_obj.hash() prev_index = i self.assertEqual( prev_tx_obj.id(), 'e3930e1e566ca9b75d53b0eb9acb7607f547e1182d1d22bd4b661cfe18dcddf1' ) self.assertEqual(i, 0) tx_in = TxIn(prev_tx, prev_index, Script([]), 0xffffff) TxFetcher.cache[prev_tx] = prev_tx_obj tx_ins = [tx_in] total = prev_tx_obj.tx_outs[prev_index].amount tx_outs = [TxOut(total - fee, target_script)] tx_obj = Tx(1, tx_ins, tx_outs, 0, testnet=True) tx_obj.sign_input(0, private_key) self.assertEqual( tx_obj.serialize().hex(), '0100000001f1dddc18fe1c664bbd221d2d18e147f50776cb9aebb0535db7a96c561e0e93e3000000006a473044022046a49962540a89e83da0636455b6c81c11c2844b7f3cd414c02e1a13741f4d15022006eed4eeda994d2bfebb9f1a494bfa3c8bab96e7e4c82623f4a29736dfe309e70121021cdd761c7eb1c90c0af0a5963e94bf0203176b4662778d32bd6d7ab5d8628b32ffffff0001a1629ef5000000001976a914ad346f8eb57dee9a37981716e498120ae80e44f788ac00000000' )
class Wallet: def __init__(self, encrypted_private, public, next_external, next_internal, creation_height, sync_height, utxos, stxos, filename): self.encrypted_private = encrypted_private self.public = public self.next_external = next_external self.next_internal = next_internal self.creation_height = creation_height self.sync_height = sync_height self.utxo_lookup = {(utxo.txo.tx_id, utxo.txo.tx_index): utxo for utxo in utxos} self.stxo_lookup = {(stxo.spending_tx_id, stxo.spending_tx_index): stxo for stxo in stxos} self.filename = filename self.testnet = self.public.testnet self.tx_store = TxStore.get_store(testnet=self.testnet) self.node = SimpleNode(host='192.168.1.200', testnet=self.testnet) self.block_store = BlockStore(node=self.node, include=self.sync_height) sleep(1) if not self.creation_height: # this is a new wallet self.creation_height = self.sync_height = self.node.latest_block @classmethod def create(cls, filename=None, testnet=False): if filename is None: if testnet: filename = 'testnet.wallet' else: filename = 'mainnet.wallet' if isfile(filename): raise RuntimeError('file exists: {}'.format(filename)) mnemonic, encrypted_private = EncryptedPrivateKey.generate(testnet) if testnet: network = b"1'" else: network = b"0'" path = b"m/84'/" + network + b"/0'" public = encrypted_private.private_key.traverse(path).pub wallet = cls(encrypted_private, public, 0, 0, None, None, [], [], filename=filename) wallet.save() return mnemonic, wallet @classmethod def recover(cls, mnemonic, filename=None, testnet=False): if filename is None: if testnet: filename = 'testnet.wallet' else: filename = 'mainnet.wallet' if isfile(filename): raise RuntimeError('file exists: {}'.format(filename)) encrypted_private = EncryptedPrivateKey.from_mnemonic( mnemonic, testnet) if testnet: network = b"1'" else: network = b"0'" path = b"m/84'/" + network + b"/0'" public = encrypted_private.private_key.traverse(path).pub wallet = cls(encrypted_private, public, 0, 0, None, None, [], [], filename=filename) # TODO: determine how many addresses have been used # TODO: determine what height to start at wallet.save() return wallet @classmethod def open(cls, filename='testnet.wallet'): if not isfile(filename): raise RuntimeError('No such file {}'.format(filename)) with open(filename, 'rb') as f: encrypted_private = EncryptedPrivateKey.parse(f) public = HDPublicKey.parse(f) next_external = read_varint(f) next_internal = read_varint(f) creation_height = read_varint(f) sync_height = read_varint(f) num_utxos = read_varint(f) utxos = [] for _ in range(num_utxos): utxos.append(UTXO.parse(f)) num_stxos = read_varint(f) stxos = [] for _ in range(num_stxos): stxos.append(STXO.parse(f)) return cls(encrypted_private, public, next_external, next_internal, creation_height, sync_height, utxos, stxos, filename=filename) def save(self): with open(self.filename, 'wb') as f: f.write(self.encrypted_private.serialize()) f.write(self.public.serialize()) f.write(encode_varint(self.next_external)) f.write(encode_varint(self.next_internal)) f.write(encode_varint(self.creation_height)) f.write(encode_varint(self.sync_height)) utxos = [u for u in self.utxo_lookup.values()] f.write(encode_varint(len(utxos))) for utxo in utxos: f.write(utxo.serialize()) stxos = [s for s in self.stxo_lookup.values()] f.write(encode_varint(len(stxos))) for stxo in stxos: f.write(stxo.serialize()) def address(self): pub = self.public.child(0).child(self.next_external) self.next_external += 1 self.save() return pub.bech32_address() def change_script_pubkey(self): pub = self.public.child(1).child(self.next_internal) self.next_internal += 1 self.save() return pub.p2wpkh_script_pubkey() def script_pubkey_lookup(self): script_pubkey_lookup = {} external = self.public.child(0) for i in range(self.next_external): script_pubkey_lookup[external.child( i).p2wpkh_script_pubkey().raw_serialize()] = (0, i) internal = self.public.child(1) for i in range(self.next_internal): script_pubkey_lookup[internal.child( i).p2wpkh_script_pubkey().raw_serialize()] = (1, i) return script_pubkey_lookup def rescan(self): self.update(self.creation_height) def update(self, height=None): '''Scan from the block at height to the block that the block_store has''' self.block_store.update() if height is None: height = self.sync_height block_hashes = self.get_relevant_block_hashes(height) self.scan_blocks(block_hashes) def get_relevant_block_hashes(self, height): # start from height, download the compact filters num_requests, r = divmod(self.block_store.store_height() - height, 1000) if r > 0: num_requests += 1 block_hashes = [] script_pubkey_lookup = self.script_pubkey_lookup() LOGGER.info('scanning blocks for relevant transactions') for i in range(num_requests): start_height = height + i * 1000 if i == num_requests - 1: end_height = self.block_store.store_height() else: end_height = height + (i + 1) * 1000 - 1 stop_hash = self.block_store.header_by_height(end_height).hash() LOGGER.info('scanning from {} to {}'.format( start_height, end_height)) getcfilters = GetCFiltersMessage( start_height=start_height, stop_hash=stop_hash, ) self.node.send(getcfilters) for h in range(start_height, end_height + 1): cfilter = self.node.wait_for(CFilterMessage) header = self.block_store.header_by_height(h) if header.hash() != cfilter.block_hash: raise RuntimeError('bad block') computed = hash256(cfilter.filter_bytes)[::-1] if computed != header.cfhash: raise RuntimeError('not the right cf hash') if script_pubkey_lookup.keys() in cfilter: LOGGER.info('interesting block {}: {}'.format( h, header.id())) header.cfilter = cfilter block_hashes.append(cfilter.block_hash) return block_hashes def scan_blocks(self, block_hashes): script_pubkey_lookup = self.script_pubkey_lookup() # download the blocks we know are interesting for h, b in self.node.get_blocks(block_hashes): if h != b.hash(): raise RuntimeError('bad block: {} vs {}'.format( h.hex(), b.id())) header = self.block_store.header_by_hash(h) for raw_script_pubkey in b.get_outpoints(): if raw_script_pubkey not in header.cfilter: raise RuntimeError('script_pubkeys are not in the filter') for tx_obj in b.txs: add_tx = False tx_hash = tx_obj.hash() if not tx_obj.is_coinbase(): for i, tx_in in enumerate(tx_obj.tx_ins): # if this transaction has a utxo as an input (spending) key = (tx_in.prev_tx, tx_in.prev_index) if self.utxo_lookup.get(key): add_tx = True utxo = self.utxo_lookup.pop(key) self.stxo_lookup[key] = STXO(utxo.txo, tx_hash, i) for i, tx_out in enumerate(tx_obj.tx_outs): # if this transaction has an output that's ours (receiving) raw_script_pubkey = tx_out.script_pubkey.raw_serialize() if raw_script_pubkey in script_pubkey_lookup.keys(): add_tx = True txo = TXO(tx_hash, i, tx_out.amount, tx_out.script_pubkey, script_pubkey_lookup[raw_script_pubkey]) self.utxo_lookup[key] = UTXO(txo) if add_tx: LOGGER.info('interesting tx {}'.format(tx_obj.id())) self.tx_store.add(tx_obj) self.sync_height = self.block_store.store_height() self.save() self.tx_store.save() def balance(self): balance = 0 for utxo in self.utxo_lookup.values(): balance += utxo.txo.amount return balance def spent(self): spent = 0 for stxo in self.stxo_lookup.values(): spent += stxo.txo.amount return spent def total_received(self): return self.balance() + self.spent() def simple_send(self, address, amount, satoshi_per_byte=1): if amount > self.balance(): raise RuntimeError( 'Balance is less than how much you want to send') input_total = 0 tx_ins = [] paths = [] # gather inputs using our utxos for utxo in self.utxo_lookup.values(): input_total += utxo.txo.amount tx_ins.append(TxIn(utxo.txo.tx_id, utxo.txo.tx_index)) paths.append(utxo.txo.path) if input_total > amount: break # target output tx_outs = [TxOut(amount, address_to_script_pubkey(address))] tx_outs_size = len(tx_outs[0].serialize()) # calculate how much fees should be at this point # our utxos are always bech32, so each input is 32+4+1+4 = 41 bytes # our change outputs are bech32, so each output is 8+23 = 31 bytes # the witness will be 33 + 72 bytes per input but count 1/4 tx_size = 4 + 1 + len(tx_ins) * 41 + 1 + tx_outs_size + 31 + len( tx_ins) * (2 + 33 + 72) fee = tx_size * satoshi_per_byte if input_total < fee + amount: raise RuntimeError('Balance does not cover fees') # change output change_amount = input_total - amount - fee # don't bother if we're creating dust if change_amount > 200: tx_outs.append(TxOut(change_amount, self.change_script_pubkey())) # create transaction and sign tx_obj = Tx(1, tx_ins, tx_outs, 0, testnet=self.testnet, segwit=True) # signing the transaction self.encrypted_private.sign(tx_obj, paths) LOGGER.info('sending the transaction') return self.node.send_tx(tx_obj)
# # Exercise 6 # # print("Exercise 6") # run(network.GetHeadersMessageTest("test_serialize")) previous = Block.parse(BytesIO(GENESIS_BLOCK)) first_epoch_timestamp = previous.timestamp expected_bits = LOWEST_BITS count = 1 node = SimpleNode('mainnet.programmingbitcoin.com', testnet=False) node.handshake() for _ in range(19): getheaders = GetHeadersMessage(start_block=previous.hash()) node.send(getheaders) headers = node.wait_for(HeadersMessage) for header in headers.blocks: if not header.check_pow(): raise RuntimeError('bad PoW at block {}'.format(count)) if header.prev_block != previous.hash(): raise RuntimeError('discontinuous block at {}'.format(count)) if count % 2016 == 0: time_diff = previous.timestamp - first_epoch_timestamp expected_bits = calculate_new_bits(previous.bits, time_diff) print("Range: {}. Expected Bits: {}.".format(_, expected_bits.hex())) first_epoch_timestamp = header.timestamp if header.bits != expected_bits: raise RuntimeError('bad bits at block {}'.format(count))