Compare commits

...

27 Commits

Author SHA1 Message Date
Vivek Teega
3f628b70a5 Merging upstrem changes 2018-12-16 17:44:11 +05:30
Vivek Teega
64733e93d9 FLO URI on the QT wallet 2018-11-29 12:50:15 +05:30
Vivek Teega
dd6e5ae9ac Changing icons from BTC -> FLO 2018-11-22 17:15:58 +05:30
Vivek Teega
34e84858c8 Changed naming from BTC -> FLO 2018-11-22 11:03:15 +05:30
Vivek Teega
ecea61d9ca Fixed display of saved transactions 2018-11-21 21:15:28 +05:30
Vivek Teega
b3a21f53bc Chunk verification without saving headers locally 2018-11-08 21:07:26 +05:30
Vivek Teega
085cd9919e Target calculation & chunk verification
This commit adds the code for FLO chunk verification. FLO chunk verification is different from Bitcoin chunk verification as the blockchain has 3 different target recalculation times. ie. 90 blocks at height 0->208440, 15 blocks at height 208440->426000 & 1 block further down

Changes have also been maded such that chunk doesnt need to be saved in parts
2018-10-21 07:42:07 +05:30
Vivek Teega
d418976f5e GUI changes on KIVY 2018-10-18 00:19:37 +05:30
Vivek Teega
91273f7114 Adding FLO data support
1. All the GUI changes for the QT version of the wallet
2. Saving flodata locally in the wallet
2018-09-29 23:09:21 +05:30
Vivek Teega
4b1b6e3adf Renaming everything from FloData to txcomment 2018-09-29 18:20:06 +05:30
Vivek Teega
6a2d2fbc3d 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
2018-09-29 17:52:30 +05:30
Vivek Teega
826a56311c
Merge pull request #7 from spesmilo/master
Pulling upstream changes
2018-09-28 18:02:50 +05:30
Vivek Teega
2ce11fa83b
Merge pull request #6 from ranchimall/updateFork
Pulling upstream data
2018-09-23 22:04:46 +05:30
Vivek Teega
4253dd27eb Fixing upstream merge conflicts 2018-09-23 22:02:05 +05:30
Vivek Teega
f2e93e323a Fixing target calculation after changes from the upstream 2018-09-12 06:39:04 +05:30
Vivek Teega
9e0777a7b4 Solved upstream merge conflicts 2018-09-12 03:38:43 +05:30
Vivek Teega
278a6bcb4d
Merge pull request #3 from ranchimall/txcomment
Updating further changes to master branch
2018-09-11 00:14:45 +05:30
Vivek Teega
66777d4566 FLO data box moved up 2018-09-10 11:27:27 +05:30
Vivek Teega
d8d2f3329b Save txcomments/floData locally 2018-09-10 11:12:05 +05:30
Vivek Teega
1e6edd0abc Fixing exchanges and block explorer for FLO 2018-08-18 11:39:26 +05:30
Vivek Teega
c0daef2657 More changes to for save_chunk()
save_chunk() required more changes to save the block header chunks properly locally.
2018-08-14 15:10:19 +05:30
Vivek Teegalapally
a64228ba2c Small change to fix save_chunk_part() 2018-08-05 23:21:46 +05:30
Vivek Teegalapally
85bae50e62 Changes for target calculation
All the changes required for target calculation, verification and saving parts of chunks for FLO
@akhil2015

Co-authored-by: Akhil Bharti <akhil2015@users.noreply.github.com>
2018-08-05 23:18:30 +05:30
Vivek Teegalapally
ca30705c69 Commiting comment/floData changes
The changes made in this commit make sure the transaction comments or floData are being sent out of the wallet.
It still has some errors left though, related to verifying SegWit address.
2018-08-05 21:12:18 +05:30
Vivek Teegalapally
95b7aef579 Changing data directories to reflect FLO 2018-08-05 16:18:53 +05:30
Bitspill
e0d33279de Commiting changes made by @bitspill
The following are the changes made by @bitspill to the original repo.
1. Changes to transaction.py to accomodate the txcomment/floData feature
2. Changes in server list
3. Renaming of BTC to FLO
The GUI and code changes required for exchange_rate.py file haven't been implemented. Its an independant module of the wallet, so will be changed later on
2018-08-05 16:10:36 +05:30
Vivek Teega
f008f2e4dc
Merge pull request #2 from spesmilo/master
Merging upstream commits
2018-08-05 14:42:45 +05:30
27 changed files with 378 additions and 4239 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 811 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

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

View File

@ -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)
try: difficulty_interval = self.DifficultyAdjustmentInterval(current_header)
expected_header_hash = self.get_hash(height) if current_header < 426000:
except MissingHeader: target = self.get_target(current_header - 1, headerLast, headerFirst)
expected_header_hash = None try:
raw_header = data[i*HEADER_SIZE : (i+1)*HEADER_SIZE] expected_header_hash = self.get_hash(current_header)
header = deserialize_header(raw_header, index*2016 + i) except MissingHeader:
self.verify_header(header, prev_hash, target, expected_header_hash) expected_header_hash = None
prev_hash = hash_header(header) 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
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:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -37,7 +37,7 @@ def plot_history(history):
plt.subplots_adjust(bottom=0.2) plt.subplots_adjust(bottom=0.2)
plt.xticks( rotation=25 ) plt.xticks( rotation=25 )
ax = plt.gca() ax = plt.gca()
plt.ylabel('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)

View File

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

View File

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

View File

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

View File

@ -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,11 +1066,17 @@ 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)
preimage = nVersion + hashPrevouts + hashSequence + outpoint + scriptCode + amount + nSequence + hashOutputs + nLocktime + nHashType 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
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)
preimage = nVersion + txins + txouts + nLocktime + nHashType if self.version >= 2:
preimage = nVersion + txins + txouts + nLocktime + nFloData + nHashType
else:
preimage = nVersion + txins + txouts + nLocktime + nHashType
return preimage return preimage
def is_segwit(self, guess_for_address=False): def is_segwit(self, guess_for_address=False):
@ -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,9 +1112,15 @@ 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)
return nVersion + marker + flag + txins + txouts + witness + nLocktime if self.version >= 2:
return nVersion + marker + flag + txins + txouts + witness + nLocktime + nFloData
else:
return nVersion + marker + flag + txins + txouts + witness + nLocktime
else: else:
return nVersion + txins + txouts + nLocktime if self.version >= 2:
return nVersion + txins + txouts + nLocktime + nFloData
else:
return nVersion + txins + txouts + nLocktime
def txid(self): def txid(self):
self.deserialize() self.deserialize()

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 144 KiB