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:
parent
826a56311c
commit
6a2d2fbc3d
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,7 +14,6 @@ env/
|
||||
.buildozer/
|
||||
bin/
|
||||
/app.fil
|
||||
|
||||
.idea
|
||||
|
||||
# icons
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"),
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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']
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,14 +1,8 @@
|
||||
{
|
||||
"ranchimall.duckdns.org": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.2"
|
||||
},
|
||||
"localhost": {
|
||||
"pruning": "-",
|
||||
"s": "50002",
|
||||
"t": "50001",
|
||||
"version": "1.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user