def create_menu(self, position: QPoint) -> None: self.selectedIndexes() item = self.currentItem() if not item: return column = self.currentColumn() account_id = item.data(Columns.STATUS, self.ACCOUNT_ROLE) tx_hash = item.data(Columns.STATUS, self.TX_ROLE) if account_id is None or tx_hash is None: return if column == 0: column_title = "ID" column_data = hash_to_hex_str(tx_hash) else: column_title = self.headerItem().text(column) column_data = item.text(column).strip() account = self._wallet.get_account(account_id) tx_id = hash_to_hex_str(tx_hash) tx_URL = web.BE_URL(self.config, 'tx', tx_id) height, _conf, _timestamp = self._wallet.get_tx_height(tx_hash) tx = account.get_transaction(tx_hash) if not tx: return # this happens sometimes on account synch when first starting up. is_unconfirmed = height <= 0 menu = QMenu() menu.addAction( _("Copy {}").format(column_title), lambda: self._main_window.app.clipboard().setText(column_data)) if column in self.editable_columns: # We grab a fresh reference to the current item, as it has been deleted in a # reported issue. menu.addAction( _("Edit {}").format(column_title), lambda: self.currentItem() and self.editItem( self.currentItem(), column)) menu.addAction(_("Details"), lambda: self._main_window.show_transaction(account, tx)) if is_unconfirmed and tx: child_tx = account.cpfp(tx, 0) if child_tx: menu.addAction( _("Child pays for parent"), lambda: self._main_window.cpfp(account, tx, child_tx)) entry = self._account.get_transaction_entry(tx_hash) if entry.flags & TxFlags.PaysInvoice: invoice_row = self._account.invoices.get_invoice_for_tx_hash( tx_hash) invoice_id = invoice_row.invoice_id if invoice_row is not None else None action = menu.addAction( read_QIcon(ICON_NAME_INVOICE_PAYMENT), _("View invoice"), partial(self._show_invoice_window, invoice_id)) action.setEnabled(invoice_id is not None) if tx_URL: menu.addAction(_("View on block explorer"), lambda: webbrowser.open(tx_URL)) menu.exec_(self.viewport().mapToGlobal(position))
def create_menu(self, position): self.selectedIndexes() item = self.currentItem() if not item: return column = self.currentColumn() edit_data = item.data(0, Qt.UserRole) if not edit_data: return account_id, tx_hash = edit_data if column == 0: column_title = "ID" column_data = hash_to_hex_str(tx_hash) else: column_title = self.headerItem().text(column) column_data = item.text(column).strip() account = self._wallet.get_account(account_id) tx_id = hash_to_hex_str(tx_hash) tx_URL = web.BE_URL(self.config, 'tx', tx_id) height, _conf, _timestamp = account.get_tx_height(tx_hash) tx = account.get_transaction(tx_hash) if not tx: return # this happens sometimes on account synch when first starting up. is_unconfirmed = height <= 0 pr_key = account.invoices.paid.get(tx_hash) menu = QMenu() menu.addAction( _("Copy {}").format(column_title), lambda: self._main_window.app.clipboard().setText(column_data)) if column in self.editable_columns: # We grab a fresh reference to the current item, as it has been deleted in a # reported issue. menu.addAction( _("Edit {}").format(column_title), lambda: self.currentItem() and self.editItem( self.currentItem(), column)) label = account.get_transaction_label(tx_hash) or None menu.addAction( _("Details"), lambda: self._main_window.show_transaction(account, tx, label)) if is_unconfirmed and tx: child_tx = account.cpfp(tx, 0) if child_tx: menu.addAction( _("Child pays for parent"), lambda: self._main_window.cpfp(account, tx, child_tx)) if pr_key: menu.addAction(read_QIcon("seal"), _("View invoice"), lambda: self._main_window.show_invoice(pr_key)) if tx_URL: menu.addAction(_("View on block explorer"), lambda: webbrowser.open(tx_URL)) menu.exec_(self.viewport().mapToGlobal(position))
def deserialize_getheaders(cls, f): """for checking my own getheaders request""" version = bitcoinx.read_le_uint32(f.read) hash_count = bitcoinx.read_varint(f.read) block_locator_hashes = [] for i in range(hash_count): block_locator_hashes.append(bitcoinx.hash_to_hex_str(f.read(32))) hash_stop = bitcoinx.hash_to_hex_str(f.read(32)) message = {'version': version, 'hash_count': hash_count, 'block_locator_hashes': block_locator_hashes, 'hash_stop': hash_stop} return message
def delete(self, tx_hash: bytes, completion_callback: Optional[CompletionCallbackType]=None) -> None: with self._lock: self._logger.debug("cache_deletion: %s", hash_to_hex_str(tx_hash)) del self._cache[tx_hash] self._store.delete([ tx_hash ], completion_callback=completion_callback)
def _on_transaction_deleted(self, account_id: int, tx_hash: bytes) -> None: if not self._validate_account_event({account_id}): return self._logger.debug("_on_transaction_deleted %s", hash_to_hex_str(tx_hash)) self._mark_transactions_removed([tx_hash])
def validate_block_header(header): """ Takes in a single block header; returns True if valid block; False if invalid Validation checks to be performed (1) checks that calculated block hash is < target (difficulty) (2) checks that this hashed block contains a "prev_block_hash" field equal to the prior block calculated hash (3) check than target is set as per the consensus difficulty adjustment algorithm (NOT DONE HERE) see 'validate_header_difficulty()' --> this phase 2 check is done on the whole database in one go """ # extract nbits as int direct from byte position 72:76 in header target = Validate.bits_to_target(bitcoinx.unpack_le_int32(header[72:72 + 4])[0]) # get block hash block_hash = get_block_hash(header) prev_hash_que.append(block_hash) # check if hash of current block is less than nbits "target" is_less_than_target = int(block_hash, 16) < target if is_less_than_target: pass else: raise ValueError("hash of current block is not less than nbits target!") # check if prev_block_hash matches the last block on record in db # genesis block (#0) is not sent with "headers" message so first block is the block after genesis (#1) prev_block_hash = bitcoinx.hash_to_hex_str(header[4:36]) if prev_block_hash == prev_hash_que.pop(0): return True else: return False
def test_update_flags(self): cache = TxCache(self.store) tx_bytes_1 = bytes.fromhex(tx_hex_1) tx_hash_bytes_1 = bitcoinx.double_sha256(tx_bytes_1) tx_id_1 = bitcoinx.hash_to_hex_str(tx_hash_bytes_1) data = TxData(position=11) cache.add([(tx_id_1, data, tx_bytes_1, TxFlags.StateDispatched)]) self.assertTrue(cache.is_cached(tx_id_1)) entry = cache.get_entry(tx_id_1) self.assertEqual( TxFlags.HasByteData | TxFlags.HasPosition | TxFlags.StateDispatched, entry.flags) self.assertIsNotNone(entry.bytedata) cache.update_flags(tx_id_1, TxFlags.StateCleared, TxFlags.HasByteData | TxFlags.HasProofData) entry = cache.get_entry(tx_id_1) store_flags = self.store.get_flags(tx_id_1) expected_flags = TxFlags.HasByteData | TxFlags.HasPosition | TxFlags.StateCleared self.assertEqual( expected_flags, store_flags, f"{TxFlags.to_repr(expected_flags)} != {TxFlags.to_repr(store_flags)}" ) self.assertEqual( expected_flags, entry.flags, f"{TxFlags.to_repr(expected_flags)} != {TxFlags.to_repr(entry.flags)}" ) self.assertIsNotNone(entry.bytedata)
def _parse_input(vds): d = {} prevout_hash = hash_to_hex_str(vds.read_bytes(32)) prevout_n = vds.read_uint32() scriptSig = vds.read_bytes(vds.read_compact_size()) sequence = vds.read_uint32() d['prevout_hash'] = prevout_hash d['prevout_n'] = prevout_n d['sequence'] = sequence d['address'] = UnknownAddress() if prevout_hash == '00' * 32: d['type'] = 'coinbase' d['scriptSig'] = bh2u(scriptSig) else: d['x_pubkeys'] = [] d['pubkeys'] = [] d['signatures'] = {} d['address'] = None d['type'] = 'unknown' d['num_sig'] = 0 d['scriptSig'] = bh2u(scriptSig) _parse_scriptSig(d, scriptSig) if not Transaction.is_txin_complete(d): d['value'] = vds.read_uint64() return d
def test_add_then_update(self): cache = TxCache(self.store) bytedata_1 = bytes.fromhex(tx_hex_1) tx_id_1 = bitcoinx.hash_to_hex_str(bitcoinx.double_sha256(bytedata_1)) metadata_1 = TxData(position=11) cache.add([(tx_id_1, metadata_1, bytedata_1, TxFlags.StateDispatched)]) self.assertTrue(cache.is_cached(tx_id_1)) entry = cache.get_entry(tx_id_1) self.assertEqual( TxFlags.HasByteData | TxFlags.HasPosition | TxFlags.StateDispatched, entry.flags) self.assertIsNotNone(entry.bytedata) metadata_2 = TxData(fee=10, height=88) propagate_flags = TxFlags.HasFee | TxFlags.HasHeight cache.update([(tx_id_1, metadata_2, None, propagate_flags | TxFlags.HasPosition)]) entry = cache.get_entry(tx_id_1) expected_flags = propagate_flags | TxFlags.StateDispatched | TxFlags.HasByteData self.assertEqual( expected_flags, entry.flags, f"{TxFlags.to_repr(expected_flags)} != {TxFlags.to_repr(entry.flags)}" ) self.assertIsNotNone(entry.bytedata)
def _create_menu(self, position) -> None: item = self.currentItem() if not item: return column = self.currentColumn() column_title = self.headerItem().text(column) column_data = item.text(column).strip() tx_hash = item.data(InputColumns.INDEX, Roles.TX_HASH) have_tx = self.parent()._account.have_transaction_data(tx_hash) tx_id = hash_to_hex_str(tx_hash) tx_URL = web.BE_URL(self._main_window.config, 'tx', tx_id) menu = QMenu() menu.addAction( _("Copy {}").format(column_title), lambda: self._main_window.app.clipboard().setText(column_data)) details_menu = menu.addAction( _("Transaction details"), partial(self._show_other_transaction, tx_hash)) details_menu.setEnabled(have_tx) if tx_URL: menu.addAction(_("View on block explorer"), lambda: webbrowser.open(tx_URL)) menu.exec_(self.viewport().mapToGlobal(position))
def test_update_many(self): to_add = [] for i in range(10): tx_bytes = os.urandom(10) tx_hash_bytes = bitcoinx.double_sha256(tx_bytes) tx_id = bitcoinx.hash_to_hex_str(tx_hash_bytes) tx_data = TxData(height=None, fee=2, position=None, timestamp=None) to_add.append((tx_id, tx_data, tx_bytes, TxFlags.Unset)) self.store.add_many(to_add) to_update = [] for tx_id, metadata, tx_bytes, flags in to_add: tx_metadata = TxData(height=1, fee=2, position=None, timestamp=None) to_update.append((tx_id, tx_metadata, tx_bytes, flags)) self.store.update_many(to_update) for tx_id_get, metadata_get, bytedata_get, flags_get in self.store.get_many( ): for update_tx_id, update_metadata, update_tx_bytes, update_flags in to_update: if update_tx_id == tx_id_get: self.assertEqual(metadata_get, update_metadata) self.assertEqual(bytedata_get, update_tx_bytes) continue
def test_update_flags(self): bytedata = os.urandom(10) tx_hash_bytes = bitcoinx.double_sha256(bytedata) tx_id = bitcoinx.hash_to_hex_str(tx_hash_bytes) metadata = TxData(height=1, fee=2, position=None, timestamp=None) self.store.add(tx_id, metadata, bytedata) # Verify the field flags are assigned correctly on the add. expected_flags = TxFlags.HasFee | TxFlags.HasHeight | TxFlags.HasByteData flags = self.store.get_flags(tx_id) self.assertEqual(expected_flags, flags) flags = TxFlags.StateReceived mask = TxFlags.METADATA_FIELD_MASK | TxFlags.HasByteData | TxFlags.HasProofData self.store.update_flags(tx_id, flags, mask) # Verify the state flag is correctly added via the mask. flags_get = self.store.get_flags(tx_id) expected_flags |= TxFlags.StateReceived self.assertEqual( expected_flags, flags_get, f"{TxFlags.to_repr(expected_flags)} != {TxFlags.to_repr(flags_get)}" ) flags = TxFlags.StateReceived mask = TxFlags.Unset self.store.update_flags(tx_id, flags, mask) # Verify the state flag is correctly set via the mask. flags = self.store.get_flags(tx_id) self.assertEqual(TxFlags.StateReceived, flags)
def test_Bitcoin(raw_header, header_hash, version, prev_hash, merkle_root, timestamp, bits, nonce): header_hash = hex_str_to_hash(header_hash) prev_hash = hex_str_to_hash(prev_hash) merkle_root = hex_str_to_hash(merkle_root) assert Bitcoin.header_hash(raw_header) == header_hash assert Bitcoin.header_prev_hash(raw_header) == prev_hash assert Bitcoin.header_work(raw_header) == bits_to_work(bits) assert Bitcoin.header_timestamp(raw_header) == timestamp header = Bitcoin.deserialized_header(raw_header, 0) assert header.version == version assert header.prev_hash == prev_hash assert header.merkle_root == merkle_root assert header.timestamp == timestamp assert header.bits == bits assert header.nonce == nonce assert header.raw == raw_header assert header.hash == header_hash assert header.height == 0 assert header.work() == Bitcoin.header_work(raw_header) assert header.target() == bits_to_target(bits) assert header.hash_value() == hash_to_value(header_hash) assert header.hex_str() == hash_to_hex_str(header_hash) assert 'height=0' in str(header)
def _write(db: sqlite3.Connection): self._logger.debug("deleting transactions %s", [hash_to_hex_str(b[0]) for b in datas]) db.executemany(TransactionDeltaTable.DELETE_TRANSACTION_SQL, datas) db.executemany(TransactionOutputTable.DELETE_TRANSACTION_SQL, datas) db.executemany(self.DELETE_SQL, datas)
def _update_transactions(self, tx_hashes: List[bytes], state: Dict[bytes, EventFlags]) -> None: self._logger.debug("_update_transactions %d", len(tx_hashes)) if not len(tx_hashes): return matches = self._match_transactions(tx_hashes) if len(matches) != len(tx_hashes): matched_tx_hashes = [ line.hash for (row, line) in matches ] # The add database write has not completed yet. self._logger.debug("_update_transactions missing entries %s", [ hash_to_hex_str(a) for a in tx_hashes if a not in matched_tx_hashes ]) if not len(matches): return matches_by_hash = dict((t[1].hash, t) for t in matches) matched_tx_hashes = list(matches_by_hash.keys()) for tx_hash, tx_flags, tx_data in self._wallet.read_transaction_metadatas( tx_hashes=matched_tx_hashes, account_id=self._account_id): row, line = matches_by_hash[tx_hash] new_line = self._create_transaction_entry(tx_hash, tx_data) self._data[row] = new_line self._base_model.invalidate_row(row) self.changed_signal.emit(self._account_id)
def test_update_or_add(self): cache = TxCache(self.store) # Add. bytedata_1 = bytes.fromhex(tx_hex_1) tx_hash_bytes_1 = bitcoinx.double_sha256(bytedata_1) tx_id_1 = bitcoinx.hash_to_hex_str(tx_hash_bytes_1) metadata_1 = TxData() cache.update_or_add([(tx_id_1, metadata_1, bytedata_1, TxFlags.StateCleared)]) self.assertTrue(cache.is_cached(tx_id_1)) entry = cache.get_entry(tx_id_1) self.assertEqual(TxFlags.HasByteData | TxFlags.StateCleared, entry.flags) self.assertIsNotNone(entry.bytedata) # Update. metadata_2 = TxData(position=22) cache.update_or_add([(tx_id_1, metadata_2, None, TxFlags.HasPosition | TxFlags.StateDispatched)]) entry = cache.get_entry(tx_id_1) store_flags = self.store.get_flags(tx_id_1) # State flags if present get set in an update otherwise they remain the same. expected_flags = TxFlags.HasPosition | TxFlags.HasByteData | TxFlags.StateDispatched self.assertEqual( expected_flags, store_flags, f"{TxFlags.to_repr(expected_flags)} != {TxFlags.to_repr(store_flags)}" ) self.assertEqual( expected_flags, entry.flags, f"{TxFlags.to_repr(expected_flags)} != {TxFlags.to_repr(entry.flags)}" ) self.assertEqual(bytedata_1, entry.bytedata) self.assertEqual(metadata_2.position, entry.metadata.position)
def update_row(self, row: int, values: Dict[int, Any]) -> bool: old_line = self._data[row] tx_id = hash_to_hex_str(old_line.hash) self._logger.debug("update_line tx=%s idx=%d", tx_id, row) if len(values): l = list(old_line) for value_index, value in values.items(): l[value_index] = value new_line = self._data[row] = TxLine(*l) old_key = get_sort_key(old_line) new_key = get_sort_key(new_line) if old_key != new_key: # We need to move the line, so it is more than a simple row update. self.remove_row(row) insert_row = self._add_line(new_line) return True start_index = self.createIndex(row, 0) column_count = self.columnCount(start_index) end_index = self.createIndex(row, column_count-1) self.dataChanged.emit(start_index, end_index) return True
def test_get_many(self): to_add = [] for i in range(10): tx_bytes = os.urandom(10) tx_hash_bytes = bitcoinx.double_sha256(tx_bytes) tx_id = bitcoinx.hash_to_hex_str(tx_hash_bytes) tx_data = TxData(height=None, fee=2, position=None, timestamp=None) to_add.append((tx_id, tx_data, tx_bytes, TxFlags.HasFee)) self.store.add_many(to_add) # Test the first "add" id is matched. matches = self.store.get_many(tx_ids=[to_add[0][0]]) self.assertEqual(to_add[0][0], matches[0][0]) # Test no id is matched. matches = self.store.get_many(tx_ids=["aaaa"]) self.assertEqual(0, len(matches)) # Test flag and mask combinations. matches = self.store.get_many(flags=TxFlags.HasFee) self.assertEqual(10, len(matches)) matches = self.store.get_many(flags=TxFlags.Unset, mask=TxFlags.HasHeight) self.assertEqual(10, len(matches)) matches = self.store.get_many(flags=TxFlags.HasFee, mask=TxFlags.HasFee) self.assertEqual(10, len(matches)) matches = self.store.get_many(flags=TxFlags.Unset, mask=TxFlags.HasFee) self.assertEqual(0, len(matches))
def _validate_new_flags(tx_hash: bytes, flags: TxFlags) -> None: # All current states are expected to have bytedata. if (flags & TxFlags.STATE_MASK) == 0 or (flags & TxFlags.HasByteData) != 0: return tx_id = hash_to_hex_str(tx_hash) raise InvalidDataError("setting uncleared state without bytedata " f"{tx_id} {TxFlags.to_repr(flags)}")
def get_name(self, other_chain): if other_chain is self: return f'our_chain' else: fork_height = self.common_height(other_chain) + 1 header = self.header_at_height(fork_height) prefix = hash_to_hex_str(header.hash).lstrip('00')[0:10] return f'{prefix}@{fork_height}'
def _on_transaction_added(self, tx_hash: bytes, tx: Transaction, account_ids: Set[int]) \ -> None: if not self._validate_account_event(account_ids): return self._logger.debug("_on_transaction_added %s", hash_to_hex_str(tx_hash)) with self._update_lock: self._pending_state[tx_hash] = EventFlags.TX_ADDED
def _on_transaction_deleted(self, wallet_path: str, account_id: int, tx_hash: bytes) -> None: if not self._validate_event(wallet_path, account_id): return self._logger.debug("_on_transaction_deleted %s", hash_to_hex_str(tx_hash)) self.remove_transactions([tx_hash])
def chain_name(self, chain, our_chain): if chain is our_chain: return f'our_chain' _chain, common_height = our_chain.common_chain_and_height(chain) fork_height = common_height + 1 header = self.header_at_height(fork_height) prefix = hash_to_hex_str(header.hash).lstrip('00')[0:10] return f'{prefix}@{fork_height}'
def get_and_set_frozen_utxos_for_tx(self, tx: Transaction, child_wallet: AbstractAccount, freeze: bool=True) -> List[UTXO]: spendable_coins = child_wallet.get_utxos(exclude_frozen=False) input_keys = set( [(bitcoinx.hash_to_hex_str(input.prev_hash), input.prev_idx) for input in tx.inputs]) frozen_utxos = [utxo for utxo in spendable_coins if utxo.key() in input_keys] child_wallet.set_frozen_coin_state(frozen_utxos, freeze) return frozen_utxos
def test_delete(self): tx_bytes = os.urandom(10) tx_hash_bytes = bitcoinx.double_sha256(tx_bytes) tx_id = bitcoinx.hash_to_hex_str(tx_hash_bytes) data = TxData(height=1, fee=2, position=None, timestamp=None) self.store.add(tx_id, data, tx_bytes) self.assertTrue(self.store.has(tx_id)) self.store.delete(tx_id) self.assertFalse(self.store.has(tx_id))
def _write(db: sqlite3.Connection) -> None: if len(entries) < 20: self._logger.debug( "update %d transactions: %s", len(entries), [(hash_to_hex_str(a), b, byte_repr(c), TxFlags.to_repr(d)) for (a, b, c, d) in entries]) else: self._logger.debug("update %d transactions (too many to show)", len(entries)) db.executemany(self.UPDATE_MANY_SQL, datas)
def utxo_as_dict(self, utxo): return {"value": utxo.value, "script_pubkey": utxo.script_pubkey.to_hex(), "script_type": utxo.script_type, "tx_hash": hash_to_hex_str(utxo.tx_hash), "out_index": utxo.out_index, "keyinstance_id": utxo.keyinstance_id, "address": utxo.address.to_string(), "is_coinbase": utxo.is_coinbase, "flags": utxo.flags} # TransactionOutputFlag(s) only
def deserialize_headers(cls, f): """deserialize block headers into a list of dicts""" lst_headers = [] global headers_stream global hashes_stream # Store headers temporarily to memory as binary stream headers_stream.seek(0) headers_stream.write(f.read()) # make a list of block hashes for validating headers_stream.seek(0) count = bitcoinx.read_varint(headers_stream.read) # count of headers for i in range(count): header = headers_stream.read(80) # minus final txn count (1 byte) headers_stream.read(1) # discard txn count _hash = simple_spv.tools.get_block_hash(header) # calculates hash as part of validation hashes_stream.write(_hash + '\n') f.seek(0) number_headers = bitcoinx.read_varint(f.read) for i in range(number_headers): # TODO make into single function call for readability and reuse version = bitcoinx.read_le_int32(f.read) prev_block = bitcoinx.hash_to_hex_str(f.read(32)) merkle_root = bitcoinx.hash_to_hex_str(f.read(32)) timestamp = bitcoinx.read_le_uint32(f.read) bits = ut.int_to_hex(bitcoinx.read_le_uint32(f.read)) nonce = bitcoinx.read_le_uint32(f.read) txn_count = bitcoinx.read_varint(f.read) block_header = {'version': version, 'prev_block_hash': prev_block, 'merkle_root': merkle_root, 'timestamp': timestamp, 'bits': bits, 'nonce': nonce, 'txn_count': txn_count} lst_headers.append(block_header) return lst_headers
def test_get_unverified_entries_too_high(self): cache = TxCache(self.store) tx_bytes_1 = bytes.fromhex(tx_hex_1) tx_hash_bytes_1 = bitcoinx.double_sha256(tx_bytes_1) tx_id_1 = bitcoinx.hash_to_hex_str(tx_hash_bytes_1) data = TxData(height=11, position=22) cache.add([(tx_id_1, data, tx_bytes_1, TxFlags.StateCleared)]) results = cache.get_unverified_entries(100) self.assertEqual(0, len(results))
def test_get_transaction(self): bytedata = bytes.fromhex(tx_hex_1) tx_hash_bytes = bitcoinx.double_sha256(bytedata) tx_id = bitcoinx.hash_to_hex_str(tx_hash_bytes) metadata = TxData(height=1, fee=2, position=None, timestamp=None) self.store.add(tx_id, metadata, bytedata) cache = TxCache(self.store) tx = cache.get_transaction(tx_id) self.assertIsNotNone(tx) self.assertEqual(tx_id, tx.txid())