Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f628b70a5 | ||
|
|
64733e93d9 | ||
|
|
dd6e5ae9ac | ||
|
|
34e84858c8 | ||
|
|
ecea61d9ca | ||
|
|
b3a21f53bc | ||
|
|
085cd9919e | ||
|
|
d418976f5e | ||
|
|
91273f7114 | ||
|
|
4b1b6e3adf | ||
|
|
6a2d2fbc3d | ||
|
|
826a56311c | ||
|
|
2ce11fa83b | ||
|
|
4253dd27eb | ||
|
|
f2e93e323a | ||
|
|
9e0777a7b4 | ||
|
|
278a6bcb4d | ||
|
|
66777d4566 | ||
|
|
d8d2f3329b | ||
|
|
1e6edd0abc | ||
|
|
c0daef2657 | ||
|
|
a64228ba2c | ||
|
|
85bae50e62 | ||
|
|
ca30705c69 | ||
|
|
95b7aef579 | ||
|
|
e0d33279de | ||
|
|
f008f2e4dc |
BIN
electrum.icns
|
Before Width: | Height: | Size: 811 KiB After Width: | Height: | Size: 39 KiB |
@ -78,7 +78,8 @@ class AddressSynchronizer(PrintError):
|
|||||||
conf=None,
|
conf=None,
|
||||||
timestamp=timestamp,
|
timestamp=timestamp,
|
||||||
txpos=txpos,
|
txpos=txpos,
|
||||||
header_hash=header_hash)
|
header_hash=header_hash,
|
||||||
|
flodata=flodata)
|
||||||
# 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
|
||||||
@ -626,6 +627,23 @@ class AddressSynchronizer(PrintError):
|
|||||||
# local transaction
|
# local transaction
|
||||||
return TxMinedInfo(height=TX_HEIGHT_LOCAL, conf=0)
|
return TxMinedInfo(height=TX_HEIGHT_LOCAL, conf=0)
|
||||||
|
|
||||||
|
def get_flodata(self, tx_hash: str):
|
||||||
|
""" Given a transaction, returns flodata """
|
||||||
|
with self.lock:
|
||||||
|
if tx_hash in self.verified_tx:
|
||||||
|
info = self.verified_tx[tx_hash]
|
||||||
|
flodata = info[4]
|
||||||
|
return flodata
|
||||||
|
elif tx_hash in self.unverified_tx:
|
||||||
|
tx = self.transactions.get(tx_hash)
|
||||||
|
flodata = tx.flodata[5:]
|
||||||
|
return flodata
|
||||||
|
else:
|
||||||
|
# local transaction
|
||||||
|
tx = self.transactions.get(tx_hash)
|
||||||
|
flodata = tx.flodata[5:]
|
||||||
|
return flodata
|
||||||
|
|
||||||
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
|
||||||
@ -775,7 +793,7 @@ class AddressSynchronizer(PrintError):
|
|||||||
|
|
||||||
@with_local_height_cached
|
@with_local_height_cached
|
||||||
def get_addr_balance(self, address):
|
def get_addr_balance(self, address):
|
||||||
"""Return the balance of a bitcoin address:
|
"""Return the balance of a FLO address:
|
||||||
confirmed and matured, unconfirmed, unmatured
|
confirmed and matured, unconfirmed, unmatured
|
||||||
"""
|
"""
|
||||||
received, sent = self.get_addr_io(address)
|
received, sent = self.get_addr_io(address)
|
||||||
|
|||||||
@ -31,9 +31,16 @@ from . import constants
|
|||||||
from .util import bfh, bh2u
|
from .util import bfh, bh2u
|
||||||
from .simple_config import SimpleConfig
|
from .simple_config import SimpleConfig
|
||||||
|
|
||||||
|
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 = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
|
MAX_TARGET = 0x00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||||
|
|
||||||
|
|
||||||
class MissingHeader(Exception):
|
class MissingHeader(Exception):
|
||||||
@ -254,34 +261,70 @@ class Blockchain(util.PrintError):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def verify_header(cls, header: dict, prev_hash: str, target: int, expected_header_hash: str=None) -> None:
|
def verify_header(cls, 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
|
||||||
bits = cls.target_to_bits(target)
|
#bits = cls.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_as_num = int.from_bytes(bfh(_hash), byteorder='big')
|
block_hash = int('0x' + _hash, 16)
|
||||||
if block_hash_as_num > target:
|
target_val = self.bits_to_target(bits)
|
||||||
raise Exception(f"insufficient proof of work: {block_hash_as_num} vs target {target}")
|
if int('0x' + _powhash, 16) > target_val:
|
||||||
|
raise Exception("insufficient proof of work: %s vs target %s" % (int('0x' + _hash, 16), target_val))
|
||||||
|
|
||||||
def verify_chunk(self, index: int, data: bytes) -> None:
|
def verify_chunk(self, index: int, data: bytes) -> None:
|
||||||
num = len(data) // HEADER_SIZE
|
num = len(data) // HEADER_SIZE
|
||||||
start_height = index * 2016
|
current_header = index * 2016
|
||||||
prev_hash = self.get_hash(start_height - 1)
|
prev_hash = self.get_hash(current_header - 1)
|
||||||
target = self.get_target(index-1)
|
headerLast = None
|
||||||
|
headerFirst = None
|
||||||
|
capture = None
|
||||||
|
lst = []
|
||||||
for i in range(num):
|
for i in range(num):
|
||||||
height = start_height + i
|
averaging_interval = self.AveragingInterval(current_header)
|
||||||
|
difficulty_interval = self.DifficultyAdjustmentInterval(current_header)
|
||||||
|
if current_header < 426000:
|
||||||
|
target = self.get_target(current_header - 1, headerLast, headerFirst)
|
||||||
try:
|
try:
|
||||||
expected_header_hash = self.get_hash(height)
|
expected_header_hash = self.get_hash(current_header)
|
||||||
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)
|
||||||
self.verify_header(header, prev_hash, target, expected_header_hash)
|
self.verify_header(header, prev_hash, target, expected_header_hash)
|
||||||
prev_hash = hash_header(header)
|
prev_hash = hash_header(header)
|
||||||
|
headerLast = header
|
||||||
|
if current_header == 0:
|
||||||
|
headerFirst = header
|
||||||
|
elif (current_header + averaging_interval + 1) % difficulty_interval == 0:
|
||||||
|
capture = header
|
||||||
|
if current_header != 0 and current_header % difficulty_interval == 0:
|
||||||
|
headerFirst = capture
|
||||||
|
if current_header >= 425993:
|
||||||
|
lst.append(headerLast)
|
||||||
|
current_header = current_header + 1
|
||||||
|
else:
|
||||||
|
if len(lst)>6:
|
||||||
|
headerFirst = lst[0]
|
||||||
|
target = self.get_target(current_header - 1, headerLast, headerFirst)
|
||||||
|
try:
|
||||||
|
expected_header_hash = self.get_hash(current_header)
|
||||||
|
except MissingHeader:
|
||||||
|
expected_header_hash = None
|
||||||
|
raw_header = data[i * HEADER_SIZE: (i + 1) * HEADER_SIZE]
|
||||||
|
header = deserialize_header(raw_header, current_header)
|
||||||
|
self.verify_header(header, prev_hash, target, expected_header_hash)
|
||||||
|
prev_hash = hash_header(header)
|
||||||
|
headerLast = header
|
||||||
|
lst.append(header)
|
||||||
|
if len(lst)>7:
|
||||||
|
lst.pop(0)
|
||||||
|
current_header = current_header + 1
|
||||||
|
|
||||||
@with_lock
|
@with_lock
|
||||||
def path(self):
|
def path(self):
|
||||||
@ -446,30 +489,60 @@ class Blockchain(util.PrintError):
|
|||||||
raise MissingHeader(height)
|
raise MissingHeader(height)
|
||||||
return hash_header(header)
|
return hash_header(header)
|
||||||
|
|
||||||
def get_target(self, index: int) -> int:
|
def get_target(self, index: int, headerLast: dict=None, headerFirst: dict=None) -> int:
|
||||||
# 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
|
||||||
if index == -1:
|
# The range is first 90 blocks because FLO's block time was 90 blocks when it started
|
||||||
return MAX_TARGET
|
if -1 <= index <= 88:
|
||||||
|
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
|
||||||
first = self.read_header(index * 2016)
|
if headerLast is None:
|
||||||
last = self.read_header(index * 2016 + 2015)
|
headerLast = self.read_header(index)
|
||||||
if not first or not last:
|
height = headerLast["block_height"]
|
||||||
raise MissingHeader()
|
# check if the height passes is in range for retargeting
|
||||||
bits = last.get('bits')
|
if (height + 1) % self.DifficultyAdjustmentInterval(height + 1) != 0:
|
||||||
target = self.bits_to_target(bits)
|
return int(headerLast["bits"])
|
||||||
nActualTimespan = last.get('timestamp') - first.get('timestamp')
|
if headerFirst is None:
|
||||||
nTargetTimespan = 14 * 24 * 60 * 60
|
averagingInterval = self.AveragingInterval(height + 1)
|
||||||
nActualTimespan = max(nActualTimespan, nTargetTimespan // 4)
|
blockstogoback = averagingInterval - 1
|
||||||
nActualTimespan = min(nActualTimespan, nTargetTimespan * 4)
|
# print("Blocks to go back = " + str(blockstogoback))
|
||||||
new_target = min(MAX_TARGET, (target * nActualTimespan) // nTargetTimespan)
|
if (height + 1) != averagingInterval:
|
||||||
# not any target can be represented in 32 bits:
|
blockstogoback = averagingInterval
|
||||||
new_target = self.bits_to_target(self.target_to_bits(new_target))
|
firstHeight = height - blockstogoback
|
||||||
return new_target
|
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
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def bits_to_target(cls, bits: int) -> int:
|
def bits_to_target(cls, bits: int) -> int:
|
||||||
@ -542,7 +615,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 // 2016 - 1)
|
target = self.get_target(height - 1)
|
||||||
except MissingHeader:
|
except MissingHeader:
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
@ -573,6 +646,61 @@ class Blockchain(util.PrintError):
|
|||||||
cp.append((h, target))
|
cp.append((h, target))
|
||||||
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 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:
|
||||||
|
|||||||
@ -47,59 +47,104 @@ class AbstractNet:
|
|||||||
class BitcoinMainnet(AbstractNet):
|
class BitcoinMainnet(AbstractNet):
|
||||||
|
|
||||||
TESTNET = False
|
TESTNET = False
|
||||||
WIF_PREFIX = 0x80
|
WIF_PREFIX = 0xa3
|
||||||
ADDRTYPE_P2PKH = 0
|
ADDRTYPE_P2PKH = 35
|
||||||
ADDRTYPE_P2SH = 5
|
ADDRTYPE_P2SH = 8
|
||||||
SEGWIT_HRP = "bc"
|
SEGWIT_HRP = "ltc"
|
||||||
GENESIS = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
|
GENESIS = "09c7781c9df90708e278c35d38ea5c9041d7ecfcdd1c56ba67274b7cff3e1cea"
|
||||||
DEFAULT_PORTS = {'t': '50001', 's': '50002'}
|
DEFAULT_PORTS = {'t': '50001', 's': '50002'}
|
||||||
DEFAULT_SERVERS = read_json('servers.json', {})
|
DEFAULT_SERVERS = read_json('servers.json', {})
|
||||||
CHECKPOINTS = read_json('checkpoints.json', [])
|
CHECKPOINTS = read_json('checkpoints.json', [])
|
||||||
|
|
||||||
XPRV_HEADERS = {
|
XPRV_HEADERS = {
|
||||||
'standard': 0x0488ade4, # xprv
|
'standard': 0x01343c31, # xprv
|
||||||
'p2wpkh-p2sh': 0x049d7878, # yprv
|
'p2wpkh-p2sh': 0x049d7878, # yprv
|
||||||
'p2wsh-p2sh': 0x0295b005, # Yprv
|
'p2wsh-p2sh': 0x0295b005, # Yprv
|
||||||
'p2wpkh': 0x04b2430c, # zprv
|
'p2wpkh': 0x04b2430c, # zprv
|
||||||
'p2wsh': 0x02aa7a99, # Zprv
|
'p2wsh': 0x02aa7a99, # Zprv
|
||||||
}
|
}
|
||||||
XPUB_HEADERS = {
|
XPUB_HEADERS = {
|
||||||
'standard': 0x0488b21e, # xpub
|
'standard': 0x0134406b, # xpub
|
||||||
'p2wpkh-p2sh': 0x049d7cb2, # ypub
|
'p2wpkh-p2sh': 0x049d7cb2, # ypub
|
||||||
'p2wsh-p2sh': 0x0295b43f, # Ypub
|
'p2wsh-p2sh': 0x0295b43f, # Ypub
|
||||||
'p2wpkh': 0x04b24746, # zpub
|
'p2wpkh': 0x04b24746, # zpub
|
||||||
'p2wsh': 0x02aa7ed3, # Zpub
|
'p2wsh': 0x02aa7ed3, # Zpub
|
||||||
}
|
}
|
||||||
BIP44_COIN_TYPE = 0
|
BIP44_COIN_TYPE = 2
|
||||||
|
# FLO Network constants
|
||||||
|
fPowAllowMinDifficultyBlocks = False
|
||||||
|
fPowNoRetargeting = False
|
||||||
|
nRuleChangeActivationThreshold = 6048 # 75% of 8064
|
||||||
|
nMinerConfirmationWindow = 8064
|
||||||
|
# Difficulty adjustments
|
||||||
|
nPowTargetSpacing = 40 # 40s block time
|
||||||
|
# V1
|
||||||
|
nTargetTimespan_Version1 = 60 * 60
|
||||||
|
nInterval_Version1 = nTargetTimespan_Version1 / nPowTargetSpacing
|
||||||
|
nMaxAdjustUp_Version1 = 75
|
||||||
|
nMaxAdjustDown_Version1 = 300
|
||||||
|
nAveragingInterval_Version1 = nInterval_Version1
|
||||||
|
# V2
|
||||||
|
nHeight_Difficulty_Version2 = 208440
|
||||||
|
nInterval_Version2 = 15
|
||||||
|
nMaxAdjustDown_Version2 = 300
|
||||||
|
nMaxAdjustUp_Version2 = 75
|
||||||
|
nAveragingInterval_Version2 = nInterval_Version2
|
||||||
|
# V3
|
||||||
|
nHeight_Difficulty_Version3 = 426000
|
||||||
|
nInterval_Version3 = 1
|
||||||
|
nMaxAdjustDown_Version3 = 3
|
||||||
|
nMaxAdjustUp_Version3 = 2
|
||||||
|
nAveragingInterval_Version3 = 6
|
||||||
|
|
||||||
|
|
||||||
class BitcoinTestnet(AbstractNet):
|
class BitcoinTestnet(AbstractNet):
|
||||||
|
|
||||||
TESTNET = True
|
TESTNET = True
|
||||||
WIF_PREFIX = 0xef
|
WIF_PREFIX = 0xef
|
||||||
ADDRTYPE_P2PKH = 111
|
ADDRTYPE_P2PKH = 115
|
||||||
ADDRTYPE_P2SH = 196
|
ADDRTYPE_P2SH = 198
|
||||||
SEGWIT_HRP = "tb"
|
SEGWIT_HRP = "tltc"
|
||||||
GENESIS = "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"
|
GENESIS = "9b7bc86236c34b5e3a39367c036b7fe8807a966c22a7a1f0da2a198a27e03731"
|
||||||
DEFAULT_PORTS = {'t': '51001', 's': '51002'}
|
DEFAULT_PORTS = {'t': '51001', 's': '51002'}
|
||||||
DEFAULT_SERVERS = read_json('servers_testnet.json', {})
|
DEFAULT_SERVERS = read_json('servers_testnet.json', {})
|
||||||
CHECKPOINTS = read_json('checkpoints_testnet.json', [])
|
CHECKPOINTS = read_json('checkpoints_testnet.json', [])
|
||||||
|
|
||||||
XPRV_HEADERS = {
|
XPRV_HEADERS = {
|
||||||
'standard': 0x04358394, # tprv
|
'standard': 0x01343c23, # tprv
|
||||||
'p2wpkh-p2sh': 0x044a4e28, # uprv
|
'p2wpkh-p2sh': 0x044a4e28, # uprv
|
||||||
'p2wsh-p2sh': 0x024285b5, # Uprv
|
'p2wsh-p2sh': 0x024285b5, # Uprv
|
||||||
'p2wpkh': 0x045f18bc, # vprv
|
'p2wpkh': 0x045f18bc, # vprv
|
||||||
'p2wsh': 0x02575048, # Vprv
|
'p2wsh': 0x02575048, # Vprv
|
||||||
}
|
}
|
||||||
XPUB_HEADERS = {
|
XPUB_HEADERS = {
|
||||||
'standard': 0x043587cf, # tpub
|
'standard': 0x013440e2, # tpub
|
||||||
'p2wpkh-p2sh': 0x044a5262, # upub
|
'p2wpkh-p2sh': 0x044a5262, # upub
|
||||||
'p2wsh-p2sh': 0x024289ef, # Upub
|
'p2wsh-p2sh': 0x024289ef, # Upub
|
||||||
'p2wpkh': 0x045f1cf6, # vpub
|
'p2wpkh': 0x045f1cf6, # vpub
|
||||||
'p2wsh': 0x02575483, # Vpub
|
'p2wsh': 0x02575483, # Vpub
|
||||||
}
|
}
|
||||||
BIP44_COIN_TYPE = 1
|
BIP44_COIN_TYPE = 1
|
||||||
|
#Difficulty adjustments
|
||||||
|
nPowTargetSpacing = 40 # 40 block time
|
||||||
|
# V1
|
||||||
|
nTargetTimespan_Version1 = 60 * 60
|
||||||
|
nInterval_Version1 = nTargetTimespan_Version1 / nPowTargetSpacing;
|
||||||
|
nMaxAdjustUp_Version1 = 75
|
||||||
|
nMaxAdjustDown_Version1 = 300
|
||||||
|
nAveragingInterval_Version1 = nInterval_Version1
|
||||||
|
# V2
|
||||||
|
nHeight_Difficulty_Version2 = 50000
|
||||||
|
nInterval_Version2 = 15
|
||||||
|
nMaxAdjustDown_Version2 = 300
|
||||||
|
nMaxAdjustUp_Version2 = 75
|
||||||
|
nAveragingInterval_Version2 = nInterval_Version2
|
||||||
|
# V3
|
||||||
|
nHeight_Difficulty_Version3 = 60000
|
||||||
|
nInterval_Version3 = 1
|
||||||
|
nMaxAdjustDown_Version3 = 3
|
||||||
|
nMaxAdjustUp_Version3 = 2
|
||||||
|
nAveragingInterval_Version3 = 6
|
||||||
|
|
||||||
|
|
||||||
class BitcoinRegtest(BitcoinTestnet):
|
class BitcoinRegtest(BitcoinTestnet):
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 2.3 KiB |
@ -941,6 +941,16 @@ class ElectrumWindow(App):
|
|||||||
d = LabelDialog(_('Enter description'), text, callback)
|
d = LabelDialog(_('Enter description'), text, callback)
|
||||||
d.open()
|
d.open()
|
||||||
|
|
||||||
|
def flodata_dialog(self, screen):
|
||||||
|
from .uix.dialogs.label_dialog import LabelDialog
|
||||||
|
text = screen.flodata
|
||||||
|
|
||||||
|
def callback(text):
|
||||||
|
screen.flodata = text
|
||||||
|
|
||||||
|
d = LabelDialog(_('Enter FLO data'), text, callback)
|
||||||
|
d.open()
|
||||||
|
|
||||||
def amount_dialog(self, screen, show_max):
|
def amount_dialog(self, screen, show_max):
|
||||||
from .uix.dialogs.amount_dialog import AmountDialog
|
from .uix.dialogs.amount_dialog import AmountDialog
|
||||||
amount = screen.amount
|
amount = screen.amount
|
||||||
@ -990,6 +1000,7 @@ class ElectrumWindow(App):
|
|||||||
def on_fee(self, event, *arg):
|
def on_fee(self, event, *arg):
|
||||||
self.fee_status = self.electrum_config.get_fee_status()
|
self.fee_status = self.electrum_config.get_fee_status()
|
||||||
|
|
||||||
|
|
||||||
def protected(self, msg, f, args):
|
def protected(self, msg, f, args):
|
||||||
if self.wallet.has_password():
|
if self.wallet.has_password():
|
||||||
on_success = lambda pw: f(*(args + (pw,)))
|
on_success = lambda pw: f(*(args + (pw,)))
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 15 KiB |
@ -177,7 +177,7 @@ class SendScreen(CScreen):
|
|||||||
try:
|
try:
|
||||||
uri = electrum.util.parse_URI(text, self.app.on_pr)
|
uri = electrum.util.parse_URI(text, self.app.on_pr)
|
||||||
except:
|
except:
|
||||||
self.app.show_info(_("Not a Bitcoin URI"))
|
self.app.show_info(_("Not a FLO URI"))
|
||||||
return
|
return
|
||||||
amount = uri.get('amount')
|
amount = uri.get('amount')
|
||||||
self.screen.address = uri.get('address', '')
|
self.screen.address = uri.get('address', '')
|
||||||
@ -185,6 +185,7 @@ class SendScreen(CScreen):
|
|||||||
self.screen.amount = self.app.format_amount_and_units(amount) if amount else ''
|
self.screen.amount = self.app.format_amount_and_units(amount) if amount else ''
|
||||||
self.payment_request = None
|
self.payment_request = None
|
||||||
self.screen.is_pr = False
|
self.screen.is_pr = False
|
||||||
|
self.screen.flodata = uri.get('flodata', '')
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
if self.app.wallet and self.payment_request_queued:
|
if self.app.wallet and self.payment_request_queued:
|
||||||
@ -197,6 +198,7 @@ class SendScreen(CScreen):
|
|||||||
self.screen.address = ''
|
self.screen.address = ''
|
||||||
self.payment_request = None
|
self.payment_request = None
|
||||||
self.screen.is_pr = False
|
self.screen.is_pr = False
|
||||||
|
self.screen.flodata = ''
|
||||||
|
|
||||||
def set_request(self, pr):
|
def set_request(self, pr):
|
||||||
self.screen.address = pr.get_requestor()
|
self.screen.address = pr.get_requestor()
|
||||||
@ -248,10 +250,10 @@ class SendScreen(CScreen):
|
|||||||
else:
|
else:
|
||||||
address = str(self.screen.address)
|
address = str(self.screen.address)
|
||||||
if not address:
|
if not address:
|
||||||
self.app.show_error(_('Recipient not specified.') + ' ' + _('Please scan a Bitcoin address or a payment request'))
|
self.app.show_error(_('Recipient not specified.') + ' ' + _('Please scan a FLO address or a payment request'))
|
||||||
return
|
return
|
||||||
if not bitcoin.is_address(address):
|
if not bitcoin.is_address(address):
|
||||||
self.app.show_error(_('Invalid Bitcoin Address') + ':\n' + address)
|
self.app.show_error(_('Invalid FLO Address') + ':\n' + address)
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
amount = self.app.get_amount(self.screen.amount)
|
amount = self.app.get_amount(self.screen.amount)
|
||||||
@ -261,19 +263,20 @@ class SendScreen(CScreen):
|
|||||||
outputs = [TxOutput(bitcoin.TYPE_ADDRESS, address, amount)]
|
outputs = [TxOutput(bitcoin.TYPE_ADDRESS, address, amount)]
|
||||||
message = self.screen.message
|
message = self.screen.message
|
||||||
amount = sum(map(lambda x:x[2], outputs))
|
amount = sum(map(lambda x:x[2], outputs))
|
||||||
|
flodata = str(self.screen.flodata)
|
||||||
if self.app.electrum_config.get('use_rbf'):
|
if self.app.electrum_config.get('use_rbf'):
|
||||||
from .dialogs.question import Question
|
from .dialogs.question import Question
|
||||||
d = Question(_('Should this transaction be replaceable?'), lambda b: self._do_send(amount, message, outputs, b))
|
d = Question(_('Should this transaction be replaceable?'), lambda b: self._do_send(amount, message, outputs, b, flodata))
|
||||||
d.open()
|
d.open()
|
||||||
else:
|
else:
|
||||||
self._do_send(amount, message, outputs, False)
|
self._do_send(amount, message, outputs, False, flodata)
|
||||||
|
|
||||||
def _do_send(self, amount, message, outputs, rbf):
|
def _do_send(self, amount, message, outputs, rbf, flodata):
|
||||||
# make unsigned transaction
|
# make unsigned transaction
|
||||||
config = self.app.electrum_config
|
config = self.app.electrum_config
|
||||||
coins = self.app.wallet.get_spendable_coins(None, config)
|
coins = self.app.wallet.get_spendable_coins(None, config)
|
||||||
try:
|
try:
|
||||||
tx = self.app.wallet.make_unsigned_transaction(coins, outputs, config, None)
|
tx = self.app.wallet.make_unsigned_transaction(coins, outputs, config, None, flodata=flodata)
|
||||||
except NotEnoughFunds:
|
except NotEnoughFunds:
|
||||||
self.app.show_error(_("Not enough funds"))
|
self.app.show_error(_("Not enough funds"))
|
||||||
return
|
return
|
||||||
@ -379,7 +382,7 @@ class ReceiveScreen(CScreen):
|
|||||||
|
|
||||||
def do_share(self):
|
def do_share(self):
|
||||||
uri = self.get_URI()
|
uri = self.get_URI()
|
||||||
self.app.do_share(uri, _("Share Bitcoin Request"))
|
self.app.do_share(uri, _("Share FLO Request"))
|
||||||
|
|
||||||
def do_copy(self):
|
def do_copy(self):
|
||||||
uri = self.get_URI()
|
uri = self.get_URI()
|
||||||
|
|||||||
@ -11,6 +11,7 @@ SendScreen:
|
|||||||
address: ''
|
address: ''
|
||||||
amount: ''
|
amount: ''
|
||||||
message: ''
|
message: ''
|
||||||
|
flodata: ''
|
||||||
is_pr: False
|
is_pr: False
|
||||||
BoxLayout
|
BoxLayout
|
||||||
padding: '12dp', '12dp', '12dp', '12dp'
|
padding: '12dp', '12dp', '12dp', '12dp'
|
||||||
@ -38,6 +39,24 @@ SendScreen:
|
|||||||
CardSeparator:
|
CardSeparator:
|
||||||
opacity: int(not root.is_pr)
|
opacity: int(not root.is_pr)
|
||||||
color: blue_bottom.foreground_color
|
color: blue_bottom.foreground_color
|
||||||
|
BoxLayout:
|
||||||
|
id: flodata_selection
|
||||||
|
size_hint: 1, None
|
||||||
|
height: blue_bottom.item_height
|
||||||
|
spacing: '5dp'
|
||||||
|
Image:
|
||||||
|
source: 'atlas://electrum/gui/kivy/theming/light/pen'
|
||||||
|
size_hint: None, None
|
||||||
|
size: '22dp', '22dp'
|
||||||
|
pos_hint: {'center_y': .5}
|
||||||
|
BlueButton:
|
||||||
|
id: flodata
|
||||||
|
text: s.flodata if s.flodata else (_('No FLO data') if root.is_pr else _('FLO data'))
|
||||||
|
disabled: root.is_pr
|
||||||
|
on_release: Clock.schedule_once(lambda dt: app.flodata_dialog(s))
|
||||||
|
CardSeparator:
|
||||||
|
opacity: int(not root.is_pr)
|
||||||
|
color: blue_bottom.foreground_color
|
||||||
BoxLayout:
|
BoxLayout:
|
||||||
size_hint: 1, None
|
size_hint: 1, None
|
||||||
height: blue_bottom.item_height
|
height: blue_bottom.item_height
|
||||||
|
|||||||
@ -459,13 +459,13 @@ class HistoryList(MyTreeView, 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')) + '/BTC'), 0, 2)
|
grid.addWidget(QLabel(str(h.get('start_fiat_value')) + '/FLO'), 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')) + '/BTC'), 2, 2)
|
grid.addWidget(QLabel(str(h.get('end_fiat_value')) + '/FLO'), 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)
|
||||||
|
|||||||
@ -433,8 +433,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||||||
if self.wallet.is_watching_only():
|
if self.wallet.is_watching_only():
|
||||||
msg = ' '.join([
|
msg = ' '.join([
|
||||||
_("This wallet is watching-only."),
|
_("This wallet is watching-only."),
|
||||||
_("This means you will not be able to spend Bitcoins with it."),
|
_("This means you will not be able to spend FLO with it."),
|
||||||
_("Make sure you own the seed phrase or the private keys, before you request Bitcoins to be sent to this wallet.")
|
_("Make sure you own the seed phrase or the private keys, before you request FLO to be sent to this wallet.")
|
||||||
])
|
])
|
||||||
self.show_warning(msg, title=_('Information'))
|
self.show_warning(msg, title=_('Information'))
|
||||||
|
|
||||||
@ -596,11 +596,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||||||
def show_about(self):
|
def show_about(self):
|
||||||
QMessageBox.about(self, "Electrum",
|
QMessageBox.about(self, "Electrum",
|
||||||
(_("Version")+" %s" % ELECTRUM_VERSION + "\n\n" +
|
(_("Version")+" %s" % ELECTRUM_VERSION + "\n\n" +
|
||||||
_("Electrum's focus is speed, with low resource usage and simplifying Bitcoin.") + " " +
|
_("Electrum's focus is speed, with low resource usage and simplifying FLO.") + " " +
|
||||||
_("You do not need to perform regular backups, because your wallet can be "
|
_("You do not need to perform regular backups, because your wallet can be "
|
||||||
"recovered from a secret phrase that you can memorize or write on paper.") + " " +
|
"recovered from a secret phrase that you can memorize or write on paper.") + " " +
|
||||||
_("Startup times are instant because it operates in conjunction with high-performance "
|
_("Startup times are instant because it operates in conjunction with high-performance "
|
||||||
"servers that handle the most complicated parts of the Bitcoin system.") + "\n\n" +
|
"servers that handle the most complicated parts of the FLO system.") + "\n\n" +
|
||||||
_("Uses icons from the Icons8 icon pack (icons8.com).")))
|
_("Uses icons from the Icons8 icon pack (icons8.com).")))
|
||||||
|
|
||||||
def show_report_bug(self):
|
def show_report_bug(self):
|
||||||
@ -835,7 +835,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||||||
self.receive_address_e = ButtonsLineEdit()
|
self.receive_address_e = ButtonsLineEdit()
|
||||||
self.receive_address_e.addCopyButton(self.app)
|
self.receive_address_e.addCopyButton(self.app)
|
||||||
self.receive_address_e.setReadOnly(True)
|
self.receive_address_e.setReadOnly(True)
|
||||||
msg = _('Bitcoin address where the payment should be received. Note that each payment request uses a different Bitcoin address.')
|
msg = _('FLO address where the payment should be received. Note that each payment request uses a different Bitcoin address.')
|
||||||
self.receive_address_label = HelpLabel(_('Receiving address'), msg)
|
self.receive_address_label = HelpLabel(_('Receiving address'), msg)
|
||||||
self.receive_address_e.textChanged.connect(self.update_receive_qr)
|
self.receive_address_e.textChanged.connect(self.update_receive_qr)
|
||||||
self.receive_address_e.setFocusPolicy(Qt.ClickFocus)
|
self.receive_address_e.setFocusPolicy(Qt.ClickFocus)
|
||||||
@ -865,8 +865,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||||||
msg = ' '.join([
|
msg = ' '.join([
|
||||||
_('Expiration date of your request.'),
|
_('Expiration date of your request.'),
|
||||||
_('This information is seen by the recipient if you send them a signed payment request.'),
|
_('This information is seen by the recipient if you send them a signed payment request.'),
|
||||||
_('Expired requests have to be deleted manually from your list, in order to free the corresponding Bitcoin addresses.'),
|
_('Expired requests have to be deleted manually from your list, in order to free the corresponding FLO addresses.'),
|
||||||
_('The bitcoin address never expires and will always be part of this electrum wallet.'),
|
_('The FLO address never expires and will always be part of this electrum wallet.'),
|
||||||
])
|
])
|
||||||
grid.addWidget(HelpLabel(_('Request expires'), msg), 3, 0)
|
grid.addWidget(HelpLabel(_('Request expires'), msg), 3, 0)
|
||||||
grid.addWidget(self.expires_combo, 3, 1)
|
grid.addWidget(self.expires_combo, 3, 1)
|
||||||
@ -1096,7 +1096,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||||||
self.amount_e = BTCAmountEdit(self.get_decimal_point)
|
self.amount_e = BTCAmountEdit(self.get_decimal_point)
|
||||||
self.payto_e = PayToEdit(self)
|
self.payto_e = PayToEdit(self)
|
||||||
msg = _('Recipient of the funds.') + '\n\n'\
|
msg = _('Recipient of the funds.') + '\n\n'\
|
||||||
+ _('You may enter a Bitcoin address, a label from your list of contacts (a list of completions will be proposed), or an alias (email-like address that forwards to a Bitcoin address)')
|
+ _('You may enter a FLO address, a label from your list of contacts (a list of completions will be proposed), or an alias (email-like address that forwards to a Bitcoin address)')
|
||||||
payto_label = HelpLabel(_('Pay to'), msg)
|
payto_label = HelpLabel(_('Pay to'), msg)
|
||||||
grid.addWidget(payto_label, 1, 0)
|
grid.addWidget(payto_label, 1, 0)
|
||||||
grid.addWidget(self.payto_e, 1, 1, 1, -1)
|
grid.addWidget(self.payto_e, 1, 1, 1, -1)
|
||||||
@ -1113,10 +1113,16 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||||||
self.message_e = MyLineEdit()
|
self.message_e = MyLineEdit()
|
||||||
grid.addWidget(self.message_e, 2, 1, 1, -1)
|
grid.addWidget(self.message_e, 2, 1, 1, -1)
|
||||||
|
|
||||||
|
msg = _('This is where you write the FLO Data for the transaction')
|
||||||
|
flodata_label = HelpLabel(_('FLO Data'), msg)
|
||||||
|
grid.addWidget(flodata_label, 3, 0)
|
||||||
|
self.message_tx_e = MyLineEdit()
|
||||||
|
grid.addWidget(self.message_tx_e, 3, 1, 1, -1)
|
||||||
|
|
||||||
self.from_label = QLabel(_('From'))
|
self.from_label = QLabel(_('From'))
|
||||||
grid.addWidget(self.from_label, 3, 0)
|
grid.addWidget(self.from_label, 4, 0)
|
||||||
self.from_list = FromList(self, self.from_list_menu)
|
self.from_list = FromList(self, self.from_list_menu)
|
||||||
grid.addWidget(self.from_list, 3, 1, 1, -1)
|
grid.addWidget(self.from_list, 4, 1, 1, -1)
|
||||||
self.set_pay_from([])
|
self.set_pay_from([])
|
||||||
|
|
||||||
msg = _('Amount to be sent.') + '\n\n' \
|
msg = _('Amount to be sent.') + '\n\n' \
|
||||||
@ -1124,24 +1130,24 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||||||
+ _('Note that if you have frozen some of your addresses, the available funds will be lower than your total balance.') + '\n\n' \
|
+ _('Note that if you have frozen some of your addresses, the available funds will be lower than your total balance.') + '\n\n' \
|
||||||
+ _('Keyboard shortcut: type "!" to send all your coins.')
|
+ _('Keyboard shortcut: type "!" to send all your coins.')
|
||||||
amount_label = HelpLabel(_('Amount'), msg)
|
amount_label = HelpLabel(_('Amount'), msg)
|
||||||
grid.addWidget(amount_label, 4, 0)
|
grid.addWidget(amount_label, 5, 0)
|
||||||
grid.addWidget(self.amount_e, 4, 1)
|
grid.addWidget(self.amount_e, 5, 1)
|
||||||
|
|
||||||
self.fiat_send_e = AmountEdit(self.fx.get_currency if self.fx else '')
|
self.fiat_send_e = AmountEdit(self.fx.get_currency if self.fx else '')
|
||||||
if not self.fx or not self.fx.is_enabled():
|
if not self.fx or not self.fx.is_enabled():
|
||||||
self.fiat_send_e.setVisible(False)
|
self.fiat_send_e.setVisible(False)
|
||||||
grid.addWidget(self.fiat_send_e, 4, 2)
|
grid.addWidget(self.fiat_send_e, 5, 2)
|
||||||
self.amount_e.frozen.connect(
|
self.amount_e.frozen.connect(
|
||||||
lambda: self.fiat_send_e.setFrozen(self.amount_e.isReadOnly()))
|
lambda: self.fiat_send_e.setFrozen(self.amount_e.isReadOnly()))
|
||||||
|
|
||||||
self.max_button = EnterButton(_("Max"), self.spend_max)
|
self.max_button = EnterButton(_("Max"), self.spend_max)
|
||||||
self.max_button.setFixedWidth(140)
|
self.max_button.setFixedWidth(140)
|
||||||
grid.addWidget(self.max_button, 4, 3)
|
grid.addWidget(self.max_button, 5, 3)
|
||||||
hbox = QHBoxLayout()
|
hbox = QHBoxLayout()
|
||||||
hbox.addStretch(1)
|
hbox.addStretch(1)
|
||||||
grid.addLayout(hbox, 4, 4)
|
grid.addLayout(hbox, 5, 4)
|
||||||
|
|
||||||
msg = _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
|
msg = _('FLO 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)
|
||||||
@ -1221,7 +1227,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||||||
vbox_feelabel = QVBoxLayout()
|
vbox_feelabel = QVBoxLayout()
|
||||||
vbox_feelabel.addWidget(self.fee_e_label)
|
vbox_feelabel.addWidget(self.fee_e_label)
|
||||||
vbox_feelabel.addStretch(1)
|
vbox_feelabel.addStretch(1)
|
||||||
grid.addLayout(vbox_feelabel, 5, 0)
|
grid.addLayout(vbox_feelabel, 6, 0)
|
||||||
|
|
||||||
self.fee_adv_controls = QWidget()
|
self.fee_adv_controls = QWidget()
|
||||||
hbox = QHBoxLayout(self.fee_adv_controls)
|
hbox = QHBoxLayout(self.fee_adv_controls)
|
||||||
@ -1236,7 +1242,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||||||
vbox_feecontrol.addWidget(self.fee_adv_controls)
|
vbox_feecontrol.addWidget(self.fee_adv_controls)
|
||||||
vbox_feecontrol.addWidget(self.fee_slider)
|
vbox_feecontrol.addWidget(self.fee_slider)
|
||||||
|
|
||||||
grid.addLayout(vbox_feecontrol, 5, 1, 1, -1)
|
grid.addLayout(vbox_feecontrol, 6, 1, 1, -1)
|
||||||
|
|
||||||
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)
|
||||||
@ -1250,7 +1256,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, 6, 1, 1, 3)
|
grid.addLayout(buttons, 7, 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)
|
||||||
@ -1508,6 +1514,7 @@ 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()
|
||||||
|
flodata = self.message_tx_e.text()
|
||||||
|
|
||||||
if self.payment_request:
|
if self.payment_request:
|
||||||
outputs = self.payment_request.get_outputs()
|
outputs = self.payment_request.get_outputs()
|
||||||
@ -1532,7 +1539,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||||||
|
|
||||||
for o in outputs:
|
for o in outputs:
|
||||||
if o.address is None:
|
if o.address is None:
|
||||||
self.show_error(_('Bitcoin Address is None'))
|
self.show_error(_('FLO Address is None'))
|
||||||
return
|
return
|
||||||
if o.type == TYPE_ADDRESS and not bitcoin.is_address(o.address):
|
if o.type == TYPE_ADDRESS and not bitcoin.is_address(o.address):
|
||||||
self.show_error(_('Invalid Bitcoin Address'))
|
self.show_error(_('Invalid Bitcoin Address'))
|
||||||
@ -1543,7 +1550,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
|
return outputs, fee_estimator, label, coins, flodata
|
||||||
|
|
||||||
def do_preview(self):
|
def do_preview(self):
|
||||||
self.do_send(preview = True)
|
self.do_send(preview = True)
|
||||||
@ -1554,12 +1561,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 = r
|
outputs, fee_estimator, tx_desc, coins, flodata = 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)
|
is_sweep=is_sweep, flodata=flodata)
|
||||||
except (NotEnoughFunds, NoDynamicFeeEstimates) as e:
|
except (NotEnoughFunds, NoDynamicFeeEstimates) as e:
|
||||||
self.show_message(str(e))
|
self.show_message(str(e))
|
||||||
return
|
return
|
||||||
@ -1792,7 +1799,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
|
|||||||
self.not_enough_funds = False
|
self.not_enough_funds = False
|
||||||
self.payment_request = None
|
self.payment_request = None
|
||||||
self.payto_e.is_pr = False
|
self.payto_e.is_pr = False
|
||||||
for e in [self.payto_e, self.message_e, self.amount_e, self.fiat_send_e,
|
for e in [self.payto_e, self.message_e, self.message_tx_e, self.amount_e, self.fiat_send_e,
|
||||||
self.fee_e, self.feerate_e]:
|
self.fee_e, self.feerate_e]:
|
||||||
e.setText('')
|
e.setText('')
|
||||||
e.setFrozen(False)
|
e.setFrozen(False)
|
||||||
|
|||||||
@ -114,6 +114,8 @@ 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.flodata_label = QLabel()
|
||||||
|
vbox.addWidget(self.flodata_label)
|
||||||
|
|
||||||
self.add_io(vbox)
|
self.add_io(vbox)
|
||||||
|
|
||||||
@ -271,6 +273,7 @@ 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.flodata_label.setText("FLO data: " + self.tx.flodata)
|
||||||
run_hook('transaction_dialog_update', self)
|
run_hook('transaction_dialog_update', self)
|
||||||
|
|
||||||
def add_io(self, vbox):
|
def add_io(self, vbox):
|
||||||
|
|||||||
@ -337,7 +337,7 @@ class ElectrumGui:
|
|||||||
|
|
||||||
def do_send(self):
|
def do_send(self):
|
||||||
if not is_address(self.str_recipient):
|
if not is_address(self.str_recipient):
|
||||||
self.show_message(_('Invalid Bitcoin address'))
|
self.show_message(_('Invalid FLO address'))
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
amount = int(Decimal(self.str_amount) * COIN)
|
amount = int(Decimal(self.str_amount) * COIN)
|
||||||
|
|||||||
@ -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('BTC')
|
plt.ylabel('FLO')
|
||||||
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)
|
||||||
|
|||||||
@ -490,9 +490,9 @@ class DeviceMgr(ThreadJob, PrintError):
|
|||||||
# or it is not pairable
|
# or it is not pairable
|
||||||
raise DeviceUnpairableError(
|
raise DeviceUnpairableError(
|
||||||
_('Electrum cannot pair with your {}.\n\n'
|
_('Electrum cannot pair with your {}.\n\n'
|
||||||
'Before you request bitcoins to be sent to addresses in this '
|
'Before you request FLO to be sent to addresses in this '
|
||||||
'wallet, ensure you can pair with your device, or that you have '
|
'wallet, ensure you can pair with your device, or that you have '
|
||||||
'its seed (and passphrase, if any). Otherwise all bitcoins you '
|
'its seed (and passphrase, if any). Otherwise all FLO you '
|
||||||
'receive will be unspendable.').format(plugin.device))
|
'receive will be unspendable.').format(plugin.device))
|
||||||
|
|
||||||
def unpaired_device_infos(self, handler, plugin: 'HW_PluginBase', devices=None):
|
def unpaired_device_infos(self, handler, plugin: 'HW_PluginBase', devices=None):
|
||||||
|
|||||||
@ -1,409 +1,5 @@
|
|||||||
{
|
{
|
||||||
"3smoooajg7qqac2y.onion": {
|
"localhost": {
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"81-7-10-251.blue.kundencontroller.de": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"E-X.not.fyi": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"MEADS.hopto.org": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"VPS.hsmiths.com": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"b.ooze.cc": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"bauerjda5hnedjam.onion": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"bauerjhejlv6di7s.onion": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"bitcoin.corgi.party": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"bitcoin3nqy3db7c.onion": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"bitcoins.sk": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"btc.cihar.com": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"btc.smsys.me": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "995",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"btc.xskyx.net": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"cashyes.zapto.org": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"currentlane.lovebitco.in": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"daedalus.bauerj.eu": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"dedi.jochen-hoenicke.de": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"dragon085.startdedicated.de": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"e-1.claudioboxx.com": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"e.keff.org": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"elec.luggs.co": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "443",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"electrum-server.ninja": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"electrum-unlimited.criptolayer.net": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"electrum.eff.ro": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"electrum.festivaldelhumor.org": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"electrum.hsmiths.com": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"electrum.leblancnet.us": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"electrum.mindspot.org": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"electrum.qtornado.com": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"electrum.taborsky.cz": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"electrum.villocq.com": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"electrum2.eff.ro": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"electrum2.villocq.com": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"electrum3.hachre.de": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"electrumx.bot.nu": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"electrumx.ddns.net": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"electrumx.ftp.sh": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"electrumx.ml": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"electrumx.nmdps.net": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"electrumx.soon.it": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"electrumxhqdsmlu.onion": {
|
|
||||||
"pruning": "-",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"elx01.knas.systems": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"enode.duckdns.org": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"erbium1.sytes.net": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"fedaykin.goip.de": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"fn.48.org": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50003",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"helicarrier.bauerj.eu": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"hsmiths4fyqlw5xw.onion": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"hsmiths5mjk6uijs.onion": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"icarus.tetradrachm.net": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"kirsche.emzy.de": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"luggscoqbymhvnkp.onion": {
|
|
||||||
"pruning": "-",
|
|
||||||
"t": "80",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"ndnd.selfhost.eu": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"ndndword5lpb7eex.onion": {
|
|
||||||
"pruning": "-",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"oneweek.duckdns.org": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"orannis.com": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"ozahtqwp25chjdjd.onion": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"qtornadoklbgdyww.onion": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"rbx.curalle.ovh": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"s7clinmo4cazmhul.onion": {
|
|
||||||
"pruning": "-",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"tardis.bauerj.eu": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"technetium.network": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"tomscryptos.com": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"ulrichard.ch": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"us.electrum.be": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"vmd27610.contaboserver.net": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"vmd30612.contaboserver.net": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"wsw6tua3xl24gsmi264zaep6seppjyrkyucpsmuxnjzyt3f3j6swshad.onion": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"t": "50001",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"xray587.startdedicated.de": {
|
|
||||||
"pruning": "-",
|
|
||||||
"s": "50002",
|
|
||||||
"version": "1.4"
|
|
||||||
},
|
|
||||||
"yuio.top": {
|
|
||||||
"pruning": "-",
|
"pruning": "-",
|
||||||
"s": "50002",
|
"s": "50002",
|
||||||
"t": "50001",
|
"t": "50001",
|
||||||
|
|||||||
@ -202,7 +202,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_ = {'btc':8, 'mbtc':5, 'ubtc':2, 'bits':2, 'sat':0}
|
map_ = {'flo':8, 'mflo':5, 'uflo':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)
|
||||||
|
|
||||||
|
|||||||
@ -27,6 +27,8 @@
|
|||||||
|
|
||||||
# Note: The deserialization code originally comes from ABE.
|
# Note: The deserialization code originally comes from ABE.
|
||||||
|
|
||||||
|
|
||||||
|
import codecs
|
||||||
import struct
|
import struct
|
||||||
import traceback
|
import traceback
|
||||||
import sys
|
import sys
|
||||||
@ -642,6 +644,10 @@ def deserialize(raw: str, force_full_parse=False) -> dict:
|
|||||||
txin = d['inputs'][i]
|
txin = d['inputs'][i]
|
||||||
parse_witness(vds, txin, full_parse=full_parse)
|
parse_witness(vds, txin, full_parse=full_parse)
|
||||||
d['lockTime'] = vds.read_uint32()
|
d['lockTime'] = vds.read_uint32()
|
||||||
|
if d['version'] >= 2:
|
||||||
|
d['flodata'] = vds.read_string()
|
||||||
|
else:
|
||||||
|
d['flodata'] = ""
|
||||||
if vds.can_read_more():
|
if vds.can_read_more():
|
||||||
raise SerializationError('extra junk at the end')
|
raise SerializationError('extra junk at the end')
|
||||||
return d
|
return d
|
||||||
@ -679,6 +685,7 @@ class Transaction:
|
|||||||
self._inputs = None
|
self._inputs = None
|
||||||
self._outputs = None # type: List[TxOutput]
|
self._outputs = None # type: List[TxOutput]
|
||||||
self.locktime = 0
|
self.locktime = 0
|
||||||
|
self.flodata = ""
|
||||||
self.version = 1
|
self.version = 1
|
||||||
# by default we assume this is a partial txn;
|
# by default we assume this is a partial txn;
|
||||||
# this value will get properly set when deserializing
|
# this value will get properly set when deserializing
|
||||||
@ -782,16 +789,18 @@ class Transaction:
|
|||||||
self._outputs = [TxOutput(x['type'], x['address'], x['value']) for x in d['outputs']]
|
self._outputs = [TxOutput(x['type'], x['address'], x['value']) for x in d['outputs']]
|
||||||
self.locktime = d['lockTime']
|
self.locktime = d['lockTime']
|
||||||
self.version = d['version']
|
self.version = d['version']
|
||||||
|
self.flodata = d['flodata']
|
||||||
self.is_partial_originally = d['partial']
|
self.is_partial_originally = d['partial']
|
||||||
self._segwit_ser = d['segwit_ser']
|
self._segwit_ser = d['segwit_ser']
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_io(klass, inputs, outputs, locktime=0):
|
def from_io(klass, inputs, outputs, locktime=0, flodata=""):
|
||||||
self = klass(None)
|
self = klass(None)
|
||||||
self._inputs = inputs
|
self._inputs = inputs
|
||||||
self._outputs = outputs
|
self._outputs = outputs
|
||||||
self.locktime = locktime
|
self.locktime = locktime
|
||||||
|
self.flodata = flodata
|
||||||
self.BIP69_sort()
|
self.BIP69_sort()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@ -1042,6 +1051,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)
|
||||||
|
nFloData = var_int(len(self.flodata)) + str(codecs.encode(bytes(self.flodata, 'utf-8'), 'hex_codec'),
|
||||||
|
'utf-8')
|
||||||
inputs = self.inputs()
|
inputs = self.inputs()
|
||||||
outputs = self.outputs()
|
outputs = self.outputs()
|
||||||
txin = inputs[i]
|
txin = inputs[i]
|
||||||
@ -1055,10 +1066,16 @@ class Transaction:
|
|||||||
scriptCode = var_int(len(preimage_script) // 2) + preimage_script
|
scriptCode = var_int(len(preimage_script) // 2) + preimage_script
|
||||||
amount = int_to_hex(txin['value'], 8)
|
amount = int_to_hex(txin['value'], 8)
|
||||||
nSequence = int_to_hex(txin.get('sequence', 0xffffffff - 1), 4)
|
nSequence = int_to_hex(txin.get('sequence', 0xffffffff - 1), 4)
|
||||||
|
if self.version >= 2:
|
||||||
|
preimage = nVersion + hashPrevouts + hashSequence + outpoint + scriptCode + amount + nSequence + hashOutputs + nLocktime + nFloData + nHashType
|
||||||
|
else:
|
||||||
preimage = nVersion + hashPrevouts + hashSequence + outpoint + scriptCode + amount + nSequence + hashOutputs + nLocktime + nHashType
|
preimage = nVersion + hashPrevouts + hashSequence + outpoint + scriptCode + amount + nSequence + hashOutputs + nLocktime + nHashType
|
||||||
else:
|
else:
|
||||||
txins = var_int(len(inputs)) + ''.join(self.serialize_input(txin, self.get_preimage_script(txin) if i==k else '') for k, txin in enumerate(inputs))
|
txins = var_int(len(inputs)) + ''.join(self.serialize_input(txin, self.get_preimage_script(txin) if i==k else '') for k, txin in enumerate(inputs))
|
||||||
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:
|
||||||
|
preimage = nVersion + txins + txouts + nLocktime + nFloData + nHashType
|
||||||
|
else:
|
||||||
preimage = nVersion + txins + txouts + nLocktime + nHashType
|
preimage = nVersion + txins + txouts + nLocktime + nHashType
|
||||||
return preimage
|
return preimage
|
||||||
|
|
||||||
@ -1081,6 +1098,8 @@ class Transaction:
|
|||||||
self.deserialize()
|
self.deserialize()
|
||||||
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)
|
||||||
|
nFloData = var_int(len(self.flodata)) + str(codecs.encode(bytes(self.flodata, '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)
|
||||||
@ -1093,7 +1112,13 @@ class Transaction:
|
|||||||
marker = '00'
|
marker = '00'
|
||||||
flag = '01'
|
flag = '01'
|
||||||
witness = ''.join(self.serialize_witness(x, estimate_size) for x in inputs)
|
witness = ''.join(self.serialize_witness(x, estimate_size) for x in inputs)
|
||||||
|
if self.version >= 2:
|
||||||
|
return nVersion + marker + flag + txins + txouts + witness + nLocktime + nFloData
|
||||||
|
else:
|
||||||
return nVersion + marker + flag + txins + txouts + witness + nLocktime
|
return nVersion + marker + flag + txins + txouts + witness + nLocktime
|
||||||
|
else:
|
||||||
|
if self.version >= 2:
|
||||||
|
return nVersion + txins + txouts + nLocktime + nFloData
|
||||||
else:
|
else:
|
||||||
return nVersion + txins + txouts + nLocktime
|
return nVersion + txins + txouts + nLocktime
|
||||||
|
|
||||||
|
|||||||
@ -62,11 +62,11 @@ def inv_dict(d):
|
|||||||
ca_path = certifi.where()
|
ca_path = certifi.where()
|
||||||
|
|
||||||
|
|
||||||
base_units = {'BTC':8, 'mBTC':5, 'bits':2, 'sat':0}
|
base_units = {'FLO':8, 'mFLO':5, 'bits':2, 'sat':0}
|
||||||
base_units_inverse = inv_dict(base_units)
|
base_units_inverse = inv_dict(base_units)
|
||||||
base_units_list = ['BTC', 'mBTC', 'bits', 'sat'] # list(dict) does not guarantee order
|
base_units_list = ['FLO', 'mFLO', 'bits', 'sat'] # list(dict) does not guarantee order
|
||||||
|
|
||||||
DECIMAL_POINT_DEFAULT = 5 # mBTC
|
DECIMAL_POINT_DEFAULT = 5 # mFLO
|
||||||
|
|
||||||
|
|
||||||
class UnknownBaseUnit(Exception): pass
|
class UnknownBaseUnit(Exception): pass
|
||||||
@ -149,7 +149,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) + " BTC"
|
return format_satoshis(self.value) + " FLO"
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return self.value == other.value
|
return self.value == other.value
|
||||||
@ -715,12 +715,12 @@ def parse_URI(uri: str, on_pr: Callable=None) -> dict:
|
|||||||
|
|
||||||
if ':' not in uri:
|
if ':' not in uri:
|
||||||
if not bitcoin.is_address(uri):
|
if not bitcoin.is_address(uri):
|
||||||
raise Exception("Not a bitcoin address")
|
raise Exception("Not a FLO address")
|
||||||
return {'address': uri}
|
return {'address': uri}
|
||||||
|
|
||||||
u = urllib.parse.urlparse(uri)
|
u = urllib.parse.urlparse(uri)
|
||||||
if u.scheme != 'bitcoin':
|
if u.scheme != 'bitcoin':
|
||||||
raise Exception("Not a bitcoin URI")
|
raise Exception("Not a FLO URI")
|
||||||
address = u.path
|
address = u.path
|
||||||
|
|
||||||
# python for android fails to parse query
|
# python for android fails to parse query
|
||||||
@ -737,7 +737,7 @@ def parse_URI(uri: str, on_pr: Callable=None) -> dict:
|
|||||||
out = {k: v[0] for k, v in pq.items()}
|
out = {k: v[0] for k, v in pq.items()}
|
||||||
if address:
|
if address:
|
||||||
if not bitcoin.is_address(address):
|
if not bitcoin.is_address(address):
|
||||||
raise Exception("Invalid bitcoin address:" + address)
|
raise Exception("Invalid FLO address:" + address)
|
||||||
out['address'] = address
|
out['address'] = address
|
||||||
if 'amount' in out:
|
if 'amount' in out:
|
||||||
am = out['amount']
|
am = out['amount']
|
||||||
@ -786,7 +786,7 @@ def create_URI(addr, amount, message):
|
|||||||
query.append('amount=%s'%format_satoshis_plain(amount))
|
query.append('amount=%s'%format_satoshis_plain(amount))
|
||||||
if message:
|
if message:
|
||||||
query.append('message=%s'%urllib.parse.quote(message))
|
query.append('message=%s'%urllib.parse.quote(message))
|
||||||
p = urllib.parse.ParseResult(scheme='bitcoin', netloc='', path=addr, params='', query='&'.join(query), fragment='')
|
p = urllib.parse.ParseResult(scheme='flo', netloc='', path=addr, params='', query='&'.join(query), fragment='')
|
||||||
return urllib.parse.urlunparse(p)
|
return urllib.parse.urlunparse(p)
|
||||||
|
|
||||||
|
|
||||||
@ -917,6 +917,7 @@ class TxMinedInfo(NamedTuple):
|
|||||||
timestamp: Optional[int] = None # timestamp of block that mined tx
|
timestamp: Optional[int] = None # timestamp of block that mined tx
|
||||||
txpos: Optional[int] = None # position of tx in serialized block
|
txpos: Optional[int] = None # position of tx in serialized block
|
||||||
header_hash: Optional[str] = None # hash of block that mined tx
|
header_hash: Optional[str] = None # hash of block that mined tx
|
||||||
|
flodata: Optional[str] = None # flodata of the tx
|
||||||
|
|
||||||
|
|
||||||
def make_aiohttp_session(proxy: dict, headers=None, timeout=None):
|
def make_aiohttp_session(proxy: dict, headers=None, timeout=None):
|
||||||
|
|||||||
@ -124,10 +124,12 @@ class SPV(NetworkJobOnDefaultServer):
|
|||||||
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)
|
||||||
|
flodata = self.wallet.get_flodata(tx_hash)
|
||||||
tx_info = TxMinedInfo(height=tx_height,
|
tx_info = TxMinedInfo(height=tx_height,
|
||||||
timestamp=header.get('timestamp'),
|
timestamp=header.get('timestamp'),
|
||||||
txpos=pos,
|
txpos=pos,
|
||||||
header_hash=header_hash)
|
header_hash=header_hash,
|
||||||
|
flodata=flodata)
|
||||||
self.wallet.add_verified_tx(tx_hash, tx_info)
|
self.wallet.add_verified_tx(tx_hash, tx_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)
|
||||||
|
|||||||
@ -603,7 +603,7 @@ class Abstract_Wallet(AddressSynchronizer):
|
|||||||
return candidate
|
return candidate
|
||||||
|
|
||||||
def make_unsigned_transaction(self, coins, outputs, config, fixed_fee=None,
|
def make_unsigned_transaction(self, coins, outputs, config, fixed_fee=None,
|
||||||
change_addr=None, is_sweep=False):
|
change_addr=None, is_sweep=False, flodata=None):
|
||||||
# check outputs
|
# check outputs
|
||||||
i_max = None
|
i_max = None
|
||||||
for i, o in enumerate(outputs):
|
for i, o in enumerate(outputs):
|
||||||
@ -621,6 +621,9 @@ class Abstract_Wallet(AddressSynchronizer):
|
|||||||
for item in coins:
|
for item in coins:
|
||||||
self.add_input_info(item)
|
self.add_input_info(item)
|
||||||
|
|
||||||
|
if flodata is None:
|
||||||
|
flodata=''
|
||||||
|
|
||||||
# change address
|
# change address
|
||||||
# if we leave it empty, coin_chooser will set it
|
# if we leave it empty, coin_chooser will set it
|
||||||
change_addrs = []
|
change_addrs = []
|
||||||
@ -693,6 +696,10 @@ class Abstract_Wallet(AddressSynchronizer):
|
|||||||
|
|
||||||
# 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 flodata != "":
|
||||||
|
tx.version = 2
|
||||||
|
tx.flodata = "text:" + flodata
|
||||||
run_hook('make_unsigned_transaction', self, tx)
|
run_hook('make_unsigned_transaction', self, tx)
|
||||||
return tx
|
return tx
|
||||||
|
|
||||||
@ -962,7 +969,7 @@ class Abstract_Wallet(AddressSynchronizer):
|
|||||||
if not r:
|
if not r:
|
||||||
return
|
return
|
||||||
out = copy.copy(r)
|
out = copy.copy(r)
|
||||||
out['URI'] = 'bitcoin:' + addr + '?amount=' + format_satoshis(out.get('amount'))
|
out['URI'] = 'flo:' + addr + '?amount=' + format_satoshis(out.get('amount'))
|
||||||
status, conf = self.get_request_status(addr)
|
status, conf = self.get_request_status(addr)
|
||||||
out['status'] = status
|
out['status'] = status
|
||||||
if conf is not None:
|
if conf is not None:
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 144 KiB |