├── .github └── workflows │ └── python.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── decred ├── .coveragerc ├── .flake8 ├── README.md ├── build.py ├── coverage-html.sh ├── debugger.sh ├── decred │ ├── __init__.py │ ├── btc │ │ └── nets │ │ │ ├── __init__.py │ │ │ ├── mainnet.py │ │ │ ├── regtest.py │ │ │ ├── simnet.py │ │ │ └── testnet.py │ ├── crypto │ │ ├── __init__.py │ │ ├── crypto.py │ │ ├── gcs.py │ │ ├── mnemonic.py │ │ ├── opcode.py │ │ ├── rando.py │ │ └── secp256k1 │ │ │ ├── __init__.py │ │ │ ├── bytepoints.py │ │ │ ├── curve.py │ │ │ ├── field.pxd │ │ │ └── field.py │ ├── dcr │ │ ├── __init__.py │ │ ├── account.py │ │ ├── addrlib.py │ │ ├── agenda.py │ │ ├── assets │ │ │ ├── favicon-32x32.png │ │ │ └── logo.svg │ │ ├── blockchain.py │ │ ├── constants.py │ │ ├── dcrdata.py │ │ ├── nets │ │ │ ├── __init__.py │ │ │ ├── mainnet.py │ │ │ ├── simnet.py │ │ │ └── testnet.py │ │ ├── rpc.py │ │ ├── txscript.py │ │ ├── vsp.py │ │ └── wire │ │ │ ├── __init__.py │ │ │ ├── msgblock.py │ │ │ ├── msgping.py │ │ │ ├── msgpong.py │ │ │ ├── msgtx.py │ │ │ ├── msgverack.py │ │ │ ├── msgversion.py │ │ │ ├── netaddress.py │ │ │ └── wire.py │ ├── util │ │ ├── __init__.py │ │ ├── chains.py │ │ ├── database.py │ │ ├── encode.py │ │ ├── helpers.py │ │ ├── tinyhttp.py │ │ └── ws.py │ └── wallet │ │ ├── __init__.py │ │ ├── accounts.py │ │ └── wallet.py ├── examples │ ├── create_testnet_wallet.py │ ├── plot_ticket_price.py │ └── send_testnet.py ├── pyproject.toml └── tests │ ├── benchmark │ ├── crypto │ │ └── secp256k1 │ │ │ └── test_field_benchmark.py │ └── util │ │ └── test_database_benchmark.py │ ├── conftest.py │ ├── integration │ ├── dcr │ │ ├── test_blockchain_live.py │ │ ├── test_dcrdata_live.py │ │ ├── test_rpc_live.py │ │ └── test_vsp_live.py │ ├── util │ │ └── test_ws.py │ └── wallet │ │ └── test_wallet.py │ ├── test_examples.py │ └── unit │ ├── btc │ └── test_nets_btc.py │ ├── crypto │ ├── secp256k1 │ │ ├── test_curve.py │ │ └── test_field.py │ ├── test_crypto.py │ ├── test_gcs.py │ ├── test_mnemonic.py │ └── test_rando.py │ ├── dcr │ ├── conftest.py │ ├── test-data │ │ ├── dcrdata.json │ │ └── sighash.json │ ├── test_account.py │ ├── test_addrlib.py │ ├── test_agenda.py │ ├── test_blockchain_unit.py │ ├── test_dcrdata_unit.py │ ├── test_nets.py │ ├── test_rpc_unit.py │ ├── test_txscript.py │ ├── test_vsp_unit.py │ └── wire │ │ ├── test_msgblock.py │ │ ├── test_msgping.py │ │ ├── test_msgpong.py │ │ ├── test_msgtx.py │ │ ├── test_msgverack.py │ │ ├── test_msgversion.py │ │ ├── test_netaddress.py │ │ └── test_wire.py │ ├── util │ ├── test_chains.py │ ├── test_database.py │ ├── test_encode.py │ ├── test_helpers.py │ └── test_tinyhttp.py │ └── wallet │ └── test_accounts.py └── tinywallet ├── .coveragerc ├── .flake8 ├── README.md ├── coverage-html.sh ├── poetry.lock ├── pyproject.toml ├── tests └── unit │ └── test_config.py └── tinywallet ├── __init__.py ├── app.py ├── config.py ├── fonts ├── EBGaramond-Bold.ttf ├── EBGaramond-BoldItalic.ttf ├── EBGaramond-ExtraBold.ttf ├── EBGaramond-ExtraBoldItalic.ttf ├── EBGaramond-Italic.ttf ├── EBGaramond-Medium.ttf ├── EBGaramond-MediumItalic.ttf ├── EBGaramond-Regular.ttf ├── EBGaramond-SemiBold.ttf ├── EBGaramond-SemiBoldItalic.ttf ├── MaterialIcons-Regular.ttf ├── Roboto-Black.ttf ├── Roboto-BlackItalic.ttf ├── Roboto-Bold.ttf ├── Roboto-BoldItalic.ttf ├── Roboto-Italic.ttf ├── Roboto-Light.ttf ├── Roboto-LightItalic.ttf ├── Roboto-Medium.ttf ├── Roboto-MediumItalic.ttf ├── Roboto-Regular.ttf ├── Roboto-Thin.ttf ├── Roboto-ThinItalic.ttf ├── RobotoMono-Bold.ttf ├── RobotoMono-BoldItalic.ttf ├── RobotoMono-Italic.ttf ├── RobotoMono-Light.ttf ├── RobotoMono-LightItalic.ttf ├── RobotoMono-Medium.ttf ├── RobotoMono-MediumItalic.ttf ├── RobotoMono-Regular.ttf ├── RobotoMono-Thin.ttf └── RobotoMono-ThinItalic.ttf ├── icons ├── back.svg ├── check.svg ├── closed-eye.svg ├── folder.svg ├── gear.svg ├── home.svg ├── locked.svg ├── open-eye.svg ├── plus.svg ├── send.svg ├── spinner.svg ├── unlocked.svg └── x.svg ├── qutilities.py ├── screens.py └── ui.py /.github/workflows/python.yml: -------------------------------------------------------------------------------- 1 | name: Check and test both packages 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | check-test: 6 | runs-on: ubuntu-latest 7 | strategy: 8 | matrix: 9 | python-version: [3.6, 3.7, 3.8] 10 | package: [decred, tinywallet] 11 | steps: 12 | - uses: actions/checkout@v1 13 | - name: Set up Python ${{ matrix.python-version }} 14 | uses: actions/setup-python@v1 15 | with: 16 | python-version: ${{ matrix.python-version }} 17 | - name: Install pip 18 | run: | 19 | python -m pip install --upgrade pip 20 | - name: Install the poetry tool 21 | run: | 22 | pip install poetry 23 | - name: Install the ${{ matrix.package }} package and its dependencies 24 | working-directory: ${{ format('./{0}/', matrix.package) }} 25 | run: | 26 | poetry install 27 | - name: Lint the ${{ matrix.package }} code with black 28 | working-directory: ${{ format('./{0}/', matrix.package) }} 29 | run: | 30 | # stop the build if the code was not reformatted by black 31 | poetry run black --check --diff . 32 | - name: Sort ${{ matrix.package }} import lines with isort 33 | working-directory: ${{ format('./{0}/', matrix.package) }} 34 | run: | 35 | # stop the build if import lines were not sorted by isort 36 | poetry run isort --check-only --diff --recursive . 37 | - name: Lint the ${{ matrix.package }} code with flake8 38 | working-directory: ${{ format('./{0}/', matrix.package) }} 39 | run: | 40 | # stop the build if there are syntax errors or undefined names 41 | poetry run flake8 --select=E9,F63,F7,F82 --show-source . 42 | # exit-zero treats all errors as warnings 43 | poetry run flake8 --ignore=E203,E221,E261,E731,W503 --exit-zero . 44 | - name: Test the ${{ matrix.package }} package with pytest 45 | working-directory: ${{ format('./{0}/', matrix.package) }} 46 | run: | 47 | poetry run pytest ./tests/unit/ 48 | # - name: Build the ${{ matrix.package }} package 49 | # working-directory: ${{ format('./{0}/', matrix.package) }} 50 | # run: | 51 | # poetry build 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | decred/examples/wallets 3 | decred/dist/ 4 | decred/poetry.lock 5 | tinywallet/dist/ 6 | htmlcov/ 7 | prof/ 8 | .venv/ 9 | .mypy_cache/ 10 | __pycache__/ 11 | .pytest_cache 12 | .coverage 13 | *.c 14 | *.so 15 | *.dll 16 | *.html 17 | *.egg-info 18 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution guidelines 2 | 3 | Thank you for contributing to this project! The developers are part of the 4 | [Decred community](https://decred.org/community/) and coordinate in a 5 | [chat](https://chat.decred.org/#/room/#tinydecred:decred.org) on the 6 | [Matrix](https://matrix.org/) platform, you'll need a (free) account to access 7 | it. 8 | 9 | ## Bugs and new features 10 | 11 | If you found a bug, before creating an issue please search among the 12 | [open ones](https://github.com/decred/tinydecred/issues). Please add as many 13 | useful details as you can. 14 | 15 | If you'd like to request a new feature, either create an issue (again, after 16 | searching first) or chat with us on Matrix (see above). 17 | 18 | ## Development 19 | 20 | The [Poetry](https://python-poetry.org/) tool is used for dependency management 21 | and packaging. You'll reformat your changes with the 22 | [Black](https://black.readthedocs.io/) tool and run tests using 23 | [pytest](https://www.pytest.org/). 24 | 25 | Before each pull request is merged, a Github workflow action is run to make 26 | sure that the changes meet some minimum requirements. The action definition 27 | [file](./.github/workflows/python.yml) is a useful summary of the commands 28 | you'll run while developing. 29 | 30 | Tests are written in the [pytest](https://docs.pytest.org/) format, and may be 31 | debugged more conveniently using the 32 | [PuDB](https://documen.tician.de/pudb/) console-based visual debugger, invoked 33 | via the `./decred/debugger.sh` script. 34 | 35 | For displaying line-by-line test coverage in a web browser see the 36 | `./decred/coverage-html.sh` script. 37 | 38 | ## More information 39 | 40 | Please find more information in the dcrd 41 | [contribution guidelines](https://github.com/decred/dcrd/blob/master/docs/code_contribution_guidelines.md). 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2019, Brian Stafford 4 | Copyright (c) 2019, The Decred developers 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tinydecred 2 | 3 | TinyDecred is a Python 3 toolkit that can be used to integrate 4 | [Decred](https://decred.org/) into Python projects. 5 | 6 | The [`decred`](./decred) package contains everything needed to create wallets 7 | to send and receive DCR. 8 | 9 | The [`tinywallet`](./tinywallet) package contains a wallet based on the 10 | `decred` toolkit. 11 | 12 | Each package may be installed from the [Python Package Index](https://pypi.org/) 13 | using the [`pip`](https://pip.pypa.io/) command as usual. 14 | 15 | ## Status 16 | 17 | [![Check and test both packages](https://github.com/decred/tinydecred/workflows/Check%20and%20test%20both%20packages/badge.svg)](https://github.com/decred/tinydecred/actions) 18 | [![Test coverage](https://img.shields.io/badge/coverage-98%25-green)](./decred/coverage-html.sh) 19 | 20 | ### PyPI 21 | 22 | [![PyPI release](https://img.shields.io/pypi/v/decred)](https://pypi.org/project/decred/) 23 | [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/decred)](https://docs.python.org/3/) 24 | 25 | ### GitHub 26 | 27 | [![GitHub commit activity](https://img.shields.io/github/commit-activity/y/decred/tinydecred)](https://github.com/decred/tinydecred/graphs/commit-activity) 28 | [![GitHub contributors](https://img.shields.io/github/contributors/decred/tinydecred)](https://github.com/decred/tinydecred/graphs/contributors) 29 | [![GitHub](https://img.shields.io/github/license/decred/tinydecred)](./LICENSE) 30 | 31 | ## Roadmap 32 | 33 | In no particular order: 34 | 35 | - Staking 36 | - Schnorr signatures and Edwards curve 37 | - SPV Node 38 | - Bitcoin accounts 39 | - Decred DEX integration 40 | - Lightning network 41 | 42 | ## Contributing 43 | 44 | See the [contribution guidelines](./CONTRIBUTING.md). 45 | -------------------------------------------------------------------------------- /decred/.coveragerc: -------------------------------------------------------------------------------- 1 | # Test coverage tool configuration, see reference at 2 | # https://coverage.readthedocs.io/en/latest/config.html 3 | 4 | [run] 5 | omit = tests/* 6 | dynamic_context = test_function 7 | 8 | [report] 9 | # Regexes for lines to exclude from consideration 10 | exclude_lines = 11 | nocover 12 | 13 | # Don't complain about debug-only code. 14 | def __repr__ 15 | 16 | # Don't complain if non-runnable code isn't run. 17 | if __name__ == .__main__.: 18 | 19 | # Don't complain if tests don't hit defensive assertion code. 20 | raise AssertionError 21 | raise NotImplementedError 22 | raise CrazyKeyError 23 | raise ParameterRangeError 24 | 25 | ignore_errors = True 26 | # skip_covered = True 27 | skip_empty = True 28 | 29 | [html] 30 | show_contexts = True 31 | -------------------------------------------------------------------------------- /decred/.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | count = True 3 | max-complexity = 10 4 | max-line-length = 88 5 | statistics = True 6 | exclude = 7 | .git, 8 | .venv, 9 | .pytest_cache, 10 | __pycache__ 11 | -------------------------------------------------------------------------------- /decred/build.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, The Decred developers 3 | See LICENSE for details 4 | 5 | This file implements Cython integration (see https://cython.org/) to generate 6 | a C extension that speeds up the low-level secp256k1 crypto code. This is used 7 | by the Poetry tool when generating the wheel archive via its `build` command. 8 | 9 | It uses a currently undocumented Poetry feature, see: 10 | https://github.com/python-poetry/poetry/issues/11#issuecomment-379484540 11 | 12 | If Cython or a C compiler cannot be found, we skip the compilation 13 | of the C extension, and the Python code will be used. 14 | 15 | The shared library can also be built manually using the command: 16 | 17 | $ cythonize -X language_level=3 -a -i ./decred/crypto/secp256k1/field.py 18 | """ 19 | 20 | from distutils.command.build_ext import build_ext 21 | 22 | 23 | class BuildExt(build_ext): 24 | def build_extensions(self): 25 | try: 26 | super().build_extensions() 27 | except Exception: 28 | pass 29 | 30 | 31 | def build(setup_kwargs): 32 | try: 33 | from Cython.Build import cythonize 34 | 35 | setup_kwargs.update( 36 | dict( 37 | ext_modules=cythonize(["decred/crypto/secp256k1/field.py"]), 38 | cmdclass=dict(build_ext=BuildExt), 39 | ) 40 | ) 41 | except Exception: 42 | pass 43 | -------------------------------------------------------------------------------- /decred/coverage-html.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ## To generate line-by-line test coverage viewable in a web browser, 3 | ## change to this directory, then install the dependencies: 4 | # poetry install 5 | ## The coverage report will be in the ./htmlcov/ directory. 6 | poetry run pytest --cov-report=html --cov=decred ./tests/ 7 | -------------------------------------------------------------------------------- /decred/debugger.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Debug tests using the interactive pudb debugger. 3 | poetry run pytest --pdbcls=pudb.debugger:Debugger --trace "$1" 4 | -------------------------------------------------------------------------------- /decred/decred/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019-2020, the Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | 7 | class DecredError(Exception): 8 | pass 9 | -------------------------------------------------------------------------------- /decred/decred/btc/nets/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, The Decred developers 3 | See LICENSE for details. 4 | """ 5 | 6 | from decred import DecredError 7 | 8 | from . import mainnet, regtest, simnet, testnet 9 | 10 | 11 | the_nets = {n.Name: n for n in (mainnet, testnet, simnet, regtest)} 12 | if "testnet3" in the_nets: 13 | the_nets["testnet"] = the_nets["testnet3"] 14 | if "regtest" in the_nets: 15 | the_nets["regnet"] = the_nets["regtest"] 16 | 17 | 18 | RPCPorts = { 19 | mainnet.Name: "8332", 20 | testnet.Name: "18332", 21 | simnet.Name: "18554", 22 | regtest.Name: "18443", 23 | } 24 | 25 | 26 | def parse(name): 27 | """ 28 | Get the network parameters based on the network name. 29 | """ 30 | try: 31 | return the_nets[name] 32 | except KeyError: 33 | raise DecredError(f"unrecognized network name {name}") 34 | 35 | 36 | def normalizeName(netName): 37 | """ 38 | Remove the numerals from testnet. 39 | 40 | Args: 41 | netName (string): The raw network name. 42 | 43 | Returns: 44 | string: The network name with numerals stripped. 45 | """ 46 | return "testnet" if "testnet" in netName else netName 47 | -------------------------------------------------------------------------------- /decred/decred/btc/nets/mainnet.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, The Decred developers 3 | See LICENSE for details. 4 | 5 | mainnet holds mainnet parameters. Any values should mirror exactly 6 | https://github.com/btcsuite/btcd/blob/master/chaincfg/params.go 7 | """ 8 | 9 | Name = "mainnet" 10 | DefaultPort = "8333" 11 | DNSSeeds = [ 12 | ("seed.bitcoin.sipa.be", True), 13 | ("dnsseed.bluematt.me", True), 14 | ("dnsseed.bitcoin.dashjr.org", False), 15 | ("seed.bitcoinstats.com", True), 16 | ("seed.bitnodes.io", False), 17 | ("seed.bitcoin.jonasschnelli.ch", True), 18 | ] 19 | 20 | # Chain parameters 21 | GenesisHash = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" 22 | PowLimit = 2 ^ 224 - 1 23 | PowLimitBits = 0x1D00FFFF 24 | BIP0034Height = ( 25 | 227931 # 000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8 26 | ) 27 | BIP0065Height = ( 28 | 388381 # 000000000000000004c2b624ed5d7756c508d90fd0da2c7c679febfa6c4735f0 29 | ) 30 | BIP0066Height = ( 31 | 363725 # 00000000000000000379eaa19dce8c9b722d46ae6a57c2f1a988119488b50931 32 | ) 33 | CoinbaseMaturity = 100 34 | SubsidyReductionInterval = 210000 35 | TargetTimespan = 60 * 60 * 24 * 14 # 14 days 36 | TargetTimePerBlock = 60 * 10 # 10 minutes 37 | RetargetAdjustmentFactor = 4 # 25% less, 400% more 38 | ReduceMinDifficulty = False 39 | MinDiffReductionTime = 0 40 | GenerateSupported = False 41 | 42 | # Checkpoints ordered from oldest to newest. 43 | Checkpoints = [ 44 | (11111, "0000000069e244f73d78e8fd29ba2fd2ed618bd6fa2ee92559f542fdb26e7c1d"), 45 | (33333, "000000002dd5588a74784eaa7ab0507a18ad16a236e7b1ce69f00d7ddfb5d0a6"), 46 | (74000, "0000000000573993a3c9e41ce34471c079dcf5f52a0e824a81e7f953b8661a20"), 47 | (105000, "00000000000291ce28027faea320c8d2b054b2e0fe44a773f3eefb151d6bdc97"), 48 | (134444, "00000000000005b12ffd4cd315cd34ffd4a594f430ac814c91184a0d42d2b0fe"), 49 | (168000, "000000000000099e61ea72015e79632f216fe6cb33d7899acb35b75c8303b763"), 50 | (193000, "000000000000059f452a5f7340de6682a977387c17010ff6e6c3bd83ca8b1317"), 51 | (210000, "000000000000048b95347e83192f69cf0366076336c639f9b7228e9ba171342e"), 52 | (216116, "00000000000001b4f4b433e81ee46494af945cf96014816a4e2370f11b23df4e"), 53 | (225430, "00000000000001c108384350f74090433e7fcf79a606b8e797f065b130575932"), 54 | (250000, "000000000000003887df1f29024b06fc2200b55f8af8f35453d7be294df2d214"), 55 | (267300, "000000000000000a83fbd660e918f218bf37edd92b748ad940483c7c116179ac"), 56 | (279000, "0000000000000001ae8c72a0b0c301f67e3afca10e819efa9041e458e9bd7e40"), 57 | (300255, "0000000000000000162804527c6e9b9f0563a280525f9d08c12041def0a0f3b2"), 58 | (319400, "000000000000000021c6052e9becade189495d1c539aa37c58917305fd15f13b"), 59 | (343185, "0000000000000000072b8bf361d01a6ba7d445dd024203fafc78768ed4368554"), 60 | (352940, "000000000000000010755df42dba556bb72be6a32f3ce0b6941ce4430152c9ff"), 61 | (382320, "00000000000000000a8dc6ed5b133d0eb2fd6af56203e4159789b092defd8ab2"), 62 | (400000, "000000000000000004ec466ce4732fe6f1ed1cddc2ed4b328fff5224276e3f6f"), 63 | (430000, "000000000000000001868b2bb3a285f3cc6b33ea234eb70facf4dcdf22186b87"), 64 | (460000, "000000000000000000ef751bbce8e744ad303c47ece06c8d863e4d417efc258c"), 65 | (490000, "000000000000000000de069137b17b8d5a3dfbd5b145b2dcfb203f15d0c4de90"), 66 | (520000, "0000000000000000000d26984c0229c9f6962dc74db0a6d525f2f1640396f69c"), 67 | (550000, "000000000000000000223b7a2298fb1c6c75fb0efc28a4c56853ff4112ec6bc9"), 68 | (560000, "0000000000000000002c7b276daf6efb2b6aa68e2ce3be67ef925b3264ae7122"), 69 | ] 70 | 71 | # Consensus rule change deployments. 72 | # 73 | # The miner confirmation window is defined as: 74 | # target proof of work timespan / target proof of work spacing 75 | RuleChangeActivationThreshold = 1916 # 95% of MinerConfirmationWindow 76 | MinerConfirmationWindow = 2016 77 | 78 | # Mempool parameters 79 | RelayNonStdTxs = False 80 | 81 | # Human-readable part for Bech32 encoded segwit addresses, as defined in 82 | # BIP 173. 83 | Bech32HRPSegwit = "bc" # always bc for main net 84 | 85 | # Address encoding magics 86 | PubKeyHashAddrID = 0x00 # starts with 1 87 | ScriptHashAddrID = 0x05 # starts with 3 88 | PrivateKeyID = 0x80 # starts with 5 (uncompressed) or K (compressed) 89 | WitnessPubKeyHashAddrID = 0x06 # starts with p2 90 | WitnessScriptHashAddrID = 0x0A # starts with 7Xh 91 | 92 | # BIP32 hierarchical deterministic extended key magics 93 | HDPrivateKeyID = (0x0488ADE4).to_bytes(4, byteorder="big") # starts with xprv 94 | HDPublicKeyID = (0x0488B21E).to_bytes(4, byteorder="big") # starts with xpub 95 | 96 | # BIP44 coin type used in the hierarchical deterministic path for 97 | # address generation. 98 | HDCoinType = 0 99 | -------------------------------------------------------------------------------- /decred/decred/btc/nets/regtest.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, The Decred developers 3 | See LICENSE for details. 4 | 5 | regtest holds regtest parameters. Any values should mirror exactly 6 | https://github.com/btcsuite/btcd/blob/master/chaincfg/params.go 7 | """ 8 | 9 | Name = "regtest" 10 | DefaultPort = "18444" 11 | DNSSeeds = [] 12 | 13 | # Chain parameters 14 | GenesisHash = "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206" 15 | PowLimit = 2 ^ 255 - 1 16 | PowLimitBits = 0x207FFFFF 17 | CoinbaseMaturity = 100 18 | BIP0034Height = 100000000 # Not active - Permit ver 1 blocks 19 | BIP0065Height = 1351 # Used by regression tests 20 | BIP0066Height = 1251 # Used by regression tests 21 | SubsidyReductionInterval = 150 # TargetTimespan: 14 days 22 | TargetTimePerBlock = 60 * 10 # 10 minutes 23 | RetargetAdjustmentFactor = 4 # 25% less, 400% more 24 | ReduceMinDifficulty = True 25 | MinDiffReductionTime = 60 * 20 # TargetTimePerBlock * 2 26 | GenerateSupported = True 27 | 28 | # Checkpoints ordered from oldest to newest. 29 | Checkpoints = [] 30 | 31 | # Consensus rule change deployments. 32 | # 33 | # The miner confirmation window is defined as: 34 | # target proof of work timespan / target proof of work spacing 35 | RuleChangeActivationThreshold = 108 # 75% of MinerConfirmationWindow 36 | MinerConfirmationWindow = 144 37 | 38 | # Mempool parameters 39 | RelayNonStdTxs = True 40 | 41 | # Human-readable part for Bech32 encoded segwit addresses, as defined in 42 | # BIP 173. 43 | Bech32HRPSegwit = "bcrt" # always bcrt for reg test net 44 | 45 | # Address encoding magics 46 | PubKeyHashAddrID = 0x6F # starts with m or n 47 | ScriptHashAddrID = 0xC4 # starts with 2 48 | PrivateKeyID = 0xEF # starts with 9 (uncompressed) or c (compressed) 49 | 50 | # BIP32 hierarchical deterministic extended key magics 51 | HDPrivateKeyID = (0x04358394).to_bytes(4, byteorder="big") # starts with tprv 52 | HDPublicKeyID = (0x043587CF).to_bytes(4, byteorder="big") # starts with tpub 53 | 54 | # BIP44 coin type used in the hierarchical deterministic path for 55 | # address generation. 56 | HDCoinType = 1 57 | -------------------------------------------------------------------------------- /decred/decred/btc/nets/simnet.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, The Decred developers 3 | See LICENSE for details. 4 | 5 | simnet holds simnet parameters. Any values should mirror exactly 6 | https://github.com/btcsuite/btcd/blob/master/chaincfg/params.go 7 | """ 8 | 9 | Name = "simnet" 10 | DefaultPort = "18555" 11 | DNSSeeds = [] # NOTE: There must NOT be any seeds. 12 | 13 | # Chain parameters 14 | GenesisHash = "683e86bd5c6d110d91b94b97137ba6bfe02dbbdb8e3dff722a669b5d69d77af6" 15 | PowLimit = 2 ^ 255 - 1 16 | PowLimitBits = 0x207FFFFF 17 | BIP0034Height = 0 # Always active on simnet 18 | BIP0065Height = 0 # Always active on simnet 19 | BIP0066Height = 0 # Always active on simnet 20 | CoinbaseMaturity = 100 21 | SubsidyReductionInterval = 210000 22 | TargetTimespan = 60 * 60 * 24 * 14 # 14 days 23 | TargetTimePerBlock = 60 * 10 # 10 minutes 24 | RetargetAdjustmentFactor = 4 # 25% less, 400% more 25 | ReduceMinDifficulty = True 26 | MinDiffReductionTime = 60 * 20 # TargetTimePerBlock * 2 27 | GenerateSupported = True 28 | 29 | # Checkpoints ordered from oldest to newest. 30 | Checkpoints = [] 31 | 32 | # Consensus rule change deployments. 33 | # 34 | # The miner confirmation window is defined as: 35 | # target proof of work timespan / target proof of work spacing 36 | RuleChangeActivationThreshold = 75 # 75% of MinerConfirmationWindow 37 | MinerConfirmationWindow = 100 38 | 39 | # Mempool parameters 40 | RelayNonStdTxs = True 41 | 42 | # Human-readable part for Bech32 encoded segwit addresses, as defined in 43 | # BIP 173. 44 | Bech32HRPSegwit = "sb" # always sb for sim net 45 | 46 | # Address encoding magics 47 | PubKeyHashAddrID = 0x3F # starts with S 48 | ScriptHashAddrID = 0x7B # starts with s 49 | PrivateKeyID = 0x64 # starts with 4 (uncompressed) or F (compressed) 50 | WitnessPubKeyHashAddrID = 0x19 # starts with Gg 51 | WitnessScriptHashAddrID = 0x28 # starts with ? 52 | 53 | # BIP32 hierarchical deterministic extended key magics 54 | HDPrivateKeyID = (0x0420B900).to_bytes(4, byteorder="big") # starts with sprv 55 | HDPublicKeyID = (0x0420BD3A).to_bytes(4, byteorder="big") # starts with spub 56 | 57 | # BIP44 coin type used in the hierarchical deterministic path for 58 | # address generation. 59 | HDCoinType = 115 # ASCII for s 60 | -------------------------------------------------------------------------------- /decred/decred/btc/nets/testnet.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, The Decred developers 3 | See LICENSE for details. 4 | 5 | testnet holds testnet parameters. Any values should mirror exactly 6 | https://github.com/btcsuite/btcd/blob/master/chaincfg/params.go 7 | """ 8 | 9 | Name = "testnet3" 10 | DefaultPort = "18333" 11 | DNSSeeds = [ 12 | ("testnet-seed.bitcoin.jonasschnelli.ch", True), 13 | ("testnet-seed.bitcoin.schildbach.de", False), 14 | ("seed.tbtc.petertodd.org", True), 15 | ("testnet-seed.bluematt.me", False), 16 | ] 17 | 18 | # Chain parameters 19 | GenesisHash = "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943" 20 | PowLimit = 2 ^ 224 - 1 21 | PowLimitBits = 0x1D00FFFF 22 | BIP0034Height = ( 23 | 21111 # 0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8 24 | ) 25 | BIP0065Height = ( 26 | 581885 # 00000000007f6655f22f98e72ed80d8b06dc761d5da09df0fa1dc4be4f861eb6 27 | ) 28 | BIP0066Height = ( 29 | 330776 # 000000002104c8c45e99a8853285a3b592602a3ccde2b832481da85e9e4ba182 30 | ) 31 | CoinbaseMaturity = 100 32 | SubsidyReductionInterval = 210000 33 | TargetTimespan = 60 * 60 * 24 * 14 # 14 days 34 | TargetTimePerBlock = 60 * 10 # 10 minutes 35 | RetargetAdjustmentFactor = 4 # 25% less, 400% more 36 | ReduceMinDifficulty = True 37 | MinDiffReductionTime = 60 * 20 # TargetTimePerBlock * 2 38 | GenerateSupported = False 39 | 40 | # Checkpoints ordered from oldest to newest. 41 | Checkpoints = [ 42 | (546, "000000002a936ca763904c3c35fce2f3556c559c0214345d31b1bcebf76acb70"), 43 | (100000, "00000000009e2958c15ff9290d571bf9459e93b19765c6801ddeccadbb160a1e"), 44 | (200000, "0000000000287bffd321963ef05feab753ebe274e1d78b2fd4e2bfe9ad3aa6f2"), 45 | (300001, "0000000000004829474748f3d1bc8fcf893c88be255e6d7f571c548aff57abf4"), 46 | (400002, "0000000005e2c73b8ecb82ae2dbc2e8274614ebad7172b53528aba7501f5a089"), 47 | (500011, "00000000000929f63977fbac92ff570a9bd9e7715401ee96f2848f7b07750b02"), 48 | (600002, "000000000001f471389afd6ee94dcace5ccc44adc18e8bff402443f034b07240"), 49 | (700000, "000000000000406178b12a4dea3b27e13b3c4fe4510994fd667d7c1e6a3f4dc1"), 50 | (800010, "000000000017ed35296433190b6829db01e657d80631d43f5983fa403bfdb4c1"), 51 | (900000, "0000000000356f8d8924556e765b7a94aaebc6b5c8685dcfa2b1ee8b41acd89b"), 52 | (1000007, "00000000001ccb893d8a1f25b70ad173ce955e5f50124261bbbc50379a612ddf"), 53 | (1100007, "00000000000abc7b2cd18768ab3dee20857326a818d1946ed6796f42d66dd1e8"), 54 | (1200007, "00000000000004f2dc41845771909db57e04191714ed8c963f7e56713a7b6cea"), 55 | (1300007, "0000000072eab69d54df75107c052b26b0395b44f77578184293bf1bb1dbd9fa"), 56 | ] 57 | 58 | # Consensus rule change deployments. 59 | # 60 | # The miner confirmation window is defined as: 61 | # target proof of work timespan / target proof of work spacing 62 | RuleChangeActivationThreshold = 1512 # 75% of MinerConfirmationWindow 63 | MinerConfirmationWindow = 2016 64 | 65 | # Mempool parameters 66 | RelayNonStdTxs = True 67 | 68 | # Human-readable part for Bech32 encoded segwit addresses, as defined in 69 | # BIP 173. 70 | Bech32HRPSegwit = "tb" # always tb for test net 71 | 72 | # Address encoding magics 73 | PubKeyHashAddrID = 0x6F # starts with m or n 74 | ScriptHashAddrID = 0xC4 # starts with 2 75 | WitnessPubKeyHashAddrID = 0x03 # starts with QW 76 | WitnessScriptHashAddrID = 0x28 # starts with T7n 77 | PrivateKeyID = 0xEF # starts with 9 (uncompressed) or c (compressed) 78 | 79 | # BIP32 hierarchical deterministic extended key magics 80 | HDPrivateKeyID = (0x04358394).to_bytes(4, byteorder="big") # starts with tprv 81 | HDPublicKeyID = (0x043587CF).to_bytes(4, byteorder="big") # starts with tpub 82 | 83 | # BIP44 coin type used in the hierarchical deterministic path for 84 | # address generation. 85 | HDCoinType = 1 86 | -------------------------------------------------------------------------------- /decred/decred/crypto/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/decred/decred/crypto/__init__.py -------------------------------------------------------------------------------- /decred/decred/crypto/rando.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019, Brian Stafford 3 | Copyright (c) 2019-20, The Decred developers 4 | See LICENSE for details 5 | """ 6 | 7 | import os 8 | 9 | from decred import DecredError 10 | from decred.util.encode import ByteArray 11 | 12 | 13 | KEY_SIZE = 32 14 | HASH_SIZE = 32 15 | 16 | MinSeedBytes = 16 # 128 bits 17 | MaxSeedBytes = 64 # 512 bits 18 | 19 | 20 | def checkSeedLength(length): 21 | """ 22 | Check that seed length is correct. 23 | 24 | Args: 25 | length int: the seed length to be checked. 26 | 27 | Raises: 28 | DecredError if length is not between MinSeedBytes and MaxSeedBytes 29 | included. 30 | """ 31 | if length < MinSeedBytes or length > MaxSeedBytes: 32 | raise DecredError(f"Invalid seed length {length}") 33 | 34 | 35 | def generateSeed(length=MaxSeedBytes): 36 | """ 37 | Generate a cryptographically-strong random seed. 38 | 39 | Returns: 40 | bytes: a random bytes object of the given length. 41 | 42 | Raises: 43 | DecredError if length is not between MinSeedBytes and MaxSeedBytes 44 | included. 45 | """ 46 | checkSeedLength(length) 47 | return os.urandom(length) 48 | 49 | 50 | def newHashRaw(): 51 | """ 52 | Generate a random hash of HASH_SIZE length. 53 | 54 | Returns: 55 | bytes: a random object of HASH_SIZE length. 56 | """ 57 | return generateSeed(HASH_SIZE) 58 | 59 | 60 | def newHash(): 61 | """ 62 | Generate a wrapped random hash of HASH_SIZE length. 63 | 64 | Returns: 65 | ByteArray: a random object of HASH_SIZE length. 66 | """ 67 | return ByteArray(newHashRaw()) 68 | 69 | 70 | def newKeyRaw(): 71 | """ 72 | Generate a random key of KEY_SIZE length. 73 | 74 | Returns: 75 | bytes: a random object of KEY_SIZE length. 76 | """ 77 | return generateSeed(KEY_SIZE) 78 | 79 | 80 | def newKey(): 81 | """ 82 | Generate a wrapped random key of KEY_SIZE length. 83 | 84 | Returns: 85 | ByteArray: a random object of KEY_SIZE length. 86 | """ 87 | return ByteArray(newKeyRaw()) 88 | -------------------------------------------------------------------------------- /decred/decred/crypto/secp256k1/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ["curve"] 2 | -------------------------------------------------------------------------------- /decred/decred/crypto/secp256k1/field.pxd: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, The Decred developers 3 | See LICENSE for details 4 | 5 | Definitions allowing Cython to generate optimized C code that builds a 6 | dynamic library speeding up the field.py code. 7 | """ 8 | 9 | import cython 10 | 11 | 12 | cdef unsigned long twoBitsMask = 0x03 13 | cdef unsigned long fourBitsMask = 0x0F 14 | cdef unsigned long sixBitsMask = 0x3F 15 | cdef unsigned long eightBitsMask = 0xFF 16 | cdef unsigned long fieldWords = 10 17 | cdef unsigned long fieldBase = 26 18 | cdef unsigned long fieldBaseMask = (1 << fieldBase) - 1 19 | cdef unsigned long fieldMSBBits = 256 - (fieldBase * (fieldWords - 1)) 20 | cdef unsigned long fieldMSBMask = (1 << fieldMSBBits) - 1 21 | cdef unsigned long fieldPrimeWordZero = 0x3FFFC2F 22 | cdef unsigned long fieldPrimeWordOne = 0x3FFFFBF 23 | cdef unsigned long primePartBy16 = 68719492368 24 | 25 | 26 | cdef class FieldVal: 27 | cdef public unsigned long n[10] 28 | 29 | @cython.locals( 30 | m=cython.ulong, 31 | t0=cython.ulong, 32 | t1=cython.ulong, 33 | t2=cython.ulong, 34 | t3=cython.ulong, 35 | t4=cython.ulong, 36 | t5=cython.ulong, 37 | t6=cython.ulong, 38 | t7=cython.ulong, 39 | t8=cython.ulong, 40 | t9=cython.ulong, 41 | ) 42 | cpdef normalize(self) 43 | 44 | cpdef negateVal(self, FieldVal val, unsigned long magnitude) 45 | 46 | cpdef add(self, FieldVal val) 47 | 48 | @cython.locals( 49 | m=cython.ulong, 50 | n=cython.ulong, 51 | t0=cython.ulong, 52 | t1=cython.ulong, 53 | t2=cython.ulong, 54 | t3=cython.ulong, 55 | t4=cython.ulong, 56 | t5=cython.ulong, 57 | t6=cython.ulong, 58 | t7=cython.ulong, 59 | t8=cython.ulong, 60 | t9=cython.ulong, 61 | t10=cython.ulong, 62 | t11=cython.ulong, 63 | t12=cython.ulong, 64 | t13=cython.ulong, 65 | t14=cython.ulong, 66 | t15=cython.ulong, 67 | t16=cython.ulong, 68 | t17=cython.ulong, 69 | t18=cython.ulong, 70 | t19=cython.ulong, 71 | ) 72 | cpdef squareVal(self, FieldVal val) 73 | 74 | cpdef mulInt(self, long val) 75 | 76 | @cython.locals( 77 | d=cython.ulong, 78 | m=cython.ulong, 79 | t0=cython.ulong, 80 | t1=cython.ulong, 81 | t2=cython.ulong, 82 | t3=cython.ulong, 83 | t4=cython.ulong, 84 | t5=cython.ulong, 85 | t6=cython.ulong, 86 | t7=cython.ulong, 87 | t8=cython.ulong, 88 | t9=cython.ulong, 89 | t10=cython.ulong, 90 | t11=cython.ulong, 91 | t12=cython.ulong, 92 | t13=cython.ulong, 93 | t14=cython.ulong, 94 | t15=cython.ulong, 95 | t16=cython.ulong, 96 | t17=cython.ulong, 97 | t18=cython.ulong, 98 | t19=cython.ulong, 99 | ) 100 | cpdef mul2(self, FieldVal val, FieldVal val2) 101 | 102 | cpdef add2(self, FieldVal val, FieldVal val2) 103 | 104 | cpdef putBytes(self, char b[32]) 105 | 106 | # inverse relies heavily on FieldVal methods, preventing optimization. 107 | # @cython.locals( 108 | # a2=FieldVal, 109 | # a3=FieldVal, 110 | # a4=FieldVal, 111 | # a10=FieldVal, 112 | # a11=FieldVal, 113 | # a21=FieldVal, 114 | # a42=FieldVal, 115 | # a45=FieldVal, 116 | # a63=FieldVal, 117 | # a1019=FieldVal, 118 | # a1023=FieldVal, 119 | # ) 120 | # cpdef inverse(self) 121 | -------------------------------------------------------------------------------- /decred/decred/dcr/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/decred/decred/dcr/__init__.py -------------------------------------------------------------------------------- /decred/decred/dcr/agenda.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019-2020, Brian Stafford 3 | Copyright (c) 2020, the Decred developers 4 | See LICENSE for details 5 | """ 6 | 7 | 8 | class AgendaInfo: 9 | """ 10 | Models data for deployments of a GetBlockChainInfoResult. 11 | """ 12 | 13 | def __init__(self, status, since, startTime, expireTime): 14 | """ 15 | Args: 16 | status (str): one of "defined", "started", "lockedin", "active", 17 | "failed". 18 | since (int): height of last state change. 19 | startTime (int): start time. 20 | expireTime (int): end time. 21 | """ 22 | self.status = status 23 | self.since = since 24 | self.startTime = startTime 25 | self.expireTime = expireTime 26 | 27 | @staticmethod 28 | def parse(obj): 29 | """ 30 | Parse the AgendaInfo from the decoded RPC response. 31 | 32 | Args: 33 | obj (dict): the decoded dcrd RPC response. 34 | 35 | Returns: 36 | AgendaInfo: the AgendaInfo. 37 | """ 38 | return AgendaInfo( 39 | status=obj["status"], 40 | since=obj.get("since", 0), 41 | startTime=obj["starttime"], 42 | expireTime=obj["expiretime"], 43 | ) 44 | 45 | 46 | class AgendaChoices: 47 | """ 48 | Agenda individual choices such as abstain, yes, no. 49 | """ 50 | 51 | def __init__(self, id_, description, bits, isAbstain, isNo, count, progress): 52 | """ 53 | Args: 54 | id_ (str): unique identifier of this choice. 55 | description (str): description of this choice. 56 | bits (int): bits that identify this choice. 57 | isAbstain (bool): this choice is to abstain from change. 58 | isNo (bool): hard no choice (1 and only 1 per agenda). 59 | count (int): how many votes received. 60 | progress (float): progress of the overall count. 61 | """ 62 | self.id = id_ 63 | self.description = description 64 | self.bits = bits 65 | self.isAbstain = isAbstain 66 | self.isNo = isNo 67 | self.count = count 68 | self.progress = progress 69 | 70 | def __eq__(self, other): 71 | try: 72 | return self.id == other.id 73 | except AttributeError: 74 | return False 75 | 76 | @staticmethod 77 | def parse(obj): 78 | """ 79 | Parse the AgendaChoices from the decoded RPC response. 80 | 81 | Args: 82 | obj (dict): the decoded dcrd RPC response. 83 | 84 | Returns: 85 | AgendaChoices: the parsed AgendaChoices. 86 | """ 87 | return AgendaChoices( 88 | id_=obj["id"], 89 | description=obj["description"], 90 | bits=obj["bits"], 91 | isAbstain=obj["isabstain"], 92 | isNo=obj["isno"], 93 | count=obj["count"], 94 | progress=obj["progress"], 95 | ) 96 | 97 | 98 | class Agenda: 99 | """ 100 | An agenda with name, description, and its AgendaChoices. 101 | """ 102 | 103 | def __init__( 104 | self, 105 | id_, 106 | description, 107 | mask, 108 | startTime, 109 | expireTime, 110 | status, 111 | quorumProgress, 112 | choices, 113 | ): 114 | """ 115 | Args: 116 | id_ (str): unique identifier of this agenda. 117 | description (str): description of this agenda. 118 | mask (int): agenda mask. 119 | startTime (int): time agenda becomes valid. 120 | expireTime (int): time agenda becomes invalid. 121 | status (str): agenda status. 122 | quorumProgress (float): progress of quorum reached. 123 | choices list(AgendaChoices): all choices in this agenda. 124 | """ 125 | self.id = id_ 126 | self.description = description 127 | self.mask = mask 128 | self.startTime = startTime 129 | self.expireTime = expireTime 130 | self.status = status 131 | self.quorumProgress = quorumProgress 132 | self.choices = choices 133 | 134 | def __eq__(self, other): 135 | try: 136 | return self.id == other.id 137 | except AttributeError: 138 | return False 139 | 140 | @staticmethod 141 | def parse(obj): 142 | """ 143 | Parse the Agenda from the decoded RPC response. 144 | 145 | Args: 146 | obj (dict): the decoded dcrd RPC response. 147 | 148 | Returns: 149 | Agenda: the parsed Agenda info. 150 | """ 151 | return Agenda( 152 | id_=obj["id"], 153 | description=obj["description"], 154 | mask=obj["mask"], 155 | startTime=obj["starttime"], 156 | expireTime=obj["expiretime"], 157 | status=obj["status"], 158 | quorumProgress=obj["quorumprogress"], 159 | choices=[AgendaChoices.parse(choice) for choice in obj["choices"]], 160 | ) 161 | 162 | 163 | class AgendasInfo: 164 | """ 165 | All current agenda information for the current network. agendas contains 166 | a list of Agenda. 167 | """ 168 | 169 | def __init__( 170 | self, 171 | currentHeight, 172 | startHeight, 173 | endHeight, 174 | hash_, 175 | voteVersion, 176 | quorum, 177 | totalVotes, 178 | agendas, 179 | ): 180 | """ 181 | Args: 182 | currentHeight (int): the current height. 183 | startHeight (int): the initial height. 184 | endHeight (int): the final height. 185 | hash_ (str): the hash. 186 | voteVersion (int): the vote version. 187 | quorum (float): the quorum. 188 | totalVotes (int): the total number of votes. 189 | agendas list(Agenda): all agendas in this AgendasInfo. 190 | """ 191 | self.currentHeight = currentHeight 192 | self.startHeight = startHeight 193 | self.endHeight = endHeight 194 | self.hash = hash_ 195 | self.voteVersion = voteVersion 196 | self.quorum = quorum 197 | self.totalVotes = totalVotes 198 | self.agendas = agendas 199 | 200 | @staticmethod 201 | def parse(obj): 202 | """ 203 | Parse the AgendasInfo from the decoded RPC response. 204 | 205 | Args: 206 | obj (dict): the decoded dcrd RPC response. 207 | 208 | Returns: 209 | AgendasInfo: the AgendasInfo. 210 | """ 211 | return AgendasInfo( 212 | currentHeight=obj["currentheight"], 213 | startHeight=obj["startheight"], 214 | endHeight=obj["endheight"], 215 | hash_=obj["hash"], 216 | voteVersion=obj["voteversion"], 217 | quorum=obj["quorum"], 218 | totalVotes=obj["totalvotes"], 219 | agendas=[Agenda.parse(agenda) for agenda in obj["agendas"]], 220 | ) 221 | -------------------------------------------------------------------------------- /decred/decred/dcr/assets/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/decred/decred/dcr/assets/favicon-32x32.png -------------------------------------------------------------------------------- /decred/decred/dcr/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 37 | 39 | 40 | 42 | image/svg+xml 43 | 45 | 46 | 47 | 48 | 49 | 51 | 59 | 63 | 67 | 68 | 76 | 80 | 84 | 85 | 88 | 93 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /decred/decred/dcr/constants.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019, Brian Stafford 3 | See LICENSE for details 4 | 5 | Just some constants. 6 | """ 7 | 8 | import os.path 9 | 10 | 11 | ASSETS_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "assets") 12 | FAVICON = os.path.join(ASSETS_DIR, "favicon-32x32.png") 13 | LOGO = os.path.join(ASSETS_DIR, "logo.svg") 14 | INF = float("inf") 15 | MINUTE = 60 16 | DAY = 86400 17 | HOUR = 3600 18 | SYMBOL = "DCR" 19 | -------------------------------------------------------------------------------- /decred/decred/dcr/nets/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019, The Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | from decred import DecredError 7 | 8 | from . import mainnet, simnet, testnet 9 | 10 | 11 | the_nets = {n.Name: n for n in (mainnet, testnet, simnet)} 12 | if "testnet3" in the_nets: 13 | the_nets["testnet"] = the_nets["testnet3"] 14 | 15 | 16 | DcrdPorts = { 17 | mainnet.Name: "9109", 18 | testnet.Name: "19109", 19 | simnet.Name: "19556", 20 | } 21 | 22 | 23 | def parse(name): 24 | """ 25 | Get the network parameters based on the network name. 26 | """ 27 | # Set testnet to DCR for now. If more coins are added, a better solution 28 | # will be needed. 29 | try: 30 | return the_nets[name] 31 | except KeyError: 32 | raise DecredError(f"unrecognized network name {name}") 33 | 34 | 35 | def normalizeName(netName): 36 | """ 37 | Remove the numerals from testnet. 38 | 39 | Args: 40 | netName (string): The raw network name. 41 | 42 | Returns: 43 | string: The network name with numerals stripped. 44 | """ 45 | return "testnet" if "testnet" in netName else netName 46 | -------------------------------------------------------------------------------- /decred/decred/dcr/nets/mainnet.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019, Brian Stafford 3 | Copyright (c) 2019, The Decred developers 4 | See LICENSE for details 5 | 6 | mainnet holds mainnet parameters. Any values should mirror exactly 7 | https://github.com/decred/dcrd/blob/master/chaincfg/mainnetparams.go 8 | """ 9 | 10 | from decred.dcr import constants as C 11 | 12 | 13 | Name = "mainnet" 14 | DefaultPort = "9108" 15 | DNSSeeds = [ 16 | ("mainnet-seed.decred.mindcry.org", True), 17 | ("mainnet-seed.decred.netpurgatory.com", True), 18 | ("mainnet-seed.decred.org", True), 19 | ] 20 | GenesisHash = "298e5cc3d985bfe7f81dc135f360abe089edd4396b86d2de66b0cef42b21d980" 21 | PowLimit = 2 ^ 224 - 1 22 | 23 | # POW parameters 24 | PowLimitBits = 0x1D00FFFF 25 | ReduceMinDifficulty = False 26 | MinDiffReductionTime = 0 # Does not apply since ReduceMinDifficulty false 27 | GenerateSupported = False 28 | MaximumBlockSizes = [393216] 29 | MaxTxSize = 393216 30 | TargetTimePerBlock = C.MINUTE * 5 31 | WorkDiffAlpha = 1 32 | WorkDiffWindowSize = 144 33 | WorkDiffWindows = 20 34 | TargetTimespan = C.MINUTE * 5 * 144 # TimePerBlock * WindowSize 35 | RetargetAdjustmentFactor = 4 36 | 37 | # Subsidy parameters. 38 | BaseSubsidy = 3119582664 # 21m 39 | MulSubsidy = 100 40 | DivSubsidy = 101 41 | SubsidyReductionInterval = 6144 42 | WorkRewardProportion = 6 43 | StakeRewardProportion = 3 44 | BlockTaxProportion = 1 45 | 46 | # Checkpoints ordered from oldest to newest. 47 | Checkpoints = [ 48 | (440, "0000000000002203eb2c95ee96906730bb56b2985e174518f90eb4db29232d93"), 49 | (24480, "0000000000000c9d4239c4ef7ef3fb5aaeed940244bc69c57c8c5e1f071b28a6"), 50 | (48590, "0000000000000d5e0de21a96d3c965f5f2db2c82612acd7389c140c9afe92ba7"), 51 | (54770, "00000000000009293d067b1126b7de07fc9b2b94ee50dfe0d48c239a7adb072c"), 52 | (60720, "0000000000000a64475d68ffb9ad89a3d147c0f5138db26b40da9d19d0004117"), 53 | (65270, "0000000000000021f107601962789b201f0a0cbb98ac5f8c12b93d94e795b441"), 54 | (75380, "0000000000000e7d13cfc85806aa720fe3670980f5b7d33253e4f41985558372"), 55 | (85410, "00000000000013ec928074bea6eac9754aa614c7acb20edf300f18b0cd122692"), 56 | (99880, "0000000000000cb2a9a9ded647b9f78aae51ace32dd8913701d420ead272913c"), 57 | (123080, "000000000000009ea6e02d0f0424f445ed50686f9ae4aecdf3b268e981114477"), 58 | (135960, "00000000000001d2f9bbca9177972c0ba45acb40836b72945a75d73b99079498"), 59 | (139740, "00000000000001397179ae1aff156fb1aea228938d06b83e43b78b1c44527b5b"), 60 | (155900, "000000000000008557e37fb05177fc5a54e693de20689753639135f85a2dcb2e"), 61 | (164300, "000000000000009ed067ff51cd5e15f3c786222a5183b20a991a80ce535907a9"), 62 | (181020, "00000000000000b77d832cb2cbed02908d69323862a53e56345400ad81a6fb8f"), 63 | (189950, "000000000000007341d8ae2ea7e41f25cee00e1a70a4a3dc1cb055d14ecb2e11"), 64 | (214672, "0000000000000021d5cbeead55cb7fd659f07e8127358929ffc34cd362209758"), 65 | (259810, "0000000000000000ee0fbf469a9f32477ffbb46ebd7a280a53c842ab4243f97c"), 66 | (295940, "0000000000000000148852c8a919addf4043f9f267b13c08df051d359f1622ca"), 67 | ] 68 | 69 | # The miner confirmation window is defined as: 70 | # target proof of work timespan / target proof of work spacing 71 | RuleChangeActivationQuorum = ( 72 | 4032 # 10 % of RuleChangeActivationInterval * TicketsPerBlock 73 | ) 74 | RuleChangeActivationMultiplier = 3 # 75% 75 | RuleChangeActivationDivisor = 4 76 | RuleChangeActivationInterval = 2016 * 4 # 4 weeks 77 | 78 | 79 | # Enforce current block version once majority of the network has 80 | # upgraded. 81 | # 75% (750 / 1000) 82 | # Reject previous block versions once a majority of the network has 83 | # upgraded. 84 | # 95% (950 / 1000) 85 | BlockEnforceNumRequired = 750 86 | BlockRejectNumRequired = 950 87 | BlockUpgradeNumToCheck = 1000 88 | 89 | # AcceptNonStdTxs is a mempool param to either accept and relay 90 | # non standard txs to the network or reject them 91 | AcceptNonStdTxs = False 92 | 93 | # Address encoding magics 94 | NetworkAddressPrefix = "D" 95 | PubKeyAddrID = (0x1386).to_bytes(2, byteorder="big") # starts with Dk 96 | PubKeyHashAddrID = (0x073F).to_bytes(2, byteorder="big") # starts with Ds 97 | PKHEdwardsAddrID = (0x071F).to_bytes(2, byteorder="big") # starts with De 98 | PKHSchnorrAddrID = (0x0701).to_bytes(2, byteorder="big") # starts with DS 99 | ScriptHashAddrID = (0x071A).to_bytes(2, byteorder="big") # starts with Dc 100 | PrivateKeyID = (0x22DE).to_bytes(2, byteorder="big") # starts with Pm 101 | 102 | # BIP32 hierarchical deterministic extended key magics 103 | HDPrivateKeyID = (0x02FDA4E8).to_bytes(4, byteorder="big") # starts with dprv 104 | HDPublicKeyID = (0x02FDA926).to_bytes(4, byteorder="big") # starts with dpub 105 | 106 | # BIP44 coin type used in the hierarchical deterministic path for 107 | # address generation. 108 | SLIP0044CoinType = 42 # SLIP0044, Decred 109 | LegacyCoinType = 20 # for backwards compatibility 110 | 111 | # Decred PoS parameters 112 | MinimumStakeDiff = 2 * 1e8 # 2 Coin 113 | TicketPoolSize = 8192 114 | TicketsPerBlock = 5 115 | TicketMaturity = 256 116 | TicketExpiry = 40960 # 5*TicketPoolSize 117 | CoinbaseMaturity = 256 118 | SStxChangeMaturity = 1 119 | TicketPoolSizeWeight = 4 120 | StakeDiffAlpha = 1 # Minimal 121 | StakeDiffWindowSize = 144 122 | StakeDiffWindows = 20 123 | StakeVersionInterval = 144 * 2 * 7 # ~1 week 124 | MaxFreshStakePerBlock = 20 # 4*TicketsPerBlock 125 | StakeEnabledHeight = 256 + 256 # CoinbaseMaturity + TicketMaturity 126 | StakeValidationHeight = 4096 # ~14 days 127 | StakeBaseSigScript = (0x0000).to_bytes(2, byteorder="big") 128 | StakeMajorityMultiplier = 3 129 | StakeMajorityDivisor = 4 130 | 131 | OrganizationPkScript = (0xA914F5916158E3E2C4551C1796708DB8367207ED13BB87).to_bytes( 132 | 23, byteorder="big" 133 | ) 134 | OrganizationPkScriptVersion = 0 135 | 136 | # Convenience constants 137 | GENESIS_STAMP = 1454954400 138 | STAKE_SPLIT = 0.3 139 | POW_SPLIT = 0.6 140 | TREASURY_SPLIT = 0.1 141 | 142 | BlockOneSubsidy = int(1680000 * 1e8) 143 | -------------------------------------------------------------------------------- /decred/decred/dcr/nets/simnet.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019, Brian Stafford 3 | Copyright (c) 2019, The Decred developers 4 | See LICENSE for details 5 | 6 | simnet holds simnet parameters. Any values should mirror exactly 7 | https://github.com/decred/dcrd/blob/master/chaincfg/simnetparams.go 8 | """ 9 | 10 | # SimNetParams defines the network parameters for the simulation test network. 11 | # This network is similar to the normal test network except it is intended for 12 | # private use within a group of individuals doing simulation testing and full 13 | # integration tests between different applications such as wallets, voting 14 | # service providers, mining pools, block explorers, and other services that 15 | # build on Decred. 16 | # 17 | # The functionality is intended to differ in that the only nodes which are 18 | # specifically specified are used to create the network rather than following 19 | # normal discovery rules. This is important as otherwise it would just turn 20 | # into another public testnet. 21 | Name = "simnet" 22 | DefaultPort = "18555" 23 | DNSSeeds = None # NOTE: There must NOT be any seeds. 24 | 25 | # Chain parameters 26 | GenesisHash = "5bec7567af40504e0994db3b573c186fffcc4edefe096ff2e58d00523bd7e8a6" 27 | PowLimit = 2 ** 255 - 1 28 | PowLimitBits = 0x207FFFFF 29 | ReduceMinDifficulty = False 30 | MinDiffReductionTime = 0 # Does not apply since ReduceMinDifficulty fals 31 | GenerateSupported = False 32 | MaximumBlockSizes = [1310720] 33 | MaxTxSize = 1000000 34 | TargetTimePerBlock = 1 # one secon 35 | WorkDiffAlpha = 1 36 | WorkDiffWindowSize = 8 37 | WorkDiffWindows = 4 38 | TargetTimespan = 8 # TimePerBlock * WindowSize 39 | RetargetAdjustmentFactor = 4 40 | 41 | # Subsidy parameters. 42 | BaseSubsidy = 50000000000 43 | MulSubsidy = 100 44 | DivSubsidy = 101 45 | SubsidyReductionInterval = 128 46 | WorkRewardProportion = 6 47 | StakeRewardProportion = 3 48 | BlockTaxProportion = 1 49 | 50 | # Checkpoints ordered from oldest to newest. 51 | Checkpoints = (None,) 52 | 53 | # Consensus rule change deployments. 54 | # 55 | # The miner confirmation window is defined as: 56 | # target proof of work timespan / target proof of work spacing 57 | # 10% of RuleChangeActivationInterval * TicketsPerBlock 58 | RuleChangeActivationQuorum = 160 59 | RuleChangeActivationMultiplier = 3 # 75% 60 | RuleChangeActivationDivisor = 4 61 | RuleChangeActivationInterval = 320 # 320 seconds 62 | 63 | # Enforce current block version once majority of the network has upgraded. 64 | # 51% (51 / 100) 65 | # Reject previous block versions once a majority of the network has upgraded. 66 | # 75% (75 / 100) 67 | BlockEnforceNumRequired = 51 68 | BlockRejectNumRequired = 75 69 | BlockUpgradeNumToCheck = 100 70 | 71 | # AcceptNonStdTxs is a mempool param to either accept and relay 72 | # non standard txs to the network or reject them 73 | AcceptNonStdTxs = True 74 | 75 | # Address encoding magics 76 | NetworkAddressPrefix = ("S",) 77 | PubKeyAddrID = (0x276F).to_bytes(2, byteorder="big") # starts with Sk 78 | PubKeyHashAddrID = (0x0E91).to_bytes(2, byteorder="big") # starts with Ss 79 | PKHEdwardsAddrID = (0x0E71).to_bytes(2, byteorder="big") # starts with Se 80 | PKHSchnorrAddrID = (0x0E53).to_bytes(2, byteorder="big") # starts with SS 81 | ScriptHashAddrID = (0x0E6C).to_bytes(2, byteorder="big") # starts with Sc 82 | PrivateKeyID = (0x2307).to_bytes(2, byteorder="big") # starts with Ps 83 | 84 | # BIP32 hierarchical deterministic extended key magics 85 | HDPrivateKeyID = (0x0420B903).to_bytes(4, byteorder="big") # starts with sprv 86 | HDPublicKeyID = (0x0420BD3D).to_bytes(4, byteorder="big") # starts with spub 87 | 88 | # BIP44 coin type used in the hierarchical deterministic path for 89 | # address generation. 90 | SLIP0044CoinType = 1 # SLIP0044, Testnet (all coins) 91 | LegacyCoinType = 115 # ASCII for s, for backwards compatibility 92 | 93 | # Decred PoS parameters 94 | MinimumStakeDiff = 20000 95 | TicketPoolSize = 64 96 | TicketsPerBlock = 5 97 | TicketMaturity = 16 98 | TicketExpiry = 384 # 6*TicketPoolSize 99 | CoinbaseMaturity = 16 100 | SStxChangeMaturity = 1 101 | TicketPoolSizeWeight = 4 102 | StakeDiffAlpha = 1 103 | StakeDiffWindowSize = 8 104 | StakeDiffWindows = 8 105 | StakeVersionInterval = 8 * 2 * 7 106 | MaxFreshStakePerBlock = 20 # 4*TicketsPerBlock 107 | StakeEnabledHeight = 16 + 16 # CoinbaseMaturity + TicketMaturity 108 | StakeValidationHeight = 16 + (64 * 2) # CoinbaseMaturity + TicketPoolSize*2 109 | StakeBaseSigScript = (0xDEADBEEF).to_bytes(4, byteorder="big") 110 | StakeMajorityMultiplier = 3 111 | StakeMajorityDivisor = 4 112 | 113 | # Decred organization related parameters 114 | # 115 | # Treasury address is a 3-of-3 P2SH going to a wallet with seed: 116 | # aardvark adroitness aardvark adroitness 117 | # aardvark adroitness aardvark adroitness 118 | # aardvark adroitness aardvark adroitness 119 | # aardvark adroitness aardvark adroitness 120 | # aardvark adroitness aardvark adroitness 121 | # aardvark adroitness aardvark adroitness 122 | # aardvark adroitness aardvark adroitness 123 | # aardvark adroitness aardvark adroitness 124 | # briefcase 125 | # (seed 0x0000000000000000000000000000000000000000000000000000000000000000) 126 | # 127 | # This same wallet owns the three ledger outputs for simnet. 128 | # 129 | # P2SH details for simnet treasury: 130 | # 131 | # redeemScript: 532103e8c60c7336744c8dcc7b85c27789950fc52aa4e48f895ebbfb 132 | # ac383ab893fc4c2103ff9afc246e0921e37d12e17d8296ca06a8f92a07fbe7857ed1d4 133 | # f0f5d94e988f21033ed09c7fa8b83ed53e6f2c57c5fa99ed2230c0d38edf53c0340d0f 134 | # c2e79c725a53ae 135 | # (3-of-3 multisig) 136 | # Pubkeys used: 137 | # SkQmxbeuEFDByPoTj41TtXat8tWySVuYUQpd4fuNNyUx51tF1csSs 138 | # SkQn8ervNvAUEX5Ua3Lwjc6BAuTXRznDoDzsyxgjYqX58znY7w9e4 139 | # SkQkfkHZeBbMW8129tZ3KspEh1XBFC1btbkgzs6cjSyPbrgxzsKqk 140 | # 141 | # Organization address is ScuQxvveKGfpG1ypt6u27F99Anf7EW3cqhq 142 | OrganizationPkScript = (0xA914CBB08D6CA783B533B2C7D24A51FBCA92D937BF9987).to_bytes( 143 | 23, byteorder="big" 144 | ) 145 | OrganizationPkScriptVersion = 0 146 | # BlockOneLedger = BlockOneLedgerSimNet, 147 | BlockOneSubsidy = int(300000 * 1e8) 148 | -------------------------------------------------------------------------------- /decred/decred/dcr/nets/testnet.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019, Brian Stafford 3 | Copyright (c) 2019, The Decred developers 4 | See LICENSE for details 5 | 6 | testnet holds testnet3 parameters. Any values should mirror exactly 7 | https://github.com/decred/dcrd/blob/master/chaincfg/testnetparams.go 8 | """ 9 | 10 | Name = "testnet3" 11 | DefaultPort = "19108" 12 | DNSSeeds = [ 13 | ("testnet-seed.decred.mindcry.org", True), 14 | ("testnet-seed.decred.netpurgatory.com", True), 15 | ("testnet-seed.decred.org", True), 16 | ] 17 | 18 | GenesisHash = "a649dce53918caf422e9c711c858837e08d626ecfcd198969b24f7b634a49bac" 19 | PowLimit = 2 ^ 232 - 1 20 | PowLimitBits = 0x1E00FFFF 21 | ReduceMinDifficulty = True 22 | MinDiffReductionTime = 60 * 10 # ~99.3% chance to be mined before reduction 23 | GenerateSupported = True 24 | MaximumBlockSizes = [1310720] 25 | MaxTxSize = 1000000 26 | TargetTimePerBlock = 60 * 2 27 | WorkDiffAlpha = 1 28 | WorkDiffWindowSize = 144 29 | WorkDiffWindows = 20 30 | TargetTimespan = 60 * 2 * 144 # TimePerBlock * WindowSize 31 | RetargetAdjustmentFactor = 4 32 | 33 | # Subsidy parameters. 34 | BaseSubsidy = 2500000000 # 25 Coin 35 | MulSubsidy = 100 36 | DivSubsidy = 101 37 | SubsidyReductionInterval = 2048 38 | WorkRewardProportion = 6 39 | StakeRewardProportion = 3 40 | BlockTaxProportion = 1 41 | 42 | # Consensus rule change deployments. 43 | # 44 | # The miner confirmation window is defined as: 45 | # target proof of work timespan / target proof of work spacing 46 | RuleChangeActivationQuorum = ( 47 | 2520 # 10 % of RuleChangeActivationInterval * TicketsPerBlock 48 | ) 49 | RuleChangeActivationMultiplier = 3 # 75% 50 | RuleChangeActivationDivisor = 4 51 | RuleChangeActivationInterval = 5040 # 1 week 52 | 53 | # Enforce current block version once majority of the network has 54 | # upgraded. 55 | # 51% (51 / 100) 56 | # Reject previous block versions once a majority of the network has 57 | # upgraded. 58 | # 75% (75 / 100) 59 | BlockEnforceNumRequired = 51 60 | BlockRejectNumRequired = 75 61 | BlockUpgradeNumToCheck = 100 62 | 63 | # AcceptNonStdTxs is a mempool param to either accept and relay 64 | # non standard txs to the network or reject them 65 | AcceptNonStdTxs = True 66 | 67 | # Address encoding magics 68 | NetworkAddressPrefix = "T" 69 | PubKeyAddrID = (0x28F7).to_bytes(2, byteorder="big") # starts with Tk 70 | PubKeyHashAddrID = (0x0F21).to_bytes(2, byteorder="big") # starts with Ts 71 | PKHEdwardsAddrID = (0x0F01).to_bytes(2, byteorder="big") # starts with Te 72 | PKHSchnorrAddrID = (0x0EE3).to_bytes(2, byteorder="big") # starts with TS 73 | ScriptHashAddrID = (0x0EFC).to_bytes(2, byteorder="big") # starts with Tc 74 | PrivateKeyID = (0x230E).to_bytes(2, byteorder="big") # starts with Pt 75 | 76 | # BIP32 hierarchical deterministic extended key magics 77 | HDPrivateKeyID = (0x04358397).to_bytes(4, byteorder="big") # starts with tprv 78 | HDPublicKeyID = (0x043587D1).to_bytes(4, byteorder="big") # starts with tpub 79 | 80 | # BIP44 coin type used in the hierarchical deterministic path for 81 | # address generation. 82 | SLIP0044CoinType = 1 # SLIP0044, Testnet (all coins) 83 | LegacyCoinType = 11 # for backwards compatibility 84 | 85 | # Decred PoS parameters 86 | MinimumStakeDiff = 20000000 # 0.2 Coin 87 | TicketPoolSize = 1024 88 | TicketsPerBlock = 5 89 | TicketMaturity = 16 90 | TicketExpiry = 6144 # 6*TicketPoolSize 91 | CoinbaseMaturity = 16 92 | SStxChangeMaturity = 1 93 | TicketPoolSizeWeight = 4 94 | StakeDiffAlpha = 1 95 | StakeDiffWindowSize = 144 96 | StakeDiffWindows = 20 97 | StakeVersionInterval = 144 * 2 * 7 # ~1 week 98 | MaxFreshStakePerBlock = 20 # 4*TicketsPerBlock 99 | StakeEnabledHeight = 16 + 16 # CoinbaseMaturity + TicketMaturity 100 | StakeValidationHeight = 768 # Arbitrary 101 | StakeBaseSigScript = (0x0000).to_bytes(2, byteorder="big") 102 | StakeMajorityMultiplier = 3 103 | StakeMajorityDivisor = 4 104 | 105 | # Decred organization related parameters. 106 | # Organization address is TcrypGAcGCRVXrES7hWqVZb5oLJKCZEtoL1. 107 | OrganizationPkScript = (0xA914D585CD7426D25B4EA5FAF1E6987AACFEDA3DB94287).to_bytes( 108 | 23, byteorder="big" 109 | ) 110 | OrganizationPkScriptVersion = 0 111 | 112 | GENESIS_STAMP = 1533513600 113 | STAKE_SPLIT = 0.3 114 | POW_SPLIT = 0.6 115 | TREASURY_SPLIT = 0.1 116 | 117 | BlockOneSubsidy = int(100000 * 1e8) 118 | -------------------------------------------------------------------------------- /decred/decred/dcr/wire/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/decred/decred/dcr/wire/__init__.py -------------------------------------------------------------------------------- /decred/decred/dcr/wire/msgping.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, The Decred developers 3 | See LICENSE for details. 4 | 5 | Based on dcrd MsgPing. 6 | """ 7 | 8 | from decred.util.encode import ByteArray 9 | 10 | 11 | CmdPing = "ping" 12 | 13 | NonceLength = 8 14 | 15 | 16 | class MsgPing: 17 | """ 18 | MsgPing implements the Message API and represents a Decred ping message. 19 | 20 | For versions BIP0031Version and earlier, it is used primarily to confirm 21 | that a connection is still valid. A transmission error is typically 22 | interpreted as a closed connection and that the peer should be removed. 23 | For versions AFTER BIP0031Version it contains an identifier which can be 24 | returned in the pong message to determine network timing. 25 | 26 | The payload for this message just consists of a nonce used for identifying 27 | it later. 28 | """ 29 | 30 | def __init__(self, nonce): 31 | """ 32 | Args: 33 | nonce (int): A value unique to each ping message. 34 | """ 35 | self.nonce = nonce 36 | 37 | @staticmethod 38 | def btcDecode(b, pver): 39 | """ 40 | btcDecode decodes b using the Decred protocol encoding into the 41 | receiver. This is part of the Message API. 42 | 43 | Args: 44 | b (ByteArray): The encoded MsgPing. 45 | pver (int): The protocol version. Unused. 46 | 47 | Returns: 48 | MsgPing: The MsgPing. 49 | """ 50 | return MsgPing(nonce=b.unLittle().int()) 51 | 52 | def btcEncode(self, pver): 53 | """ 54 | btcEncode encodes the receiver using the Decred protocol encoding. 55 | This is part of the Message API. 56 | 57 | Args: 58 | pver (int): The protocol version. Unused. 59 | 60 | Returns: 61 | ByteArray: The encoded MsgPing 62 | """ 63 | return ByteArray(self.nonce, length=8).littleEndian() 64 | 65 | @staticmethod 66 | def command(): 67 | """ 68 | The protocol command string for the message. This is part of the 69 | Message API. 70 | 71 | Returns: 72 | str: The command string. 73 | """ 74 | return CmdPing 75 | 76 | @staticmethod 77 | def maxPayloadLength(pver): 78 | """ 79 | The maximum length the payload can be for the receiver. This is part of 80 | the Message API. 81 | 82 | Args: 83 | pver (int): The protocol version. Unused. 84 | 85 | Returns: 86 | int: The maximum payload length. 87 | """ 88 | return NonceLength 89 | -------------------------------------------------------------------------------- /decred/decred/dcr/wire/msgpong.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, The Decred developers 3 | See LICENSE for details. 4 | 5 | Based on dcrd MsgPing. 6 | """ 7 | 8 | from decred.util.encode import ByteArray 9 | 10 | 11 | CmdPong = "pong" 12 | 13 | NonceLength = 8 14 | 15 | 16 | class MsgPong: 17 | """ 18 | MsgPong implements the Message API and represents a Decred pong message 19 | which is used primarily to confirm that a connection is still valid in 20 | response to a Decred ping message (MsgPing). 21 | 22 | This message was not added until protocol versions AFTER BIP0031Version. 23 | """ 24 | 25 | def __init__(self, nonce): 26 | """ 27 | Args: 28 | nonce (int): Unique value associated with associated with a specific 29 | ping message. 30 | """ 31 | self.nonce = nonce 32 | 33 | @staticmethod 34 | def btcDecode(b, pver): 35 | """ 36 | btcDecode decodes b using the Decred protocol encoding into the receiver. This 37 | is part of the Message API. 38 | 39 | Args: 40 | b (ByteArray): The encoded MsgPong. 41 | pver (int): The protocol version. Unused. 42 | 43 | Returns: 44 | MsgPong. The MsgPong. 45 | """ 46 | return MsgPong(nonce=b.unLittle().int()) 47 | 48 | def btcEncode(self, pver): 49 | """ 50 | btcEncode encodes the receiver using the Decred protocol encoding. 51 | This is part of the Message API. 52 | 53 | Args: 54 | pver (int): The protocol version. Unused. 55 | 56 | Returns: 57 | ByteArray: The encoded MsgPong 58 | """ 59 | return ByteArray(self.nonce, length=8).littleEndian() 60 | 61 | @staticmethod 62 | def command(): 63 | """ 64 | command returns the protocol command string for the message. This is 65 | part of the Message API. 66 | 67 | Returns: 68 | str: The command string. 69 | """ 70 | return CmdPong 71 | 72 | @staticmethod 73 | def maxPayloadLength(pver): 74 | """ 75 | maxPayloadLength returns the maximum length the payload can be for the 76 | receiver. This is part of the Message API. 77 | 78 | Args: 79 | pver (int): The protocol version. Unused. 80 | 81 | Returns: 82 | int: The maximum payload length. 83 | """ 84 | return NonceLength 85 | -------------------------------------------------------------------------------- /decred/decred/dcr/wire/msgverack.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, The Decred developers 3 | See LICENSE for details. 4 | 5 | Based on dcrd MsgVerAck. 6 | """ 7 | 8 | from decred.util.encode import ByteArray 9 | 10 | 11 | CmdVerAck = "verack" 12 | 13 | 14 | class MsgVerAck: 15 | """ 16 | MsgVerAck defines a Decred verack message which is used for a peer to 17 | acknowledge a version message (MsgVersion) after it has used the information 18 | to negotiate parameters. It implements the Message API. 19 | 20 | This message has no payload. 21 | """ 22 | 23 | @staticmethod 24 | def btcDecode(b, pver): 25 | """ 26 | Decode b using the Decred protocol encoding into the receiver. This is 27 | part of the Message API. 28 | 29 | Args: 30 | b (ByteArray): The encoded MsgVerAck. Unused, since MsgVerAck has 31 | a zero-length payload. 32 | pver (int): The protocol version. Unused. 33 | """ 34 | return MsgVerAck() 35 | 36 | def btcEncode(self, pver): 37 | """ 38 | btcEncode encodes the MsgVerAck using the Decred protocol encoding. This 39 | is part of the Message API. 40 | 41 | Args: 42 | pver (int): The protocol version. Unused. 43 | 44 | Returns: 45 | ByteArray: The encoded MsgVerAck. 46 | """ 47 | return ByteArray() 48 | 49 | def command(self): 50 | """ 51 | The protocol command string for the message. This is part of the 52 | Message API. 53 | 54 | Returns: 55 | str: The command string. 56 | """ 57 | return CmdVerAck 58 | 59 | def maxPayloadLength(self, pver): 60 | """ 61 | The maximum length the payload can be for the receiver. This is part of 62 | the Message API. 63 | 64 | Args: 65 | pver (int): The protocol version. Unused. 66 | 67 | Returns: 68 | int: The maximum payload length. 69 | """ 70 | return 0 71 | -------------------------------------------------------------------------------- /decred/decred/dcr/wire/netaddress.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, the Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | import socket 7 | import time 8 | 9 | from decred import DecredError 10 | from decred.util.encode import ByteArray 11 | 12 | 13 | MaxNetAddressPayload = 30 14 | 15 | # Prefix for a IPv4 adderess encoded as 16 bytes. 16 | ipv4to16prefix = ByteArray(0xFFFF, length=12) 17 | 18 | 19 | class NetAddress: 20 | """ 21 | NetAddress defines information about a peer on the network including the time 22 | it was last seen, the services it supports, its IP address, and port. 23 | """ 24 | 25 | def __init__(self, ip, port, services, stamp=None): 26 | """ 27 | Args: 28 | ip (str or bytes-like): The peer's IP address. 29 | port (int): Port the peer is using. This is encoded in big endian 30 | on the wire which differs from most everything else. 31 | services (int): Bitfield which identifies the services supported by 32 | the peer. 33 | stamp (int): Optional. Default: current time. The last time the peer 34 | was seen. This is, unfortunately, encoded as an int on the wire 35 | and therefore is limited to 2106. This field is not present in 36 | the Decred version message (MsgVersion) nor was it added until 37 | protocol version >= NetAddressTimeVersion. 38 | """ 39 | self.timestamp = stamp if stamp else int(time.time()) 40 | self.services = services 41 | 42 | # If the IP is a string, parse it to bytes. 43 | if isinstance(ip, str): 44 | ip = decodeStringIP(ip) 45 | self.ip = ip 46 | 47 | self.port = port 48 | 49 | def hasService(self, service): 50 | """ 51 | Whether the specified service is supported by the address. 52 | 53 | Args: 54 | service (int): Bitfield which identifies the service(s) to check. 55 | 56 | Returns: 57 | bool: True if this peer offers the service(s). 58 | """ 59 | return self.services & service == service 60 | 61 | def addService(self, service): 62 | """ 63 | Adds service(s) as a supported service. 64 | 65 | Args: 66 | service (int): Bitfield which identifies the service(s) to add. 67 | """ 68 | self.services |= service 69 | 70 | 71 | def readNetAddress(b, hasStamp): 72 | """ 73 | Reads an encoded NetAddress from b depending on the protocol version and 74 | whether or not the timestamp is included per hasStamp. Some messages like 75 | version do not include the timestamp. 76 | 77 | Args: 78 | b (ByteArray): The encoded NetAddress. 79 | hasStamp (bool): Whether or not the NetAddress has a timestamp. 80 | 81 | Returns: 82 | NetAddress: The decoded NetAddress. 83 | """ 84 | expLen = 30 if hasStamp else 26 85 | if len(b) != expLen: 86 | raise DecredError( 87 | f"readNetAddress wrong length (hasStamp={hasStamp}) expected {expLen}, got {len(b)}" 88 | ) 89 | 90 | # NOTE: The Decred protocol uses a uint32 for the timestamp so it will 91 | # stop working somewhere around 2106. Also timestamp wasn't added until 92 | # protocol version >= NetAddressTimeVersion 93 | stamp = b.pop(4).unLittle().int() if hasStamp else 0 94 | services = b.pop(8).unLittle().int() 95 | ip = b.pop(16) 96 | if ip[:12] == ipv4to16prefix: 97 | ip = ip[12:] 98 | 99 | # Sigh. Decred protocol mixes little and big endian. 100 | port = b.pop(2).int() 101 | 102 | return NetAddress(ip=ip, port=port, services=services, stamp=stamp,) 103 | 104 | 105 | def writeNetAddress(netAddr, hasStamp): 106 | """ 107 | writeNetAddress serializes a NetAddress depending on the protocol 108 | version and whether or not the timestamp is included per hasStamp. Some 109 | messages like version do not include the timestamp. 110 | 111 | Args: 112 | netAddr (NetAddress): The peer's NetAddress. 113 | hasStamp (bool): Whether to encode the timestamp. 114 | 115 | Returns: 116 | ByteArray: The encoded NetAddress. 117 | """ 118 | # NOTE: The Decred protocol uses a uint32 for the timestamp so it will 119 | # stop working somewhere around 2106. Also timestamp wasn't added until 120 | # until protocol version >= NetAddressTimeVersion. 121 | b = ( 122 | ByteArray(netAddr.timestamp, length=4).littleEndian() 123 | if hasStamp 124 | else ByteArray() 125 | ) 126 | 127 | # Ensure to always write 16 bytes even if the ip is nil. 128 | ip = netAddr.ip 129 | if ip is None: 130 | ip = ByteArray(length=16) 131 | if len(ip) == 4: 132 | ip = ipv4to16prefix + ip 133 | 134 | b += ByteArray(netAddr.services, length=8).littleEndian() 135 | b += ByteArray(ip, length=16) 136 | b += ByteArray(netAddr.port, length=2) 137 | 138 | return b 139 | 140 | 141 | def decodeStringIP(ip): 142 | """ 143 | Parse an IP string to bytes. 144 | 145 | Args: 146 | ip (str): The string-encoded IP address. 147 | 148 | Returns: 149 | bytes-like: The byte-encoded IP address. 150 | """ 151 | try: 152 | return socket.inet_pton(socket.AF_INET, ip) 153 | except OSError: 154 | pass 155 | try: 156 | return socket.inet_pton(socket.AF_INET6, ip) 157 | except OSError: 158 | raise DecredError(f"failed to decode IP {ip}") 159 | -------------------------------------------------------------------------------- /decred/decred/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/decred/decred/util/__init__.py -------------------------------------------------------------------------------- /decred/decred/util/chains.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019-2020, The Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | from decred import DecredError 7 | from decred.dcr import account as dcracct, nets as dcrnets 8 | 9 | 10 | class BipIDs: 11 | """ 12 | BIP0044 IDs for supported assets. 13 | """ 14 | 15 | decred = dcracct.BIPID 16 | 17 | 18 | """IDSymbols converts BIP0044 ID to a lower-case ticker symbol.""" 19 | IDSymbols = { 20 | BipIDs.decred: "dcr", 21 | } 22 | 23 | 24 | """ 25 | SymbolIDs is a Python dict mapping ticker symbols for supported assets to 26 | their BIP0044 ID. 27 | """ 28 | SymbolIDs = {v: k for k, v in IDSymbols.items()} 29 | 30 | 31 | """ 32 | AccountConstructors maps the asset's BIP0044 ID to the constructor for the 33 | Account object. 34 | """ 35 | AccountConstructors = { 36 | BipIDs.decred: dcracct.Account, 37 | } 38 | 39 | 40 | """ 41 | NetworkParams maps the asset's BIP0044 ID to a dict of network parameters. 42 | """ 43 | NetworkParams = { 44 | BipIDs.decred: { 45 | "mainnet": dcrnets.mainnet, 46 | "testnet3": dcrnets.testnet, 47 | "simnet": dcrnets.simnet, 48 | } 49 | } 50 | 51 | 52 | def parseCoinType(coinType): 53 | """ 54 | Parse the coin type. If coinType is a string, it will be converted to the 55 | BIP0044 ID. If it is already an integer, it is returned as is. 56 | 57 | Args: 58 | coinType (int or str): The asset. BIP0044 ID or ticker symbol. 59 | 60 | Returns: 61 | int: The BIP0044 ID. 62 | """ 63 | if isinstance(coinType, str): 64 | ticker = coinType.lower() 65 | if ticker not in SymbolIDs: 66 | raise DecredError(f"ticker symbol {ticker} not found") 67 | coinType = SymbolIDs[ticker] 68 | if not isinstance(coinType, int): 69 | raise DecredError(f"unsupported type for coinType {type(coinType)}") 70 | return coinType 71 | 72 | 73 | _chains = {} 74 | 75 | 76 | def registerChain(coinType, chain): 77 | """ 78 | Set the app-wide network parameters for a particular asset. 79 | 80 | Args: 81 | coinType (int or str): The asset. BIP0044 ID or ticker symbol. 82 | chain (obj): Network parameters. 83 | """ 84 | _chains[parseCoinType(coinType)] = chain 85 | 86 | 87 | def chain(coinType): 88 | """ 89 | Fetch the registered network parameters for an asset. 90 | 91 | Args: 92 | coinType (int or str): The asset. BIP0044 ID or ticker symbol. 93 | 94 | Returns: 95 | obj: Network parameters. 96 | """ 97 | return _chains.get(parseCoinType(coinType)) 98 | -------------------------------------------------------------------------------- /decred/decred/util/tinyhttp.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019, Brian Stafford 3 | See LICENSE for details 4 | 5 | DcrdataClient.endpointList() for available enpoints. 6 | """ 7 | 8 | import json 9 | from urllib.parse import urlencode 10 | import urllib.request as urlrequest 11 | 12 | from decred import DecredError 13 | 14 | from .helpers import formatTraceback 15 | 16 | 17 | def get(url, **kwargs): 18 | return request(url, **kwargs) 19 | 20 | 21 | def post(url, data, **kwargs): 22 | return request(url, data, **kwargs) 23 | 24 | 25 | def request(url, postData=None, headers=None, urlEncode=False, context=None): 26 | # GET method used when encoded data is None. 27 | encoded = None 28 | if postData: 29 | if urlEncode: 30 | # Encode the data in URL query string form. 31 | encoded = urlencode(postData).encode("utf-8") 32 | else: 33 | # Encode the data as JSON. 34 | encoded = json.dumps(postData).encode("utf-8") 35 | 36 | headers = headers if headers else {} 37 | req = urlrequest.Request(url, headers=headers, data=encoded) 38 | 39 | try: 40 | raw = urlrequest.urlopen(req, context=context).read().decode() 41 | except Exception as err: 42 | raise DecredError(f"Error in requesting URL {url}: {formatTraceback(err)}") 43 | 44 | try: 45 | # Try to decode the response as JSON, but fall back to just 46 | # returning the string. 47 | return json.loads(raw) 48 | except json.JSONDecodeError: 49 | return raw 50 | -------------------------------------------------------------------------------- /decred/decred/util/ws.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, the Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | import threading 7 | 8 | import websocket 9 | 10 | 11 | class Client(websocket.WebSocketApp): 12 | """ 13 | A websocket client. On top of the base functionality offered by 14 | websocket.WebSocketApp, Client handles some initialization and shutdown 15 | related tasks. 16 | """ 17 | 18 | def __init__(self, url, certPath=None, **k): 19 | """ 20 | Args: 21 | url str: The websocket server URL. 22 | 23 | Additional keyword arguments are passed directly to the WebSocketApp 24 | constructor. The caller should provide callback functions for various 25 | events. The most common callbacks are listed here. 26 | 27 | on_open: Called at opening websocket. This function has one 28 | argument, the instance of Client. 29 | 30 | on_message: Called when data is received. on_message has 2 31 | arguments. The 1st argument is the instance of Client. The 2nd 32 | argument is the received message decoded (utf-8) as a str. 33 | 34 | on_error: Called when an exception is encountered. on_error has 2 35 | arguments. The 1st argument is the instance of Client. The 2nd 36 | argument is an Exception. 37 | 38 | on_close: Called when the connection is closed. This function has 39 | one argument, the instance of Client. 40 | """ 41 | # The constructor will block until the initEvent is set so that the 42 | # Client is immediately usable by the caller. 43 | initEvent = threading.Event() 44 | 45 | cleanURL = url.replace("https:", "wss:").replace("http:", "ws:") 46 | 47 | user_open = k.pop("on_open", None) 48 | 49 | def on_open(ws): 50 | if user_open: 51 | user_open(ws) 52 | initEvent.set() 53 | 54 | user_close = k.pop("on_close", None) 55 | 56 | def on_close(ws): 57 | # Some initialization errors won't call on_open, but they will call 58 | # on_close, so set the initEvent here too. 59 | if user_close: 60 | user_close(ws) 61 | initEvent.set() 62 | 63 | super().__init__(cleanURL, on_open=on_open, on_close=on_close, **k) 64 | 65 | sslopt = {"ca_certs": certPath} if certPath else None 66 | 67 | self.thread = threading.Thread( 68 | None, self.run_forever, kwargs={"sslopt": sslopt} 69 | ) 70 | self.thread.start() 71 | initEvent.wait() 72 | 73 | def close(self): 74 | """ 75 | Close the connection and wait for shutdown. 76 | """ 77 | super().close() 78 | if self.thread: 79 | self.thread.join(20) 80 | self.thread = None 81 | -------------------------------------------------------------------------------- /decred/decred/wallet/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/decred/decred/wallet/__init__.py -------------------------------------------------------------------------------- /decred/examples/create_testnet_wallet.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019, The Decred developers 3 | 4 | This example script will prompt for a password and create a password-encrypted 5 | testnet wallet. The mnemonic seed and an address are printed. 6 | """ 7 | 8 | from getpass import getpass 9 | 10 | from decred.wallet.wallet import SimpleWallet 11 | 12 | 13 | def main(): 14 | # Create an encrypted, password-protected wallet file. 15 | password = getpass() 16 | walletDir = "wallets" 17 | print("Creating and synchronizing wallet") 18 | wallet, words = SimpleWallet.create(walletDir, password, "testnet") 19 | 20 | # Print the seed words and an address. 21 | try: 22 | print("Mnemonic seed\n-------------") 23 | print(" ".join(words)) 24 | print("Receive DCR at %s" % wallet.currentAddress()) 25 | finally: 26 | wallet.close() 27 | 28 | 29 | if __name__ == "__main__": 30 | main() 31 | -------------------------------------------------------------------------------- /decred/examples/plot_ticket_price.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019, The Decred developers 3 | 4 | This example script will pull ticket price data from dcrdata and plot using 5 | matplotlib. The matplotlib package is not a decred dependency, so it should 6 | be installed separately with `pip3 install matplotlib`. 7 | """ 8 | 9 | from decred.dcr.dcrdata import DcrdataClient 10 | from decred.util.helpers import mktime 11 | 12 | 13 | try: 14 | from matplotlib import pyplot as plt 15 | except ImportError: 16 | print("matplotlib import error. Did you 'pip3 install matplotlib'?") 17 | exit() 18 | 19 | 20 | def main(): 21 | # Create a dcrdata client and grab the ticket price data. 22 | dcrdata = DcrdataClient("https://dcrdata.decred.org") 23 | ticketPrice = dcrdata.chart("ticket-price") 24 | # ticketPrice["t"] is UNIX timestamp (was "x") 25 | # ticketPrice["price"] is ticket price, in atoms (was "y") 26 | # These keys changed, see https://github.com/decred/dcrdata/pull/1507 27 | 28 | # Make the axes pretty. 29 | ax = plt.gca() # gca = get current axes 30 | years = range(2016, 2026) 31 | ax.set_xticks([mktime(year) for year in years]) 32 | ax.set_xticklabels([str(year) for year in years]) 33 | ax.set_xlabel("date") 34 | ax.set_ylabel("ticket price (DCR)") 35 | 36 | ax.plot( 37 | ticketPrice["t"], 38 | [atoms * 1e-8 for atoms in ticketPrice["price"]], 39 | color="#222222", 40 | ) 41 | plt.show() 42 | 43 | 44 | if __name__ == "__main__": 45 | main() 46 | -------------------------------------------------------------------------------- /decred/examples/send_testnet.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019, The Decred developers 3 | 4 | This example script will send 1 DCR from a wallet as created with the 5 | create_testnet_wallet.py example script to the return address from the testnet 6 | faucet at https://faucet.decred.org/. 7 | Before running this script, send the wallet some DCR from the faucet. 8 | """ 9 | 10 | from getpass import getpass 11 | 12 | from decred.wallet.wallet import SimpleWallet 13 | 14 | # Testnet return address for faucet.decred.org. 15 | TESTNET_ADDRESS = "TsfDLrRkk9ciUuwfp2b8PawwnukYD7yAjGd" 16 | 17 | 18 | def main(): 19 | value = int(1 * 1e8) # 1 DCR, atoms 20 | password = getpass() 21 | walletDir = "wallets" 22 | try: 23 | print("Opening and synchronizing wallet") 24 | wallet = SimpleWallet(walletDir, password, "testnet") 25 | except Exception as e: 26 | print("Failed to open wallet with provided password: %s" % e) 27 | exit() 28 | 29 | try: 30 | # Send some DCR. 31 | tx = wallet.sendToAddress(value, TESTNET_ADDRESS) 32 | # Print the transaction ID and a dcrdata link. 33 | print("Transaction ID: %s" % tx.id()) 34 | print("See transaction at https://testnet.dcrdata.org/tx/%s" % tx.id()) 35 | except Exception as e: 36 | print("Failed to send transaction: %s" % e) 37 | finally: 38 | wallet.close() 39 | 40 | 41 | if __name__ == "__main__": 42 | main() 43 | -------------------------------------------------------------------------------- /decred/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "decred" 3 | version = "0.1.2-dev" 4 | description = "A Python 3 Decred toolkit." 5 | license = "ISC" 6 | homepage = "https://decred.org/" 7 | repository = "https://github.com/decred/tinydecred/" 8 | documentation = "https://github.com/decred/tinydecred/blob/master/decred" 9 | authors = [ 10 | "Brian Stafford ", 11 | "The Decred developers " 12 | ] 13 | classifiers = [ 14 | "Development Status :: 3 - Alpha", 15 | "Intended Audience :: Developers", 16 | "License :: OSI Approved :: ISC License (ISCL)", 17 | "Operating System :: OS Independent", 18 | "Programming Language :: Python :: 3.6", 19 | "Programming Language :: Python :: 3.7", 20 | "Programming Language :: Python :: 3.8", 21 | "Topic :: Office/Business :: Financial" 22 | ] 23 | build = "build.py" 24 | 25 | [tool.poetry.dependencies] 26 | python = "^3.6" 27 | appdirs = "^1.4.3" 28 | base58 = "^2.0.0" 29 | blake256 = "^0.1.1" 30 | pynacl = "^1.3.0" 31 | websocket_client = "^0.57.0" 32 | 33 | [tool.poetry.dev-dependencies] 34 | pytest = "^5.3" 35 | pytest-cov = "^2.8" 36 | pytest-pudb = "^0.7.0" 37 | flake8 = "^3.7" 38 | black = "^19.10b0" 39 | isort = "^4.3" 40 | websocket-server = "^0.4" 41 | pytest-benchmark = "^3.2.3" 42 | pytest-profiling = "^1.7.0" 43 | Cython = "^0.29.16" 44 | 45 | [tool.isort] 46 | atomic = "true" 47 | combine_as_imports = "true" 48 | combine_star = "true" 49 | filter_files = "true" 50 | force_grid_wrap = 0 51 | force_sort_within_sections = "true" 52 | include_trailing_comma = "true" 53 | line_length = 88 54 | lines_after_imports = 2 55 | multi_line_output = 3 56 | use_parentheses = "true" 57 | virtual_env = "./.venv/" 58 | skip = [ 59 | "./examples/create_testnet_wallet.py", 60 | "./examples/send_testnet.py" 61 | ] 62 | 63 | [build-system] 64 | requires = ["setuptools", "poetry>=1.0.3"] 65 | build-backend = "poetry.masonry.api" 66 | -------------------------------------------------------------------------------- /decred/tests/benchmark/crypto/secp256k1/test_field_benchmark.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, the Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | from decred.crypto.secp256k1 import field 7 | 8 | 9 | H1 = "b3d9aac9c5e43910b4385b53c7e78c21d4cd5f8e683c633aed04c233efc2e120" 10 | H2 = "b0ba920360ea8436a216128047aab9766d8faf468895eb5090fc8241ec758896" 11 | 12 | 13 | class Test_FieldVal: 14 | def test_normalize(self, benchmark): 15 | f = field.FieldVal() 16 | f.n = [0xFFFFFFFF, 0xFFFFFFC0, 0xFC0, 0, 0, 0, 0, 0, 0, 0] 17 | benchmark(f.normalize) 18 | 19 | def test_negate(self, benchmark): 20 | f = field.FieldVal.fromHex(H1) 21 | benchmark(f.negate, 1) 22 | 23 | def test_add(self, benchmark): 24 | f1 = field.FieldVal.fromHex(H1) 25 | f2 = field.FieldVal.fromHex(H2) 26 | benchmark(f1.add, f2) 27 | 28 | def test_add2(self, benchmark): 29 | f1 = field.FieldVal.fromHex(H1) 30 | f2 = field.FieldVal.fromHex(H2) 31 | benchmark(f1.add2, f1, f2) 32 | 33 | def test_square(self, benchmark): 34 | f = field.FieldVal.fromHex(H1) 35 | benchmark(f.square) 36 | 37 | def test_mul(self, benchmark): 38 | f = field.FieldVal.fromHex(H1) 39 | f2 = field.FieldVal.fromHex(H2) 40 | benchmark(f.mul, f2) 41 | 42 | def test_inverse(self, benchmark): 43 | f = field.FieldVal.fromHex(H1) 44 | benchmark(f.inverse) 45 | -------------------------------------------------------------------------------- /decred/tests/benchmark/util/test_database_benchmark.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019-2020, the Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | import pytest 7 | 8 | from decred.util import database 9 | 10 | 11 | @pytest.fixture 12 | def setup_db(tmpdir): 13 | # Open a key value db in the temp directory. 14 | db = database.KeyValueDatabase(tmpdir.join("bm.sqlite")).child("testdb") 15 | # Generate some data. 16 | return db, [(str(i).encode(), str(i).encode()) for i in range(10)] 17 | 18 | 19 | def test_insert(setup_db, benchmark): 20 | def insert(db, data): 21 | for k, v in data: 22 | db[k] = v 23 | 24 | db, data = setup_db 25 | benchmark(insert, db, data) 26 | assert len(db) == len(data) 27 | 28 | 29 | def test_clear(setup_db, benchmark): 30 | db, data = setup_db 31 | db.batchInsert(data) 32 | benchmark(db.clear) 33 | assert len(db) == 0 34 | 35 | 36 | def test_batchInsert(setup_db, benchmark): 37 | db, data = setup_db 38 | benchmark(db.batchInsert, data) 39 | assert len(db) == len(data) 40 | -------------------------------------------------------------------------------- /decred/tests/conftest.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019, the Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | import os 7 | import random 8 | 9 | import pytest 10 | 11 | from decred.util import chains, helpers 12 | 13 | 14 | @pytest.fixture 15 | def sign(): 16 | def _sign(x): 17 | """ 18 | x: number 19 | """ 20 | if x < 0: 21 | return -1 22 | elif x > 0: 23 | return 1 24 | else: 25 | return 0 26 | 27 | return _sign 28 | 29 | 30 | # Seed initialization is delegated to tests. 31 | # random.seed(0) 32 | 33 | 34 | @pytest.fixture 35 | def randBytes(): 36 | def _randBytes(low=0, high=50): 37 | return bytes(random.randint(0, 255) for _ in range(random.randint(low, high))) 38 | 39 | return _randBytes 40 | 41 | 42 | @pytest.fixture(scope="module") 43 | def prepareLogger(request): 44 | helpers.prepareLogging() 45 | 46 | 47 | @pytest.fixture(scope="class") 48 | def registerChain(request): 49 | chains.registerChain("dcr", None) 50 | 51 | 52 | @pytest.fixture(scope="session") 53 | def dcrdConfig(): 54 | dcrdCfgDir = helpers.appDataDir("dcrd") 55 | cfgPath = os.path.join(dcrdCfgDir, "dcrd.conf") 56 | if not os.path.isfile(cfgPath): 57 | return None 58 | cfg = helpers.readINI(cfgPath, ["rpcuser", "rpcpass", "rpccert"]) 59 | assert "rpcuser" in cfg 60 | assert "rpcpass" in cfg 61 | if "rpccert" not in cfg: 62 | cfg["rpccert"] = os.path.join(dcrdCfgDir, "rpc.cert") 63 | if "rpclisten" not in cfg: 64 | cfg["rpclisten"] = "localhost:9109" 65 | return cfg 66 | -------------------------------------------------------------------------------- /decred/tests/integration/dcr/test_blockchain_live.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, the Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | import pytest 7 | 8 | from decred.dcr.blockchain import LocalNode 9 | from decred.dcr.nets import mainnet 10 | from decred.dcr.wire.msgtx import OutPoint 11 | from decred.util.encode import ByteArray, rba 12 | 13 | 14 | @pytest.fixture 15 | def node(dcrdConfig, tmp_path): 16 | if dcrdConfig is None: 17 | return "no configuration found" 18 | try: 19 | bc = LocalNode( 20 | netParams=mainnet, 21 | dbPath=tmp_path / "tmp.db", 22 | url="https://" + dcrdConfig["rpclisten"], 23 | user=dcrdConfig["rpcuser"], 24 | pw=dcrdConfig["rpcpass"], 25 | certPath=dcrdConfig["rpccert"], 26 | ) 27 | except Exception as e: 28 | return e 29 | yield bc 30 | bc.close() 31 | 32 | 33 | def test_syncAccount(node): 34 | # sync a small range of headers. 35 | node.syncHeaderRange(400000, 400100) 36 | 37 | # An address from block 400,051, a split transaction to fund a ticket in 38 | # block 400,053 39 | addr51_0out = "DsSMy2pPZmS7Jd9QoGbzX2DfhAJDqwftWTR" 40 | # An address from a previous outpoint being spent in block 400,074. Also 41 | # has an output in block 400,053. 42 | addr74_0in = "DsbZZXdkA4JKBUK6YbqGmDkgDryKBVdGVD5" 43 | # A ticket being spent in transaction 400,025 44 | ticket = OutPoint( 45 | txHash=rba("60d97229b6229923d6c4c3c6f290fbc507833186f3d1eed1e603e205e0dfe493"), 46 | idx=0, 47 | tree=1, 48 | ) 49 | 50 | startHash = node.mainchainDB.first()[1] 51 | print("startHash", startHash.rhex()) 52 | 53 | scanBlocks = node.syncAccount( 54 | addrs=[addr51_0out, addr74_0in], outPoints=[ticket], startHash=startHash, 55 | ) 56 | 57 | # A mapping of block hashes to number of transactions expected. 58 | expTxs = { 59 | rba("00000000000000000f9aed681de1bb5ee8d0022ec427d2df526534eab4675c59"): 1, 60 | rba("000000000000000015749883c3cf975ea3565695a833e6f44a5caabf8e132ff3"): 1, 61 | rba("000000000000000002e6de7314fe8062e1f8abf0711b8cf1f026aff0876466b6"): 2, 62 | rba("0000000000000000097661b13a8d4d8d99753445b21f91ab53f66bdbc088338c"): 1, 63 | } 64 | 65 | assert len(scanBlocks) == len(expTxs) 66 | for block in scanBlocks: 67 | assert block.hash in expTxs 68 | assert expTxs[block.hash] == len(block.txs) 69 | 70 | 71 | def test_syncHeaders(node): 72 | # Get the current best tip. 73 | tip = node.rpc.getBestBlock() 74 | # Go back 10 blocks, inclusive of ends, so 11 blocks total to sync. 75 | rootHash = node.rpc.getBlockHash(tip.height - 10) 76 | rootHeight = node.rpc.getBlock(rootHash).height 77 | 78 | synced, orphans = node.syncHeaders(rootHash) 79 | firstHeight, firstHash = node.mainchainDB.first() 80 | assert firstHeight == rootHeight 81 | assert firstHash == rootHash 82 | assert len(node.headerDB) >= 11 83 | assert len(node.mainchainDB) >= 11 84 | assert synced >= 11 85 | assert len(orphans) == 0 86 | for height, blockHash in node.mainchainDB.items(): 87 | assert node.rpc.getBlock(blockHash).height == height 88 | 89 | # Insert a wrong hash at the tip height 90 | node.mainchainDB[tip.height] = ByteArray(length=32) 91 | 92 | # Start with an earlier root 93 | newRootHash = node.rpc.getBlockHash(rootHeight - 2) 94 | 95 | preSyncHeight = node.mainchainDB.last()[0] 96 | synced, orphans = node.syncHeaders(newRootHash) 97 | # Expect 3 to be synced. 2 from the beginning, 1 from the reorg. 98 | assert synced == node.mainchainDB.last()[0] - preSyncHeight + 3 99 | assert len(orphans) == 1 100 | 101 | # Clear the database and test syncing from genesis and more than 102 | # maxBlocksPerRescan, just to exercise the paths. 103 | node.mainchainDB.clear() 104 | node.syncHeaderRange(0, 3000) 105 | assert len(node.mainchainDB) == 3000 106 | -------------------------------------------------------------------------------- /decred/tests/integration/dcr/test_vsp_live.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019-2020, the Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | from decred.dcr import vsp 7 | from decred.dcr.nets import testnet 8 | 9 | 10 | VSP_URL = "https://dcrstakedinner.com" 11 | API_KEY = ( 12 | "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1Nzc0MzM0NDIsIm" 13 | "lzcyI6Imh0dHBzOi8vd3d3LmRjcnN0YWtlZGlubmVyLmNvbSIsImxvZ2dlZEluQ" 14 | "XMiOjQ2fQ.PEb000_TjQuBYxjRdh-VOaXMdV2GUw3_ZyIyp_tfpFE" 15 | ) 16 | # Signing address is needed to validate server-reported redeem script. 17 | SIGNING_ADDRESS = "TkKmVKG7u7PwhQaYr7wgMqBwHneJ2cN4e5YpMVUsWSopx81NFXEzK" 18 | 19 | 20 | def test_vsp_live(): 21 | the_vsp = vsp.VotingServiceProvider(VSP_URL, API_KEY, testnet.Name) 22 | the_vsp.authorize(SIGNING_ADDRESS) 23 | the_vsp.getStats() 24 | purc_info = the_vsp.getPurchaseInfo() 25 | # Test voting. 26 | if purc_info.voteBits & (1 << 1) != 0: 27 | nextVote = 1 | (1 << 2) 28 | else: 29 | nextVote = 1 | (1 << 1) 30 | the_vsp.setVoteBits(nextVote) 31 | purc_info = the_vsp.getPurchaseInfo() 32 | assert purc_info.voteBits == nextVote 33 | -------------------------------------------------------------------------------- /decred/tests/integration/util/test_ws.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, the Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | from queue import Queue 7 | import threading 8 | import time 9 | from urllib.parse import urlunsplit 10 | 11 | import pytest 12 | import websocket 13 | from websocket_server import WebsocketServer 14 | 15 | from decred.util import ws 16 | 17 | 18 | HOST = "localhost" 19 | PORT = 53791 20 | URL = urlunsplit(("http", f"{HOST}:{PORT}", "/", "", "")) 21 | QUIT = "QUIT" 22 | HELLO = "HELLO" 23 | YO = "YO" 24 | CLOSE_HANDSHAKE = bytearray([0x88, 0]) 25 | 26 | 27 | def test_websocket(): 28 | """ 29 | Start a ws.Client and check basic functionality. 30 | """ 31 | serverQ = Queue(1) 32 | clientQ = Queue(1) 33 | errorQ = Queue(1) 34 | 35 | def receive(client, server, message): 36 | if message == HELLO: 37 | server.send_message(client, YO) 38 | elif message == QUIT: 39 | client["handler"].request.send(CLOSE_HANDSHAKE) 40 | server.shutdown() 41 | serverQ.put(message) 42 | 43 | server = WebsocketServer(PORT, HOST) 44 | server.set_fn_message_received(receive) 45 | serverThread = threading.Thread(None, server.run_forever) 46 | serverThread.start() 47 | time.sleep(1) 48 | 49 | class test: 50 | was_opened = False 51 | was_closed = False 52 | 53 | def on_open(ws): 54 | test.was_opened = True 55 | 56 | def on_message(ws, msg): 57 | clientQ.put(msg, timeout=1) 58 | 59 | def on_close(ws): 60 | test.was_closed = True 61 | 62 | def on_error(ws, error): 63 | errorQ.put(error, timeout=1) 64 | 65 | def client(url): 66 | return ws.Client( 67 | url=url, 68 | on_open=on_open, 69 | on_message=on_message, 70 | on_close=on_close, 71 | on_error=on_error, 72 | ) 73 | 74 | try: 75 | # Send a hello and make sure it is received. 76 | cl = client(URL) 77 | assert test.was_opened 78 | 79 | cl.send(HELLO) 80 | msg = serverQ.get(timeout=1) 81 | assert msg == HELLO 82 | 83 | # Make sure we got the response. 84 | msg = clientQ.get(timeout=1) 85 | assert msg == YO 86 | 87 | # Trigger a server shutdown. 88 | cl.send(QUIT) 89 | serverQ.get(timeout=1) 90 | 91 | # Make sure the close callback was called. 92 | assert test.was_closed 93 | 94 | # Make sure a send fails. 95 | with pytest.raises(websocket.WebSocketConnectionClosedException): 96 | cl.send("should be closed") 97 | 98 | # Force a client error and ensure it comes through the callback. 99 | assert errorQ.empty() 100 | cl = client("notaurl") 101 | errorQ.get(timeout=1) 102 | 103 | except Exception as e: 104 | server.shutdown() 105 | raise e 106 | finally: 107 | cl.close() 108 | 109 | serverThread.join() 110 | -------------------------------------------------------------------------------- /decred/tests/integration/wallet/test_wallet.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, The Decred developers 3 | """ 4 | 5 | from decred.dcr import nets 6 | from decred.wallet.wallet import SimpleWallet, Wallet 7 | 8 | 9 | PASSWORD = "test_password" 10 | NET_NAME = "testnet" 11 | 12 | # Testnet return address for faucet.decred.org. 13 | TESTNET_ADDRESS = "TsfDLrRkk9ciUuwfp2b8PawwnukYD7yAjGd" 14 | 15 | 16 | def test_SimpleWallet(tmp_path): 17 | wallet, _ = SimpleWallet.create(tmp_path, PASSWORD, NET_NAME) 18 | wallet.close() 19 | wallet = SimpleWallet(tmp_path, PASSWORD, NET_NAME) 20 | wallet.close() 21 | 22 | 23 | def test_Wallet(tmp_path): 24 | first_wallet_path = tmp_path / "first_wallet" 25 | netParams = nets.parse(NET_NAME) 26 | words, _ = Wallet.create(first_wallet_path, PASSWORD, netParams) 27 | second_wallet_path = tmp_path / "second_wallet" 28 | Wallet.createFromMnemonic(words, second_wallet_path, PASSWORD, netParams) 29 | -------------------------------------------------------------------------------- /decred/tests/test_examples.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019, The Decred developers 3 | """ 4 | 5 | import importlib.util 6 | import os 7 | from pathlib import Path 8 | import py_compile 9 | 10 | 11 | def test_compile(): 12 | exampleDir = Path(__file__).resolve().parent.parent / "examples" 13 | 14 | for filename in os.listdir(exampleDir): 15 | # The "plot_ticket_price.py" file imports matplotlib, which is not a TD 16 | # dependency, so skip that file. 17 | if not filename.endswith(".py") or "plot_" in filename: 18 | continue 19 | path = Path(exampleDir) / filename 20 | assert py_compile.compile(path) is not None 21 | spec = importlib.util.spec_from_file_location(filename.split(".")[0], path) 22 | m = importlib.util.module_from_spec(spec) 23 | spec.loader.exec_module(m) 24 | -------------------------------------------------------------------------------- /decred/tests/unit/btc/test_nets_btc.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019, the Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | import pytest 7 | 8 | from decred import DecredError 9 | from decred.btc import nets 10 | 11 | 12 | def test_nets(): 13 | assert nets.parse("mainnet") is nets.mainnet 14 | 15 | assert nets.normalizeName("testnet3") == "testnet" 16 | 17 | with pytest.raises(DecredError): 18 | nets.parse("nonet") 19 | -------------------------------------------------------------------------------- /decred/tests/unit/crypto/test_mnemonic.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019-2020, the Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | import pytest 7 | 8 | from decred import DecredError 9 | from decred.crypto import mnemonic 10 | from decred.util.encode import ByteArray 11 | 12 | 13 | class TestMnemonic: 14 | def test_all(self): 15 | # fmt: off 16 | tests = [ 17 | ( 18 | "topmost Istanbul Pluto vagabond treadmill Pacific brackish dictator" 19 | " goldfish Medusa afflict bravado chatter revolver Dupont midsummer" 20 | " stopwatch whimsical cowbell bottomless", 21 | ByteArray([ 22 | 0xE5, 0x82, 0x94, 0xF2, 0xE9, 0xA2, 0x27, 0x48, 23 | 0x6E, 0x8B, 0x06, 0x1B, 0x31, 0xCC, 0x52, 0x8F, 24 | 0xD7, 0xFA, 0x3F, 0x19 25 | ]), 26 | ), 27 | ( 28 | "stairway souvenir flytrap recipe adrift upcoming artist positive" 29 | " spearhead Pandora spaniel stupendous tonic concurrent transit Wichita" 30 | " lockup visitor flagpole escapade", 31 | ByteArray([ 32 | 0xD1, 0xD4, 0x64, 0xC0, 0x04, 0xF0, 0x0F, 0xB5, 33 | 0xC9, 0xA4, 0xC8, 0xD8, 0xE4, 0x33, 0xE7, 0xFB, 34 | 0x7F, 0xF5, 0x62, 0x56 35 | ]), 36 | ), 37 | ] 38 | # fmt: on 39 | listToLower = lambda l: [x.lower() for x in l] 40 | for i, (words, seed) in enumerate(tests): 41 | unWords = mnemonic.encode(seed) 42 | assert listToLower(unWords[: len(unWords) - 1]) == listToLower( 43 | words.split() 44 | ) 45 | unSeed = mnemonic.decode(words.split()) 46 | assert seed == unSeed 47 | 48 | def test_bad_paths(self): 49 | wordlists = (["", "meme"], ["acme", "kiwi"]) 50 | with pytest.raises(DecredError): 51 | for wordlist in wordlists: 52 | mnemonic.decode(wordlist) 53 | -------------------------------------------------------------------------------- /decred/tests/unit/crypto/test_rando.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, The Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | import pytest 7 | 8 | from decred import DecredError 9 | from decred.crypto import rando 10 | 11 | 12 | def test_checkSeedLength(): 13 | with pytest.raises(DecredError): 14 | rando.checkSeedLength(rando.MinSeedBytes - 1) 15 | assert rando.checkSeedLength(rando.MinSeedBytes) is None 16 | assert rando.checkSeedLength(rando.HASH_SIZE) is None 17 | assert rando.checkSeedLength(rando.KEY_SIZE) is None 18 | assert rando.checkSeedLength(rando.MaxSeedBytes) is None 19 | with pytest.raises(DecredError): 20 | rando.checkSeedLength(rando.MaxSeedBytes + 1) 21 | -------------------------------------------------------------------------------- /decred/tests/unit/dcr/conftest.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019-2020, the Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | import pytest 7 | 8 | from decred.util import tinyhttp 9 | 10 | 11 | @pytest.fixture 12 | def http_get_post(monkeypatch): 13 | """ 14 | Tests will use the returned "queue" function to add responses they want 15 | returned from both tinyhttp "get" and "post" functions. 16 | The "get" responses will use "url" as a key, while the "post" responses 17 | will use "(url, repr(data))". 18 | """ 19 | q = {} 20 | 21 | def mock_get(url, **kwargs): 22 | return q[url].pop() 23 | 24 | def mock_post(url, data, **kwargs): 25 | return q[(url, repr(data))].pop() 26 | 27 | monkeypatch.setattr(tinyhttp, "get", mock_get) 28 | monkeypatch.setattr(tinyhttp, "post", mock_post) 29 | 30 | def queue(k, v): 31 | q.setdefault(k, []).append(v) 32 | 33 | return queue 34 | 35 | 36 | @pytest.fixture 37 | def MockWebSocketClient(): 38 | class MockWebSocketClient_inner: 39 | def __init__(self, **kargs): 40 | self.on_message = kargs["on_message"] 41 | self.on_close = kargs["on_close"] 42 | self.on_error = kargs["on_error"] 43 | self.sent = [] 44 | self.emitted = [] 45 | 46 | def send(self, msg): 47 | self.sent.append(msg) 48 | 49 | def close(self): 50 | self.on_close(self) 51 | 52 | def emit(self, msg): 53 | self.emitted.append(msg) 54 | 55 | return MockWebSocketClient_inner 56 | -------------------------------------------------------------------------------- /decred/tests/unit/dcr/test-data/dcrdata.json: -------------------------------------------------------------------------------- 1 | [ 2 | "/", 3 | "/address", 4 | "/address/{address}", 5 | "/address/{address}/", 6 | "/address/{address}/amountflow/{chartgrouping}", 7 | "/address/{address}/count/{N}", 8 | "/address/{address}/count/{N}/", 9 | "/address/{address}/count/{N}/raw", 10 | "/address/{address}/count/{N}/skip/{M}", 11 | "/address/{address}/count/{N}/skip/{M}/", 12 | "/address/{address}/count/{N}/skip/{M}/raw", 13 | "/address/{address}/raw", 14 | "/address/{address}/totals", 15 | "/address/{address}/types/{chartgrouping}", 16 | "/agenda", 17 | "/agenda/{agendaId}", 18 | "/agendas", 19 | "/agendas/", 20 | "/block", 21 | "/block/best", 22 | "/block/best/", 23 | "/block/best/hash", 24 | "/block/best/header", 25 | "/block/best/header/", 26 | "/block/best/header/raw", 27 | "/block/best/height", 28 | "/block/best/pos", 29 | "/block/best/raw", 30 | "/block/best/size", 31 | "/block/best/subsidy", 32 | "/block/best/tx", 33 | "/block/best/tx/", 34 | "/block/best/tx/count", 35 | "/block/best/verbose", 36 | "/block/hash/{blockhash}", 37 | "/block/hash/{blockhash}/", 38 | "/block/hash/{blockhash}/header", 39 | "/block/hash/{blockhash}/header/", 40 | "/block/hash/{blockhash}/header/raw", 41 | "/block/hash/{blockhash}/height", 42 | "/block/hash/{blockhash}/pos", 43 | "/block/hash/{blockhash}/raw", 44 | "/block/hash/{blockhash}/size", 45 | "/block/hash/{blockhash}/subsidy", 46 | "/block/hash/{blockhash}/tx", 47 | "/block/hash/{blockhash}/tx/", 48 | "/block/hash/{blockhash}/tx/count", 49 | "/block/hash/{blockhash}/verbose", 50 | "/block/range/{idx0}/{idx}", 51 | "/block/range/{idx0}/{idx}/", 52 | "/block/range/{idx0}/{idx}/size", 53 | "/block/range/{idx0}/{idx}/{step}", 54 | "/block/range/{idx0}/{idx}/{step}/", 55 | "/block/range/{idx0}/{idx}/{step}/size", 56 | "/block/{idx}", 57 | "/block/{idx}/", 58 | "/block/{idx}/hash", 59 | "/block/{idx}/header", 60 | "/block/{idx}/header/", 61 | "/block/{idx}/header/raw", 62 | "/block/{idx}/pos", 63 | "/block/{idx}/raw", 64 | "/block/{idx}/size", 65 | "/block/{idx}/subsidy", 66 | "/block/{idx}/tx", 67 | "/block/{idx}/tx/", 68 | "/block/{idx}/tx/count", 69 | "/block/{idx}/verbose", 70 | "/chart", 71 | "/chart/market/{token}", 72 | "/chart/market/{token}/candlestick/{bin}", 73 | "/chart/market/{token}/depth", 74 | "/chart/{charttype}", 75 | "/exchanges", 76 | "/exchanges/", 77 | "/exchanges/codes", 78 | "/mempool", 79 | "/mempool/", 80 | "/mempool/sstx", 81 | "/mempool/sstx/", 82 | "/mempool/sstx/details", 83 | "/mempool/sstx/details/{N}", 84 | "/mempool/sstx/fees", 85 | "/mempool/sstx/fees/{N}", 86 | "/proposal", 87 | "/proposal/{token}", 88 | "/stake", 89 | "/stake/diff", 90 | "/stake/diff/", 91 | "/stake/diff/b/{idx}", 92 | "/stake/diff/current", 93 | "/stake/diff/estimates", 94 | "/stake/diff/r/{idx0}/{idx}", 95 | "/stake/pool", 96 | "/stake/pool/", 97 | "/stake/pool/b/{idx}", 98 | "/stake/pool/b/{idxorhash}/full", 99 | "/stake/pool/full", 100 | "/stake/pool/r/{idx0}/{idx}", 101 | "/stake/powerless", 102 | "/stake/vote", 103 | "/stake/vote/info", 104 | "/status", 105 | "/status/happy", 106 | "/supply", 107 | "/ticketpool", 108 | "/ticketpool/", 109 | "/ticketpool/bydate/{tp}", 110 | "/ticketpool/charts", 111 | "/tx", 112 | "/tx/decoded/{txid}", 113 | "/tx/hex/{txid}", 114 | "/tx", 115 | "/tx/{txid}", 116 | "/tx/{txid}/", 117 | "/tx/{txid}/in", 118 | "/tx/{txid}/in/", 119 | "/tx/{txid}/in/{txinoutindex}", 120 | "/tx/{txid}/out", 121 | "/tx/{txid}/out/", 122 | "/tx/{txid}/out/{txinoutindex}", 123 | "/tx/{txid}/tinfo", 124 | "/tx/{txid}/trimmed", 125 | "/tx/{txid}/vinfo", 126 | "/txs", 127 | "/txs/", 128 | "/txs/trimmed" 129 | ] 130 | -------------------------------------------------------------------------------- /decred/tests/unit/dcr/test_agenda.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, the Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | from decred.dcr.agenda import Agenda, AgendaChoices, AgendaInfo, AgendasInfo 7 | 8 | 9 | AGENDA_INFO_RAW = dict(status="defined", since=1, starttime=2, expiretime=3) 10 | # Parsed values are the same as the raw ones. 11 | AGENDA_INFO_PARSED = AGENDA_INFO_RAW 12 | AGENDA_INFO_ATTRS = ( 13 | "status", 14 | "since", 15 | "startTime", 16 | "expireTime", 17 | ) 18 | 19 | 20 | def test_agenda_info(): 21 | do_test( 22 | AgendaInfo, AGENDA_INFO_RAW, AGENDA_INFO_PARSED, AGENDA_INFO_ATTRS, 23 | ) 24 | 25 | 26 | AGENDA_CHOICES_RAW = dict( 27 | id="choices_id", 28 | description="description", 29 | bits=0, 30 | isabstain=False, 31 | isno=False, 32 | count=0, 33 | progress=0.0, 34 | ) 35 | # Parsed values are the same as the raw ones. 36 | AGENDA_CHOICES_PARSED = AGENDA_CHOICES_RAW 37 | AGENDA_CHOICES_ATTRS = ( 38 | "id", 39 | "description", 40 | "bits", 41 | "isAbstain", 42 | "isNo", 43 | "count", 44 | "progress", 45 | ) 46 | 47 | 48 | def test_agenda_choices(): 49 | do_test( 50 | AgendaChoices, AGENDA_CHOICES_RAW, AGENDA_CHOICES_PARSED, AGENDA_CHOICES_ATTRS, 51 | ) 52 | 53 | 54 | AGENDA_RAW = dict( 55 | id="agenda_id", 56 | description="description", 57 | mask=0, 58 | starttime=0, 59 | expiretime=0, 60 | status="status", 61 | quorumprogress=0.0, 62 | choices=[AGENDA_CHOICES_RAW], 63 | ) 64 | # Make a copy of the raw dict in order not to overwrite it. 65 | AGENDA_PARSED = dict(AGENDA_RAW) 66 | AGENDA_PARSED["choices"] = [AgendaChoices.parse(AGENDA_CHOICES_RAW)] 67 | AGENDA_ATTRS = ( 68 | "id", 69 | "description", 70 | "mask", 71 | "startTime", 72 | "expireTime", 73 | "status", 74 | "quorumProgress", 75 | "choices", 76 | ) 77 | 78 | 79 | def test_agenda(): 80 | do_test( 81 | Agenda, AGENDA_RAW, AGENDA_PARSED, AGENDA_ATTRS, 82 | ) 83 | 84 | 85 | AGENDAS_INFO_RAW = { 86 | "currentheight": 0, 87 | "startheight": 0, 88 | "endheight": 0, 89 | "hash": "hash", 90 | "voteversion": 0, 91 | "quorum": 0.0, 92 | "totalvotes": 0, 93 | "agendas": [AGENDA_RAW], 94 | } 95 | # Make a copy of the raw dict in order not to overwrite it. 96 | AGENDAS_INFO_PARSED = dict(AGENDAS_INFO_RAW) 97 | AGENDAS_INFO_PARSED["agendas"] = [Agenda.parse(AGENDA_RAW)] 98 | AGENDAS_INFO_ATTRS = ( 99 | "currentHeight", 100 | "startHeight", 101 | "endHeight", 102 | "hash", 103 | "voteVersion", 104 | "quorum", 105 | "totalVotes", 106 | "agendas", 107 | ) 108 | 109 | 110 | def test_agendas_info(): 111 | do_test( 112 | AgendasInfo, AGENDAS_INFO_RAW, AGENDAS_INFO_PARSED, AGENDAS_INFO_ATTRS, 113 | ) 114 | 115 | 116 | def do_test(class_, raw, parsed, attrs): 117 | """ 118 | Iterate over the attributes defined in _ATTRS and make sure that 119 | the values generated by its "parse" method are the same as the ones in 120 | _PARSED. 121 | 122 | Separate _ATTRS tuples are needed because the attribute names are 123 | different from the keys of the dicts the values are gotten from. 124 | """ 125 | obj = class_.parse(raw) 126 | for attr in attrs: 127 | assert getattr(obj, attr) == parsed[attr.lower()] 128 | 129 | 130 | def test_eq(): 131 | # AgendaChoices 132 | choices = AgendaChoices.parse(AGENDA_CHOICES_RAW) 133 | assert choices != object() 134 | assert choices == choices 135 | 136 | # Agenda 137 | agenda = Agenda.parse(AGENDA_RAW) 138 | assert agenda != object() 139 | assert agenda == agenda 140 | -------------------------------------------------------------------------------- /decred/tests/unit/dcr/test_blockchain_unit.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, the Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | import pytest 7 | 8 | from decred.crypto import crypto 9 | from decred.dcr import rpc 10 | from decred.dcr.blockchain import LocalNode 11 | from decred.dcr.nets import mainnet 12 | from decred.util.encode import ByteArray 13 | 14 | 15 | @pytest.fixture 16 | def node(monkeypatch, tmp_path): 17 | existsAddresses = set() 18 | 19 | class TestWsClient: 20 | 21 | closed = False 22 | 23 | def __init__(self, url, user, pw, cert=None): 24 | pass 25 | 26 | def existsAddresses(self, addrs): 27 | return [addr in existsAddresses for addr in addrs] 28 | 29 | monkeypatch.setattr(rpc, "WebsocketClient", TestWsClient) 30 | bc = LocalNode( 31 | netParams=mainnet, 32 | dbPath=tmp_path / "tmp.db", 33 | url="url", 34 | user="user", 35 | pw="pass", 36 | ) 37 | assert bc.connected() 38 | bc._existsAddresses = existsAddresses 39 | return bc 40 | 41 | 42 | def newExtendedKey(): 43 | xk = crypto.ExtendedKey.new( 44 | ByteArray("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef").b 45 | ) 46 | return xk.deriveCoinTypeKey(mainnet) 47 | 48 | 49 | # A sampling of addresses derived from the newExtendedKey key. 50 | testAddrs = { 51 | 3: "DsapZcX3rBfrx85nbWLVoL5PpQAFUJW8ygV", 52 | 4: "Dscb7StES7pLCgWUVA6jBVpPMdHyKjqSVc4", 53 | 9: "DsTTd9nYHsE6ygQSYkeCF3Lt9x3gfnzJNNh", 54 | } 55 | 56 | 57 | def test_discoverAddresses(node): 58 | existAddrs = node._existsAddresses 59 | 60 | dcrKey = newExtendedKey() 61 | 62 | # Calling with index 0 and no addresses should return an empty result set. 63 | discovered = node.discoverAddresses(dcrKey, 0, 5) 64 | assert len(discovered) == 0 65 | 66 | # Stick an address at indexes 3 and 4 67 | existAddrs.add(testAddrs[3]) 68 | existAddrs.add(testAddrs[4]) 69 | discovered = node.discoverAddresses(dcrKey, 0, 5) 70 | assert len(discovered) == 2 71 | assert discovered[0] == 3 72 | assert discovered[1] == 4 73 | 74 | # Now stick one at 4 + gap = 9 75 | existAddrs.add(testAddrs[9]) 76 | discovered = node.discoverAddresses(dcrKey, 0, 5) 77 | assert len(discovered) == 3 78 | assert discovered[2] == 9 79 | 80 | # But sticking in just the one at 9 should return nothing still 81 | existAddrs.clear() 82 | existAddrs.add(testAddrs[9]) 83 | discovered = node.discoverAddresses(dcrKey, 0, 5) 84 | assert len(discovered) == 0 85 | -------------------------------------------------------------------------------- /decred/tests/unit/dcr/test_nets.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019, the Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | import pytest 7 | 8 | from decred import DecredError 9 | from decred.dcr import nets 10 | 11 | 12 | def test_nets(): 13 | assert nets.parse("mainnet") is nets.mainnet 14 | 15 | with pytest.raises(DecredError): 16 | nets.parse("nonet") 17 | -------------------------------------------------------------------------------- /decred/tests/unit/dcr/test_rpc_unit.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, the Decred developers 3 | See LICENSE for details 4 | 5 | Tests use the "http_get_post" and "MockWebSocketClient" fixtures in conftest.py 6 | """ 7 | 8 | import pytest 9 | 10 | from decred import DecredError 11 | from decred.dcr import rpc 12 | from decred.util import ws 13 | from decred.util.encode import ByteArray 14 | 15 | 16 | def test_Client(http_get_post): 17 | http_get_post( 18 | ( 19 | "https://example.org", 20 | "{'jsonrpc': '2.0', 'id': 0, 'method': 'stop', 'params': ()}", 21 | ), 22 | '{"id": 0, "result": "dcrd stopping.", "error": ""}', 23 | ) 24 | client = rpc.Client(url="https://example.org", user="username", pw="password",) 25 | assert client.stop() == "dcrd stopping." 26 | 27 | 28 | GET_ADDED_NODE_INFO_RESULT_ADDR_RAW = dict(address="127.0.0.1", connected="false") 29 | 30 | 31 | def test_GetAddedNodeInfoResultAddr(): 32 | # Parsed values are the same as the raw ones. 33 | GET_ADDED_NODE_INFO_RESULT_ADDR_PARSED = GET_ADDED_NODE_INFO_RESULT_ADDR_RAW 34 | GET_ADDED_NODE_INFO_RESULT_ADDR_ATTRS = ("address", "connected") 35 | 36 | do_test( 37 | class_=rpc.GetAddedNodeInfoResultAddr, 38 | raw=GET_ADDED_NODE_INFO_RESULT_ADDR_RAW, 39 | parsed=GET_ADDED_NODE_INFO_RESULT_ADDR_PARSED, 40 | attrs=GET_ADDED_NODE_INFO_RESULT_ADDR_ATTRS, 41 | ) 42 | 43 | 44 | def test_GetAddedNodeInfoResult(): 45 | GET_ADDED_NODE_INFO_RESULT_RAW = dict( 46 | addednode="127.0.0.1", 47 | connected=False, 48 | addresses=[GET_ADDED_NODE_INFO_RESULT_ADDR_RAW], 49 | ) 50 | # Make a copy of the raw dict in order not to overwrite it. 51 | GET_ADDED_NODE_INFO_RESULT_PARSED = dict(GET_ADDED_NODE_INFO_RESULT_RAW) 52 | GET_ADDED_NODE_INFO_RESULT_PARSED["addresses"] = [ 53 | rpc.GetAddedNodeInfoResultAddr.parse(GET_ADDED_NODE_INFO_RESULT_ADDR_RAW) 54 | ] 55 | GET_ADDED_NODE_INFO_RESULT_ATTRS = ("addedNode", "connected", "addresses") 56 | 57 | do_test( 58 | class_=rpc.GetAddedNodeInfoResult, 59 | raw=GET_ADDED_NODE_INFO_RESULT_RAW, 60 | parsed=GET_ADDED_NODE_INFO_RESULT_PARSED, 61 | attrs=GET_ADDED_NODE_INFO_RESULT_ATTRS, 62 | ) 63 | 64 | 65 | def test_GetWorkResult(): 66 | GET_WORK_RESULT_RAW = dict(data=[0, 1], target=[2, 3]) 67 | GET_WORK_RESULT_PARSED = dict(data=ByteArray([0, 1]), target=ByteArray([2, 3])) 68 | GET_WORK_RESULT_ATTRS = ("data", "target") 69 | 70 | do_test( 71 | class_=rpc.GetWorkResult, 72 | raw=GET_WORK_RESULT_RAW, 73 | parsed=GET_WORK_RESULT_PARSED, 74 | attrs=GET_WORK_RESULT_ATTRS, 75 | ) 76 | 77 | 78 | def test_PrevOut(): 79 | PREV_OUT_RAW = dict(value=1.0, addresses=["addr"]) 80 | # Parsed values are the same as the raw ones. 81 | PREV_OUT_PARSED = PREV_OUT_RAW 82 | PREV_OUT_ATTRS = ("value", "addresses") 83 | 84 | do_test( 85 | class_=rpc.PrevOut, 86 | raw=PREV_OUT_RAW, 87 | parsed=PREV_OUT_PARSED, 88 | attrs=PREV_OUT_ATTRS, 89 | ) 90 | 91 | 92 | def do_test(class_, raw, parsed, attrs): 93 | """ 94 | Iterate over the attributes defined in _ATTRS and make sure that 95 | the values generated by its "parse" method are the same as the ones in 96 | _PARSED. 97 | 98 | Separate _ATTRS tuples are needed because the attribute names are 99 | different from the keys of the dicts the values are gotten from. 100 | """ 101 | obj = class_.parse(raw) 102 | for attr in attrs: 103 | assert getattr(obj, attr) == parsed[attr.lower()] 104 | 105 | 106 | def test_eq(): 107 | address = rpc.GetAddedNodeInfoResultAddr.parse(GET_ADDED_NODE_INFO_RESULT_ADDR_RAW) 108 | assert address != object() 109 | assert address == address 110 | 111 | 112 | def test_WebsocketClient(monkeypatch, MockWebSocketClient): 113 | 114 | monkeypatch.setattr(ws, "Client", MockWebSocketClient) 115 | 116 | wsClient = rpc.WebsocketClient( 117 | url="https://example.org", user="username", pw="password", 118 | ) 119 | wsClient.requestTimeout = 1 120 | 121 | msg = '{"id": -1, "result": "", "error": "error"}' 122 | assert wsClient.on_message(msg) is None 123 | 124 | msg = "not_json" 125 | assert wsClient.on_message(msg) is None 126 | 127 | assert wsClient.call("no_reply") is None 128 | 129 | def send_with_reply(msg): 130 | wsClient.ws.on_message('{"id": 1, "result": "", "error": "error"}') 131 | 132 | # The response is sent back right away by the above class. 133 | wsClient.ws.send = send_with_reply 134 | with pytest.raises(DecredError): 135 | wsClient.call("no_such_method") 136 | 137 | assert wsClient.on_close(wsClient.ws) is None 138 | 139 | assert wsClient.on_error("error") is None 140 | -------------------------------------------------------------------------------- /decred/tests/unit/dcr/wire/test_msgblock.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019-2020, the Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | from decred.dcr.wire import msgblock 7 | from decred.util.encode import ByteArray 8 | 9 | 10 | class TestBlockHeader: 11 | def make_block_header(self): 12 | bh = msgblock.BlockHeader() 13 | bh.version = 6 14 | bh.prevBlock = reversed( 15 | ByteArray( 16 | "5cd0f1367afac81a6371785ad09dd67358b56257e6fc9e39f8f69be90855d20b" 17 | ) 18 | ) 19 | bh.merkleRoot = reversed( 20 | ByteArray( 21 | "da4fb4c83681ab9e7a186a9ccdf51e031a8319d57d8fbc5c4105b94050a77e97" 22 | ) 23 | ) 24 | bh.stakeRoot = reversed( 25 | ByteArray( 26 | "0000000000000000000000000000000000000000000000000000000000000000" 27 | ) 28 | ) 29 | bh.voteBits = 1 30 | bh.finalState = ByteArray("000000000000") 31 | bh.voters = 0 32 | bh.freshStake = 0 33 | bh.revocations = 0 34 | bh.poolSize = 0 35 | bh.bits = 545259519 36 | bh.sBits = 20000 37 | bh.height = 27 38 | bh.size = 358 39 | bh.timestamp = 1560468906 40 | bh.nonce = 0 41 | bh.extraData = ByteArray( 42 | "255221163779dfe8000000000000000000000000000000000000000000000000" 43 | ) 44 | bh.stakeVersion = 0 45 | encoded = ByteArray( 46 | "060000000bd25508e99bf6f8399efce65762b55873d69dd05a7871631ac8fa7a36f1d05c" 47 | "977ea75040b905415cbc8f7dd519831a031ef5cd9c6a187a9eab8136c8b44fda00000000" 48 | "000000000000000000000000000000000000000000000000000000000100000000000000" 49 | "0000000000000000ffff7f20204e0000000000001b00000066010000aadd025d00000000" 50 | "255221163779dfe800000000000000000000000000000000000000000000000000000000" 51 | ) 52 | return bh, encoded 53 | 54 | def test_decode(self): 55 | bh, encoded = self.make_block_header() 56 | b = bh.serialize() 57 | assert b == encoded 58 | reBH = msgblock.BlockHeader.unblob(ByteArray.hex(b)) 59 | assert bh.version == reBH.version 60 | assert bh.prevBlock == reBH.prevBlock 61 | assert bh.merkleRoot == reBH.merkleRoot 62 | assert bh.stakeRoot == reBH.stakeRoot 63 | assert bh.voteBits == reBH.voteBits 64 | assert bh.finalState == reBH.finalState 65 | assert bh.voters == reBH.voters 66 | assert bh.freshStake == reBH.freshStake 67 | assert bh.revocations == reBH.revocations 68 | assert bh.poolSize == reBH.poolSize 69 | assert bh.bits == reBH.bits 70 | assert bh.sBits == reBH.sBits 71 | assert bh.height == reBH.height 72 | assert bh.size == reBH.size 73 | assert bh.timestamp == reBH.timestamp 74 | assert bh.nonce == reBH.nonce 75 | assert bh.extraData == reBH.extraData 76 | assert bh.stakeVersion == reBH.stakeVersion 77 | assert bh.id() == reBH.id() 78 | 79 | def test_cached_hash(self): 80 | bh, _ = self.make_block_header() 81 | assert bh.cachedH is None 82 | hash_ = bh.cachedHash() 83 | assert hash_ == bh.cachedHash() 84 | -------------------------------------------------------------------------------- /decred/tests/unit/dcr/wire/test_msgping.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, The Decred developers 3 | See LICENSE for details. 4 | 5 | Based on dcrd MsgPing. 6 | """ 7 | 8 | import random 9 | 10 | from decred.dcr.wire import wire 11 | from decred.dcr.wire.msgping import MsgPing 12 | from decred.util.encode import ByteArray 13 | 14 | 15 | def test_Ping(): 16 | """ 17 | Test the MsgPing API against the latest protocol version. 18 | """ 19 | pver = wire.ProtocolVersion 20 | 21 | # Ensure we get the same nonce back out. 22 | nonce = random.randint(0, 0xFFFFFFFFFFFFFFFF) 23 | msg = MsgPing(nonce=nonce) 24 | assert msg.nonce == nonce 25 | 26 | # Ensure the command is expected value. 27 | assert msg.command() == "ping" 28 | 29 | maxPayload = msg.maxPayloadLength(pver) 30 | # Ensure max payload is expected value for latest protocol version. 31 | assert maxPayload == 8 32 | 33 | # Ensure max payload length is not more than MaxMessagePayload. 34 | assert maxPayload <= wire.MaxMessagePayload 35 | 36 | 37 | def test_PingWire(): 38 | """ 39 | Test the MsgPing wire encode and decode for various protocol versions. 40 | """ 41 | nonce = 0x1E0F3 42 | msg = MsgPing(nonce) 43 | msgEncoded = ByteArray("f3e0010000000000") 44 | 45 | # Encode the message to wire format. 46 | b = msg.btcEncode(wire.ProtocolVersion) 47 | assert b == msgEncoded 48 | 49 | # Decode the message from wire format. 50 | reMsg = MsgPing.btcDecode(b, wire.ProtocolVersion) 51 | assert reMsg.nonce == nonce 52 | -------------------------------------------------------------------------------- /decred/tests/unit/dcr/wire/test_msgpong.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, The Decred developers 3 | See LICENSE for details. 4 | 5 | Based on dcrd MsgPing. 6 | """ 7 | 8 | import random 9 | 10 | from decred.dcr.wire import wire 11 | from decred.dcr.wire.msgpong import MsgPong 12 | from decred.util.encode import ByteArray 13 | 14 | 15 | def test_Pong(): 16 | """ 17 | Test the MsgPong API against the latest protocol version. 18 | """ 19 | pver = wire.ProtocolVersion 20 | 21 | nonce = random.randint(0, 0xFFFFFFFFFFFFFFFF) 22 | msg = MsgPong(nonce) 23 | assert msg.nonce == nonce 24 | 25 | # Ensure the command is expected value. 26 | assert msg.command() == "pong" 27 | 28 | # Ensure max payload is expected value for latest protocol version. 29 | maxPayload = msg.maxPayloadLength(pver) 30 | assert maxPayload == 8 31 | 32 | # Ensure max payload length is not more than MaxMessagePayload. 33 | assert maxPayload <= wire.MaxMessagePayload 34 | 35 | # Test encode with latest protocol version. 36 | b = msg.btcEncode(pver) 37 | 38 | # Test decode with latest protocol version. 39 | reMsg = MsgPong.btcDecode(b, pver) 40 | 41 | # Ensure nonce is the same. 42 | assert msg.nonce == reMsg.nonce 43 | 44 | 45 | def test_PongWire(): 46 | """ 47 | Test the MsgPong wire encode and decode for various protocol versions. 48 | """ 49 | nonce = 0x1E0F3 50 | msg = MsgPong(nonce) 51 | msgEncoded = ByteArray("f3e0010000000000") 52 | 53 | # Encode the message to wire format. 54 | b = msg.btcEncode(wire.ProtocolVersion) 55 | assert b == msgEncoded 56 | 57 | # Decode the message from wire format. 58 | reMsg = MsgPong.btcDecode(b, wire.ProtocolVersion) 59 | assert reMsg.nonce == nonce 60 | -------------------------------------------------------------------------------- /decred/tests/unit/dcr/wire/test_msgverack.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, The Decred developers 3 | See LICENSE for details. 4 | """ 5 | 6 | from decred.dcr.wire import wire 7 | from decred.dcr.wire.msgverack import MsgVerAck 8 | from decred.util.encode import ByteArray 9 | 10 | 11 | def test_VerAck(): 12 | """ Test the MsgVerAck API. """ 13 | pver = wire.ProtocolVersion 14 | 15 | # Ensure the command is expected value. 16 | msg = MsgVerAck() 17 | assert msg.command() == "verack" 18 | 19 | # Ensure max payload is expected value. 20 | maxPayload = msg.maxPayloadLength(pver) 21 | assert msg.maxPayloadLength(pver) == 0 22 | 23 | # Ensure max payload length is not more than MaxMessagePayload. 24 | assert maxPayload <= wire.MaxMessagePayload 25 | 26 | 27 | def test_VerAckWire(): 28 | """ 29 | Test the MsgVerAck wire encode and decode for various protocol versions. 30 | """ 31 | msgVerAck = MsgVerAck() 32 | msgVerAckEncoded = ByteArray() 33 | 34 | # Encode the message to wire format. 35 | b = msgVerAck.btcEncode(wire.ProtocolVersion) 36 | assert b == msgVerAckEncoded 37 | 38 | # Decode the message from wire format. Just looking for exceptions. 39 | MsgVerAck.btcDecode(msgVerAckEncoded, wire.ProtocolVersion) 40 | -------------------------------------------------------------------------------- /decred/tests/unit/dcr/wire/test_netaddress.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, the Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | import pytest 7 | 8 | from decred import DecredError 9 | from decred.dcr.wire import netaddress, wire 10 | from decred.util.encode import ByteArray 11 | 12 | 13 | byteIP4 = bytes([127, 0, 0, 1]) 14 | 15 | 16 | def test_NetAddress(): 17 | ip = "127.0.0.1" 18 | port = 8333 19 | 20 | # Test NewNetAddress. 21 | na = netaddress.NetAddress(ip, port, 0) 22 | 23 | # Ensure we get the same ip, port, and services back out. 24 | assert byteIP4 == na.ip 25 | assert port == na.port 26 | assert na.services == 0 27 | assert not na.hasService(wire.SFNodeNetwork) 28 | 29 | # Ensure adding the full service node flag works. 30 | na.addService(wire.SFNodeNetwork) 31 | assert na.services == wire.SFNodeNetwork 32 | assert na.hasService(wire.SFNodeNetwork) 33 | 34 | # Ensure max payload is expected value for latest protocol version. 35 | wantPayload = 30 36 | maxPayload = netaddress.MaxNetAddressPayload 37 | assert maxPayload == wantPayload 38 | 39 | # Ensure max payload length is not more than MaxMessagePayload. 40 | assert maxPayload <= wire.MaxMessagePayload 41 | 42 | 43 | def test_NetAddressWire(): 44 | """ 45 | test the NetAddress wire encode and decode for various protocol versions and 46 | timestamp flag combinations. 47 | """ 48 | # baseNetAddr is used in the various tests as a baseline NetAddress. 49 | baseNetAddr = netaddress.NetAddress( 50 | ip="127.0.0.1", 51 | port=8333, 52 | services=wire.SFNodeNetwork, 53 | stamp=0x495FAB29, # 2009-01-03 12:15:05 -0600 CST 54 | ) 55 | 56 | # baseNetAddrNoTS is baseNetAddr with a zero value for the timestamp. 57 | baseNetAddrNoTS = netaddress.NetAddress( 58 | ip=baseNetAddr.ip, 59 | port=baseNetAddr.port, 60 | services=baseNetAddr.services, 61 | stamp=0, 62 | ) 63 | 64 | # fmt: off 65 | # baseNetAddrEncoded is the wire encoded bytes of baseNetAddr. 66 | baseNetAddrEncoded = ByteArray([ 67 | 0x29, 0xab, 0x5f, 0x49, # Timestamp 68 | 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # SFNodeNetwork 69 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 70 | 0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x01, # IP 127.0.0.1 71 | 0x20, 0x8d, # Port 8333 in big-endian 72 | ]) 73 | 74 | # baseNetAddrNoTSEncoded is the wire encoded bytes of baseNetAddrNoTS. 75 | baseNetAddrNoTSEncoded = ByteArray([ 76 | # No timestamp 77 | 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # SFNodeNetwork 78 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 79 | 0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x01, # IP 127.0.0.1 80 | 0x20, 0x8d, # Port 8333 in big-endian 81 | ]) 82 | # fmt: on 83 | 84 | """ 85 | addrIn NetAddress to encode 86 | out Expected decoded NetAddress 87 | ts Include timestamp? 88 | buf Wire encoding 89 | pver Protoc 90 | """ 91 | 92 | tests = [ 93 | # Latest protocol version without ts flag. 94 | dict( 95 | addrIn=baseNetAddr, 96 | out=baseNetAddrNoTS, 97 | ts=False, 98 | buf=baseNetAddrNoTSEncoded, 99 | pver=wire.ProtocolVersion, 100 | ), 101 | # Latest protocol version with ts flag. 102 | dict( 103 | addrIn=baseNetAddr, 104 | out=baseNetAddr, 105 | ts=True, 106 | buf=baseNetAddrEncoded, 107 | pver=wire.ProtocolVersion, 108 | ), 109 | ] 110 | 111 | for test in tests: 112 | # Encode to wire format. 113 | b = netaddress.writeNetAddress(test["addrIn"], test["ts"]) 114 | assert b == test["buf"] 115 | 116 | # Decode the message from wire format. 117 | na = netaddress.readNetAddress(test["buf"], test["ts"]) 118 | assert byteIP4 == test["out"].ip 119 | assert na.port == test["out"].port 120 | assert na.services == test["out"].services 121 | assert na.timestamp == test["out"].timestamp 122 | 123 | baseNetAddr.ip = None 124 | b = netaddress.writeNetAddress(baseNetAddr, True) 125 | reNA = netaddress.readNetAddress(b, True) 126 | assert reNA.ip == ByteArray(length=16) 127 | 128 | # make sure a ipv6 address parses without an exception. 129 | netaddress.NetAddress( 130 | ip="2001:0db8:85a3:0000:0000:8a2e:0370:7334", 131 | port=8333, 132 | services=wire.SFNodeNetwork, 133 | stamp=0x495FAB29, # 2009-01-03 12:15:05 -0600 CST 134 | ) 135 | 136 | 137 | def test_NetAddressDecodeErrors(): 138 | """ 139 | Peform negative tests against wire encode and decode NetAddress to confirm 140 | error paths work correctly. 141 | """ 142 | 143 | # baseNetAddr is used in the various tests as a baseline NetAddress. 144 | baseNetAddr = netaddress.NetAddress( 145 | ip="127.0.0.1", 146 | port=8333, 147 | services=wire.SFNodeNetwork, 148 | stamp=0x495FAB29, # 2009-01-03 12:15:05 -0600 CST 149 | ) 150 | 151 | bufWithTime = netaddress.writeNetAddress(baseNetAddr, True) 152 | bufWithoutTime = netaddress.writeNetAddress(baseNetAddr, False) 153 | 154 | tests = [ 155 | (bufWithTime[:-1], True), 156 | (bufWithTime + [0], True), 157 | (bufWithoutTime[:-1], False), 158 | (bufWithoutTime + [0], False), 159 | (ByteArray(), True), 160 | ] 161 | 162 | for buf, hasStamp in tests: 163 | with pytest.raises(DecredError): 164 | netaddress.readNetAddress(buf, hasStamp) 165 | -------------------------------------------------------------------------------- /decred/tests/unit/dcr/wire/test_wire.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019-2020, the Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | import pytest 7 | 8 | from decred import DecredError 9 | from decred.dcr.wire import wire 10 | from decred.util.encode import ByteArray 11 | 12 | 13 | class TestWire: 14 | # fmt: off 15 | data = ( 16 | (0xFC, [0xFC]), 17 | (0xFD, [0xFD, 0xFD, 0x0]), 18 | (wire.MaxUint16, [0xFD, 0xFF, 0xFF]), 19 | (wire.MaxUint16 + 1, [0xFE, 0x0, 0x0, 0x1, 0x0]), 20 | (wire.MaxUint32, [0xFE, 0xFF, 0xFF, 0xFF, 0xFF]), 21 | (wire.MaxUint32 + 1, [0xFF, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0]), 22 | (wire.MaxUint64, [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]), 23 | ) 24 | # fmt: on 25 | 26 | def test_write_var_int(self, prepareLogger): 27 | for val, bytes_ in self.data: 28 | from_val = wire.writeVarInt(wire.ProtocolVersion, val) 29 | from_bytes = ByteArray(bytes_) 30 | assert from_val == from_bytes 31 | val_from_bytes = wire.readVarInt(from_bytes, wire.ProtocolVersion) 32 | assert val_from_bytes == val 33 | with pytest.raises(DecredError): 34 | wire.writeVarInt(wire.ProtocolVersion, wire.MaxUint64 + 1) 35 | 36 | def test_read_var_int(self, prepareLogger): 37 | assert wire.readVarInt(ByteArray([0xFC]), wire.ProtocolVersion) == 0xFC 38 | with pytest.raises(DecredError): 39 | wire.readVarInt( 40 | ByteArray([0xFE, 0xFF, 0xFF, 0x0, 0x0]), wire.ProtocolVersion 41 | ) 42 | with pytest.raises(DecredError): 43 | wire.readVarInt(ByteArray([0xFD, 0xFC, 0x0]), wire.ProtocolVersion) 44 | -------------------------------------------------------------------------------- /decred/tests/unit/util/test_chains.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, the Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | import pytest 7 | 8 | from decred import DecredError 9 | from decred.util import chains 10 | 11 | 12 | def test_parseCoinType(): 13 | with pytest.raises(DecredError): 14 | chains.parseCoinType("not_a_coin") 15 | 16 | assert chains.parseCoinType("DCR") == 42 17 | assert chains.parseCoinType(42) == 42 18 | assert chains.parseCoinType(-1) == -1 19 | 20 | with pytest.raises(DecredError): 21 | chains.parseCoinType(None) 22 | -------------------------------------------------------------------------------- /decred/tests/unit/util/test_database.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019-2020, the Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | import random 7 | 8 | import pytest 9 | 10 | from decred import DecredError 11 | from decred.util import database 12 | from decred.util.encode import ByteArray 13 | 14 | 15 | class TBlobber: 16 | def __init__(self, b): 17 | self.b = b 18 | 19 | @staticmethod 20 | def unblob(b): 21 | return TBlobber(ByteArray(b)) 22 | 23 | @staticmethod 24 | def blob(tb): 25 | """Satisfies the encode.Blobber API""" 26 | return tb.b.bytes() 27 | 28 | def __eq__(self, other): 29 | return self.b == other.b 30 | 31 | 32 | def test_database(prepareLogger, randBytes): 33 | # Open a key value db in the temp directory. 34 | master = database.KeyValueDatabase(":memory:") 35 | 36 | # '$' in bucket name is illegal. 37 | with pytest.raises(DecredError): 38 | master.child("a$b") 39 | 40 | try: 41 | db = master.child("test") 42 | 43 | # Again, '$' in bucket name is illegal. 44 | with pytest.raises(DecredError): 45 | db.child("c$d") 46 | 47 | # Create some test data. 48 | random.seed(0) 49 | testPairs = [(randBytes(low=1), randBytes()) for _ in range(20)] 50 | runPairs(db, testPairs) 51 | 52 | # check integer keys and child naming scheme 53 | intdb = db.child("inttest", datatypes=("INTEGER", "BLOB")) 54 | assert intdb.name == "test$inttest" 55 | intdb[5] = b"asdf" 56 | assert intdb[5] == b"asdf" 57 | intdb[6] = b"jkl;" 58 | assert intdb.first() == (5, b"asdf") 59 | assert intdb.last() == (6, b"jkl;") 60 | 61 | # check uniqueness of keys: 62 | k = testPairs[0][0] 63 | db[k] = b"some new bytes" 64 | assert len([key for key in db if key == k]) == 1 65 | 66 | # test a serializable object 67 | randBlobber = lambda: TBlobber(ByteArray(randBytes())) 68 | objDB = master.child("blobber", blobber=TBlobber, unique=False) 69 | testPairs = [(randBytes(low=1), randBlobber()) for _ in range(20)] 70 | runPairs(objDB, testPairs) 71 | 72 | # non-uniqueness of keys 73 | k = testPairs[0][0] 74 | objDB[k] = randBlobber() 75 | assert len([key for key in objDB if key == k]) == 2 76 | 77 | # test a second-level child 78 | kidDB = db.child("kid") 79 | testPairs = [(randBytes(low=1), randBytes()) for _ in range(20)] 80 | runPairs(kidDB, testPairs) 81 | 82 | # uniqueness of table keys 83 | k = testPairs[0][0] 84 | kidDB[k] = b"some new bytes" 85 | assert len([key for key in kidDB if key == k]) == 1 86 | 87 | # slice notation 88 | sliceDB = db.child("slice", datatypes=("INTEGER", "TEXT")) 89 | n = 5 90 | for i in range(n): 91 | sliceDB[i] = str(i) 92 | nums = sliceDB[:n] 93 | assert len(nums) == 5 94 | assert all(i == int(s) for i, s in nums) 95 | assert sliceDB.last()[0] == 4 96 | 97 | finally: 98 | master.close() 99 | 100 | 101 | def runPairs(db, testPairs): 102 | ogKeys = {k: v for k, v in testPairs} 103 | values = [v for _, v in testPairs] 104 | 105 | # Ensure the db has zero length. 106 | assert len(db) == 0 107 | 108 | # Insert the test pairs. 109 | for k, v in testPairs: 110 | db[k] = v 111 | 112 | # Check length again 113 | assert len(db) == len(testPairs) 114 | 115 | # Check items iteration 116 | for k, v in db.items(): 117 | assert k in ogKeys 118 | assert v == ogKeys[k] 119 | 120 | # Check key iteration. 121 | for k in db: 122 | assert k in ogKeys 123 | del ogKeys[k] 124 | assert len(ogKeys) == 0 125 | 126 | # Check value iteration. 127 | for v in db.values(): 128 | values.remove(v) 129 | assert len(values) == 0 130 | 131 | # Delete an item 132 | k = testPairs[0][0] 133 | del db[k] 134 | # Check the length again 135 | assert len(db) == len(testPairs) - 1 136 | 137 | # Make sure the right row was deleted. 138 | with pytest.raises(database.NoValueError): 139 | v = db[k] 140 | 141 | # Remmove the corresponding test pair from the dict. 142 | testPairs.pop(0) 143 | 144 | # Make sure the rest are retrievable. 145 | for k, _ in testPairs: 146 | v = db[k] 147 | 148 | # Delete the rest 149 | for k, v in testPairs: 150 | del db[k] 151 | 152 | # Check the length 153 | assert len(db) == 0 154 | 155 | # Insert again 156 | for k, v in testPairs: 157 | db[k] = v 158 | 159 | # Make sure nothing has changed. 160 | for k, _ in testPairs: 161 | assert k in db 162 | 163 | # Clear the database, batch insert, and try again. 164 | db.clear() 165 | assert len(db) == 0 166 | db.batchInsert(testPairs) 167 | for k, _ in testPairs: 168 | assert k in db 169 | -------------------------------------------------------------------------------- /decred/tests/unit/util/test_encode.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019-2020, the Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | import pytest 7 | 8 | from decred import DecredError 9 | from decred.util.encode import ( 10 | BuildyBytes, 11 | ByteArray, 12 | decodeBlob, 13 | extractPushes, 14 | unblobCheck, 15 | ) 16 | 17 | 18 | def test_ByteArray(): 19 | makeA = lambda: ByteArray([0, 0, 255]) 20 | makeB = lambda: ByteArray([0, 255, 0]) 21 | makeC = lambda: ByteArray([255, 0, 0]) 22 | zero = ByteArray([0, 0, 0]) 23 | 24 | a = makeA() 25 | b = makeB() 26 | a |= b 27 | assert a == bytearray([0, 255, 255]) 28 | 29 | c = makeC() 30 | a &= c 31 | assert a == zero 32 | 33 | a = makeA() 34 | a.zero() 35 | assert a == zero 36 | 37 | c = makeA() 38 | c |= 0 39 | assert a == zero 40 | 41 | a = makeA() 42 | c = makeC() 43 | assert a & c == zero 44 | 45 | assert makeA().iseven() is False 46 | assert makeB().iseven() 47 | assert makeC().iseven() 48 | assert zero.iseven() 49 | 50 | zero2 = ByteArray(zero) 51 | assert zero.b is not zero2.b 52 | assert zero == zero2 53 | 54 | zero2 = ByteArray(zero, copy=False) 55 | assert zero.b is zero2.b 56 | 57 | a = makeA() 58 | a |= makeC() 59 | a |= 65280 60 | assert a == bytearray([255, 255, 255]) 61 | assert a != makeB() 62 | assert a != None # noqa 63 | 64 | assert makeA() < makeB() 65 | assert makeC() > makeB() 66 | assert makeA() != makeB() 67 | assert not (makeA() == None) # noqa 68 | assert makeA() != None # noqa 69 | assert makeA() <= makeA() 70 | assert makeB() >= makeA() 71 | 72 | a = makeA() 73 | a2 = ByteArray(zero) 74 | a2 |= a[2:] 75 | assert a is not a2 76 | assert a == a2 77 | assert a[2] == 255 78 | 79 | z = ByteArray(zero) 80 | z[2] = 255 81 | assert makeA() == z 82 | 83 | # encode.Blobber API 84 | assert isinstance(ByteArray.unblob(zero), ByteArray) 85 | assert ByteArray.blob(zero) == zero.b 86 | 87 | with pytest.raises(DecredError): 88 | zero[3] = 0 89 | 90 | 91 | def test_BuildyBytes(): 92 | assert BuildyBytes().hex() == "" 93 | 94 | d0 = ByteArray([0x01, 0x02]) 95 | d1 = ByteArray([0x03, 0x04, 0x05]) 96 | d2 = ByteArray(b"") 97 | res = BuildyBytes(0).addData(d0).addData(d1).addData(d2) 98 | exp = ByteArray([0x00, 0x02, 0x01, 0x02, 0x03, 0x03, 0x04, 0x05, 0x00]) 99 | assert res == exp 100 | 101 | # Now do a versioned blob with a data push > 255 bytes. 102 | dBig = ByteArray(b"", length=257) 103 | dBig[100] = 0x12 104 | res = BuildyBytes(5).addData(d0).addData(d1).addData(d2).addData(dBig) 105 | ver, pushes = decodeBlob(res) 106 | assert ver == 5 107 | assert d0 == pushes[0] 108 | assert d1 == pushes[1] 109 | assert d2 == pushes[2] 110 | assert dBig == pushes[3] 111 | 112 | dHuge = ByteArray(b"", length=65536) 113 | with pytest.raises(DecredError): 114 | BuildyBytes(0).addData(dHuge) 115 | 116 | 117 | def test_decodeBlob(): 118 | with pytest.raises(DecredError): 119 | decodeBlob(b"") 120 | assert decodeBlob(bytes((0, 0x01, 0x02))) == (0, [b"\x02"]) 121 | 122 | 123 | def test_extractPushes(): 124 | assert len(extractPushes(b"")) == 0 125 | 126 | with pytest.raises(DecredError): 127 | extractPushes(ByteArray([0xFF])) 128 | 129 | assert extractPushes(ByteArray([0xFF, 0x00, 0x00])) == [ByteArray()] 130 | 131 | with pytest.raises(DecredError): 132 | extractPushes(ByteArray([0x01])) 133 | 134 | assert extractPushes(ByteArray([0x00])) == [ByteArray()] 135 | assert extractPushes(ByteArray([0x01, 0x00])) == [ByteArray([0x00])] 136 | 137 | 138 | def test_unblobCheck(): 139 | data = {0: 1} 140 | # Unsupported version. 141 | with pytest.raises(NotImplementedError): 142 | unblobCheck("test", 1, 0, data) 143 | # Unexpected pushes. 144 | with pytest.raises(DecredError): 145 | unblobCheck("test", 0, 2, data) 146 | # No errors. 147 | assert unblobCheck("test", 0, 1, data) is None 148 | -------------------------------------------------------------------------------- /decred/tests/unit/util/test_tinyhttp.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, the Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | import urllib.request as urlrequest 7 | 8 | import pytest 9 | 10 | from decred import DecredError 11 | from decred.util import tinyhttp 12 | 13 | 14 | @pytest.fixture 15 | def urlopen(monkeypatch): 16 | """ 17 | Tests will use the returned "queue" function to add responses they want 18 | returned from "urllib.request.urlopen" when used in tinyhttp. 19 | """ 20 | q = {} 21 | 22 | def mock_urlopen(req, context=None): 23 | """ 24 | Args: 25 | req: an object with "full_url" and "data" attributes, for instance 26 | urllib.request.Request. 27 | Return: 28 | an object with "read" and "decode" methods, for instance 29 | http.client.HTTPResponse. 30 | """ 31 | 32 | class Response(str): 33 | read = lambda self: self 34 | decode = lambda self: self 35 | 36 | key = (req.full_url, req.data) 37 | print(req.data) 38 | reply = q[key].pop() 39 | return Response(reply) 40 | 41 | monkeypatch.setattr(urlrequest, "urlopen", mock_urlopen) 42 | 43 | def queue(url, data=None, reply=""): 44 | q.setdefault((url, data), []).append(reply) 45 | 46 | return queue 47 | 48 | 49 | def test_get(urlopen): 50 | urlopen("http://example.org/", None, "[0, 1, 2]") 51 | assert tinyhttp.get("http://example.org/") == [0, 1, 2] 52 | 53 | 54 | def test_post(urlopen): 55 | urlopen("http://example.org/", b'{"a": "b"}', '{"c": "d"}') 56 | assert tinyhttp.post("http://example.org/", {"a": "b"}) == {"c": "d"} 57 | 58 | 59 | def test_request_urlencoded(urlopen): 60 | urlopen("http://example.org/", b"a=b", '{"c": "d"}') 61 | assert tinyhttp.request( 62 | "http://example.org/", postData={"a": "b"}, urlEncode=True, 63 | ) == {"c": "d"} 64 | 65 | 66 | def test_request_urlopen_error(monkeypatch): 67 | def urlopen_error(req, context=None): 68 | raise DecredError("test error") 69 | 70 | monkeypatch.setattr(urlrequest, "urlopen", urlopen_error) 71 | with pytest.raises(DecredError): 72 | tinyhttp.get("http://example.org/") 73 | 74 | 75 | def test_request_json_decode_error(urlopen): 76 | urlopen("http://example.org/", b'{"a": "b"}', "nojson") 77 | assert tinyhttp.request("http://example.org/", postData={"a": "b"}) == "nojson" 78 | -------------------------------------------------------------------------------- /decred/tests/unit/wallet/test_accounts.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019-2020, the Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | import pytest 7 | 8 | from decred import DecredError 9 | from decred.crypto import crypto, rando 10 | from decred.dcr import account, addrlib, nets 11 | from decred.util import chains, database, encode 12 | from decred.wallet import accounts 13 | 14 | 15 | testSeed = encode.ByteArray( 16 | "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" 17 | ).b 18 | 19 | tRoot = crypto.ExtendedKey.new(testSeed) 20 | 21 | 22 | def test_child_neuter(prepareLogger): 23 | """ 24 | Test the ExtendedKey.neuter method. 25 | """ 26 | extKey = crypto.ExtendedKey.new(testSeed) 27 | extKey.setNetwork(nets.mainnet) 28 | extKey.child(0) 29 | pub = extKey.neuter() 30 | expStr = ( 31 | "dpubZ9169KDAEUnyo8vdTJcpFWeaUEKH3G6detaXv46HxtQcENwxGBbRqbfTCJ9BUnWP" 32 | + "CkE8WApKPJ4h7EAapnXCZq1a9AqWWzs1n31VdfwbrQk" 33 | ) 34 | assert pub.string() == expStr 35 | 36 | 37 | def test_change_addresses(prepareLogger): 38 | """ 39 | Test internal branch address derivation. 40 | """ 41 | cryptoKey = rando.newKey() 42 | db = database.KeyValueDatabase(":memory:").child("tmp") 43 | # ticker for coin type is ok. Case insensitive. 44 | acctMgr = accounts.createNewAccountManager( 45 | tRoot, cryptoKey, "DcR", nets.mainnet, db 46 | ) 47 | acct = acctMgr.openAccount(0, cryptoKey) 48 | for i in range(10): 49 | acct.nextInternalAddress() 50 | 51 | 52 | def test_account_manager(monkeypatch, prepareLogger): 53 | # Set up globals for test. 54 | monkeypatch.setattr(account, "DefaultGapLimit", 2) 55 | 56 | cryptoKey = rando.newKey() 57 | db = database.KeyValueDatabase(":memory:").child("tmp") 58 | # 42 = Decred 59 | acctMgr = accounts.createNewAccountManager(tRoot, cryptoKey, 42, nets.mainnet, db) 60 | 61 | acct = acctMgr.openAccount(0, cryptoKey) 62 | tempAcct = acctMgr.addAccount(cryptoKey, "temp") 63 | assert acctMgr.account(1) == tempAcct 64 | assert acctMgr.listAccounts() == [acct, tempAcct] 65 | 66 | acctMgr.setNode("node") 67 | assert acctMgr.node == "node" 68 | assert tempAcct.node == "node" 69 | 70 | acctMgr.accounts[3] = tempAcct 71 | del acctMgr.accounts[1] 72 | with pytest.raises(DecredError): 73 | acctMgr.listAccounts() 74 | del acctMgr.accounts[3] 75 | 76 | with pytest.raises(DecredError): 77 | accounts.AccountManager.unblob(encode.BuildyBytes(0)) 78 | 79 | zeroth = acct.currentAddress() 80 | b = acctMgr.serialize() 81 | reAM = accounts.AccountManager.unblob(b.b) 82 | assert acctMgr.coinType == reAM.coinType 83 | assert acctMgr.netName == reAM.netName 84 | assert acctMgr.netName == reAM.netName 85 | 86 | reAM.load(db, None) 87 | reAcct = reAM.openAccount(0, cryptoKey) 88 | reZeroth = reAcct.currentAddress() 89 | assert zeroth == reZeroth 90 | 91 | acctMgr.saveAccount(0) 92 | db = acctMgr.dbForAcctIdx(0) 93 | assert db.name == "tmp$accts$0" 94 | 95 | 96 | def test_discover(monkeypatch, prepareLogger): 97 | cryptoKey = rando.newKey() 98 | db = database.KeyValueDatabase(":memory:").child("tmp") 99 | acctMgr = accounts.createNewAccountManager( 100 | tRoot, cryptoKey, "dcr", nets.mainnet, db 101 | ) 102 | txs = {} 103 | 104 | class Blockchain: 105 | params = nets.mainnet 106 | addrsHaveTxs = lambda addrs: any(a in txs for a in addrs) 107 | 108 | # Set up globals for test. 109 | origChain = chains.chain("dcr") 110 | chains.registerChain("dcr", Blockchain) 111 | # Set up globals for test. 112 | monkeypatch.setattr(accounts, "ACCOUNT_GAP_LIMIT", 2) 113 | monkeypatch.setattr(account, "DefaultGapLimit", 4) 114 | 115 | acctMgr.discover(cryptoKey) 116 | assert len(acctMgr.accounts) == 1 117 | 118 | coinExtKey = acctMgr.coinKey(cryptoKey) 119 | acct2ExtKey = coinExtKey.deriveAccountKey(2).neuter().child(0) 120 | acct2Addr3 = addrlib.deriveChildAddress(acct2ExtKey, 3, nets.mainnet) 121 | txs[acct2Addr3] = ["tx"] 122 | acctMgr.discover(cryptoKey) 123 | assert len(acctMgr.accounts) == 3 124 | 125 | # Restore globals. 126 | chains.registerChain("dcr", origChain) 127 | -------------------------------------------------------------------------------- /tinywallet/.coveragerc: -------------------------------------------------------------------------------- 1 | # Test coverage tool configuration, see reference at 2 | # https://coverage.readthedocs.io/en/latest/config.html 3 | 4 | [run] 5 | omit = tests/* 6 | dynamic_context = test_function 7 | 8 | [report] 9 | # Regexes for lines to exclude from consideration 10 | exclude_lines = 11 | nocover 12 | 13 | # Don't complain about debug-only code. 14 | def __repr__ 15 | 16 | # Don't complain if non-runnable code isn't run. 17 | if __name__ == .__main__.: 18 | 19 | # Don't complain if tests don't hit defensive assertion code. 20 | raise AssertionError 21 | raise NotImplementedError 22 | raise CrazyKeyError 23 | raise ParameterRangeError 24 | 25 | ignore_errors = True 26 | # skip_covered = True 27 | skip_empty = True 28 | 29 | [html] 30 | show_contexts = True 31 | -------------------------------------------------------------------------------- /tinywallet/.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | count = True 3 | max-complexity = 10 4 | max-line-length = 88 5 | statistics = True 6 | exclude = 7 | .git, 8 | .venv, 9 | .pytest_cache, 10 | __pycache__ 11 | -------------------------------------------------------------------------------- /tinywallet/README.md: -------------------------------------------------------------------------------- 1 | # TinyWallet 2 | 3 | TinyWallet is a light [Decred](https://decred.org/) wallet GUI application 4 | based on PyQt5. 5 | 6 | **The light wallet is experimental, and should not be used on mainnet.** 7 | 8 | To start the wallet install the `tinywallet` package from the Python Package 9 | Index and run the `tinywallet` command. 10 | 11 | The wallet runs as a system-tray application, of which the major difference is 12 | that "closing" the wallet actually just removes the entry from the taskbar and 13 | minimizes the window "to the system tray". 14 | The wallet can then be "opened" again through the icon in the system tray. 15 | 16 | ![alt text][screenshot] 17 | 18 | TinyWallet is pretty small. 19 | Like Decred, it's meant to be an omnipresent yet largely invisible and 20 | unobtrusive part of your digital environment. 21 | The small dialog size keeps user interactions focused. 22 | Bells and whistles are minimized in favor of simplicity whenever possible. 23 | Blockchain mechanics are invisible. 24 | The goal is to make using Decred easier than pulling change out of your pocket. 25 | 26 | [screenshot]: 27 | https://user-images.githubusercontent.com/6109680/62095772-08b4ce80-b247-11e9-81ae-66931ebb07be.png 28 | -------------------------------------------------------------------------------- /tinywallet/coverage-html.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ## To generate line-by-line test coverage viewable in a web browser, 3 | ## change to this directory, then install the dependencies: 4 | # poetry install 5 | ## The coverage report will be in the ./htmlcov/ directory. 6 | poetry run pytest --cov-report=html --cov=tinywallet ./tests/ 7 | -------------------------------------------------------------------------------- /tinywallet/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "tinywallet" 3 | version = "0.0.1" 4 | description = "A Python 3 Decred wallet." 5 | license = "ISC" 6 | homepage = "https://decred.org/" 7 | repository = "https://github.com/decred/tinydecred/" 8 | documentation = "https://github.com/decred/tinydecred/blob/master/tinywallet" 9 | authors = [ 10 | "Brian Stafford ", 11 | "The Decred developers " 12 | ] 13 | classifiers = [ 14 | "Development Status :: 3 - Alpha", 15 | "Environment :: X11 Applications :: Qt", 16 | "Intended Audience :: End Users/Desktop", 17 | "License :: OSI Approved :: ISC License (ISCL)", 18 | "Natural Language :: English", 19 | "Operating System :: OS Independent", 20 | "Programming Language :: Python :: 3.6", 21 | "Programming Language :: Python :: 3.7", 22 | "Programming Language :: Python :: 3.8", 23 | "Topic :: Office/Business :: Financial" 24 | ] 25 | 26 | [tool.poetry.dependencies] 27 | python = "^3.6" 28 | decred = {path = "../decred/"} 29 | pyqt5 = "=5.12.3" 30 | 31 | [tool.poetry.dev-dependencies] 32 | pytest = "^5.3.5" 33 | pytest-cov = "^2.8.1" 34 | flake8 = "^3.7.9" 35 | black = "^19.10b0" 36 | isort = "^4.3.21" 37 | Cython = "^0.29.16" 38 | 39 | [tool.poetry.scripts] 40 | tinywallet = "tinywallet.app:main" 41 | 42 | [tool.isort] 43 | atomic = "true" 44 | combine_as_imports = "true" 45 | combine_star = "true" 46 | filter_files = "true" 47 | force_grid_wrap = 0 48 | force_sort_within_sections = "true" 49 | include_trailing_comma = "true" 50 | line_length = 88 51 | lines_after_imports = 2 52 | multi_line_output = 3 53 | use_parentheses = "true" 54 | virtual_env = "./.venv/" 55 | 56 | [build-system] 57 | requires = ["poetry>=1.0.3"] 58 | build-backend = "poetry.masonry.api" 59 | -------------------------------------------------------------------------------- /tinywallet/tests/unit/test_config.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2020, the Decred developers 3 | See LICENSE for details 4 | """ 5 | 6 | import logging 7 | import sys 8 | 9 | import pytest 10 | 11 | from decred.dcr.nets import DcrdPorts, simnet 12 | from decred.util import helpers 13 | from tinywallet import ui # noqa, for coverage. 14 | from tinywallet.config import CmdArgs, dcrd, load 15 | 16 | 17 | def test_CmdArgs(): 18 | sys.argv = ["cmd", "--unknown"] 19 | with pytest.raises(SystemExit): 20 | cfg = CmdArgs() 21 | 22 | sys.argv = ["cmd", "--simnet"] 23 | cfg = CmdArgs() 24 | assert cfg.netParams.Name == "simnet" 25 | 26 | sys.argv = ["cmd", "--testnet", "--loglevel", ",:"] 27 | with pytest.raises(SystemExit): 28 | cfg = CmdArgs() 29 | 30 | sys.argv = ["cmd", "--testnet", "--loglevel", "debug"] 31 | cfg = CmdArgs() 32 | assert cfg.netParams.Name == "testnet3" 33 | assert cfg.logLevel == logging.DEBUG 34 | 35 | sys.argv = ["cmd", "--loglevel", "A:Warning,B:deBug,C:Critical,D:0"] 36 | cfg = CmdArgs() 37 | assert len(cfg.moduleLevels) == 4 38 | assert cfg.moduleLevels["A"] == logging.WARNING 39 | assert cfg.moduleLevels["B"] == logging.DEBUG 40 | assert cfg.moduleLevels["C"] == logging.CRITICAL 41 | assert cfg.moduleLevels["D"] == logging.NOTSET 42 | 43 | 44 | def test_dcrd(monkeypatch, tmpdir): 45 | def mock_appDataDir(app_name): 46 | return tmpdir 47 | 48 | monkeypatch.setattr(helpers, "appDataDir", mock_appDataDir) 49 | assert dcrd(simnet) == {} 50 | 51 | cfg_file = tmpdir.join("dcrd.conf") 52 | 53 | cfg_file.write("") 54 | assert dcrd(simnet) is None 55 | 56 | cfg_file.write("rpcuser=username\n") 57 | cfg = dcrd(simnet) 58 | assert "rpc.cert" in cfg["rpccert"] 59 | assert cfg["rpclisten"] == f"localhost:{DcrdPorts[simnet.Name]}" 60 | assert not cfg["notls"] 61 | 62 | tests = [ 63 | ("rpclisten=", f"localhost:{DcrdPorts[simnet.Name]}"), 64 | ("rpclisten=0.0.0.0", f"0.0.0.0:{DcrdPorts[simnet.Name]}"), 65 | ("rpclisten=::", f"localhost:{DcrdPorts[simnet.Name]}"), 66 | ("rpclisten=:9109", "localhost:9109"), 67 | ("rpclisten=0.0.0.0:9109", "0.0.0.0:9109"), 68 | ("rpclisten=[::]:9109", "localhost:9109"), 69 | ("rpclisten=[::1]:9109", "localhost:9109"), 70 | ("rpclisten=:8337\nrpclisten=:9999", "localhost:9999"), 71 | ] 72 | for rpclistenCfg, want in tests: 73 | cfg_file.write(f"rpcuser=username\n{rpclistenCfg}\n") 74 | assert dcrd(simnet)["rpclisten"] == want 75 | 76 | tests = [ 77 | ("notls=1", True), 78 | ("notls=0", False), 79 | ] 80 | for notlsCfg, want in tests: 81 | cfg_file.write(f"rpcuser=username\n{notlsCfg}\n") 82 | assert dcrd(simnet)["notls"] == want 83 | 84 | 85 | def test_load(): 86 | assert isinstance(load(), CmdArgs) 87 | -------------------------------------------------------------------------------- /tinywallet/tinywallet/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/__init__.py -------------------------------------------------------------------------------- /tinywallet/tinywallet/config.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019, Brian Stafford 3 | Copyright (c) 2019-2020, the Decred developers 4 | See LICENSE for details 5 | 6 | Configuration settings for TinyDecred. 7 | """ 8 | 9 | import argparse 10 | import logging 11 | import os 12 | import sys 13 | from urllib.parse import urlparse 14 | 15 | from appdirs import AppDirs 16 | 17 | from decred.dcr import nets 18 | from decred.util import helpers 19 | 20 | 21 | # Set the data directory in a OS-appropriate location. 22 | _ad = AppDirs("TinyWallet", False) 23 | DATA_DIR = _ad.user_data_dir 24 | 25 | helpers.mkdir(DATA_DIR) 26 | 27 | # The master configuration file name. 28 | CONFIG_NAME = "tinywallet.conf" 29 | CONFIG_PATH = os.path.join(DATA_DIR, CONFIG_NAME) 30 | 31 | # Some decred constants. 32 | MAINNET = nets.mainnet.Name 33 | TESTNET = nets.testnet.Name 34 | SIMNET = nets.simnet.Name 35 | 36 | logLevelMap = { 37 | "critical": logging.CRITICAL, 38 | "error": logging.ERROR, 39 | "warning": logging.WARNING, 40 | "debug": logging.DEBUG, 41 | "notset": logging.NOTSET, 42 | "0": logging.NOTSET, 43 | } 44 | 45 | 46 | def logLvl(s): 47 | """ 48 | Get the log level from the map. 49 | 50 | Args: 51 | s (str): A string which is a key for the logLevelMap. Case-insensitive. 52 | """ 53 | return logLevelMap[s.lower()] 54 | 55 | 56 | # Some network specific default settings. 57 | NetworkDefaults = { 58 | MAINNET: {"dcrdata": "https://explorer.dcrdata.org/"}, 59 | TESTNET: {"dcrdata": "https://testnet.dcrdata.org/"}, 60 | SIMNET: {"dcrdata": "http://localhost:17779"}, 61 | } 62 | 63 | 64 | class CmdArgs: 65 | """ 66 | CmdArgs are command-line configuration options. 67 | """ 68 | 69 | def __init__(self): 70 | self.logLevel = logging.INFO 71 | self.moduleLevels = {} 72 | parser = argparse.ArgumentParser() 73 | parser.add_argument("--loglevel") 74 | netGroup = parser.add_mutually_exclusive_group() 75 | netGroup.add_argument("--simnet", action="store_true", help="use simnet") 76 | netGroup.add_argument("--testnet", action="store_true", help="use testnet") 77 | args, unknown = parser.parse_known_args() 78 | if unknown: 79 | sys.exit(f"unknown arguments: {unknown}") 80 | self.netParams = nets.mainnet 81 | if args.simnet: 82 | self.netParams = nets.simnet 83 | elif args.testnet: 84 | self.netParams = nets.testnet 85 | else: 86 | print("**********************************************************") 87 | print(" WARNING. WALLET FOR TESTING ONLY. NOT FOR USE ON MAINNET ") 88 | print("**********************************************************") 89 | if args.loglevel: 90 | try: 91 | if any(ch in args.loglevel for ch in (",", ":")): 92 | pairs = (s.split(":") for s in args.loglevel.split(",")) 93 | self.moduleLevels = {k: logLvl(v) for k, v in pairs} 94 | else: 95 | self.logLevel = logLvl(args.loglevel) 96 | except Exception: 97 | sys.exit(f"malformed loglevel specifier: {args.loglevel}") 98 | 99 | 100 | class DB: 101 | """Various database keys used by tinywallet modules""" 102 | 103 | wallet = "wallet".encode() 104 | theme = "theme".encode() 105 | dcrdata = "dcrdata".encode() 106 | rpcuser = "rpcuser".encode() 107 | rpcpass = "rpcpass".encode() 108 | rpccert = "rpccert".encode() 109 | rpchost = "rpchost".encode() 110 | dcrdon = "dcrdon".encode() 111 | 112 | 113 | def dcrd(netParams): 114 | """ 115 | Attempt to fetch the dcrd configuration settings. Values will be parsed for 116 | 'notls', 'rpcuser', 'rpcpass', 'rpclisten', and 'rpccert'. The setting for 117 | 'rpcuser' must be present in the file. If values are not found for 'rpccert' 118 | or 'rpclisten', default locations are populated. 119 | 120 | Args: 121 | netParams (bool): The network parameters. 122 | 123 | Returns: 124 | dict or None: The parsed configuration settings. 125 | """ 126 | dcrdCfgDir = helpers.appDataDir("dcrd") 127 | cfgPath = os.path.join(dcrdCfgDir, "dcrd.conf") 128 | if not os.path.isfile(cfgPath): 129 | return {} 130 | dcrdCfg = helpers.readINI( 131 | cfgPath, ["notls", "rpclisten", "rpcuser", "rpcpass", "rpccert"] 132 | ) 133 | if "rpcuser" not in dcrdCfg: 134 | return None 135 | if "rpccert" not in dcrdCfg: 136 | dcrdCfg["rpccert"] = os.path.join(dcrdCfgDir, "rpc.cert") 137 | rpcHost = "localhost" 138 | rpcPort = nets.DcrdPorts[netParams.Name] 139 | if "rpclisten" in dcrdCfg: 140 | try: 141 | rpcUrl = urlparse(f"//{dcrdCfg['rpclisten']}") 142 | if rpcUrl.hostname: 143 | rpcHost = rpcUrl.hostname.replace("::1", "").strip(":") or rpcHost 144 | if rpcUrl.port: 145 | rpcPort = rpcUrl.port 146 | except ValueError: 147 | pass 148 | dcrdCfg["rpclisten"] = f"{rpcHost}:{rpcPort}" 149 | dcrdCfg["notls"] = dcrdCfg["notls"] == "1" if "notls" in dcrdCfg else False 150 | return dcrdCfg 151 | 152 | 153 | tinyConfig = None 154 | 155 | 156 | def load(): 157 | """ 158 | Load and return the current command-line configuration. The configuration is 159 | only loaded once. Successive calls to the modular `load` function will 160 | return the same instance. 161 | 162 | Returns: 163 | CmdArgs: The current command-line configuration. 164 | """ 165 | global tinyConfig 166 | if not tinyConfig: 167 | tinyConfig = CmdArgs() 168 | return tinyConfig 169 | -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/EBGaramond-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/EBGaramond-Bold.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/EBGaramond-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/EBGaramond-BoldItalic.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/EBGaramond-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/EBGaramond-ExtraBold.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/EBGaramond-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/EBGaramond-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/EBGaramond-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/EBGaramond-Italic.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/EBGaramond-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/EBGaramond-Medium.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/EBGaramond-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/EBGaramond-MediumItalic.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/EBGaramond-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/EBGaramond-Regular.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/EBGaramond-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/EBGaramond-SemiBold.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/EBGaramond-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/EBGaramond-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/MaterialIcons-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/MaterialIcons-Regular.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/Roboto-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/Roboto-Black.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/Roboto-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/Roboto-BlackItalic.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/Roboto-Bold.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/Roboto-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/Roboto-BoldItalic.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/Roboto-Italic.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/Roboto-Light.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/Roboto-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/Roboto-LightItalic.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/Roboto-Medium.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/Roboto-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/Roboto-MediumItalic.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/Roboto-Thin.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/Roboto-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/Roboto-ThinItalic.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/RobotoMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/RobotoMono-Bold.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/RobotoMono-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/RobotoMono-BoldItalic.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/RobotoMono-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/RobotoMono-Italic.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/RobotoMono-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/RobotoMono-Light.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/RobotoMono-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/RobotoMono-LightItalic.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/RobotoMono-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/RobotoMono-Medium.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/RobotoMono-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/RobotoMono-MediumItalic.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/RobotoMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/RobotoMono-Regular.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/RobotoMono-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/RobotoMono-Thin.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/fonts/RobotoMono-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/decred/tinydecred/f7f7d9f7da8d49d9ae9a72e5579b07a3b8572267/tinywallet/tinywallet/fonts/RobotoMono-ThinItalic.ttf -------------------------------------------------------------------------------- /tinywallet/tinywallet/icons/back.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 27 | 30 | 33 | 41 | 49 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /tinywallet/tinywallet/icons/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 57 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /tinywallet/tinywallet/icons/closed-eye.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /tinywallet/tinywallet/icons/folder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /tinywallet/tinywallet/icons/gear.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tinywallet/tinywallet/icons/home.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 27 | 30 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /tinywallet/tinywallet/icons/locked.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml 49 | 53 | 59 | 60 | -------------------------------------------------------------------------------- /tinywallet/tinywallet/icons/open-eye.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /tinywallet/tinywallet/icons/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 42 | 47 | 52 | 53 | 55 | 56 | 58 | image/svg+xml 59 | 61 | 62 | 63 | 64 | 65 | 70 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /tinywallet/tinywallet/icons/send.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 57 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /tinywallet/tinywallet/icons/spinner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml 51 | 55 | 60 | 61 | 64 | 65 | 68 | 69 | 72 | 73 | 76 | 77 | 80 | 81 | 84 | 85 | 88 | 89 | 92 | 93 | 96 | 97 | 100 | 101 | 104 | 105 | 108 | 109 | 112 | 113 | 116 | 117 | 120 | 121 | -------------------------------------------------------------------------------- /tinywallet/tinywallet/icons/unlocked.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml 51 | 55 | 61 | 62 | -------------------------------------------------------------------------------- /tinywallet/tinywallet/icons/x.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 27 | 30 | 33 | 41 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /tinywallet/tinywallet/ui.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (c) 2019, Brian Stafford 3 | Copyright (c) 2019, the Decred developers 4 | See LICENSE for details 5 | """ 6 | 7 | import os 8 | 9 | 10 | PACKAGEDIR = os.path.dirname(os.path.realpath(__file__)) 11 | FONTDIR = os.path.join(PACKAGEDIR, "fonts") 12 | 13 | TINY = "tiny" 14 | SMALL = "small" 15 | MEDIUM = "medium" 16 | LARGE = "large" 17 | 18 | BALANCE_SIGNAL = "balance_signal" 19 | SYNC_SIGNAL = "sync_signal" 20 | WORKING_SIGNAL = "working_signal" 21 | DONE_SIGNAL = "done_signal" 22 | PURCHASEINFO_SIGNAL = "purchaseinfo_signal" 23 | SPENT_TICKETS_SIGNAL = "spent_tickets_signal" 24 | --------------------------------------------------------------------------------