Creating Morph branch

This is an attempt to commit the changes made to Electrum-BTC to morph it into FLO, on top of the latest Electrum-BTC codebase at the time of writing. The code has become messy pulling upstream changes and its become difficult to debug issues
This commit is contained in:
Vivek Teega 2018-09-29 17:52:30 +05:30
parent 826a56311c
commit 6a2d2fbc3d
20 changed files with 169 additions and 292 deletions

1
.gitignore vendored
View File

@ -14,7 +14,6 @@ env/
.buildozer/
bin/
/app.fil
.idea
# icons

View File

@ -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

View File

@ -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

View File

@ -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"),

View File

@ -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",

View File

@ -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']

View File

@ -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):

View File

@ -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

View File

@ -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()

View File

@ -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):

View File

@ -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:

View File

@ -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)

View File

@ -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

View File

@ -1,14 +1,8 @@
{
"ranchimall.duckdns.org": {
"pruning": "-",
"s": "50002",
"t": "50001",
"version": "1.2"
},
"localhost": {
"pruning": "-",
"s": "50002",
"t": "50001",
"version": "1.2"
}
}
}

View File

@ -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"
}
}
}

View File

@ -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)

View File

@ -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())

View File

@ -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:

View File

@ -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)

View File

@ -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):