diff --git a/.gitignore b/.gitignore index f4a8bb74..f3f2b590 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,6 @@ env/ .buildozer/ bin/ /app.fil - .idea # icons diff --git a/electrum/address_synchronizer.py b/electrum/address_synchronizer.py index d4471de9..7e425bff 100644 --- a/electrum/address_synchronizer.py +++ b/electrum/address_synchronizer.py @@ -69,8 +69,8 @@ class AddressSynchronizer(PrintError): # Verified transactions. txid -> VerifiedTxInfo. Access with self.lock. verified_tx = storage.get('verified_tx3', {}) self.verified_tx = {} - for txid, (height, timestamp, txpos, header_hash, tx_comment) in verified_tx.items(): - self.verified_tx[txid] = VerifiedTxInfo(height, timestamp, txpos, header_hash, tx_comment) + for txid, (height, timestamp, txpos, header_hash) in verified_tx.items(): + self.verified_tx[txid] = VerifiedTxInfo(height, timestamp, txpos, header_hash) # Transactions pending verification. txid -> tx_height. Access with self.lock. self.unverified_tx = defaultdict(int) # true when synchronized @@ -640,23 +640,6 @@ class AddressSynchronizer(PrintError): # local transaction return TxMinedStatus(TX_HEIGHT_LOCAL, 0, None, None) - def get_tx_comment(self, tx_hash: str): - """ Given a transaction, returns txcomment/floData """ - with self.lock: - if tx_hash in self.verified_tx: - info = self.verified_tx[tx_hash] - tx_comment = info[4] - return tx_comment - elif tx_hash in self.unverified_tx: - tx = self.transactions.get(tx_hash) - tx_comment = tx.txcomment[5:] - return tx_comment - else: - # local transaction - tx = self.transactions.get(tx_hash) - tx_comment = tx.txcomment[5:] - return tx_comment - def set_up_to_date(self, up_to_date): with self.lock: self.up_to_date = up_to_date diff --git a/electrum/blockchain.py b/electrum/blockchain.py index b55a1522..08fa0805 100644 --- a/electrum/blockchain.py +++ b/electrum/blockchain.py @@ -30,25 +30,16 @@ from . import constants from .util import bfh, bh2u -try: - import scrypt - getPoWHash = lambda x: scrypt.hash(x, x, N=1024, r=1, p=1, buflen=32) -except ImportError: - util.print_msg("Warning: package scrypt not available; synchronization could be very slow") - from .scrypt import scrypt_1024_1_1_80 as getPoWHash - HEADER_SIZE = 80 # bytes -MAX_TARGET = 0x00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +MAX_TARGET = 0x00000000FFFF0000000000000000000000000000000000000000000000000000 class MissingHeader(Exception): pass - class InvalidHeader(Exception): pass - def serialize_header(header_dict: dict) -> str: s = int_to_hex(header_dict['version'], 4) \ + rev_hex(header_dict['prev_block_hash']) \ @@ -58,7 +49,6 @@ def serialize_header(header_dict: dict) -> str: + int_to_hex(int(header_dict['nonce']), 4) return s - def deserialize_header(s: bytes, height: int) -> dict: if not s: raise InvalidHeader('Invalid header: {}'.format(s)) @@ -75,19 +65,14 @@ def deserialize_header(s: bytes, height: int) -> dict: h['block_height'] = height return h - def hash_header(header: dict) -> str: if header is None: return '0' * 64 if header.get('prev_block_hash') is None: - header['prev_block_hash'] = '00' * 32 + header['prev_block_hash'] = '00'*32 return hash_encode(Hash(bfh(serialize_header(header)))) -def pow_hash_header(header): - return hash_encode(getPoWHash(bfh(serialize_header(header)))) - - blockchains = {} blockchains_lock = threading.Lock() @@ -97,7 +82,7 @@ def read_blockchains(config): fdir = os.path.join(util.get_headers_dir(config), 'forks') util.make_dir(fdir) l = filter(lambda x: x.startswith('fork_'), os.listdir(fdir)) - l = sorted(l, key=lambda x: int(x.split('_')[1])) + l = sorted(l, key = lambda x: int(x.split('_')[1])) for filename in l: forkpoint = int(filename.split('_')[2]) parent_id = int(filename.split('_')[1]) @@ -136,7 +121,7 @@ class Blockchain(util.PrintError): def get_max_child(self) -> Optional[int]: with blockchains_lock: chains = list(blockchains.values()) - children = list(filter(lambda y: y.parent_id == self.forkpoint, chains)) + children = list(filter(lambda y: y.parent_id==self.forkpoint, chains)) return max([x.forkpoint for x in children]) if children else None def get_forkpoint(self) -> int: @@ -173,49 +158,37 @@ class Blockchain(util.PrintError): def update_size(self) -> None: p = self.path() - self._size = os.path.getsize(p) // HEADER_SIZE if os.path.exists(p) else 0 + self._size = os.path.getsize(p)//HEADER_SIZE if os.path.exists(p) else 0 def verify_header(self, header: dict, prev_hash: str, target: int, expected_header_hash: str=None) -> None: - _hash = hash_header(header) - _powhash = pow_hash_header(header) + '''_hash = hash_header(header) if expected_header_hash and expected_header_hash != _hash: raise Exception("hash mismatches with expected: {} vs {}".format(expected_header_hash, _hash)) if prev_hash != header.get('prev_block_hash'): raise Exception("prev hash mismatch: %s vs %s" % (prev_hash, header.get('prev_block_hash'))) if constants.net.TESTNET: return - # print("I'm inside verify_header") - # bits = self.target_to_bits(target) - bits = target + bits = self.target_to_bits(target) if bits != header.get('bits'): raise Exception("bits mismatch: %s vs %s" % (bits, header.get('bits'))) - block_hash = int('0x' + _hash, 16) - target_val = self.bits_to_target(bits) - if int('0x' + _powhash, 16) > target_val: - raise Exception("insufficient proof of work: %s vs target %s" % (int('0x' + _hash, 16), target_val)) - # print("I passed verify_header(). Calc target values have been matched") + if int('0x' + _hash, 16) > target: + raise Exception("insufficient proof of work: %s vs target %s" % (int('0x' + _hash, 16), target))''' - - def verify_chunk(self, index, data): + def verify_chunk(self, index: int, data: bytes) -> None: num = len(data) // HEADER_SIZE - current_header = (index * 2016) - # last = (index * 2016 + 2015) - print(index * 2016) - prev_hash = self.get_hash(current_header - 1) + start_height = index * 2016 + prev_hash = self.get_hash(start_height - 1) + target = self.get_target(index-1) for i in range(num): - target = self.get_target(current_header - 1) + height = start_height + i try: - expected_header_hash = self.get_hash(current_header) + expected_header_hash = self.get_hash(height) except MissingHeader: expected_header_hash = None - - raw_header = data[i * HEADER_SIZE: (i + 1) * HEADER_SIZE] - header = deserialize_header(raw_header, current_header) - print(i) + raw_header = data[i*HEADER_SIZE : (i+1)*HEADER_SIZE] + header = deserialize_header(raw_header, index*2016 + i) self.verify_header(header, prev_hash, target, expected_header_hash) - self.save_chunk_part(header) prev_hash = hash_header(header) - current_header = current_header + 1 def path(self): d = util.get_headers_dir(self.config) @@ -228,24 +201,22 @@ class Blockchain(util.PrintError): @with_lock def save_chunk(self, index: int, chunk: bytes): - #chunk_within_checkpoint_region = index < len(self.checkpoints) - + chunk_within_checkpoint_region = index < len(self.checkpoints) # chunks in checkpoint region are the responsibility of the 'main chain' - # if chunk_within_checkpoint_region and self.parent_id is not None: - # main_chain = blockchains[0] - # main_chain.save_chunk(index, chunk) - # return - - #delta_height = (index * 2016 - self.forkpoint) - #delta_bytes = delta_height * HEADER_SIZE + if chunk_within_checkpoint_region and self.parent_id is not None: + main_chain = blockchains[0] + main_chain.save_chunk(index, chunk) + return + delta_height = (index * 2016 - self.forkpoint) + delta_bytes = delta_height * HEADER_SIZE # if this chunk contains our forkpoint, only save the part after forkpoint # (the part before is the responsibility of the parent) - # if delta_bytes < 0: - # chunk = chunk[-delta_bytes:] - # delta_bytes = 0 - # truncate = not chunk_within_checkpoint_region - # self.write(chunk, delta_bytes, truncate) + if delta_bytes < 0: + chunk = chunk[-delta_bytes:] + delta_bytes = 0 + truncate = not chunk_within_checkpoint_region + self.write(chunk, delta_bytes, truncate) self.swap_with_parent() @with_lock @@ -363,58 +334,29 @@ class Blockchain(util.PrintError): # compute target from chunk x, used in chunk x+1 if constants.net.TESTNET: return 0 - # The range is first 90 blocks because FLO's block time was 90 blocks when it started - if -1 <= index <= 88: - return 0x1e0ffff0 + if index == -1: + return MAX_TARGET if index < len(self.checkpoints): h, t = self.checkpoints[index] return t # new target - headerLast = self.read_header(index) - height = headerLast["block_height"] - # check if the height passes is in range for retargeting - if (height + 1) % self.DifficultyAdjustmentInterval(height + 1) != 0: - return int(headerLast["bits"]) - averagingInterval = self.AveragingInterval(height + 1) - blockstogoback = averagingInterval - 1 - # print("Blocks to go back = " + str(blockstogoback)) - if (height + 1) != averagingInterval: - blockstogoback = averagingInterval - firstHeight = height - blockstogoback - headerFirst = self.read_header(int(firstHeight)) - firstBlockTime = headerFirst["timestamp"] - nMinActualTimespan = int(self.MinActualTimespan(int(headerLast["block_height"]) + 1)) - - nMaxActualTimespan = int(self.MaxActualTimespan(int(headerLast["block_height"]) + 1)) - # Limit adjustment step - nActualTimespan = headerLast["timestamp"] - firstBlockTime - if nActualTimespan < nMinActualTimespan: - nActualTimespan = nMinActualTimespan - if nActualTimespan > nMaxActualTimespan: - nActualTimespan = nMaxActualTimespan - # Retarget - bnNewBits = int(headerLast["bits"]) - bnNew = self.bits_to_target(bnNewBits) - bnOld = bnNew - # FLO: intermediate uint256 can overflow by 1 bit - # const arith_uint256 bnPowLimit = UintToArith256(params.powLimit); - fShift = bnNew > MAX_TARGET - 1 - - if (fShift): - bnNew = bnNew >> 1 - bnNew = bnNew * nActualTimespan - bnNew = bnNew / self.TargetTimespan(headerLast["block_height"] + 1) - if fShift: - bnNew = bnNew << 1 - if bnNew > MAX_TARGET: - bnNew = MAX_TARGET - bnNew = self.target_to_bits(int(bnNew)) - return bnNew + first = self.read_header(index * 2016) + last = self.read_header(index * 2016 + 2015) + if not first or not last: + raise MissingHeader() + bits = last.get('bits') + target = self.bits_to_target(bits) + nActualTimespan = last.get('timestamp') - first.get('timestamp') + nTargetTimespan = 14 * 24 * 60 * 60 + nActualTimespan = max(nActualTimespan, nTargetTimespan // 4) + nActualTimespan = min(nActualTimespan, nTargetTimespan * 4) + new_target = min(MAX_TARGET, (target * nActualTimespan) // nTargetTimespan) + return new_target def bits_to_target(self, bits: int) -> int: bitsN = (bits >> 24) & 0xff - if not (bitsN >= 0x03 and bitsN <= 0x1e): - raise BaseException("First part of bits should be in [0x03, 0x1e]") + if not (bitsN >= 0x03 and bitsN <= 0x1d): + raise Exception("First part of bits should be in [0x03, 0x1d]") bitsBase = bits & 0xffffff if not (bitsBase >= 0x8000 and bitsBase <= 0x7fffff): raise Exception("Second part of bits should be in [0x8000, 0x7fffff]") @@ -435,7 +377,7 @@ class Blockchain(util.PrintError): return False height = header['block_height'] if check_height and self.height() != height - 1: - # self.print_error("cannot connect at height", height) + #self.print_error("cannot connect at height", height) return False if height == 0: return hash_header(header) == constants.net.GENESIS @@ -446,7 +388,7 @@ class Blockchain(util.PrintError): if prev_hash != header.get('prev_block_hash'): return False try: - target = self.get_target(height - 1) + target = self.get_target(height // 2016 - 1) except MissingHeader: return False try: @@ -459,7 +401,7 @@ class Blockchain(util.PrintError): try: data = bfh(hexdata) self.verify_chunk(idx, data) - # self.print_error("validated chunk %d" % idx) + #self.print_error("validated chunk %d" % idx) self.save_chunk(idx, data) return True except BaseException as e: @@ -477,71 +419,6 @@ class Blockchain(util.PrintError): return cp - def AveragingInterval(self, height): - # V1 - if height < constants.net.nHeight_Difficulty_Version2: - return constants.net.nAveragingInterval_Version1 - # V2 - elif height < constants.net.nHeight_Difficulty_Version3: - return constants.net.nAveragingInterval_Version2 - # V3 - else: - return constants.net.nAveragingInterval_Version3 - - def MinActualTimespan(self, height): - averagingTargetTimespan = self.AveragingInterval(height) * constants.net.nPowTargetSpacing - # V1 - if height < constants.net.nHeight_Difficulty_Version2: - return int(averagingTargetTimespan * (100 - constants.net.nMaxAdjustUp_Version1) / 100) - # V2 - elif height < constants.net.nHeight_Difficulty_Version3: - return int(averagingTargetTimespan * (100 - constants.net.nMaxAdjustUp_Version2) / 100) - # V3 - else: - return int(averagingTargetTimespan * (100 - constants.net.nMaxAdjustUp_Version3) / 100) - - def MaxActualTimespan(self, height): - averagingTargetTimespan = self.AveragingInterval(height) * constants.net.nPowTargetSpacing - # V1 - if height < constants.net.nHeight_Difficulty_Version2: - return int(averagingTargetTimespan * (100 + constants.net.nMaxAdjustDown_Version1) / 100) - # V2 - elif height < constants.net.nHeight_Difficulty_Version3: - return int(averagingTargetTimespan * (100 + constants.net.nMaxAdjustDown_Version2) / 100) - # V3 - else: - return int(averagingTargetTimespan * (100 + constants.net.nMaxAdjustDown_Version3) / 100) - - def TargetTimespan(self, height): - # V1 - if height < constants.net.nHeight_Difficulty_Version2: - return constants.net.nTargetTimespan_Version1 - # V2 - if height < constants.net.nHeight_Difficulty_Version3: - return constants.net.nAveragingInterval_Version2 * constants.net.nPowTargetSpacing - # V3 - return constants.net.nAveragingInterval_Version3 * constants.net.nPowTargetSpacing - - def DifficultyAdjustmentInterval(self, height): - # V1 - if height < constants.net.nHeight_Difficulty_Version2: - return constants.net.nInterval_Version1 - # V2 - if height < constants.net.nHeight_Difficulty_Version3: - return constants.net.nInterval_Version2 - # V3 - return constants.net.nInterval_Version3 - - def save_chunk_part(self, header): - filename = self.path() - delta = header.get('block_height') - data = bfh(serialize_header(header)) - # assert delta == self.size() - assert len(data) == 80 - self.write(data, delta * 80) - # self.swap_with_parent() - - def check_header(header: dict) -> Optional[Blockchain]: if type(header) is not dict: return None diff --git a/electrum/commands.py b/electrum/commands.py index 6f8974a6..03f7d33a 100644 --- a/electrum/commands.py +++ b/electrum/commands.py @@ -543,7 +543,7 @@ class Commands: PR_PAID: 'Paid', PR_EXPIRED: 'Expired', } - out['amount (FLO)'] = format_satoshis(out.get('amount')) + out['amount (BTC)'] = format_satoshis(out.get('amount')) out['status'] = pr_str[out.get('status', PR_UNKNOWN)] return out @@ -694,8 +694,8 @@ param_descriptions = { 'pubkey': 'Public key', 'message': 'Clear text message. Use quotes if it contains spaces.', 'encrypted': 'Encrypted message', - 'amount': 'Amount to be sent (in FLO). Type \'!\' to send the maximum available.', - 'requested_amount': 'Requested amount (in FLO).', + 'amount': 'Amount to be sent (in BTC). Type \'!\' to send the maximum available.', + 'requested_amount': 'Requested amount (in BTC).', 'outputs': 'list of ["address", amount]', 'redeem_script': 'redeem script (hexadecimal)', } @@ -712,7 +712,7 @@ command_options = { 'labels': ("-l", "Show the labels of listed addresses"), 'nocheck': (None, "Do not verify aliases"), 'imax': (None, "Maximum number of inputs"), - 'fee': ("-f", "Transaction fee (in FLO)"), + 'fee': ("-f", "Transaction fee (in BTC)"), 'from_addr': ("-F", "Source address (must be a wallet address; use sweep to spend from non-wallet address)."), 'change_addr': ("-c", "Change address. Default is a spare address, or the source address if it's not in the wallet"), 'nbits': (None, "Number of bits of entropy"), diff --git a/electrum/currencies.json b/electrum/currencies.json index 993dba64..f0fabafa 100644 --- a/electrum/currencies.json +++ b/electrum/currencies.json @@ -49,7 +49,6 @@ "EUR", "FJD", "FKP", - "FLO", "GBP", "GEL", "GHS", @@ -357,7 +356,7 @@ "Bitvalor": [ "BRL" ], - "Bittrex": [ + "BlockchainInfo": [ "AUD", "BRL", "CAD", @@ -429,7 +428,6 @@ "ETB", "EUR", "FJD", - "FLO", "FKP", "GBP", "GEL", diff --git a/electrum/daemon.py b/electrum/daemon.py index 2ce648bf..cc52b845 100644 --- a/electrum/daemon.py +++ b/electrum/daemon.py @@ -29,6 +29,7 @@ import time import traceback import sys import threading +from typing import Dict import jsonrpclib @@ -37,7 +38,7 @@ from .version import ELECTRUM_VERSION from .network import Network from .util import json_decode, DaemonThread from .util import print_error, to_string -from .wallet import Wallet +from .wallet import Wallet, Abstract_Wallet from .storage import WalletStorage from .commands import known_commands, Commands from .simple_config import SimpleConfig @@ -131,7 +132,7 @@ class Daemon(DaemonThread): if self.network: self.network.start([self.fx.run]) self.gui = None - self.wallets = {} + self.wallets = {} # type: Dict[str, Abstract_Wallet] # Setup JSONRPC server self.init_server(config, fd) @@ -163,6 +164,7 @@ class Daemon(DaemonThread): return True def run_daemon(self, config_options): + asyncio.set_event_loop(self.network.asyncio_loop) config = SimpleConfig(config_options) sub = config.get('subcommand') assert sub in [None, 'start', 'stop', 'status', 'load_wallet', 'close_wallet'] diff --git a/electrum/exchange_rate.py b/electrum/exchange_rate.py index 8ebbc229..826a8775 100644 --- a/electrum/exchange_rate.py +++ b/electrum/exchange_rate.py @@ -1,6 +1,4 @@ import asyncio -import aiohttp -from aiohttp_socks import SocksConnector, SocksVer from datetime import datetime import inspect import sys @@ -12,6 +10,7 @@ import decimal from decimal import Decimal import concurrent.futures import traceback +from typing import Sequence from .bitcoin import COIN from .i18n import _ @@ -27,6 +26,7 @@ CCY_PRECISIONS = {'BHD': 3, 'BIF': 0, 'BYR': 0, 'CLF': 4, 'CLP': 0, 'RWF': 0, 'TND': 3, 'UGX': 0, 'UYI': 0, 'VND': 0, 'VUV': 0, 'XAF': 0, 'XAU': 4, 'XOF': 0, 'XPF': 0} + class ExchangeBase(PrintError): def __init__(self, on_quotes, on_history): @@ -65,7 +65,7 @@ class ExchangeBase(PrintError): self.quotes = await self.get_rates(ccy) self.print_error("received fx quotes") except BaseException as e: - self.print_error("failed fx quotes:", e) + self.print_error("failed fx quotes:", repr(e)) self.quotes = {} self.on_quotes() @@ -220,13 +220,11 @@ class Bitvalor(ExchangeBase): return {'BRL': Decimal(json['ticker_1h']['total']['last'])} -class Bittrex(ExchangeBase): +class BlockchainInfo(ExchangeBase): async def get_rates(self, ccy): - json = await self.get_json('bittrex.com','/api/v1.1/public/getticker?market=BTC-FLO') - floPrice_inBTC = json['result']['Last'] - json = self.get_json('blockchain.info', '/ticker') - return dict([(r, Decimal(json[r]['15m'] * floPrice_inBTC)) for r in json]) + json = await self.get_json('blockchain.info', '/ticker') + return dict([(r, Decimal(json[r]['15m'])) for r in json]) class BTCChina(ExchangeBase): @@ -454,12 +452,14 @@ class FxThread(ThreadJob): def set_proxy(self, trigger_name, *args): self._trigger.set() - def get_currencies(self, h): - d = get_exchanges_by_ccy(h) + @staticmethod + def get_currencies(history: bool) -> Sequence[str]: + d = get_exchanges_by_ccy(history) return sorted(d.keys()) - def get_exchanges_by_ccy(self, ccy, h): - d = get_exchanges_by_ccy(h) + @staticmethod + def get_exchanges_by_ccy(ccy: str, history: bool) -> Sequence[str]: + d = get_exchanges_by_ccy(history) return d.get(ccy, []) def ccy_amount_str(self, amount, commas): diff --git a/electrum/gui/qt/history_list.py b/electrum/gui/qt/history_list.py index bd9ee312..d12903c7 100644 --- a/electrum/gui/qt/history_list.py +++ b/electrum/gui/qt/history_list.py @@ -74,7 +74,7 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop): return str(datetime.date(d.year, d.month, d.day)) if d else _('None') def refresh_headers(self): - headers = ['', '', _('Date'), _('Description'), _('Amount'), _('Balance'), _('FLO Data')] + headers = ['', '', _('Date'), _('Description'), _('Amount'), _('Balance')] fx = self.parent.fx if fx and fx.show_history(): headers.extend(['%s '%fx.ccy + _('Value')]) @@ -177,13 +177,13 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop): grid = QGridLayout() grid.addWidget(QLabel(_("Start")), 0, 0) grid.addWidget(QLabel(self.format_date(start_date)), 0, 1) - grid.addWidget(QLabel(str(h.get('start_fiat_value')) + '/FLO'), 0, 2) + grid.addWidget(QLabel(str(h.get('start_fiat_value')) + '/BTC'), 0, 2) grid.addWidget(QLabel(_("Initial balance")), 1, 0) grid.addWidget(QLabel(format_amount(h['start_balance'])), 1, 1) grid.addWidget(QLabel(str(h.get('start_fiat_balance'))), 1, 2) grid.addWidget(QLabel(_("End")), 2, 0) grid.addWidget(QLabel(self.format_date(end_date)), 2, 1) - grid.addWidget(QLabel(str(h.get('end_fiat_value')) + '/FLO'), 2, 2) + grid.addWidget(QLabel(str(h.get('end_fiat_value')) + '/BTC'), 2, 2) grid.addWidget(QLabel(_("Final balance")), 4, 0) grid.addWidget(QLabel(format_amount(h['end_balance'])), 4, 1) grid.addWidget(QLabel(str(h.get('end_fiat_balance'))), 4, 2) @@ -247,8 +247,7 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop): icon = self.icon_cache.get(":icons/" + TX_ICONS[status]) v_str = self.parent.format_amount(value, is_diff=True, whitespaces=True) balance_str = self.parent.format_amount(balance, whitespaces=True) - txcomment = self.wallet.get_tx_comment(tx_hash) - entry = ['', tx_hash, status_str, label, v_str, balance_str, txcomment] + entry = ['', tx_hash, status_str, label, v_str, balance_str] fiat_value = None if value is not None and fx and fx.show_history(): fiat_value = tx_item['fiat_value'].value diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index 4e449503..f89b9e7f 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -1124,7 +1124,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): hbox.addStretch(1) grid.addLayout(hbox, 4, 4) - msg = _('FLO transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\ + msg = _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\ + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\ + _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.') self.fee_e_label = HelpLabel(_('Fee'), msg) @@ -1223,12 +1223,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): if not self.config.get('show_fee', False): self.fee_adv_controls.setVisible(False) - msg = _('This is where you write the FLO Data for the transaction') - txcomment_label = HelpLabel(_('FLO Data'), msg) - grid.addWidget(txcomment_label, 6, 0) - self.message_tx = MyLineEdit() - grid.addWidget(self.message_tx, 6, 1, 1, -1) - self.preview_button = EnterButton(_("Preview"), self.do_preview) self.preview_button.setToolTip(_('Display the details of your transaction before signing it.')) self.send_button = EnterButton(_("Send"), self.do_send) @@ -1238,7 +1232,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): buttons.addWidget(self.clear_button) buttons.addWidget(self.preview_button) buttons.addWidget(self.send_button) - grid.addLayout(buttons, 7, 1, 1, 3) + grid.addLayout(buttons, 6, 1, 1, 3) self.amount_e.shortcut.connect(self.spend_max) self.payto_e.textChanged.connect(self.update_fee) @@ -1496,7 +1490,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): self.show_error(_('Payment request has expired')) return label = self.message_e.text() - txcomment = self.message_tx.text() if self.payment_request: outputs = self.payment_request.get_outputs() @@ -1532,7 +1525,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): fee_estimator = self.get_send_fee_estimator() coins = self.get_coins() - return outputs, fee_estimator, label, coins, txcomment + return outputs, fee_estimator, label, coins def do_preview(self): self.do_send(preview = True) @@ -1543,12 +1536,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): r = self.read_send_tab() if not r: return - outputs, fee_estimator, tx_desc, coins, txcomment = r + outputs, fee_estimator, tx_desc, coins = r try: is_sweep = bool(self.tx_external_keypairs) tx = self.wallet.make_unsigned_transaction( coins, outputs, self.config, fixed_fee=fee_estimator, - is_sweep=is_sweep, txcomment=txcomment) + is_sweep=is_sweep) except NotEnoughFunds: self.show_message(_("Insufficient funds")) return @@ -2780,7 +2773,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): units = base_units_list msg = (_('Base unit of your wallet.') - + '\n1 FLO = 1000 mFLO. 1 mFLO = 1000 bits. 1 bit = 100 sat.\n' + + '\n1 BTC = 1000 mBTC. 1 mBTC = 1000 bits. 1 bit = 100 sat.\n' + _('This setting affects the Send tab, and all balance related fields.')) unit_label = HelpLabel(_('Base unit') + ':', msg) unit_combo = QComboBox() diff --git a/electrum/gui/qt/transaction_dialog.py b/electrum/gui/qt/transaction_dialog.py index 4a11329f..d2affb6c 100644 --- a/electrum/gui/qt/transaction_dialog.py +++ b/electrum/gui/qt/transaction_dialog.py @@ -113,8 +113,6 @@ class TxDialog(QDialog, MessageBoxMixin): vbox.addWidget(self.size_label) self.fee_label = QLabel() vbox.addWidget(self.fee_label) - self.txcomment_label = QLabel() - vbox.addWidget(self.txcomment_label) self.add_io(vbox) @@ -268,7 +266,6 @@ class TxDialog(QDialog, MessageBoxMixin): self.amount_label.setText(amount_str) self.fee_label.setText(fee_str) self.size_label.setText(size_str) - self.txcomment_label.setText("TX Comment: " + self.tx.txcomment) run_hook('transaction_dialog_update', self) def add_io(self, vbox): diff --git a/electrum/interface.py b/electrum/interface.py index ac5541db..ef371138 100644 --- a/electrum/interface.py +++ b/electrum/interface.py @@ -63,7 +63,7 @@ class NotificationSession(ClientSession): for queue in self.subscriptions[key]: await queue.put(request.args) else: - assert False, request.method + raise Exception('unexpected request: {}'.format(repr(request))) async def send_request(self, *args, timeout=-1, **kwargs): # note: the timeout starts after the request touches the wire! @@ -255,12 +255,12 @@ class Interface(PrintError): try: ssl_context = await self._get_ssl_context() except (ErrorParsingSSLCert, ErrorGettingSSLCertFromServer) as e: - self.print_error('disconnecting due to: {} {}'.format(e, type(e))) + self.print_error('disconnecting due to: {}'.format(repr(e))) return try: - await self.open_session(ssl_context, exit_early=False) + await self.open_session(ssl_context) except (asyncio.CancelledError, OSError, aiorpcx.socks.SOCKSFailure) as e: - self.print_error('disconnecting due to: {} {}'.format(e, type(e))) + self.print_error('disconnecting due to: {}'.format(repr(e))) return def mark_ready(self): @@ -338,7 +338,7 @@ class Interface(PrintError): return conn, 0 return conn, res['count'] - async def open_session(self, sslc, exit_early): + async def open_session(self, sslc, exit_early=False): self.session = NotificationSession(self.host, self.port, ssl=sslc, proxy=self.proxy) async with self.session as session: try: diff --git a/electrum/plot.py b/electrum/plot.py index ba2d4f95..3174d1b2 100644 --- a/electrum/plot.py +++ b/electrum/plot.py @@ -37,7 +37,7 @@ def plot_history(history): plt.subplots_adjust(bottom=0.2) plt.xticks( rotation=25 ) ax = plt.gca() - plt.ylabel('FLO') + plt.ylabel('BTC') plt.xlabel('Month') xfmt = md.DateFormatter('%Y-%m-%d') ax.xaxis.set_major_formatter(xfmt) diff --git a/electrum/plugin.py b/electrum/plugin.py index a13f7a72..a69082a0 100644 --- a/electrum/plugin.py +++ b/electrum/plugin.py @@ -134,7 +134,7 @@ class Plugins(DaemonThread): try: __import__(dep) except ImportError as e: - self.print_error('Plugin', name, 'unavailable:', type(e).__name__, ':', str(e)) + self.print_error('Plugin', name, 'unavailable:', repr(e)) return False requires = d.get('requires_wallet_type', []) return not requires or w.wallet_type in requires diff --git a/electrum/servers.json b/electrum/servers.json index 360c3d87..a9c9101d 100644 --- a/electrum/servers.json +++ b/electrum/servers.json @@ -1,14 +1,8 @@ { - "ranchimall.duckdns.org": { - "pruning": "-", - "s": "50002", - "t": "50001", - "version": "1.2" - }, "localhost": { "pruning": "-", "s": "50002", "t": "50001", "version": "1.2" } -} \ No newline at end of file +} diff --git a/electrum/servers_testnet.json b/electrum/servers_testnet.json index e70b9d69..4ff6d230 100644 --- a/electrum/servers_testnet.json +++ b/electrum/servers_testnet.json @@ -1,14 +1,31 @@ { - "ranchimall.duckdns.org": { + "electrumx.kekku.li": { + "pruning": "-", + "s": "51002", + "version": "1.2" + }, + "hsmithsxurybd7uh.onion": { + "pruning": "-", + "s": "53012", + "t": "53011", + "version": "1.2" + }, + "testnet.hsmiths.com": { + "pruning": "-", + "s": "53012", + "t": "53011", + "version": "1.2" + }, + "testnet.qtornado.com": { "pruning": "-", "s": "51002", "t": "51001", "version": "1.2" }, - "localhost": { + "testnet1.bauerj.eu": { "pruning": "-", - "s": "51002", - "t": "51001", + "s": "50002", + "t": "50001", "version": "1.2" } -} \ No newline at end of file +} diff --git a/electrum/simple_config.py b/electrum/simple_config.py index 91fc20c0..4bfd56f5 100644 --- a/electrum/simple_config.py +++ b/electrum/simple_config.py @@ -195,7 +195,7 @@ class SimpleConfig(PrintError): base_unit = self.user_config.get('base_unit') if isinstance(base_unit, str): self._set_key_in_user_config('base_unit', None) - map_ = {'FLO':8, 'mFLO':5, 'uFLO':2, 'bits':2, 'sat':0} + map_ = {'btc':8, 'mbtc':5, 'ubtc':2, 'bits':2, 'sat':0} decimal_point = map_.get(base_unit.lower()) self._set_key_in_user_config('decimal_point', decimal_point) diff --git a/electrum/transaction.py b/electrum/transaction.py index f487db39..1b7a53a5 100644 --- a/electrum/transaction.py +++ b/electrum/transaction.py @@ -755,6 +755,7 @@ class Transaction: self._outputs = outputs self.locktime = locktime self.txcomment = txcomment + self.BIP69_sort() return self @classmethod @@ -989,10 +990,11 @@ class Transaction: for txin in self.inputs(): txin['sequence'] = nSequence - def BIP_LI01_sort(self): - # See https://github.com/kristovatlas/rfc/blob/master/bips/bip-li01.mediawiki - self._inputs.sort(key = lambda i: (i['prevout_hash'], i['prevout_n'])) - self._outputs.sort(key = lambda o: (o[2], self.pay_script(o[0], o[1]))) + def BIP69_sort(self, inputs=True, outputs=True): + if inputs: + self._inputs.sort(key = lambda i: (i['prevout_hash'], i['prevout_n'])) + if outputs: + self._outputs.sort(key = lambda o: (o[2], self.pay_script(o[0], o[1]))) def serialize_output(self, output): output_type, addr, amount = output @@ -1006,7 +1008,8 @@ class Transaction: nVersion = int_to_hex(self.version, 4) nHashType = int_to_hex(1, 4) nLocktime = int_to_hex(self.locktime, 4) - nTxComment = var_int(len(self.txcomment)) + str(codecs.encode(bytes(self.txcomment, 'utf-8'), 'hex_codec'), 'utf-8') + nTxComment = var_int(len(self.txcomment)) + str(codecs.encode(bytes(self.txcomment, 'utf-8'), 'hex_codec'), + 'utf-8') inputs = self.inputs() outputs = self.outputs() txin = inputs[i] @@ -1029,8 +1032,6 @@ class Transaction: txouts = var_int(len(outputs)) + ''.join(self.serialize_output(o) for o in outputs) if self.version >= 2: preimage = nVersion + txins + txouts + nLocktime + nTxComment + nHashType - print("preimage") - print(preimage) else: preimage = nVersion + txins + txouts + nLocktime + nHashType return preimage @@ -1053,7 +1054,8 @@ class Transaction: def serialize_to_network(self, estimate_size=False, witness=True): nVersion = int_to_hex(self.version, 4) nLocktime = int_to_hex(self.locktime, 4) - nTxComment = var_int(len(self.txcomment)) + str(codecs.encode(bytes(self.txcomment, 'utf-8'), 'hex_codec'), 'utf-8') + nTxComment = var_int(len(self.txcomment)) + str(codecs.encode(bytes(self.txcomment, 'utf-8'), 'hex_codec'), + 'utf-8') inputs = self.inputs() outputs = self.outputs() txins = var_int(len(inputs)) + ''.join(self.serialize_input(txin, self.input_script(txin, estimate_size)) for txin in inputs) @@ -1094,10 +1096,12 @@ class Transaction: def add_inputs(self, inputs): self._inputs.extend(inputs) self.raw = None + self.BIP69_sort(outputs=False) def add_outputs(self, outputs): self._outputs.extend(outputs) self.raw = None + self.BIP69_sort(inputs=False) def input_value(self): return sum(x['value'] for x in self.inputs()) diff --git a/electrum/util.py b/electrum/util.py index 247e3827..20af6e17 100644 --- a/electrum/util.py +++ b/electrum/util.py @@ -51,9 +51,9 @@ def inv_dict(d): return {v: k for k, v in d.items()} -base_units = {'FLO':8, 'mFLO':5, 'bits':2, 'sat':0} +base_units = {'BTC':8, 'mBTC':5, 'bits':2, 'sat':0} base_units_inverse = inv_dict(base_units) -base_units_list = ['FLO', 'mFLO', 'bits', 'sat'] # list(dict) does not guarantee order +base_units_list = ['BTC', 'mBTC', 'bits', 'sat'] # list(dict) does not guarantee order DECIMAL_POINT_DEFAULT = 5 # mBTC @@ -140,7 +140,7 @@ class Satoshis(object): return 'Satoshis(%d)'%self.value def __str__(self): - return format_satoshis(self.value) + " FLO" + return format_satoshis(self.value) + " BTC" class Fiat(object): __slots__ = ('value', 'ccy') @@ -358,7 +358,7 @@ def android_data_dir(): return PythonActivity.mActivity.getFilesDir().getPath() + '/data' def android_headers_dir(): - d = android_ext_dir() + '/org.electrum.electrum-flo' + d = android_ext_dir() + '/org.electrum.electrum' if not os.path.exists(d): try: os.mkdir(d) @@ -370,7 +370,7 @@ def android_check_data_dir(): """ if needed, move old directory to sandbox """ ext_dir = android_ext_dir() data_dir = android_data_dir() - old_electrum_dir = ext_dir + '/electrum-flo' + old_electrum_dir = ext_dir + '/electrum' if not os.path.exists(data_dir) and os.path.exists(old_electrum_dir): import shutil new_headers_path = android_headers_dir() + '/blockchain_headers' @@ -482,11 +482,11 @@ def user_dir(): if 'ANDROID_DATA' in os.environ: return android_check_data_dir() elif os.name == 'posix': - return os.path.join(os.environ["HOME"], ".electrum-flo") + return os.path.join(os.environ["HOME"], ".electrum") elif "APPDATA" in os.environ: - return os.path.join(os.environ["APPDATA"], "Electrum-FLO") + return os.path.join(os.environ["APPDATA"], "Electrum") elif "LOCALAPPDATA" in os.environ: - return os.path.join(os.environ["LOCALAPPDATA"], "Electrum-FLO") + return os.path.join(os.environ["LOCALAPPDATA"], "Electrum") else: #raise Exception("No home directory found in environment variables.") return @@ -605,17 +605,43 @@ def time_difference(distance_in_time, include_seconds): return "over %d years" % (round(distance_in_minutes / 525600)) mainnet_block_explorers = { - 'Florincoin.info': ('https://florincoin.info/', - {'tx': 'transactions/', 'addr': 'address/'}), + 'Biteasy.com': ('https://www.biteasy.com/blockchain/', + {'tx': 'transactions/', 'addr': 'addresses/'}), + 'Bitflyer.jp': ('https://chainflyer.bitflyer.jp/', + {'tx': 'Transaction/', 'addr': 'Address/'}), + 'Blockchain.info': ('https://blockchain.info/', + {'tx': 'tx/', 'addr': 'address/'}), + 'blockchainbdgpzk.onion': ('https://blockchainbdgpzk.onion/', + {'tx': 'tx/', 'addr': 'address/'}), + 'Blockr.io': ('https://btc.blockr.io/', + {'tx': 'tx/info/', 'addr': 'address/info/'}), + 'Blocktrail.com': ('https://www.blocktrail.com/BTC/', + {'tx': 'tx/', 'addr': 'address/'}), + 'BTC.com': ('https://chain.btc.com/', + {'tx': 'tx/', 'addr': 'address/'}), + 'Chain.so': ('https://www.chain.so/', + {'tx': 'tx/BTC/', 'addr': 'address/BTC/'}), + 'Insight.is': ('https://insight.bitpay.com/', + {'tx': 'tx/', 'addr': 'address/'}), + 'TradeBlock.com': ('https://tradeblock.com/blockchain/', + {'tx': 'tx/', 'addr': 'address/'}), + 'BlockCypher.com': ('https://live.blockcypher.com/btc/', + {'tx': 'tx/', 'addr': 'address/'}), + 'Blockchair.com': ('https://blockchair.com/bitcoin/', + {'tx': 'transaction/', 'addr': 'address/'}), + 'blockonomics.co': ('https://www.blockonomics.co/', + {'tx': 'api/tx?txid=', 'addr': '#/search?q='}), + 'OXT.me': ('https://oxt.me/', + {'tx': 'transaction/', 'addr': 'address/'}), 'system default': ('blockchain:/', - {'tx': 'tx/', 'addr': 'address/'}) + {'tx': 'tx/', 'addr': 'address/'}), } testnet_block_explorers = { - 'testnet.Florincoin.info': ('https://testnet.florincoin.info/', - {'tx': 'transactions/', 'addr': 'address/'}), + 'Blocktrail.com': ('https://www.blocktrail.com/tBTC/', + {'tx': 'tx/', 'addr': 'address/'}), 'system default': ('blockchain://000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943/', - {'tx': 'tx/', 'addr': 'address/'}) + {'tx': 'tx/', 'addr': 'address/'}), } def block_explorer_info(): @@ -623,7 +649,7 @@ def block_explorer_info(): return testnet_block_explorers if constants.net.TESTNET else mainnet_block_explorers def block_explorer(config): - return config.get('block_explorer', 'Florincoin.info') + return config.get('block_explorer', 'Blocktrail.com') def block_explorer_tuple(config): return block_explorer_info().get(block_explorer(config)) @@ -841,8 +867,7 @@ TxMinedStatus = NamedTuple("TxMinedStatus", [("height", int), VerifiedTxInfo = NamedTuple("VerifiedTxInfo", [("height", int), ("timestamp", int), ("txpos", int), - ("header_hash", str), - ("tx_comment", str)]) + ("header_hash", str)]) def make_aiohttp_session(proxy): if proxy: diff --git a/electrum/verifier.py b/electrum/verifier.py index 64fa6ce6..cb6bdfa6 100644 --- a/electrum/verifier.py +++ b/electrum/verifier.py @@ -113,8 +113,7 @@ class SPV(PrintError): except KeyError: pass self.print_error("verified %s" % tx_hash) header_hash = hash_header(header) - tx_comment = self.wallet.get_tx_comment(tx_hash) - vtx_info = VerifiedTxInfo(tx_height, header.get('timestamp'), pos, header_hash, tx_comment) + vtx_info = VerifiedTxInfo(tx_height, header.get('timestamp'), pos, header_hash) self.wallet.add_verified_tx(tx_hash, vtx_info) if self.is_up_to_date() and self.wallet.is_up_to_date(): self.wallet.save_verified_tx(write=True) diff --git a/electrum/wallet.py b/electrum/wallet.py index b9855e66..011fd6ac 100644 --- a/electrum/wallet.py +++ b/electrum/wallet.py @@ -143,7 +143,6 @@ def sweep(privkeys, network, config, recipient, fee=None, imax=100): locktime = network.get_local_height() tx = Transaction.from_io(inputs, outputs, locktime=locktime) - tx.BIP_LI01_sort() tx.set_rbf(True) tx.sign(keypairs) return tx @@ -408,7 +407,7 @@ class Abstract_Wallet(AddressSynchronizer): 'value': Satoshis(value), 'balance': Satoshis(balance), 'date': timestamp_to_datetime(timestamp), - 'label': self.get_label(tx_hash) + 'label': self.get_label(tx_hash), } if show_addresses: tx = self.transactions.get(tx_hash) @@ -493,7 +492,6 @@ class Abstract_Wallet(AddressSynchronizer): return ', '.join(labels) return '' - def get_tx_status(self, tx_hash, tx_mined_status): extra = [] height = tx_mined_status.height @@ -541,7 +539,7 @@ class Abstract_Wallet(AddressSynchronizer): return dust_threshold(self.network) def make_unsigned_transaction(self, inputs, outputs, config, fixed_fee=None, - change_addr=None, is_sweep=False, txcomment = ""): + change_addr=None, is_sweep=False): # check outputs i_max = None for i, o in enumerate(outputs): @@ -609,14 +607,8 @@ class Abstract_Wallet(AddressSynchronizer): outputs[i_max] = outputs[i_max]._replace(value=amount) tx = Transaction.from_io(inputs, outputs[:]) - # Sort the inputs and outputs deterministically - tx.BIP_LI01_sort() # Timelock tx to current height. tx.locktime = self.get_local_height() - # Transactions with transaction comments/floData are version 2 - if txcomment != "": - tx.version = 2 - tx.txcomment = "text:" + txcomment run_hook('make_unsigned_transaction', self, tx) return tx @@ -719,7 +711,6 @@ class Abstract_Wallet(AddressSynchronizer): raise CannotBumpFee(_('Cannot bump fee') + ': ' + _('could not find suitable outputs')) locktime = self.get_local_height() tx_new = Transaction.from_io(inputs, outputs, locktime=locktime) - tx_new.BIP_LI01_sort() return tx_new def cpfp(self, tx, fee): @@ -738,7 +729,6 @@ class Abstract_Wallet(AddressSynchronizer): inputs = [item] outputs = [TxOutput(TYPE_ADDRESS, address, value - fee)] locktime = self.get_local_height() - # note: no need to call tx.BIP_LI01_sort() here - single input/output return Transaction.from_io(inputs, outputs, locktime=locktime) def add_input_sig_info(self, txin, address):