1
0

wallet: imports, whitespace, typing hints

This commit is contained in:
Sander van Grieken
2025-04-24 10:09:48 +02:00
parent 41c0558595
commit ae906fb17c

View File

@@ -44,45 +44,40 @@ import asyncio
import electrum_ecc as ecc
from aiorpcx import ignore_after, run_in_thread
from . import util, keystore, transaction, bitcoin, coinchooser, bip32, descriptor
from .i18n import _
from .bip32 import BIP32Node, convert_bip32_intpath_to_strpath, convert_bip32_strpath_to_intpath
from . import util
from .lntransport import extract_nodeid
from .logging import get_logger, Logger
from .util import (
NotEnoughFunds, UserCancelled, profiler, OldTaskGroup, format_fee_satoshis,
WalletFileException, BitcoinException, InvalidPassword, format_time, timestamp_to_datetime,
Satoshis, Fiat, TxMinedInfo, quantize_feerate, OrderedDictWithIndex
Satoshis, Fiat, TxMinedInfo, quantize_feerate, OrderedDictWithIndex, multisig_type, parse_max_spend,
OnchainHistoryItem, read_json_file, write_json_file, UserFacingException, FileImportFailed, EventListener,
event_listener
)
from .bitcoin import COIN, is_address, is_minikey, relayfee, dust_threshold, DummyAddress, DummyAddressUsedInTxException
from .keystore import (
load_keystore, Hardware_KeyStore, KeyStore, KeyStoreWithMPK, AddressIndexGeneric, CannotDerivePubkey
)
from .simple_config import SimpleConfig
from .fee_policy import FeePolicy, FixedFeePolicy, FEE_RATIO_HIGH_WARNING, FEERATE_WARNING_HIGH_FEE
from .lnutil import MIN_FUNDING_SAT
from .bitcoin import COIN, is_address, is_minikey, relayfee, dust_threshold
from .bitcoin import DummyAddress, DummyAddressUsedInTxException
from . import keystore
from .keystore import (load_keystore, Hardware_KeyStore, KeyStore, KeyStoreWithMPK,
AddressIndexGeneric, CannotDerivePubkey)
from .util import multisig_type, parse_max_spend
from .storage import StorageEncryptionVersion, WalletStorage
from .wallet_db import WalletDB
from . import transaction, bitcoin, coinchooser, bip32
from .transaction import (
Transaction, TxInput, TxOutput, PartialTransaction, PartialTxInput,
PartialTxOutput, TxOutpoint, Sighash
Transaction, TxInput, TxOutput, PartialTransaction, PartialTxInput, PartialTxOutput, TxOutpoint, Sighash
)
from .plugin import run_hook
from .address_synchronizer import (AddressSynchronizer, TX_HEIGHT_LOCAL,
TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_FUTURE, TX_TIMESTAMP_INF)
from .invoices import BaseInvoice, Invoice, Request
from .invoices import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED, PR_UNCONFIRMED, PR_INFLIGHT
from .address_synchronizer import (
AddressSynchronizer, TX_HEIGHT_LOCAL, TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_FUTURE,
TX_TIMESTAMP_INF
)
from .invoices import BaseInvoice, Invoice, Request, PR_PAID, PR_UNPAID, PR_EXPIRED, PR_UNCONFIRMED
from .contacts import Contacts
from .mnemonic import Mnemonic
from .logging import get_logger, Logger
from .lnworker import LNWallet
from .util import read_json_file, write_json_file, UserFacingException, FileImportFailed
from .util import EventListener, event_listener
from . import descriptor
from .lnutil import MIN_FUNDING_SAT
from .lntransport import extract_nodeid
from .descriptor import Descriptor
from .util import OnchainHistoryItem
from .txbatcher import TxBatcher
if TYPE_CHECKING:
@@ -177,17 +172,18 @@ async def sweep(
fee_policy: FeePolicy,
imax=100,
locktime=None,
tx_version=None) -> PartialTransaction:
tx_version=None
) -> PartialTransaction:
inputs, keypairs = await sweep_preparations(privkeys, network, imax)
total = sum(txin.value_sats() for txin in inputs)
outputs = [PartialTxOutput(scriptpubkey=bitcoin.address_to_script(to_address), value=total)]
tx = PartialTransaction.from_io(inputs, outputs)
fee = fee_policy.estimate_fee(tx.estimated_size(), network=network)
if total - fee < 0:
raise Exception(_('Not enough funds on address.') + '\nTotal: %d satoshis\nFee: %d'%(total, fee))
raise Exception(_('Not enough funds on address.') + '\nTotal: %d satoshis\nFee: %d' % (total, fee))
if total - fee < dust_threshold(network):
raise Exception(_('Not enough funds on address.') + '\nTotal: %d satoshis\nFee: %d\nDust Threshold: %d'%(total, fee, dust_threshold(network)))
raise Exception(_('Not enough funds on address.') +
'\nTotal: %d satoshis\nFee: %d\nDust Threshold: %d' % (total, fee, dust_threshold(network)))
outputs = [PartialTxOutput(scriptpubkey=bitcoin.address_to_script(to_address), value=total - fee)]
if locktime is None:
locktime = get_locktime_for_new_transaction(network)
@@ -228,6 +224,8 @@ def get_locktime_for_new_transaction(
class CannotRBFTx(Exception): pass
class TransactionPotentiallyDangerousException(Exception): pass
class TransactionDangerousException(TransactionPotentiallyDangerousException): pass
class CannotBumpFee(CannotRBFTx):
@@ -251,12 +249,6 @@ class InternalAddressCorruption(Exception):
"Please restore your wallet from seed, and compare the addresses in both files")
class TransactionPotentiallyDangerousException(Exception): pass
class TransactionDangerousException(TransactionPotentiallyDangerousException): pass
class TxSighashRiskLevel(enum.IntEnum):
# higher value -> more risk
SAFE = 0
@@ -359,6 +351,7 @@ class TxWalletDelta(NamedTuple):
delta: int
fee: Optional[int]
class TxWalletDetails(NamedTuple):
txid: Optional[str]
status: str
@@ -412,19 +405,19 @@ class Abstract_Wallet(ABC, Logger, EventListener):
self._last_full_history = None
self._tx_parents_cache = {}
self._default_labels = {}
self._accounting_addresses = set() # addresses counted as ours after successful sweep
self._accounting_addresses = set() # addresses counted as ours after successful sweep
self.taskgroup = OldTaskGroup()
# saved fields
self.use_change = db.get('use_change', True)
self.multiple_change = db.get('multiple_change', False)
self._labels = db.get_dict('labels')
self._frozen_addresses = set(db.get('frozen_addresses', []))
self._frozen_coins = db.get_dict('frozen_coins') # type: Dict[str, bool]
self._labels = db.get_dict('labels')
self._frozen_addresses = set(db.get('frozen_addresses', []))
self._frozen_coins = db.get_dict('frozen_coins') # type: Dict[str, bool]
self.fiat_value = db.get_dict('fiat_value')
self._receive_requests = db.get_dict('payment_requests') # type: Dict[str, Request]
self._invoices = db.get_dict('invoices') # type: Dict[str, Invoice]
self._receive_requests = db.get_dict('payment_requests') # type: Dict[str, Request]
self._invoices = db.get_dict('invoices') # type: Dict[str, Invoice]
self._reserved_addresses = set(db.get('reserved_addresses', []))
self._num_parents = db.get_dict('num_parents')
@@ -977,7 +970,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
else:
assert isinstance(tx, PartialTransaction)
s, r = tx.signature_count()
status = _("Unsigned") if s == 0 else _('Partially signed') + ' (%d/%d)'%(s,r)
status = _("Unsigned") if s == 0 else _('Partially signed') + ' (%d/%d)' % (s, r)
if is_relevant:
if tx_wallet_delta.is_all_input_ismine:
@@ -1276,7 +1269,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
def get_invoices(self) -> List[Invoice]:
out = list(self._invoices.values())
out.sort(key=lambda x:x.time)
out.sort(key=lambda x: x.time)
return out
def get_unpaid_invoices(self) -> List[Invoice]:
@@ -1644,10 +1637,10 @@ class Abstract_Wallet(ABC, Logger, EventListener):
label = request.get_message()
return label
def set_default_label(self, key:str, value:str):
def set_default_label(self, key: str, value: str):
self._default_labels[key] = value
def get_label_for_outpoint(self, outpoint:str) -> str:
def get_label_for_outpoint(self, outpoint: str) -> str:
return self._labels.get(outpoint) or self._get_default_label_for_outpoint(outpoint)
def _get_default_label_for_outpoint(self, outpoint: str) -> str:
@@ -1742,7 +1735,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
time_str = format_time(timestamp) if timestamp else _("unknown")
status_str = TX_STATUS[status] if status < 4 else time_str
if extra:
status_str += ' [%s]'%(', '.join(extra))
status_str += ' [%s]' % (', '.join(extra))
return status, status_str
def relayfee(self):
@@ -2015,10 +2008,10 @@ class Abstract_Wallet(ABC, Logger, EventListener):
val = int((amount/i_max_sum) * weight)
outputs[i].value = val
distr_amount += val
(x,i) = i_max[-1]
(x, i) = i_max[-1]
outputs[i].value += (amount - distr_amount)
tx_inputs = inputs + coins # these do not overlap, see above
tx_inputs = inputs + coins # these do not overlap, see above
distribute_amount(0)
tx = PartialTransaction.from_io(list(tx_inputs), list(outputs))
fee = fee_estimator(tx.estimated_size())
@@ -2032,7 +2025,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
self.logger.info(f'Adding change output to meet utxo reserve requirements')
change_addr = self.get_change_addresses_for_new_transaction(change_addr)[0]
change = PartialTxOutput.from_address_and_value(change_addr, self.config.LN_UTXO_RESERVE)
change.is_utxo_reserve = True # for GUI
change.is_utxo_reserve = True # for GUI
outputs.append(change)
to_distribute -= change.value
tx = PartialTransaction.from_io(list(tx_inputs), list(outputs))
@@ -2455,7 +2448,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
else:
raise CannotCPFP(_("Could not find suitable output"))
coins = self.adb.get_addr_utxo(address)
item = coins.get(TxOutpoint.from_str(txid+':%d'%i))
item = coins.get(TxOutpoint.from_str(txid + ':%d' % i))
if not item:
raise CannotCPFP(_("Could not find coins for output"))
inputs = [item]
@@ -2663,7 +2656,13 @@ class Abstract_Wallet(ABC, Logger, EventListener):
txout.is_change = self.is_change(address)
self._add_txinout_derivation_info(txout, address, only_der_suffix=only_der_suffix)
def sign_transaction(self, tx: Transaction, password, *, ignore_warnings: bool = False) -> Optional[PartialTransaction]:
def sign_transaction(
self,
tx: Transaction,
password,
*,
ignore_warnings: bool = False
) -> Optional[PartialTransaction]:
""" returns tx if successful else None """
if self.is_watching_only():
return
@@ -2785,7 +2784,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
paid, conf = self.is_onchain_invoice_paid(invoice)
if not paid:
if isinstance(invoice, Invoice):
if status:=invoice.get_broadcasting_status():
if status := invoice.get_broadcasting_status():
return status
status = PR_UNPAID
elif conf == 0:
@@ -2877,7 +2876,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
with self.lock, self.transaction_lock:
for txo in tx.outputs():
addr = txo.address
if request:=self.get_request_by_addr(addr):
if request := self.get_request_by_addr(addr):
request_keys.add(request.get_id())
for invoice_key in self._invoices_from_scriptpubkey_map.get(txo.scriptpubkey, set()):
invoice_keys.add(invoice_key)
@@ -2957,7 +2956,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
def add_payment_request(self, req: Request, *, write_to_disk: bool = True):
request_id = req.get_id()
self._receive_requests[request_id] = req
if addr:=req.get_address():
if addr := req.get_address():
self._requests_addr_to_key[addr].add(request_id)
if write_to_disk:
self.save_db()
@@ -2969,7 +2968,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
if req is None:
return
self._receive_requests.pop(request_id, None)
if addr:=req.get_address():
if addr := req.get_address():
self._requests_addr_to_key[addr].discard(request_id)
if req.is_lightning() and self.lnworker:
self.lnworker.delete_payment_info(req.rhash)
@@ -3341,7 +3340,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
except Exception:
zeroconf_nodeid = None
can_get_zeroconf_channel = (self.lnworker and self.config.ACCEPT_ZEROCONF_CHANNELS
and zeroconf_nodeid in self.lnworker.peers)
and zeroconf_nodeid in self.lnworker.peers)
status = self.get_invoice_status(req)
if status == PR_EXPIRED:
@@ -3381,7 +3380,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
if amount_sat < MIN_FUNDING_SAT:
ln_is_error = True
ln_help = (_('Cannot receive this payment. Request at least {} '
'to purchase a Lightning channel from your service provider.')
'to purchase a Lightning channel from your service provider.')
.format(self.config.format_amount_and_units(amount_sat=MIN_FUNDING_SAT)))
else:
ln_zeroconf_suggestion = True
@@ -3410,7 +3409,6 @@ class Abstract_Wallet(ABC, Logger, EventListener):
ln_zeroconf_suggestion=ln_zeroconf_suggestion
)
def synchronize(self) -> int:
"""Returns the number of new addresses we generated."""
return 0
@@ -3602,7 +3600,7 @@ class Imported_Wallet(Simple_Wallet):
for tx_hash in transactions_to_remove:
self.adb._remove_transaction(tx_hash)
self.set_label(address, None)
if req:= self.get_request_by_addr(address):
if req := self.get_request_by_addr(address):
self.delete_request(req.get_id())
self.set_frozen_state_of_addresses([address], False, write_to_disk=False)
pubkey = self.get_public_key(address)
@@ -3639,7 +3637,8 @@ class Imported_Wallet(Simple_Wallet):
return unused_addrs
def is_mine(self, address) -> bool:
if not address: return False
if not address:
return False
return self.db.has_imported_address(address)
def get_address_index(self, address) -> Optional[str]:
@@ -3668,7 +3667,7 @@ class Imported_Wallet(Simple_Wallet):
continue
addr = bitcoin.pubkey_to_address(txin_type, pubkey)
good_addr.append(addr)
self.db.add_imported_address(addr, {'type':txin_type, 'pubkey':pubkey})
self.db.add_imported_address(addr, {'type': txin_type, 'pubkey': pubkey})
self.adb.add_address(addr)
self.save_keystore()
if write_to_disk:
@@ -3788,7 +3787,7 @@ class Deterministic_Wallet(Abstract_Wallet):
return self.keystore.get_seed_type()
def change_gap_limit(self, value):
'''This method is not called in the code, it is kept for console use'''
"""This method is not called in the code, it is kept for console use"""
value = int(value)
if value >= self.min_acceptable_gap():
self.gap_limit = value
@@ -3924,7 +3923,8 @@ class Deterministic_Wallet(Abstract_Wallet):
if der_suffix is not None:
# note: we already know the pubkey belongs to the keystore,
# but the script template might be different
if len(der_suffix) != 2: continue
if len(der_suffix) != 2:
continue
try:
my_address = self.derive_address(*der_suffix)
except CannotDerivePubkey:
@@ -4015,7 +4015,7 @@ class Multisig_Wallet(Deterministic_Wallet):
def load_keystore(self):
self.keystores = {}
for i in range(self.n):
name = 'x%d'%(i+1)
name = 'x%d' % (i+1)
self.keystores[name] = load_keystore(self.db, name)
self.keystore = self.keystores['x1']
xtype = bip32.xpub_type(self.keystore.xpub)
@@ -4069,9 +4069,11 @@ class Multisig_Wallet(Deterministic_Wallet):
wallet_types = ['standard', 'multisig', 'imported']
def register_wallet_type(category):
wallet_types.append(category)
wallet_constructors = {
'standard': Standard_Wallet,
'old': Standard_Wallet,
@@ -4079,16 +4081,18 @@ wallet_constructors = {
'imported': Imported_Wallet
}
def register_constructor(wallet_type, constructor):
wallet_constructors[wallet_type] = constructor
# former WalletFactory
class Wallet(object):
"""The main wallet "entry point".
This class is actually a factory that will return a wallet of the correct
type when passed a WalletStorage instance."""
def __new__(self, db: 'WalletDB', *, config: SimpleConfig):
def __new__(cls, db: 'WalletDB', *, config: SimpleConfig):
wallet_type = db.get('wallet_type')
WalletClass = Wallet.wallet_class(wallet_type)
wallet = WalletClass(db, config=config)
@@ -4103,8 +4107,16 @@ class Wallet(object):
raise WalletFileException("Unknown wallet type: " + str(wallet_type))
def create_new_wallet(*, path, config: SimpleConfig, passphrase=None, password=None,
encrypt_file=True, seed_type=None, gap_limit=None) -> dict:
def create_new_wallet(
*,
path,
config: SimpleConfig,
passphrase: Optional[str] = None,
password: Optional[str] = None,
encrypt_file: bool = True,
seed_type: Optional[str] = None,
gap_limit: Optional[int] = None
) -> dict:
"""Create a new wallet"""
storage = WalletStorage(path)
if storage.file_exists():