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/
|
.buildozer/
|
||||||
bin/
|
bin/
|
||||||
/app.fil
|
/app.fil
|
||||||
|
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
# icons
|
# icons
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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"),
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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']
|
||||||
|
|||||||
@ -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):
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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):
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
|
|
||||||
|
|||||||
@ -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())
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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):
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user