From 1cdd61d5a7341d1ff8be02dfcb0f5a33e50c838a Mon Sep 17 00:00:00 2001 From: Nicola 'tekNico' Larosa Date: Wed, 25 Mar 2020 15:41:48 -0400 Subject: [PATCH 1/2] Move wallet code from simple.py to wallet.py, write tests, remove mpl.py . --- decred/decred/wallet/simple.py | 104 ------------- decred/decred/wallet/wallet.py | 107 ++++++++++++- decred/examples/create_testnet_wallet.py | 2 +- decred/examples/send_testnet.py | 2 +- .../tests/integration/wallet/test_wallet.py | 28 ++++ tinywallet/tinywallet/mpl.py | 146 ------------------ 6 files changed, 132 insertions(+), 257 deletions(-) delete mode 100644 decred/decred/wallet/simple.py create mode 100644 decred/tests/integration/wallet/test_wallet.py delete mode 100644 tinywallet/tinywallet/mpl.py diff --git a/decred/decred/wallet/simple.py b/decred/decred/wallet/simple.py deleted file mode 100644 index 921775a4..00000000 --- a/decred/decred/wallet/simple.py +++ /dev/null @@ -1,104 +0,0 @@ -""" -Copyright (c) 2019, Brian Stafford -Copyright (c) 2020, The Decred developers -See LICENSE for details -""" - -from pathlib import Path - -from decred import DecredError -from decred.crypto import mnemonic, rando -from decred.dcr import nets -from decred.dcr.dcrdata import DcrdataBlockchain -from decred.util import chains, database -from decred.util.helpers import mkdir -from decred.wallet.wallet import Wallet - - -class DefaultSignals: - """DefaultSignals prints the balance to stdout.""" - - @staticmethod - def balance(b): - print(repr(b)) - - -def paths(base, network): - """ - Get the paths for the network directory, the wallet database file, and the - blockchain database file. - """ - netDir = Path(base) / network - dbPath = netDir / "wallet.db" - dcrPath = netDir / "dcr.db" - return netDir, dbPath, dcrPath - - -DCRDATA_PATHS = { - "mainnet": "https://explorer.dcrdata.org/", - "testnet3": "https://testnet.dcrdata.org/", - "simnet": "http://localhost:17779", -} - - -class SimpleWallet(Wallet): - """ - SimpleWallet is a single-account Decred wallet. - """ - - def __init__(self, walletDir, pw, network, signals=None, allowCreate=False): - """ - Args: - dir (str): A directory for wallet database files. - pw (str): The user password. - network (str): The network name. - signals (Signals): A signal processor. - """ - signals = signals if signals else DefaultSignals - netParams = nets.parse(network) - netDir, dbPath, dcrPath = paths(walletDir, netParams.Name) - if not Path(netDir).exists(): - mkdir(netDir) - dcrdataDB = database.KeyValueDatabase(dcrPath) - # The initialized DcrdataBlockchain will not be connected, as that is a - # blocking operation. It will be called when the wallet is open. - dcrdataURL = DCRDATA_PATHS[netParams.Name] - self.dcrdata = DcrdataBlockchain(dcrdataDB, netParams, dcrdataURL) - chains.registerChain("dcr", self.dcrdata) - walletExists = Path(dbPath).is_file() - if not walletExists and not allowCreate: - raise DecredError("Wallet does not exist at %s", dbPath) - - super().__init__(dbPath) - # words is only set the first time a wallet is created. - if not walletExists: - seed = rando.newKeyRaw() - self.initialize(seed, pw.encode(), netParams) - self.words = mnemonic.encode(seed) - - cryptoKey = self.cryptoKey(pw) - acctMgr = self.accountManager(chains.BipIDs.decred, signals) - self.account = acctMgr.openAccount(0, cryptoKey) - self.account.sync() - - def __getattr__(self, name): - """Delegate unknown methods to the account.""" - return getattr(self.account, name) - - @staticmethod - def create(walletDir, pw, network, signals=None): - """ - Create a new wallet. Will not overwrite an existing wallet file. All - arguments are the same as the SimpleWallet constructor. - """ - netParams = nets.parse(network) - _, dbPath, _ = paths(walletDir, netParams.Name) - if Path(dbPath).is_file(): - raise DecredError("wallet already exists at %s" % dbPath) - wallet = SimpleWallet(walletDir, pw, network, signals, True) - words = wallet.words - wallet.words.clear() - return wallet, words - - def close(self): - self.dcrdata.close() diff --git a/decred/decred/wallet/wallet.py b/decred/decred/wallet/wallet.py index d69d9827..80e0ed67 100644 --- a/decred/decred/wallet/wallet.py +++ b/decred/decred/wallet/wallet.py @@ -4,10 +4,15 @@ See LICENSE for details """ +from pathlib import Path + from decred import DecredError from decred.crypto import crypto, mnemonic, rando -from decred.util import chains, encode, helpers -from decred.util.database import KeyValueDatabase +from decred.dcr import nets +from decred.dcr.dcrdata import DcrdataBlockchain +from decred.util import chains, database, encode, helpers +from decred.util.helpers import mkdir + from . import accounts @@ -40,7 +45,7 @@ def __init__(self, path): Args: path (str): The path to the wallet database. """ - self.db = KeyValueDatabase(path) + self.db = database.KeyValueDatabase(path) self.masterDB = self.db.child("master", blobber=encode.ByteArray) self.coinDB = self.db.child( "accts", datatypes=("INTEGER", "BLOB"), blobber=accounts.AccountManager @@ -106,7 +111,7 @@ def createFromMnemonic(words, path, password, netParams): Args: words (list(str)): Mnemonic seed. Assumed to be PGP words. path (str): Filepath to store wallet. - password (str): User password. Passed to Wallet.create. + password (str): User password. Passed to Wallet.initialize. Returns: Wallet: A wallet initialized from the seed parsed from `words`. @@ -116,7 +121,7 @@ def createFromMnemonic(words, path, password, netParams): userSeed = decoded[:-1] cs = crypto.sha256ChecksumByte(userSeed.b) if cs != cksum: - raise Exception("bad checksum %r != %r" % (cs, cksum)) + raise DecredError("bad checksum %r != %r" % (cs, cksum)) wallet = Wallet(path) wallet.initialize(userSeed.b, password.encode(), netParams) userSeed.zero() @@ -146,3 +151,95 @@ def accountManager(self, coinType, signals): acctMgr.load(acctDB, signals) self.mgrCache[coinType] = acctMgr return acctMgr + + +# SimpleWallet code. + + +class DefaultSignals: + """DefaultSignals prints the balance to stdout.""" + + @staticmethod + def balance(b): + print(repr(b)) + + +def paths(base, network): + """ + Get the paths for the network directory, the wallet database file, and the + blockchain database file. + """ + netDir = Path(base) / network + dbPath = netDir / "wallet.db" + dcrPath = netDir / "dcr.db" + return netDir, dbPath, dcrPath + + +DCRDATA_PATHS = { + "mainnet": "https://explorer.dcrdata.org/", + "testnet3": "https://testnet.dcrdata.org/", + "simnet": "http://localhost:17779", +} + + +class SimpleWallet(Wallet): + """ + SimpleWallet is a single-account Decred wallet. + """ + + def __init__(self, walletDir, pw, network, signals=None, allowCreate=False): + """ + Args: + dir (str): A directory for wallet database files. + pw (str): The user password. + network (str): The network name. + signals (Signals): A signal processor. + """ + signals = signals if signals else DefaultSignals + netParams = nets.parse(network) + netDir, dbPath, dcrPath = paths(walletDir, netParams.Name) + if not Path(netDir).exists(): + mkdir(netDir) + dcrdataDB = database.KeyValueDatabase(dcrPath) + # The initialized DcrdataBlockchain will not be connected, as that is a + # blocking operation. It will be called when the wallet is open. + dcrdataURL = DCRDATA_PATHS[netParams.Name] + self.dcrdata = DcrdataBlockchain(dcrdataDB, netParams, dcrdataURL) + chains.registerChain("dcr", self.dcrdata) + walletExists = Path(dbPath).is_file() + if not walletExists and not allowCreate: + raise DecredError("Wallet does not exist at %s", dbPath) + + super().__init__(dbPath) + # words is only set the first time a wallet is created. + if not walletExists: + seed = rando.newKeyRaw() + self.initialize(seed, pw.encode(), netParams) + self.words = mnemonic.encode(seed) + + cryptoKey = self.cryptoKey(pw) + acctMgr = self.accountManager(chains.BipIDs.decred, signals) + self.account = acctMgr.openAccount(0, cryptoKey) + self.account.sync() + + def __getattr__(self, name): + """Delegate unknown methods to the account.""" + return getattr(self.account, name) + + @staticmethod + def create(walletDir, pw, network, signals=None): + """ + Create a new wallet. Will not overwrite an existing wallet file. All + arguments are the same as the SimpleWallet constructor. + """ + netParams = nets.parse(network) + _, dbPath, _ = paths(walletDir, netParams.Name) + if Path(dbPath).is_file(): + raise DecredError("wallet already exists at %s" % dbPath) + wallet = SimpleWallet(walletDir, pw, network, signals, True) + words = wallet.words + wallet.words.clear() + return wallet, words + + def close(self): + self.dcrdata.close() diff --git a/decred/examples/create_testnet_wallet.py b/decred/examples/create_testnet_wallet.py index a7da08aa..91aaa040 100644 --- a/decred/examples/create_testnet_wallet.py +++ b/decred/examples/create_testnet_wallet.py @@ -7,7 +7,7 @@ from getpass import getpass -from decred.wallet.simple import SimpleWallet +from decred.wallet.wallet import SimpleWallet def main(): diff --git a/decred/examples/send_testnet.py b/decred/examples/send_testnet.py index c8da2736..c0e76624 100644 --- a/decred/examples/send_testnet.py +++ b/decred/examples/send_testnet.py @@ -9,7 +9,7 @@ from getpass import getpass -from decred.wallet.simple import SimpleWallet +from decred.wallet.wallet import SimpleWallet # Testnet return address for faucet.decred.org. TESTNET_ADDRESS = "TsfDLrRkk9ciUuwfp2b8PawwnukYD7yAjGd" diff --git a/decred/tests/integration/wallet/test_wallet.py b/decred/tests/integration/wallet/test_wallet.py new file mode 100644 index 00000000..afe17085 --- /dev/null +++ b/decred/tests/integration/wallet/test_wallet.py @@ -0,0 +1,28 @@ +""" +Copyright (c) 2020, The Decred developers +""" + +from decred.dcr import nets +from decred.wallet.wallet import SimpleWallet, Wallet + + +PASSWORD = "test_password" +NET_NAME = "testnet" + +# Testnet return address for faucet.decred.org. +TESTNET_ADDRESS = "TsfDLrRkk9ciUuwfp2b8PawwnukYD7yAjGd" + + +def test_SimpleWallet(tmp_path): + wallet, _ = SimpleWallet.create(tmp_path, PASSWORD, NET_NAME) + wallet.close() + wallet = SimpleWallet(tmp_path, PASSWORD, NET_NAME) + wallet.close() + + +def test_Wallet(tmp_path): + first_wallet_path = tmp_path / "first_wallet" + netParams = nets.parse(NET_NAME) + words, _ = Wallet.create(first_wallet_path, PASSWORD, netParams) + second_wallet_path = tmp_path / "second_wallet" + Wallet.createFromMnemonic(words, second_wallet_path, PASSWORD, netParams) diff --git a/tinywallet/tinywallet/mpl.py b/tinywallet/tinywallet/mpl.py deleted file mode 100644 index 4329aec8..00000000 --- a/tinywallet/tinywallet/mpl.py +++ /dev/null @@ -1,146 +0,0 @@ -""" -Copyright (c) 2019, Brian Stafford -Copyright (c) 2019, the Decred developers -See LICENSE for details -""" - -import os -import time - -from decred.dcr import constants as C -from decred.util import helpers -import matplotlib -from matplotlib import font_manager as FontManager -from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas -from matplotlib.figure import Figure -from mpl_toolkits.mplot3d import Axes3D # noqa: F401 (flake8 ignore) - -from . import ui as UI - - -MPL_COLOR = "#333333" -matplotlib.rcParams["mathtext.fontset"] = "cm" -MPL_FONTS = {} -NO_SUBPLOT_MARGINS = { - "left": 0, - "right": 1, - "bottom": 0, - "top": 1, - "wspace": 0, - "hspace": 0, -} - - -def setDefaultAxesColor(color): - """ - Set the default color axes labels and lines. - """ - matplotlib.rcParams["text.color"] = color - matplotlib.rcParams["axes.labelcolor"] = color - matplotlib.rcParams["xtick.color"] = color - matplotlib.rcParams["ytick.color"] = color - - -setDefaultAxesColor(MPL_COLOR) - - -def setFrameColor(ax, color): - """ - Sets the color of the plot frame - """ - for spine in ax.spines.values(): - spine.set_color(color) - - -def getFont(font, size): - """ - Create a `FontProperties` from a font in the /fonts directory. - """ - if font not in MPL_FONTS: - MPL_FONTS[font] = {} - if size not in MPL_FONTS[font]: - MPL_FONTS[font][size] = FontManager.FontProperties( - fname=os.path.join(UI.PACKAGEDIR, "fonts", "%s.ttf" % font), size=size - ) - return MPL_FONTS[font][size] - - -def getMonthTicks(start, end, increment, offset=0): - """ - Create a set of matplotlib compatible ticks and tick labels - for every `increment` month in the range [start, end], - beginning at the month of start + `offset` months. - """ - xLabels = [] - xTicks = [] - y, m, d = tuple( - int(x) for x in time.strftime("%Y %m %d", time.gmtime(start)).split() - ) - - def normalize(y, m): - if m > 12: - m -= 12 - y += 1 - elif m < 0: - m += 12 - y -= 1 - return y, m - - def nextyearmonth(y, m): - m += increment - return normalize(y, m) - - y, m = normalize(y, m + offset) - tick = helpers.mktime(y, m) - end = end + C.DAY * 120 # Make a few extra months worth. - while True: - xTicks.append(tick) - xLabels.append(time.strftime("%b '%y", time.gmtime(tick))) - y, m = nextyearmonth(y, m) - tick = helpers.mktime(y, m) - if tick > end: - break - return xTicks, xLabels - - -def wrapTex(tex): - """ Wrap string in the Tex delimeter `$` """ - return r"$ %s $" % tex - - -def setAxesFont(font, size, *axes): - """ Set the font for the `matplotlib.axes.Axes`""" - fontproperties = getFont(font, size) - for ax in axes: - for label in ax.get_xticklabels(): - label.set_fontproperties(fontproperties) - for label in ax.get_yticklabels(): - label.set_fontproperties(fontproperties) - - -class TexWidget(FigureCanvas): - """A Qt5 compatible widget with a Tex equation""" - - def __init__(self, equation, fontSize=20): - self.equation = equation - self.fig = Figure() - self.fig.subplots_adjust(**NO_SUBPLOT_MARGINS) - super().__init__(self.fig) - ax = self.axes = self.fig.add_subplot("111") - ax.axis("off") - ax.set_xlim(left=0, right=1) - ax.set_ylim(top=1, bottom=0) - text = ax.text( - 0.5, - 0.5, - equation, - fontsize=fontSize, - horizontalalignment="center", - verticalalignment="center", - ) - self.updateText = lambda s: text.set_text(s) - self.defaultColor = "white" - - def setFacecolor(self, color=None): - self.fig.set_facecolor(color if color else self.defaultColor) - self.fig.canvas.draw() From a4c58027e678ff26d1432edb112aa112e8d80501 Mon Sep 17 00:00:00 2001 From: Nicola 'tekNico' Larosa Date: Wed, 25 Mar 2020 15:47:17 -0400 Subject: [PATCH 2/2] Run isort. --- decred/decred/wallet/wallet.py | 1 - 1 file changed, 1 deletion(-) diff --git a/decred/decred/wallet/wallet.py b/decred/decred/wallet/wallet.py index 80e0ed67..fc493d94 100644 --- a/decred/decred/wallet/wallet.py +++ b/decred/decred/wallet/wallet.py @@ -13,7 +13,6 @@ from decred.util import chains, database, encode, helpers from decred.util.helpers import mkdir - from . import accounts