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 get_segment_block_id_hashes(run_event, btc_client, block_hashes_queue, start_block): if run_event.is_set(): get_headers_msg = GetHeadersMessage(start_block=start_block) btc_client.send(get_headers_msg) headers = btc_client.wait_for(HeadersMessage).blocks if len(headers) > 0: logging.info("[-] Downloaded {} block headers after block hash: {}".format(len(headers), start_block.hex())) block_id_hashes = [] group_start_block = start_block for i, header in enumerate(headers): if i % 5 == 0 and i > 0: while run_event.is_set(): if not block_hashes_queue.full(): block_hashes_queue.put(block_id_hashes) break if i == len(headers) - 1: break group_start_block = block_id_hashes[-1] block_id_hashes = [] block_id_hashes.append(header.hash()) elif i == len(headers) - 1: block_id_hashes.append(header.hash()) while run_event.is_set(): if not block_hashes_queue.full(): block_hashes_queue.put(block_id_hashes) break else: block_id_hashes.append(header.hash()) return headers[-1].hash() return None
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_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
def request_headers(self): # get "getheaders" message start_block = self.headers[-1].hash() getheaders = GetHeadersMessage(start_block=start_block) self.peer.send(getheaders) # wait for the "headers" response headers_msg = self.peer.wait_for(HeadersMessage) for header in headers_msg.headers: self.receive_header(header) print(f'we now have {len(self.headers)} headers')
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_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()
#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') message = node.wait_for(MerkleBlock, Tx) if message.command == b'merkleblock':
def update(self, stop_height=None): if stop_height is None: stop_height = self.node.latest_block start_height = self.store_height() previous = self.headers[-1] # find the first epoch timestamp epoch_first_block = self.headers[(len(self.headers) // 2016) * 2016] epoch_first_timestamp = epoch_first_block.timestamp epoch_bits = epoch_first_block.bits # download blocks we don't have current_height = start_height + 1 while current_height <= stop_height: LOGGER.info('downloading headers {} to {}'.format(current_height, current_height+1999)) # ask for headers getheaders = GetHeadersMessage(start_block=previous.hash()) self.node.send(getheaders) headers = self.node.wait_for(HeadersMessage) # validate headers for header in headers.blocks: if not header.check_pow(): raise RuntimeError('{}: PoW is invalid'.format(current_height)) if header.prev_block != previous.hash(): raise RuntimeError('{}: not sequential'.format(current_height)) if current_height % 2016 == 0: # difficulty adjustment time_diff = previous.timestamp - epoch_first_timestamp epoch_bits = calculate_new_bits(previous.bits, time_diff) expected_bits = epoch_bits epoch_first_timestamp = header.timestamp elif self.testnet and header.timestamp > previous.timestamp + 20 * 60: expected_bits = LOWEST_BITS else: expected_bits = epoch_bits if header.bits != expected_bits: raise RuntimeError('{}: bad bits {} vs {}'.format( current_height, header.bits.hex(), expected_bits.hex())) previous = header current_height += 1 self.headers.append(header) self.headers_lookup[header.hash()] = header if current_height > stop_height: break LOGGER.info('downloaded headers to {} inclusive'.format(stop_height)) # download compact filter checkpoints getcfcheckpoint = GetCFCheckPointMessage(stop_hash=self.headers[-1].hash()) self.node.send(getcfcheckpoint) cfcheckpoint = self.node.wait_for(CFCheckPointMessage) # download all cfheaders prev_filter_header = b'\x00' * 32 cfcheckpoint.filter_headers.append(None) for i, cpfilter_header in enumerate(cfcheckpoint.filter_headers): if i == 0: start = 0 else: start = i * 1000 + 1 end = (i+1) * 1000 if end <= start_height: prev_filter_header = cpfilter_header continue if end > stop_height: end = stop_height stop_hash = self.headers[-1].hash() else: stop_hash = self.header_by_height(end).hash() LOGGER.info('getting filters range {} to {}'.format(start, end)) getcfheaders = GetCFHeadersMessage( start_height=start, stop_hash=stop_hash) self.node.send(getcfheaders) cfheaders = self.node.wait_for(CFHeadersMessage) if prev_filter_header != cfheaders.previous_filter_header: raise RuntimeError('Non-sequential CF headers') # validate all cfheaders for j, filter_hash in enumerate(cfheaders.filter_hashes): header = self.header_by_height(start + j) filter_header = hash256(filter_hash[::-1] + prev_filter_header[::-1])[::-1] prev_filter_header = filter_header header.cfhash = filter_hash header.cfheader = filter_header if cpfilter_header is not None and cpfilter_header != prev_filter_header: raise RuntimeError('CF header not what we expected')
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' )
# # 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: