Disentangle plugins and window management; use Qt signals
This commit is contained in:
@@ -177,6 +177,8 @@ class Wallet_2fa(Multisig_Wallet):
|
||||
self.wallet_type = '2fa'
|
||||
self.m = 2
|
||||
self.n = 3
|
||||
self.is_billing = False
|
||||
self.billing_info = None
|
||||
|
||||
def get_action(self):
|
||||
xpub1 = self.master_public_keys.get("x1/")
|
||||
@@ -194,30 +196,69 @@ class Wallet_2fa(Multisig_Wallet):
|
||||
def make_seed(self):
|
||||
return Mnemonic('english').make_seed(num_bits=256, prefix=SEED_PREFIX)
|
||||
|
||||
def can_sign_without_server(self):
|
||||
return self.master_private_keys.get('x2/') is not None
|
||||
|
||||
def extra_fee(self, tx):
|
||||
if self.can_sign_without_server():
|
||||
return 0
|
||||
if self.billing_info.get('tx_remaining'):
|
||||
return 0
|
||||
if self.is_billing:
|
||||
return 0
|
||||
# trustedcoin won't charge if the total inputs is lower than their fee
|
||||
price = int(self.price_per_tx.get(1))
|
||||
assert price <= 100000
|
||||
if tx.input_value() < price:
|
||||
self.print_error("not charging for this tx")
|
||||
return 0
|
||||
return price
|
||||
|
||||
def estimated_fee(self, tx, fee_per_kb):
|
||||
fee = Multisig_Wallet.estimated_fee(self, tx, fee_per_kb)
|
||||
x = run_hook('extra_fee', self, tx)
|
||||
if x: fee += x
|
||||
fee += self.extra_fee(tx)
|
||||
return fee
|
||||
|
||||
def get_tx_fee(self, tx):
|
||||
fee = Multisig_Wallet.get_tx_fee(self, tx)
|
||||
x = run_hook('extra_fee', self, tx)
|
||||
if x: fee += x
|
||||
fee += self.extra_fee(tx)
|
||||
return fee
|
||||
|
||||
def make_unsigned_transaction(self, *args):
|
||||
tx = BIP32_Wallet.make_unsigned_transaction(self, *args)
|
||||
fee = self.extra_fee(tx)
|
||||
if fee:
|
||||
address = self.billing_info['billing_address']
|
||||
tx.outputs.append(('address', address, fee))
|
||||
return tx
|
||||
|
||||
def sign_transaction(self, tx, password):
|
||||
BIP32_Wallet.sign_transaction(self, tx, password)
|
||||
if tx.is_complete():
|
||||
return
|
||||
if not self.auth_code:
|
||||
self.print_error("sign_transaction: no auth code")
|
||||
return
|
||||
long_user_id, short_id = self.get_user_id()
|
||||
tx_dict = tx.as_dict()
|
||||
raw_tx = tx_dict["hex"]
|
||||
r = server.sign(short_id, raw_tx, self.auth_code)
|
||||
if r:
|
||||
raw_tx = r.get('transaction')
|
||||
tx.update(raw_tx)
|
||||
self.print_error("twofactor: is complete", tx.is_complete())
|
||||
|
||||
def get_user_id(self):
|
||||
def make_long_id(xpub_hot, xpub_cold):
|
||||
return bitcoin.sha256(''.join(sorted([xpub_hot, xpub_cold])))
|
||||
xpub_hot = self.master_public_keys["x1/"]
|
||||
xpub_cold = self.master_public_keys["x2/"]
|
||||
long_id = make_long_id(xpub_hot, xpub_cold)
|
||||
short_id = hashlib.sha256(long_id).hexdigest()
|
||||
return long_id, short_id
|
||||
|
||||
# Utility functions
|
||||
|
||||
def get_user_id(wallet):
|
||||
def make_long_id(xpub_hot, xpub_cold):
|
||||
return bitcoin.sha256(''.join(sorted([xpub_hot, xpub_cold])))
|
||||
|
||||
xpub_hot = wallet.master_public_keys["x1/"]
|
||||
xpub_cold = wallet.master_public_keys["x2/"]
|
||||
long_id = make_long_id(xpub_hot, xpub_cold)
|
||||
short_id = hashlib.sha256(long_id).hexdigest()
|
||||
return long_id, short_id
|
||||
|
||||
def make_xpub(xpub, s):
|
||||
_, _, _, c, cK = deserialize_xkey(xpub)
|
||||
cK2, c2 = bitcoin._CKD_pub(cK, c, s)
|
||||
@@ -225,12 +266,12 @@ def make_xpub(xpub, s):
|
||||
return EncodeBase58Check(xpub2)
|
||||
|
||||
def restore_third_key(wallet):
|
||||
long_user_id, short_id = get_user_id(wallet)
|
||||
long_user_id, short_id = wallet.get_user_id()
|
||||
xpub3 = make_xpub(signing_xpub, long_user_id)
|
||||
wallet.add_master_public_key('x3/', xpub3)
|
||||
|
||||
def make_billing_address(wallet, num):
|
||||
long_id, short_id = get_user_id(wallet)
|
||||
long_id, short_id = wallet.get_user_id()
|
||||
xpub = make_xpub(billing_xpub, long_id)
|
||||
_, _, _, c, cK = deserialize_xkey(xpub)
|
||||
cK, c = bitcoin.CKD_pub(cK, c, num)
|
||||
@@ -240,7 +281,7 @@ def make_billing_address(wallet, num):
|
||||
def need_server(wallet, tx):
|
||||
from electrum.account import BIP32_Account
|
||||
# Detect if the server is needed
|
||||
long_id, short_id = get_user_id(wallet)
|
||||
long_id, short_id = wallet.get_user_id()
|
||||
xpub3 = wallet.master_public_keys['x3/']
|
||||
for x in tx.inputs_to_sign():
|
||||
if x[0:2] == 'ff':
|
||||
@@ -254,25 +295,20 @@ class Plugin(BasePlugin):
|
||||
def __init__(self, parent, config, name):
|
||||
BasePlugin.__init__(self, parent, config, name)
|
||||
self.seed_func = lambda x: bitcoin.is_new_seed(x, SEED_PREFIX)
|
||||
# Keyed by wallet to handle multiple pertinent windows. Each
|
||||
# wallet is a 2fa wallet. Each value is a dictionary with
|
||||
# information about the wallet for the plugin
|
||||
self.wallets = {}
|
||||
|
||||
def constructor(self, s):
|
||||
return Wallet_2fa(s)
|
||||
|
||||
def is_available(self):
|
||||
return bool(self.wallets)
|
||||
return True
|
||||
|
||||
def set_enabled(self, wallet, enabled):
|
||||
wallet.storage.put('use_' + self.name, enabled)
|
||||
|
||||
def is_enabled(self):
|
||||
return (self.is_available() and
|
||||
not all(wallet.master_private_keys.get('x2/')
|
||||
for wallet in self.wallets.keys()))
|
||||
return True
|
||||
|
||||
@hook
|
||||
def on_new_window(self, window):
|
||||
wallet = window.wallet
|
||||
if wallet.storage.get('wallet_type') == '2fa':
|
||||
@@ -280,25 +316,16 @@ class Plugin(BasePlugin):
|
||||
_("TrustedCoin"),
|
||||
partial(self.settings_dialog, window))
|
||||
window.statusBar().addPermanentWidget(button)
|
||||
self.wallets[wallet] = {
|
||||
'is_billing' : False,
|
||||
'billing_info' : None,
|
||||
'button' : button, # Avoid loss to GC
|
||||
}
|
||||
t = Thread(target=self.request_billing_info, args=(wallet,))
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
|
||||
def on_close_window(self, window):
|
||||
self.wallets.pop(window.wallet, None)
|
||||
|
||||
def request_billing_info(self, wallet):
|
||||
billing_info = server.get(get_user_id(wallet)[1])
|
||||
billing_info = server.get(wallet.get_user_id()[1])
|
||||
billing_address = make_billing_address(wallet, billing_info['billing_index'])
|
||||
assert billing_address == billing_info['billing_address']
|
||||
wallet_info = self.wallets[wallet]
|
||||
wallet_info['billing_info'] = billing_info
|
||||
wallet_info['price_per_tx'] = dict(billing_info['price_per_tx'])
|
||||
wallet.billing_info = billing_info
|
||||
wallet.price_per_tx = dict(billing_info['price_per_tx'])
|
||||
return True
|
||||
|
||||
def create_extended_seed(self, wallet, window):
|
||||
@@ -352,7 +379,7 @@ class Plugin(BasePlugin):
|
||||
|
||||
@hook
|
||||
def do_clear(self, window):
|
||||
self.wallets[window.wallet]['is_billing'] = False
|
||||
window.wallet.is_billing = False
|
||||
|
||||
@hook
|
||||
def get_wizard_action(self, window, wallet, action):
|
||||
@@ -396,7 +423,7 @@ class Plugin(BasePlugin):
|
||||
xpub_cold = wallet.master_public_keys["x2/"]
|
||||
|
||||
# Generate third key deterministically.
|
||||
long_user_id, short_id = get_user_id(wallet)
|
||||
long_user_id, short_id = wallet.get_user_id()
|
||||
xpub3 = make_xpub(signing_xpub, long_user_id)
|
||||
|
||||
# secret must be sent by the server
|
||||
@@ -453,86 +480,30 @@ class Plugin(BasePlugin):
|
||||
@hook
|
||||
def sign_tx(self, window, tx):
|
||||
self.print_error("twofactor:sign_tx")
|
||||
if window.wallet in self.wallets:
|
||||
wallet = window.wallet
|
||||
if type(wallet) is Wallet_2fa and not wallet.can_sign_without_server():
|
||||
auth_code = None
|
||||
if need_server(window.wallet, tx):
|
||||
if need_server(wallet, tx):
|
||||
auth_code = self.auth_dialog(window)
|
||||
else:
|
||||
self.print_error("twofactor: xpub3 not needed")
|
||||
self.wallets[window.wallet]['auth_code'] = auth_code
|
||||
window.wallet.auth_code = auth_code
|
||||
|
||||
@hook
|
||||
def abort_send(self, window):
|
||||
if window.wallet in self.wallets:
|
||||
wallet_info = self.wallets[window.wallet]
|
||||
# request billing info before forming the transaction
|
||||
wallet_info['billing_info'] = None
|
||||
task = partial(self.request_billing_info, window.wallet)
|
||||
waiting_dialog = WaitingDialog(window, 'please wait...', task)
|
||||
waiting_dialog.start()
|
||||
waiting_dialog.wait()
|
||||
if wallet_info['billing_info'] is None:
|
||||
window.show_message('Could not contact server')
|
||||
return True
|
||||
wallet = window.wallet
|
||||
if type(wallet) is Wallet_2fa and not wallet.can_sign_without_server():
|
||||
if wallet.billing_info is None:
|
||||
# request billing info before forming the transaction
|
||||
task = partial(self.request_billing_info, wallet)
|
||||
waiting_dialog = WaitingDialog(window, 'please wait...', task)
|
||||
waiting_dialog.start()
|
||||
waiting_dialog.wait()
|
||||
if wallet.billing_info is None:
|
||||
window.show_message('Could not contact server')
|
||||
return True
|
||||
return False
|
||||
|
||||
@hook
|
||||
def extra_fee(self, wallet, tx):
|
||||
if not wallet in self.wallets:
|
||||
return 0
|
||||
wallet_info = self.wallets[wallet]
|
||||
if wallet_info['billing_info'].get('tx_remaining'):
|
||||
return 0
|
||||
if wallet_info['is_billing']:
|
||||
return 0
|
||||
# trustedcoin won't charge if the total inputs is lower than their fee
|
||||
price = int(wallet_info['price_per_tx'].get(1))
|
||||
assert price <= 100000
|
||||
if tx.input_value() < price:
|
||||
self.print_error("not charging for this tx")
|
||||
return 0
|
||||
return price
|
||||
|
||||
@hook
|
||||
def make_unsigned_transaction(self, wallet, tx):
|
||||
if wallet in self.wallets:
|
||||
price = self.extra_fee(wallet, tx)
|
||||
if not price:
|
||||
return
|
||||
address = self.wallets[wallet]['billing_info']['billing_address']
|
||||
tx.outputs.append(('address', address, price))
|
||||
|
||||
@hook
|
||||
def sign_transaction(self, wallet, tx, password):
|
||||
self.print_error("twofactor:sign")
|
||||
if not wallet in self.wallets:
|
||||
self.print_error("not 2fa wallet")
|
||||
return
|
||||
|
||||
auth_code = self.wallets[wallet]['auth_code']
|
||||
if not auth_code:
|
||||
self.print_error("sign_transaction: no auth code")
|
||||
return
|
||||
|
||||
if tx.is_complete():
|
||||
return
|
||||
|
||||
long_user_id, short_id = get_user_id(wallet)
|
||||
tx_dict = tx.as_dict()
|
||||
raw_tx = tx_dict["hex"]
|
||||
try:
|
||||
r = server.sign(short_id, raw_tx, auth_code)
|
||||
except Exception as e:
|
||||
tx.error = str(e)
|
||||
return
|
||||
|
||||
self.print_error( "received answer", r)
|
||||
if not r:
|
||||
return
|
||||
|
||||
raw_tx = r.get('transaction')
|
||||
tx.update(raw_tx)
|
||||
self.print_error("twofactor: is complete", tx.is_complete())
|
||||
|
||||
def settings_dialog(self, window):
|
||||
task = partial(self.request_billing_info, window.wallet)
|
||||
@@ -576,7 +547,7 @@ class Plugin(BasePlugin):
|
||||
grid = QGridLayout()
|
||||
vbox.addLayout(grid)
|
||||
|
||||
price_per_tx = self.wallets[wallet]['price_per_tx']
|
||||
price_per_tx = wallet.price_per_tx
|
||||
v = price_per_tx.get(1)
|
||||
grid.addWidget(QLabel(_("Price per transaction (not prepaid):")), 0, 0)
|
||||
grid.addWidget(QLabel(window.format_amount(v) + ' ' + window.base_unit()), 0, 1)
|
||||
@@ -596,7 +567,7 @@ class Plugin(BasePlugin):
|
||||
grid.addWidget(b, i, 2)
|
||||
i += 1
|
||||
|
||||
n = self.wallets[wallet]['billing_info'].get('tx_remaining', 0)
|
||||
n = wallet.billing_info.get('tx_remaining', 0)
|
||||
grid.addWidget(QLabel(_("Your wallet has %d prepaid transactions.")%n), i, 0)
|
||||
|
||||
# tranfer button
|
||||
@@ -616,9 +587,9 @@ class Plugin(BasePlugin):
|
||||
d.close()
|
||||
if window.pluginsdialog:
|
||||
window.pluginsdialog.close()
|
||||
wallet_info = self.wallets[window.wallet]
|
||||
uri = "bitcoin:" + wallet_info['billing_info']['billing_address'] + "?message=TrustedCoin %d Prepaid Transactions&amount="%k + str(Decimal(v)/100000000)
|
||||
wallet_info['is_billing'] = True
|
||||
wallet = window.wallet
|
||||
uri = "bitcoin:" + wallet.billing_info['billing_address'] + "?message=TrustedCoin %d Prepaid Transactions&amount="%k + str(Decimal(v)/100000000)
|
||||
wallet.is_billing = True
|
||||
window.pay_to_URI(uri)
|
||||
window.payto_e.setFrozen(True)
|
||||
window.message_e.setFrozen(True)
|
||||
|
||||
Reference in New Issue
Block a user