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/ .buildozer/
bin/ bin/
/app.fil /app.fil
.idea .idea
# icons # icons

View File

@ -69,8 +69,8 @@ class AddressSynchronizer(PrintError):
# Verified transactions. txid -> VerifiedTxInfo. Access with self.lock. # Verified transactions. txid -> VerifiedTxInfo. Access with self.lock.
verified_tx = storage.get('verified_tx3', {}) verified_tx = storage.get('verified_tx3', {})
self.verified_tx = {} self.verified_tx = {}
for txid, (height, timestamp, txpos, header_hash, tx_comment) in verified_tx.items(): for txid, (height, timestamp, txpos, header_hash) in verified_tx.items():
self.verified_tx[txid] = VerifiedTxInfo(height, timestamp, txpos, header_hash, tx_comment) self.verified_tx[txid] = VerifiedTxInfo(height, timestamp, txpos, header_hash)
# Transactions pending verification. txid -> tx_height. Access with self.lock. # Transactions pending verification. txid -> tx_height. Access with self.lock.
self.unverified_tx = defaultdict(int) self.unverified_tx = defaultdict(int)
# true when synchronized # true when synchronized
@ -640,23 +640,6 @@ class AddressSynchronizer(PrintError):
# local transaction # local transaction
return TxMinedStatus(TX_HEIGHT_LOCAL, 0, None, None) 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): def set_up_to_date(self, up_to_date):
with self.lock: with self.lock:
self.up_to_date = up_to_date self.up_to_date = up_to_date

View File

@ -30,25 +30,16 @@ from . import constants
from .util import bfh, bh2u 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 HEADER_SIZE = 80 # bytes
MAX_TARGET = 0x00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff MAX_TARGET = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
class MissingHeader(Exception): class MissingHeader(Exception):
pass pass
class InvalidHeader(Exception): class InvalidHeader(Exception):
pass pass
def serialize_header(header_dict: dict) -> str: def serialize_header(header_dict: dict) -> str:
s = int_to_hex(header_dict['version'], 4) \ s = int_to_hex(header_dict['version'], 4) \
+ rev_hex(header_dict['prev_block_hash']) \ + 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) + int_to_hex(int(header_dict['nonce']), 4)
return s return s
def deserialize_header(s: bytes, height: int) -> dict: def deserialize_header(s: bytes, height: int) -> dict:
if not s: if not s:
raise InvalidHeader('Invalid header: {}'.format(s)) raise InvalidHeader('Invalid header: {}'.format(s))
@ -75,19 +65,14 @@ def deserialize_header(s: bytes, height: int) -> dict:
h['block_height'] = height h['block_height'] = height
return h return h
def hash_header(header: dict) -> str: def hash_header(header: dict) -> str:
if header is None: if header is None:
return '0' * 64 return '0' * 64
if header.get('prev_block_hash') is None: 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)))) return hash_encode(Hash(bfh(serialize_header(header))))
def pow_hash_header(header):
return hash_encode(getPoWHash(bfh(serialize_header(header))))
blockchains = {} blockchains = {}
blockchains_lock = threading.Lock() blockchains_lock = threading.Lock()
@ -97,7 +82,7 @@ def read_blockchains(config):
fdir = os.path.join(util.get_headers_dir(config), 'forks') fdir = os.path.join(util.get_headers_dir(config), 'forks')
util.make_dir(fdir) util.make_dir(fdir)
l = filter(lambda x: x.startswith('fork_'), os.listdir(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: for filename in l:
forkpoint = int(filename.split('_')[2]) forkpoint = int(filename.split('_')[2])
parent_id = int(filename.split('_')[1]) parent_id = int(filename.split('_')[1])
@ -136,7 +121,7 @@ class Blockchain(util.PrintError):
def get_max_child(self) -> Optional[int]: def get_max_child(self) -> Optional[int]:
with blockchains_lock: chains = list(blockchains.values()) 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 return max([x.forkpoint for x in children]) if children else None
def get_forkpoint(self) -> int: def get_forkpoint(self) -> int:
@ -173,49 +158,37 @@ class Blockchain(util.PrintError):
def update_size(self) -> None: def update_size(self) -> None:
p = self.path() 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: def verify_header(self, header: dict, prev_hash: str, target: int, expected_header_hash: str=None) -> None:
_hash = hash_header(header) '''_hash = hash_header(header)
_powhash = pow_hash_header(header)
if expected_header_hash and expected_header_hash != _hash: if expected_header_hash and expected_header_hash != _hash:
raise Exception("hash mismatches with expected: {} vs {}".format(expected_header_hash, _hash)) raise Exception("hash mismatches with expected: {} vs {}".format(expected_header_hash, _hash))
if prev_hash != header.get('prev_block_hash'): if prev_hash != header.get('prev_block_hash'):
raise Exception("prev hash mismatch: %s vs %s" % (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: if constants.net.TESTNET:
return return
# print("I'm inside verify_header") bits = self.target_to_bits(target)
# bits = self.target_to_bits(target)
bits = target
if bits != header.get('bits'): if bits != header.get('bits'):
raise Exception("bits mismatch: %s vs %s" % (bits, header.get('bits'))) raise Exception("bits mismatch: %s vs %s" % (bits, header.get('bits')))
block_hash = int('0x' + _hash, 16) if int('0x' + _hash, 16) > target:
target_val = self.bits_to_target(bits) raise Exception("insufficient proof of work: %s vs target %s" % (int('0x' + _hash, 16), target))'''
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")
def verify_chunk(self, index: int, data: bytes) -> None:
def verify_chunk(self, index, data):
num = len(data) // HEADER_SIZE num = len(data) // HEADER_SIZE
current_header = (index * 2016) start_height = index * 2016
# last = (index * 2016 + 2015) prev_hash = self.get_hash(start_height - 1)
print(index * 2016) target = self.get_target(index-1)
prev_hash = self.get_hash(current_header - 1)
for i in range(num): for i in range(num):
target = self.get_target(current_header - 1) height = start_height + i
try: try:
expected_header_hash = self.get_hash(current_header) expected_header_hash = self.get_hash(height)
except MissingHeader: except MissingHeader:
expected_header_hash = None expected_header_hash = None
raw_header = data[i*HEADER_SIZE : (i+1)*HEADER_SIZE]
raw_header = data[i * HEADER_SIZE: (i + 1) * HEADER_SIZE] header = deserialize_header(raw_header, index*2016 + i)
header = deserialize_header(raw_header, current_header)
print(i)
self.verify_header(header, prev_hash, target, expected_header_hash) self.verify_header(header, prev_hash, target, expected_header_hash)
self.save_chunk_part(header)
prev_hash = hash_header(header) prev_hash = hash_header(header)
current_header = current_header + 1
def path(self): def path(self):
d = util.get_headers_dir(self.config) d = util.get_headers_dir(self.config)
@ -228,24 +201,22 @@ class Blockchain(util.PrintError):
@with_lock @with_lock
def save_chunk(self, index: int, chunk: bytes): 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' # chunks in checkpoint region are the responsibility of the 'main chain'
# if chunk_within_checkpoint_region and self.parent_id is not None: if chunk_within_checkpoint_region and self.parent_id is not None:
# main_chain = blockchains[0] main_chain = blockchains[0]
# main_chain.save_chunk(index, chunk) main_chain.save_chunk(index, chunk)
# return return
#delta_height = (index * 2016 - self.forkpoint)
#delta_bytes = delta_height * HEADER_SIZE
delta_height = (index * 2016 - self.forkpoint)
delta_bytes = delta_height * HEADER_SIZE
# if this chunk contains our forkpoint, only save the part after forkpoint # if this chunk contains our forkpoint, only save the part after forkpoint
# (the part before is the responsibility of the parent) # (the part before is the responsibility of the parent)
# if delta_bytes < 0: if delta_bytes < 0:
# chunk = chunk[-delta_bytes:] chunk = chunk[-delta_bytes:]
# delta_bytes = 0 delta_bytes = 0
# truncate = not chunk_within_checkpoint_region truncate = not chunk_within_checkpoint_region
# self.write(chunk, delta_bytes, truncate) self.write(chunk, delta_bytes, truncate)
self.swap_with_parent() self.swap_with_parent()
@with_lock @with_lock
@ -363,58 +334,29 @@ class Blockchain(util.PrintError):
# compute target from chunk x, used in chunk x+1 # compute target from chunk x, used in chunk x+1
if constants.net.TESTNET: if constants.net.TESTNET:
return 0 return 0
# The range is first 90 blocks because FLO's block time was 90 blocks when it started if index == -1:
if -1 <= index <= 88: return MAX_TARGET
return 0x1e0ffff0
if index < len(self.checkpoints): if index < len(self.checkpoints):
h, t = self.checkpoints[index] h, t = self.checkpoints[index]
return t return t
# new target # new target
headerLast = self.read_header(index) first = self.read_header(index * 2016)
height = headerLast["block_height"] last = self.read_header(index * 2016 + 2015)
# check if the height passes is in range for retargeting if not first or not last:
if (height + 1) % self.DifficultyAdjustmentInterval(height + 1) != 0: raise MissingHeader()
return int(headerLast["bits"]) bits = last.get('bits')
averagingInterval = self.AveragingInterval(height + 1) target = self.bits_to_target(bits)
blockstogoback = averagingInterval - 1 nActualTimespan = last.get('timestamp') - first.get('timestamp')
# print("Blocks to go back = " + str(blockstogoback)) nTargetTimespan = 14 * 24 * 60 * 60
if (height + 1) != averagingInterval: nActualTimespan = max(nActualTimespan, nTargetTimespan // 4)
blockstogoback = averagingInterval nActualTimespan = min(nActualTimespan, nTargetTimespan * 4)
firstHeight = height - blockstogoback new_target = min(MAX_TARGET, (target * nActualTimespan) // nTargetTimespan)
headerFirst = self.read_header(int(firstHeight)) return new_target
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
def bits_to_target(self, bits: int) -> int: def bits_to_target(self, bits: int) -> int:
bitsN = (bits >> 24) & 0xff bitsN = (bits >> 24) & 0xff
if not (bitsN >= 0x03 and bitsN <= 0x1e): if not (bitsN >= 0x03 and bitsN <= 0x1d):
raise BaseException("First part of bits should be in [0x03, 0x1e]") raise Exception("First part of bits should be in [0x03, 0x1d]")
bitsBase = bits & 0xffffff bitsBase = bits & 0xffffff
if not (bitsBase >= 0x8000 and bitsBase <= 0x7fffff): if not (bitsBase >= 0x8000 and bitsBase <= 0x7fffff):
raise Exception("Second part of bits should be in [0x8000, 0x7fffff]") raise Exception("Second part of bits should be in [0x8000, 0x7fffff]")
@ -435,7 +377,7 @@ class Blockchain(util.PrintError):
return False return False
height = header['block_height'] height = header['block_height']
if check_height and self.height() != height - 1: 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 return False
if height == 0: if height == 0:
return hash_header(header) == constants.net.GENESIS return hash_header(header) == constants.net.GENESIS
@ -446,7 +388,7 @@ class Blockchain(util.PrintError):
if prev_hash != header.get('prev_block_hash'): if prev_hash != header.get('prev_block_hash'):
return False return False
try: try:
target = self.get_target(height - 1) target = self.get_target(height // 2016 - 1)
except MissingHeader: except MissingHeader:
return False return False
try: try:
@ -459,7 +401,7 @@ class Blockchain(util.PrintError):
try: try:
data = bfh(hexdata) data = bfh(hexdata)
self.verify_chunk(idx, data) self.verify_chunk(idx, data)
# self.print_error("validated chunk %d" % idx) #self.print_error("validated chunk %d" % idx)
self.save_chunk(idx, data) self.save_chunk(idx, data)
return True return True
except BaseException as e: except BaseException as e:
@ -477,71 +419,6 @@ class Blockchain(util.PrintError):
return cp 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]: def check_header(header: dict) -> Optional[Blockchain]:
if type(header) is not dict: if type(header) is not dict:
return None return None

View File

@ -543,7 +543,7 @@ class Commands:
PR_PAID: 'Paid', PR_PAID: 'Paid',
PR_EXPIRED: 'Expired', 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)] out['status'] = pr_str[out.get('status', PR_UNKNOWN)]
return out return out
@ -694,8 +694,8 @@ param_descriptions = {
'pubkey': 'Public key', 'pubkey': 'Public key',
'message': 'Clear text message. Use quotes if it contains spaces.', 'message': 'Clear text message. Use quotes if it contains spaces.',
'encrypted': 'Encrypted message', 'encrypted': 'Encrypted message',
'amount': 'Amount to be sent (in FLO). Type \'!\' to send the maximum available.', 'amount': 'Amount to be sent (in BTC). Type \'!\' to send the maximum available.',
'requested_amount': 'Requested amount (in FLO).', 'requested_amount': 'Requested amount (in BTC).',
'outputs': 'list of ["address", amount]', 'outputs': 'list of ["address", amount]',
'redeem_script': 'redeem script (hexadecimal)', 'redeem_script': 'redeem script (hexadecimal)',
} }
@ -712,7 +712,7 @@ command_options = {
'labels': ("-l", "Show the labels of listed addresses"), 'labels': ("-l", "Show the labels of listed addresses"),
'nocheck': (None, "Do not verify aliases"), 'nocheck': (None, "Do not verify aliases"),
'imax': (None, "Maximum number of inputs"), '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)."), '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"), '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"), 'nbits': (None, "Number of bits of entropy"),

View File

@ -49,7 +49,6 @@
"EUR", "EUR",
"FJD", "FJD",
"FKP", "FKP",
"FLO",
"GBP", "GBP",
"GEL", "GEL",
"GHS", "GHS",
@ -357,7 +356,7 @@
"Bitvalor": [ "Bitvalor": [
"BRL" "BRL"
], ],
"Bittrex": [ "BlockchainInfo": [
"AUD", "AUD",
"BRL", "BRL",
"CAD", "CAD",
@ -429,7 +428,6 @@
"ETB", "ETB",
"EUR", "EUR",
"FJD", "FJD",
"FLO",
"FKP", "FKP",
"GBP", "GBP",
"GEL", "GEL",

View File

@ -29,6 +29,7 @@ import time
import traceback import traceback
import sys import sys
import threading import threading
from typing import Dict
import jsonrpclib import jsonrpclib
@ -37,7 +38,7 @@ from .version import ELECTRUM_VERSION
from .network import Network from .network import Network
from .util import json_decode, DaemonThread from .util import json_decode, DaemonThread
from .util import print_error, to_string from .util import print_error, to_string
from .wallet import Wallet from .wallet import Wallet, Abstract_Wallet
from .storage import WalletStorage from .storage import WalletStorage
from .commands import known_commands, Commands from .commands import known_commands, Commands
from .simple_config import SimpleConfig from .simple_config import SimpleConfig
@ -131,7 +132,7 @@ class Daemon(DaemonThread):
if self.network: if self.network:
self.network.start([self.fx.run]) self.network.start([self.fx.run])
self.gui = None self.gui = None
self.wallets = {} self.wallets = {} # type: Dict[str, Abstract_Wallet]
# Setup JSONRPC server # Setup JSONRPC server
self.init_server(config, fd) self.init_server(config, fd)
@ -163,6 +164,7 @@ class Daemon(DaemonThread):
return True return True
def run_daemon(self, config_options): def run_daemon(self, config_options):
asyncio.set_event_loop(self.network.asyncio_loop)
config = SimpleConfig(config_options) config = SimpleConfig(config_options)
sub = config.get('subcommand') sub = config.get('subcommand')
assert sub in [None, 'start', 'stop', 'status', 'load_wallet', 'close_wallet'] assert sub in [None, 'start', 'stop', 'status', 'load_wallet', 'close_wallet']

View File

@ -1,6 +1,4 @@
import asyncio import asyncio
import aiohttp
from aiohttp_socks import SocksConnector, SocksVer
from datetime import datetime from datetime import datetime
import inspect import inspect
import sys import sys
@ -12,6 +10,7 @@ import decimal
from decimal import Decimal from decimal import Decimal
import concurrent.futures import concurrent.futures
import traceback import traceback
from typing import Sequence
from .bitcoin import COIN from .bitcoin import COIN
from .i18n import _ 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, 'RWF': 0, 'TND': 3, 'UGX': 0, 'UYI': 0, 'VND': 0,
'VUV': 0, 'XAF': 0, 'XAU': 4, 'XOF': 0, 'XPF': 0} 'VUV': 0, 'XAF': 0, 'XAU': 4, 'XOF': 0, 'XPF': 0}
class ExchangeBase(PrintError): class ExchangeBase(PrintError):
def __init__(self, on_quotes, on_history): def __init__(self, on_quotes, on_history):
@ -65,7 +65,7 @@ class ExchangeBase(PrintError):
self.quotes = await self.get_rates(ccy) self.quotes = await self.get_rates(ccy)
self.print_error("received fx quotes") self.print_error("received fx quotes")
except BaseException as e: except BaseException as e:
self.print_error("failed fx quotes:", e) self.print_error("failed fx quotes:", repr(e))
self.quotes = {} self.quotes = {}
self.on_quotes() self.on_quotes()
@ -220,13 +220,11 @@ class Bitvalor(ExchangeBase):
return {'BRL': Decimal(json['ticker_1h']['total']['last'])} return {'BRL': Decimal(json['ticker_1h']['total']['last'])}
class Bittrex(ExchangeBase): class BlockchainInfo(ExchangeBase):
async def get_rates(self, ccy): async def get_rates(self, ccy):
json = await self.get_json('bittrex.com','/api/v1.1/public/getticker?market=BTC-FLO') json = await self.get_json('blockchain.info', '/ticker')
floPrice_inBTC = json['result']['Last'] return dict([(r, Decimal(json[r]['15m'])) for r in json])
json = self.get_json('blockchain.info', '/ticker')
return dict([(r, Decimal(json[r]['15m'] * floPrice_inBTC)) for r in json])
class BTCChina(ExchangeBase): class BTCChina(ExchangeBase):
@ -454,12 +452,14 @@ class FxThread(ThreadJob):
def set_proxy(self, trigger_name, *args): def set_proxy(self, trigger_name, *args):
self._trigger.set() self._trigger.set()
def get_currencies(self, h): @staticmethod
d = get_exchanges_by_ccy(h) def get_currencies(history: bool) -> Sequence[str]:
d = get_exchanges_by_ccy(history)
return sorted(d.keys()) return sorted(d.keys())
def get_exchanges_by_ccy(self, ccy, h): @staticmethod
d = get_exchanges_by_ccy(h) def get_exchanges_by_ccy(ccy: str, history: bool) -> Sequence[str]:
d = get_exchanges_by_ccy(history)
return d.get(ccy, []) return d.get(ccy, [])
def ccy_amount_str(self, amount, commas): 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') return str(datetime.date(d.year, d.month, d.day)) if d else _('None')
def refresh_headers(self): def refresh_headers(self):
headers = ['', '', _('Date'), _('Description'), _('Amount'), _('Balance'), _('FLO Data')] headers = ['', '', _('Date'), _('Description'), _('Amount'), _('Balance')]
fx = self.parent.fx fx = self.parent.fx
if fx and fx.show_history(): if fx and fx.show_history():
headers.extend(['%s '%fx.ccy + _('Value')]) headers.extend(['%s '%fx.ccy + _('Value')])
@ -177,13 +177,13 @@ class HistoryList(MyTreeWidget, AcceptFileDragDrop):
grid = QGridLayout() grid = QGridLayout()
grid.addWidget(QLabel(_("Start")), 0, 0) grid.addWidget(QLabel(_("Start")), 0, 0)
grid.addWidget(QLabel(self.format_date(start_date)), 0, 1) 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(_("Initial balance")), 1, 0)
grid.addWidget(QLabel(format_amount(h['start_balance'])), 1, 1) grid.addWidget(QLabel(format_amount(h['start_balance'])), 1, 1)
grid.addWidget(QLabel(str(h.get('start_fiat_balance'))), 1, 2) grid.addWidget(QLabel(str(h.get('start_fiat_balance'))), 1, 2)
grid.addWidget(QLabel(_("End")), 2, 0) grid.addWidget(QLabel(_("End")), 2, 0)
grid.addWidget(QLabel(self.format_date(end_date)), 2, 1) 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(_("Final balance")), 4, 0)
grid.addWidget(QLabel(format_amount(h['end_balance'])), 4, 1) grid.addWidget(QLabel(format_amount(h['end_balance'])), 4, 1)
grid.addWidget(QLabel(str(h.get('end_fiat_balance'))), 4, 2) 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]) icon = self.icon_cache.get(":icons/" + TX_ICONS[status])
v_str = self.parent.format_amount(value, is_diff=True, whitespaces=True) v_str = self.parent.format_amount(value, is_diff=True, whitespaces=True)
balance_str = self.parent.format_amount(balance, 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]
entry = ['', tx_hash, status_str, label, v_str, balance_str, txcomment]
fiat_value = None fiat_value = None
if value is not None and fx and fx.show_history(): if value is not None and fx and fx.show_history():
fiat_value = tx_item['fiat_value'].value fiat_value = tx_item['fiat_value'].value

View File

@ -1124,7 +1124,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
hbox.addStretch(1) hbox.addStretch(1)
grid.addLayout(hbox, 4, 4) 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'\ + _('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.') + _('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) self.fee_e_label = HelpLabel(_('Fee'), msg)
@ -1223,12 +1223,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
if not self.config.get('show_fee', False): if not self.config.get('show_fee', False):
self.fee_adv_controls.setVisible(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 = EnterButton(_("Preview"), self.do_preview)
self.preview_button.setToolTip(_('Display the details of your transaction before signing it.')) self.preview_button.setToolTip(_('Display the details of your transaction before signing it.'))
self.send_button = EnterButton(_("Send"), self.do_send) 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.clear_button)
buttons.addWidget(self.preview_button) buttons.addWidget(self.preview_button)
buttons.addWidget(self.send_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.amount_e.shortcut.connect(self.spend_max)
self.payto_e.textChanged.connect(self.update_fee) self.payto_e.textChanged.connect(self.update_fee)
@ -1496,7 +1490,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.show_error(_('Payment request has expired')) self.show_error(_('Payment request has expired'))
return return
label = self.message_e.text() label = self.message_e.text()
txcomment = self.message_tx.text()
if self.payment_request: if self.payment_request:
outputs = self.payment_request.get_outputs() outputs = self.payment_request.get_outputs()
@ -1532,7 +1525,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
fee_estimator = self.get_send_fee_estimator() fee_estimator = self.get_send_fee_estimator()
coins = self.get_coins() coins = self.get_coins()
return outputs, fee_estimator, label, coins, txcomment return outputs, fee_estimator, label, coins
def do_preview(self): def do_preview(self):
self.do_send(preview = True) self.do_send(preview = True)
@ -1543,12 +1536,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
r = self.read_send_tab() r = self.read_send_tab()
if not r: if not r:
return return
outputs, fee_estimator, tx_desc, coins, txcomment = r outputs, fee_estimator, tx_desc, coins = r
try: try:
is_sweep = bool(self.tx_external_keypairs) is_sweep = bool(self.tx_external_keypairs)
tx = self.wallet.make_unsigned_transaction( tx = self.wallet.make_unsigned_transaction(
coins, outputs, self.config, fixed_fee=fee_estimator, coins, outputs, self.config, fixed_fee=fee_estimator,
is_sweep=is_sweep, txcomment=txcomment) is_sweep=is_sweep)
except NotEnoughFunds: except NotEnoughFunds:
self.show_message(_("Insufficient funds")) self.show_message(_("Insufficient funds"))
return return
@ -2780,7 +2773,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
units = base_units_list units = base_units_list
msg = (_('Base unit of your wallet.') 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.')) + _('This setting affects the Send tab, and all balance related fields.'))
unit_label = HelpLabel(_('Base unit') + ':', msg) unit_label = HelpLabel(_('Base unit') + ':', msg)
unit_combo = QComboBox() unit_combo = QComboBox()

View File

@ -113,8 +113,6 @@ class TxDialog(QDialog, MessageBoxMixin):
vbox.addWidget(self.size_label) vbox.addWidget(self.size_label)
self.fee_label = QLabel() self.fee_label = QLabel()
vbox.addWidget(self.fee_label) vbox.addWidget(self.fee_label)
self.txcomment_label = QLabel()
vbox.addWidget(self.txcomment_label)
self.add_io(vbox) self.add_io(vbox)
@ -268,7 +266,6 @@ class TxDialog(QDialog, MessageBoxMixin):
self.amount_label.setText(amount_str) self.amount_label.setText(amount_str)
self.fee_label.setText(fee_str) self.fee_label.setText(fee_str)
self.size_label.setText(size_str) self.size_label.setText(size_str)
self.txcomment_label.setText("TX Comment: " + self.tx.txcomment)
run_hook('transaction_dialog_update', self) run_hook('transaction_dialog_update', self)
def add_io(self, vbox): def add_io(self, vbox):

View File

@ -63,7 +63,7 @@ class NotificationSession(ClientSession):
for queue in self.subscriptions[key]: for queue in self.subscriptions[key]:
await queue.put(request.args) await queue.put(request.args)
else: else:
assert False, request.method raise Exception('unexpected request: {}'.format(repr(request)))
async def send_request(self, *args, timeout=-1, **kwargs): async def send_request(self, *args, timeout=-1, **kwargs):
# note: the timeout starts after the request touches the wire! # note: the timeout starts after the request touches the wire!
@ -255,12 +255,12 @@ class Interface(PrintError):
try: try:
ssl_context = await self._get_ssl_context() ssl_context = await self._get_ssl_context()
except (ErrorParsingSSLCert, ErrorGettingSSLCertFromServer) as e: 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 return
try: 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: 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 return
def mark_ready(self): def mark_ready(self):
@ -338,7 +338,7 @@ class Interface(PrintError):
return conn, 0 return conn, 0
return conn, res['count'] 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) self.session = NotificationSession(self.host, self.port, ssl=sslc, proxy=self.proxy)
async with self.session as session: async with self.session as session:
try: try:

View File

@ -37,7 +37,7 @@ def plot_history(history):
plt.subplots_adjust(bottom=0.2) plt.subplots_adjust(bottom=0.2)
plt.xticks( rotation=25 ) plt.xticks( rotation=25 )
ax = plt.gca() ax = plt.gca()
plt.ylabel('FLO') plt.ylabel('BTC')
plt.xlabel('Month') plt.xlabel('Month')
xfmt = md.DateFormatter('%Y-%m-%d') xfmt = md.DateFormatter('%Y-%m-%d')
ax.xaxis.set_major_formatter(xfmt) ax.xaxis.set_major_formatter(xfmt)

View File

@ -134,7 +134,7 @@ class Plugins(DaemonThread):
try: try:
__import__(dep) __import__(dep)
except ImportError as e: 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 return False
requires = d.get('requires_wallet_type', []) requires = d.get('requires_wallet_type', [])
return not requires or w.wallet_type in requires 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": { "localhost": {
"pruning": "-", "pruning": "-",
"s": "50002", "s": "50002",
"t": "50001", "t": "50001",
"version": "1.2" "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": "-", "pruning": "-",
"s": "51002", "s": "51002",
"t": "51001", "t": "51001",
"version": "1.2" "version": "1.2"
}, },
"localhost": { "testnet1.bauerj.eu": {
"pruning": "-", "pruning": "-",
"s": "51002", "s": "50002",
"t": "51001", "t": "50001",
"version": "1.2" "version": "1.2"
} }
} }

View File

@ -195,7 +195,7 @@ class SimpleConfig(PrintError):
base_unit = self.user_config.get('base_unit') base_unit = self.user_config.get('base_unit')
if isinstance(base_unit, str): if isinstance(base_unit, str):
self._set_key_in_user_config('base_unit', None) 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()) decimal_point = map_.get(base_unit.lower())
self._set_key_in_user_config('decimal_point', decimal_point) self._set_key_in_user_config('decimal_point', decimal_point)

View File

@ -755,6 +755,7 @@ class Transaction:
self._outputs = outputs self._outputs = outputs
self.locktime = locktime self.locktime = locktime
self.txcomment = txcomment self.txcomment = txcomment
self.BIP69_sort()
return self return self
@classmethod @classmethod
@ -989,10 +990,11 @@ class Transaction:
for txin in self.inputs(): for txin in self.inputs():
txin['sequence'] = nSequence txin['sequence'] = nSequence
def BIP_LI01_sort(self): def BIP69_sort(self, inputs=True, outputs=True):
# See https://github.com/kristovatlas/rfc/blob/master/bips/bip-li01.mediawiki if inputs:
self._inputs.sort(key = lambda i: (i['prevout_hash'], i['prevout_n'])) 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]))) if outputs:
self._outputs.sort(key = lambda o: (o[2], self.pay_script(o[0], o[1])))
def serialize_output(self, output): def serialize_output(self, output):
output_type, addr, amount = output output_type, addr, amount = output
@ -1006,7 +1008,8 @@ class Transaction:
nVersion = int_to_hex(self.version, 4) nVersion = int_to_hex(self.version, 4)
nHashType = int_to_hex(1, 4) nHashType = int_to_hex(1, 4)
nLocktime = int_to_hex(self.locktime, 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() inputs = self.inputs()
outputs = self.outputs() outputs = self.outputs()
txin = inputs[i] txin = inputs[i]
@ -1029,8 +1032,6 @@ class Transaction:
txouts = var_int(len(outputs)) + ''.join(self.serialize_output(o) for o in outputs) txouts = var_int(len(outputs)) + ''.join(self.serialize_output(o) for o in outputs)
if self.version >= 2: if self.version >= 2:
preimage = nVersion + txins + txouts + nLocktime + nTxComment + nHashType preimage = nVersion + txins + txouts + nLocktime + nTxComment + nHashType
print("preimage")
print(preimage)
else: else:
preimage = nVersion + txins + txouts + nLocktime + nHashType preimage = nVersion + txins + txouts + nLocktime + nHashType
return preimage return preimage
@ -1053,7 +1054,8 @@ class Transaction:
def serialize_to_network(self, estimate_size=False, witness=True): def serialize_to_network(self, estimate_size=False, witness=True):
nVersion = int_to_hex(self.version, 4) nVersion = int_to_hex(self.version, 4)
nLocktime = int_to_hex(self.locktime, 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() inputs = self.inputs()
outputs = self.outputs() outputs = self.outputs()
txins = var_int(len(inputs)) + ''.join(self.serialize_input(txin, self.input_script(txin, estimate_size)) for txin in inputs) 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): def add_inputs(self, inputs):
self._inputs.extend(inputs) self._inputs.extend(inputs)
self.raw = None self.raw = None
self.BIP69_sort(outputs=False)
def add_outputs(self, outputs): def add_outputs(self, outputs):
self._outputs.extend(outputs) self._outputs.extend(outputs)
self.raw = None self.raw = None
self.BIP69_sort(inputs=False)
def input_value(self): def input_value(self):
return sum(x['value'] for x in self.inputs()) 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()} 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_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 DECIMAL_POINT_DEFAULT = 5 # mBTC
@ -140,7 +140,7 @@ class Satoshis(object):
return 'Satoshis(%d)'%self.value return 'Satoshis(%d)'%self.value
def __str__(self): def __str__(self):
return format_satoshis(self.value) + " FLO" return format_satoshis(self.value) + " BTC"
class Fiat(object): class Fiat(object):
__slots__ = ('value', 'ccy') __slots__ = ('value', 'ccy')
@ -358,7 +358,7 @@ def android_data_dir():
return PythonActivity.mActivity.getFilesDir().getPath() + '/data' return PythonActivity.mActivity.getFilesDir().getPath() + '/data'
def android_headers_dir(): 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): if not os.path.exists(d):
try: try:
os.mkdir(d) os.mkdir(d)
@ -370,7 +370,7 @@ def android_check_data_dir():
""" if needed, move old directory to sandbox """ """ if needed, move old directory to sandbox """
ext_dir = android_ext_dir() ext_dir = android_ext_dir()
data_dir = android_data_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): if not os.path.exists(data_dir) and os.path.exists(old_electrum_dir):
import shutil import shutil
new_headers_path = android_headers_dir() + '/blockchain_headers' new_headers_path = android_headers_dir() + '/blockchain_headers'
@ -482,11 +482,11 @@ def user_dir():
if 'ANDROID_DATA' in os.environ: if 'ANDROID_DATA' in os.environ:
return android_check_data_dir() return android_check_data_dir()
elif os.name == 'posix': 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: 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: elif "LOCALAPPDATA" in os.environ:
return os.path.join(os.environ["LOCALAPPDATA"], "Electrum-FLO") return os.path.join(os.environ["LOCALAPPDATA"], "Electrum")
else: else:
#raise Exception("No home directory found in environment variables.") #raise Exception("No home directory found in environment variables.")
return return
@ -605,17 +605,43 @@ def time_difference(distance_in_time, include_seconds):
return "over %d years" % (round(distance_in_minutes / 525600)) return "over %d years" % (round(distance_in_minutes / 525600))
mainnet_block_explorers = { mainnet_block_explorers = {
'Florincoin.info': ('https://florincoin.info/', 'Biteasy.com': ('https://www.biteasy.com/blockchain/',
{'tx': 'transactions/', 'addr': 'address/'}), {'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:/', 'system default': ('blockchain:/',
{'tx': 'tx/', 'addr': 'address/'}) {'tx': 'tx/', 'addr': 'address/'}),
} }
testnet_block_explorers = { testnet_block_explorers = {
'testnet.Florincoin.info': ('https://testnet.florincoin.info/', 'Blocktrail.com': ('https://www.blocktrail.com/tBTC/',
{'tx': 'transactions/', 'addr': 'address/'}), {'tx': 'tx/', 'addr': 'address/'}),
'system default': ('blockchain://000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943/', 'system default': ('blockchain://000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943/',
{'tx': 'tx/', 'addr': 'address/'}) {'tx': 'tx/', 'addr': 'address/'}),
} }
def block_explorer_info(): def block_explorer_info():
@ -623,7 +649,7 @@ def block_explorer_info():
return testnet_block_explorers if constants.net.TESTNET else mainnet_block_explorers return testnet_block_explorers if constants.net.TESTNET else mainnet_block_explorers
def block_explorer(config): def block_explorer(config):
return config.get('block_explorer', 'Florincoin.info') return config.get('block_explorer', 'Blocktrail.com')
def block_explorer_tuple(config): def block_explorer_tuple(config):
return block_explorer_info().get(block_explorer(config)) return block_explorer_info().get(block_explorer(config))
@ -841,8 +867,7 @@ TxMinedStatus = NamedTuple("TxMinedStatus", [("height", int),
VerifiedTxInfo = NamedTuple("VerifiedTxInfo", [("height", int), VerifiedTxInfo = NamedTuple("VerifiedTxInfo", [("height", int),
("timestamp", int), ("timestamp", int),
("txpos", int), ("txpos", int),
("header_hash", str), ("header_hash", str)])
("tx_comment", str)])
def make_aiohttp_session(proxy): def make_aiohttp_session(proxy):
if proxy: if proxy:

View File

@ -113,8 +113,7 @@ class SPV(PrintError):
except KeyError: pass except KeyError: pass
self.print_error("verified %s" % tx_hash) self.print_error("verified %s" % tx_hash)
header_hash = hash_header(header) 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)
vtx_info = VerifiedTxInfo(tx_height, header.get('timestamp'), pos, header_hash, tx_comment)
self.wallet.add_verified_tx(tx_hash, vtx_info) self.wallet.add_verified_tx(tx_hash, vtx_info)
if self.is_up_to_date() and self.wallet.is_up_to_date(): if self.is_up_to_date() and self.wallet.is_up_to_date():
self.wallet.save_verified_tx(write=True) 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() locktime = network.get_local_height()
tx = Transaction.from_io(inputs, outputs, locktime=locktime) tx = Transaction.from_io(inputs, outputs, locktime=locktime)
tx.BIP_LI01_sort()
tx.set_rbf(True) tx.set_rbf(True)
tx.sign(keypairs) tx.sign(keypairs)
return tx return tx
@ -408,7 +407,7 @@ class Abstract_Wallet(AddressSynchronizer):
'value': Satoshis(value), 'value': Satoshis(value),
'balance': Satoshis(balance), 'balance': Satoshis(balance),
'date': timestamp_to_datetime(timestamp), 'date': timestamp_to_datetime(timestamp),
'label': self.get_label(tx_hash) 'label': self.get_label(tx_hash),
} }
if show_addresses: if show_addresses:
tx = self.transactions.get(tx_hash) tx = self.transactions.get(tx_hash)
@ -493,7 +492,6 @@ class Abstract_Wallet(AddressSynchronizer):
return ', '.join(labels) return ', '.join(labels)
return '' return ''
def get_tx_status(self, tx_hash, tx_mined_status): def get_tx_status(self, tx_hash, tx_mined_status):
extra = [] extra = []
height = tx_mined_status.height height = tx_mined_status.height
@ -541,7 +539,7 @@ class Abstract_Wallet(AddressSynchronizer):
return dust_threshold(self.network) return dust_threshold(self.network)
def make_unsigned_transaction(self, inputs, outputs, config, fixed_fee=None, 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 # check outputs
i_max = None i_max = None
for i, o in enumerate(outputs): for i, o in enumerate(outputs):
@ -609,14 +607,8 @@ class Abstract_Wallet(AddressSynchronizer):
outputs[i_max] = outputs[i_max]._replace(value=amount) outputs[i_max] = outputs[i_max]._replace(value=amount)
tx = Transaction.from_io(inputs, outputs[:]) tx = Transaction.from_io(inputs, outputs[:])
# Sort the inputs and outputs deterministically
tx.BIP_LI01_sort()
# Timelock tx to current height. # Timelock tx to current height.
tx.locktime = self.get_local_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) run_hook('make_unsigned_transaction', self, tx)
return tx return tx
@ -719,7 +711,6 @@ class Abstract_Wallet(AddressSynchronizer):
raise CannotBumpFee(_('Cannot bump fee') + ': ' + _('could not find suitable outputs')) raise CannotBumpFee(_('Cannot bump fee') + ': ' + _('could not find suitable outputs'))
locktime = self.get_local_height() locktime = self.get_local_height()
tx_new = Transaction.from_io(inputs, outputs, locktime=locktime) tx_new = Transaction.from_io(inputs, outputs, locktime=locktime)
tx_new.BIP_LI01_sort()
return tx_new return tx_new
def cpfp(self, tx, fee): def cpfp(self, tx, fee):
@ -738,7 +729,6 @@ class Abstract_Wallet(AddressSynchronizer):
inputs = [item] inputs = [item]
outputs = [TxOutput(TYPE_ADDRESS, address, value - fee)] outputs = [TxOutput(TYPE_ADDRESS, address, value - fee)]
locktime = self.get_local_height() 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) return Transaction.from_io(inputs, outputs, locktime=locktime)
def add_input_sig_info(self, txin, address): def add_input_sig_info(self, txin, address):