From 163b1807dbd840b6cd126c5b857db83b6269e5c0 Mon Sep 17 00:00:00 2001 From: Neil Booth Date: Wed, 27 May 2020 14:45:19 +0100 Subject: [PATCH 001/386] Use self not mempool --- electrumx/server/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electrumx/server/session.py b/electrumx/server/session.py index 3b31e33..097d41e 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -1411,7 +1411,7 @@ def set_request_handlers(self, ptuple): 'blockchain.transaction.get': self.transaction_get, 'blockchain.transaction.get_merkle': self.transaction_merkle, 'blockchain.transaction.id_from_pos': self.transaction_id_from_pos, - 'mempool.get_fee_histogram': self.mempool.compact_fee_histogram, + 'mempool.get_fee_histogram': self.compact_fee_histogram, 'server.add_peer': self.add_peer, 'server.banner': self.banner, 'server.donation_address': self.donation_address, From f50e8b7faa06f9c94fde41d297f4088485a8a260 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 28 May 2020 17:13:02 +0200 Subject: [PATCH 002/386] README: add fork clarification --- README.rst | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 1d86b50..441e913 100644 --- a/README.rst +++ b/README.rst @@ -7,16 +7,21 @@ ElectrumX - Reimplementation of electrum-server =============================================== -For a future network with bigger blocks. - :Licence: MIT :Language: Python (>= 3.7) - :Author: Neil Booth + :Original Author: Neil Booth + +This project is a fork of `kyuupichan/electrumx `_. +The original author dropped support for Bitcoin, which we intend to keep. + +ElectrumX allows users to run their own Electrum server. It connects to your +full node and indexes the blockchain, allowing efficient querying of history of +arbitrary addresses. The server can be exposed publicly, and joined to the public network +of servers via peer discovery. As of May 2020, a significant chunk of the public +Electrum server network runs ElectrumX. Documentation ============= See `readthedocs `_. - -**Neil Booth** kyuupichan@gmail.com https://github.com/kyuupichan From f2110c22257c5eb833e36986dab0b566c3e78b84 Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Sat, 30 May 2020 20:28:24 -0500 Subject: [PATCH 003/386] Update pytest parameter-skipping and skip import error block tests. --- tests/server/test_storage.py | 2 +- tests/test_blocks.py | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/server/test_storage.py b/tests/server/test_storage.py index 183e01e..3cfb6e1 100644 --- a/tests/server/test_storage.py +++ b/tests/server/test_storage.py @@ -11,7 +11,7 @@ try: c.import_module() except ImportError: - db_engines.append(pytest.mark.skip(c.__name__)) + db_engines.append(pytest.param(c.__name__, marks=pytest.mark.skip)) else: db_engines.append(c.__name__) diff --git a/tests/test_blocks.py b/tests/test_blocks.py index 9aaa9b4..df4a5d7 100644 --- a/tests/test_blocks.py +++ b/tests/test_blocks.py @@ -62,8 +62,11 @@ def test_block(block_details): raw_block = unhexlify(block_info['block']) block = coin.block(raw_block, block_info['height']) - assert coin.header_hash( - block.header) == hex_str_to_hash(block_info['hash']) + try: + assert coin.header_hash( + block.header) == hex_str_to_hash(block_info['hash']) + except ImportError as e: + pytest.skip(str(e)) assert (coin.header_prevhash(block.header) == hex_str_to_hash(block_info['previousblockhash'])) assert len(block_info['tx']) == len(block.transactions) From 2375239c04c3a97690822c19660b8b2531e136b0 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 31 May 2020 06:22:46 +0200 Subject: [PATCH 004/386] update some URLs to refer to this fork setup travis/coveralls/readthedocs --- README.rst | 10 +++++----- contrib/Dockerfile | 2 +- contrib/raspberrypi3/install_electrumx.sh | 2 +- docs/HOWTO.rst | 8 ++++---- docs/environment.rst | 2 +- docs/index.rst | 16 ++++++++-------- docs/peer_discovery.rst | 2 +- setup.py | 4 ++-- 8 files changed, 23 insertions(+), 23 deletions(-) diff --git a/README.rst b/README.rst index 441e913..d9cf58c 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ -.. image:: https://travis-ci.org/kyuupichan/electrumx.svg?branch=master - :target: https://travis-ci.org/kyuupichan/electrumx -.. image:: https://coveralls.io/repos/github/kyuupichan/electrumx/badge.svg - :target: https://coveralls.io/github/kyuupichan/electrumx +.. image:: https://travis-ci.org/spesmilo/electrumx.svg?branch=master + :target: https://travis-ci.org/spesmilo/electrumx +.. image:: https://coveralls.io/repos/github/spesmilo/electrumx/badge.svg + :target: https://coveralls.io/github/spesmilo/electrumx =============================================== ElectrumX - Reimplementation of electrum-server @@ -23,5 +23,5 @@ Electrum server network runs ElectrumX. Documentation ============= -See `readthedocs `_. +See `readthedocs `_. diff --git a/contrib/Dockerfile b/contrib/Dockerfile index 7742dbb..bdbc403 100644 --- a/contrib/Dockerfile +++ b/contrib/Dockerfile @@ -4,7 +4,7 @@ FROM ubuntu:18.04 WORKDIR / -ADD https://github.com/kyuupichan/electrumx/archive/1.13.0.tar.gz / +ADD https://github.com/spesmilo/electrumx/archive/1.13.0.tar.gz / RUN tar zxvf *.tar.gz RUN apt-get update && \ diff --git a/contrib/raspberrypi3/install_electrumx.sh b/contrib/raspberrypi3/install_electrumx.sh index fb80043..99e550f 100644 --- a/contrib/raspberrypi3/install_electrumx.sh +++ b/contrib/raspberrypi3/install_electrumx.sh @@ -22,6 +22,6 @@ sudo apt-get install git sudo pip3 install plyvel # install electrumx -git clone https://github.com/kyuupichan/electrumx.git +git clone https://github.com/spesmilo/electrumx.git cd electrumx sudo python3 setup.py install diff --git a/docs/HOWTO.rst b/docs/HOWTO.rst index 99b279b..8fafc12 100644 --- a/docs/HOWTO.rst +++ b/docs/HOWTO.rst @@ -78,7 +78,7 @@ Install the prerequisites above. Check out the code from Github:: - git clone https://github.com/kyuupichan/electrumx.git + git clone https://github.com/spesmilo/electrumx.git cd electrumx You can install with :file:`setup.py` or run the code from the source @@ -421,11 +421,11 @@ You can then set the port as follows and advertise the service externally on the REPORT_SSL_PORT=110 -.. _`contrib/systemd/electrumx.service`: https://github.com/kyuupichan/electrumx/blob/master/contrib/systemd/electrumx.service +.. _`contrib/systemd/electrumx.service`: https://github.com/spesmilo/electrumx/blob/master/contrib/systemd/electrumx.service .. _`daemontools`: http://cr.yp.to/daemontools.html .. _`runit`: http://smarden.org/runit/index.html .. _`aiohttp`: https://pypi.python.org/pypi/aiohttp .. _`pylru`: https://pypi.python.org/pypi/pylru .. _`x11_hash`: https://pypi.python.org/pypi/x11_hash -.. _`contrib/raspberrypi3/install_electrumx.sh`: https://github.com/kyuupichan/electrumx/blob/master/contrib/raspberrypi3/install_electrumx.sh -.. _`contrib/raspberrypi3/run_electrumx.sh`: https://github.com/kyuupichan/electrumx/blob/master/contrib/raspberrypi3/run_electrumx.sh +.. _`contrib/raspberrypi3/install_electrumx.sh`: https://github.com/spesmilo/electrumx/blob/master/contrib/raspberrypi3/install_electrumx.sh +.. _`contrib/raspberrypi3/run_electrumx.sh`: https://github.com/spesmilo/electrumx/blob/master/contrib/raspberrypi3/run_electrumx.sh diff --git a/docs/environment.rst b/docs/environment.rst index 03d842c..5082af7 100644 --- a/docs/environment.rst +++ b/docs/environment.rst @@ -482,5 +482,5 @@ your available physical RAM: I do not recommend raising this above 2000. -.. _lib/coins.py: https://github.com/kyuupichan/electrumx/blob/master/electrumx/lib/coins.py +.. _lib/coins.py: https://github.com/spesmilo/electrumx/blob/master/electrumx/lib/coins.py .. _uvloop: https://pypi.python.org/pypi/uvloop diff --git a/docs/index.rst b/docs/index.rst index b2fdc78..ca480c9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -2,10 +2,10 @@ ElectrumX ========= -.. image:: https://travis-ci.org/kyuupichan/electrumx.svg?branch=master - :target: https://travis-ci.org/kyuupichan/electrumx -.. image:: https://coveralls.io/repos/github/kyuupichan/electrumx/badge.svg - :target: https://coveralls.io/github/kyuupichan/electrumx +.. image:: https://travis-ci.org/spesmilo/electrumx.svg?branch=master + :target: https://travis-ci.org/spesmilo/electrumx +.. image:: https://coveralls.io/repos/github/spesmilo/electrumx/badge.svg + :target: https://coveralls.io/github/spesmilo/electrumx A reimplementation of Electrum-Server for a future with bigger blocks. @@ -15,12 +15,12 @@ Source Code =========== The project is hosted on `GitHub -`_. and uses `Travis -`_ for Continuous +`_. and uses `Travis +`_ for Continuous Integration. Please submit an issue on the `bug tracker -`_ if you have found a +`_ if you have found a bug or have a suggestion to improve the server. Authors and License @@ -30,7 +30,7 @@ Neil Booth wrote the vast majority of the code; see :ref:`Authors`. Python version at least 3.7 is required. The code is released under the `MIT Licence -`_. +`_. Getting Started =============== diff --git a/docs/peer_discovery.rst b/docs/peer_discovery.rst index 1a4c1f0..423156c 100644 --- a/docs/peer_discovery.rst +++ b/docs/peer_discovery.rst @@ -25,7 +25,7 @@ process. Ideally it should have at least 4 servers that have shown commitment to reliable service. In ElectrumX this is a per-coin property in `lib/coins.py -`_. +`_. server.peers.subscribe diff --git a/setup.py b/setup.py index f8b802a..0bb6fd6 100644 --- a/setup.py +++ b/setup.py @@ -27,9 +27,9 @@ author='Neil Booth', author_email='kyuupichan@gmail.com', license='MIT Licence', - url='https://github.com/kyuupichan/electrumx', + url='https://github.com/spesmilo/electrumx', long_description='Server implementation for the Electrum protocol', - download_url=('https://github.com/kyuupichan/electrumX/archive/' + download_url=('https://github.com/spesmilo/electrumX/archive/' f'{version}.tar.gz'), classifiers=[ 'Development Status :: 5 - Production/Stable', From d297f240e427c5766a61309a05d4413bd311f584 Mon Sep 17 00:00:00 2001 From: ghost43 Date: Sun, 31 May 2020 04:23:49 +0000 Subject: [PATCH 005/386] travis: use other "coveralls" package (#4) see: https://github.com/z4r/python-coveralls/issues/74 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9f79b98..b54f63a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,6 +48,6 @@ script: # Dont report coverage from nightly after_success: - if [[ $(python3 -V 2>&1) == *"Python 3.7"* ]]; then - pip install python-coveralls; + pip install coveralls; coveralls; fi From 1edb1bfe05bf23790e1e31a66a8732d18e7b4087 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 31 May 2020 06:31:49 +0200 Subject: [PATCH 006/386] docs: rm some old text --- docs/changelog.rst | 11 ----------- docs/index.rst | 9 ++++++++- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 4e395e1..4e0a652 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -2,17 +2,6 @@ ChangeLog =========== -.. note:: Version 1.15.0 will be the final ElectrumX release with altcoin support, future - releases will be Bitcoin-only. ElectrumX needs to scale quickly and support for - various other coins and their idiosyncracies is distracting and unhelpful. Anyone - wishing to maintain an ElectrumX repository with altcoin support is free to do so as - explained in the licence. - -.. note:: It is strongly recommended you upgrade to Python 3.7, which - fixes bugs in asyncio that caused an ever-growing open file count - and memory consumption whilst serving clients. Those problems - should not occur with Python 3.7. - Version 1.15.0 (27 May 2020) ============================ diff --git a/docs/index.rst b/docs/index.rst index ca480c9..79d8fef 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -7,7 +7,14 @@ ElectrumX .. image:: https://coveralls.io/repos/github/spesmilo/electrumx/badge.svg :target: https://coveralls.io/github/spesmilo/electrumx -A reimplementation of Electrum-Server for a future with bigger blocks. +This project is a fork of `kyuupichan/electrumx `_. +The original author dropped support for Bitcoin, which we intend to keep. + +ElectrumX allows users to run their own Electrum server. It connects to your +full node and indexes the blockchain, allowing efficient querying of history of +arbitrary addresses. The server can be exposed publicly, and joined to the public network +of servers via peer discovery. As of May 2020, a significant chunk of the public +Electrum server network runs ElectrumX. The current version is |release|. From 16396d81af7863c0337ab6c81023f4fbdb59ec9f Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Sun, 31 May 2020 16:42:49 -0500 Subject: [PATCH 007/386] Use stdlib for scrypt, bump CI to latest LTS distro for scrypt support. --- .travis.yml | 4 +--- docs/HOWTO.rst | 4 +++- electrumx/lib/coins.py | 16 +++++++++------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index b54f63a..8ce8d6e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,8 @@ sudo: required -dist: trusty +dist: bionic language: python before_install: - sudo add-apt-repository -y ppa:streetcrypto7/rocksdb - - sudo add-apt-repository -y ppa:streetcrypto7/leveldb - sudo apt-get -qq update - sudo apt-get install -yq libsnappy-dev zlib1g-dev libbz2-dev libgflags-dev liblz4-dev librocksdb-dev libleveldb-dev python: @@ -24,7 +23,6 @@ install: # hashes - pip install tribushashm - pip install blake256 - - pip install scrypt - pip install x11_hash - pip install git+https://github.com/bitcoinplusorg/x13-hash - pip install xevan_hash diff --git a/docs/HOWTO.rst b/docs/HOWTO.rst index 8fafc12..276a32e 100644 --- a/docs/HOWTO.rst +++ b/docs/HOWTO.rst @@ -25,7 +25,9 @@ DB Engine A database engine package is required; two are ================ ======================== Some coins need an additional package, typically for their block hash -functions. For example, `x11_hash`_ is required for DASH. +functions. For example, `x11_hash`_ is required for DASH. Scrypt coins +require a Python interpreter compiled and/or linked with OpenSSL 1.1.0 +or higher. You **must** to be running a non-pruning bitcoin daemon with:: diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index f355736..1720ee8 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -317,8 +317,9 @@ class ScryptMixin(object): def header_hash(cls, header): '''Given a header return the hash.''' if cls.HEADER_HASH is None: - import scrypt - cls.HEADER_HASH = lambda x: scrypt.hash(x, x, 1024, 1, 1, 32) + # Requires OpenSSL 1.1.0+ + from hashlib import scrypt + cls.HEADER_HASH = lambda x: scrypt(x, salt=x, n=1024, r=1, p=1, dklen=32) version, = util.unpack_le_uint32_from(header) if version > 6: @@ -3301,9 +3302,9 @@ class ECCoin(Coin): @classmethod def header_hash(cls, header): - # you have to install scryp python module (pip install scrypt) - import scrypt - return scrypt.hash(header, header, 1024, 1, 1, 32) + # Requires OpenSSL 1.1.0+ + from hashlib import scrypt + return scrypt(header, salt=header, n=1024, r=1, p=1, dklen=32) class Bellcoin(Coin): @@ -3497,8 +3498,9 @@ def header_hash(cls, header): version, = util.unpack_le_uint32_from(header) if version < 7: - import scrypt - return scrypt.hash(header, header, 1024, 1, 1, 32) + # Requires OpenSSL 1.1.0+ + from hashlib import scrypt + return scrypt(header, salt=header, n=1024, r=1, p=1, dklen=32) else: return double_sha256(header) From 506bbe3c725423fa74a169ab988f0711e3d2cf0f Mon Sep 17 00:00:00 2001 From: ghost43 Date: Mon, 1 Jun 2020 01:12:19 +0000 Subject: [PATCH 008/386] coins: rename "BitcoinSegwit" to "Bitcoin"; but support both (#5) --- contrib/Dockerfile | 2 +- contrib/raspberrypi3/run_electrumx.sh | 2 +- contrib/systemd/electrumx.conf | 2 +- docs/environment.rst | 2 +- docs/rpc-interface.rst | 2 +- electrumx/lib/coins.py | 26 +++++++++++++++++++------- 6 files changed, 24 insertions(+), 12 deletions(-) diff --git a/contrib/Dockerfile b/contrib/Dockerfile index bdbc403..70a398c 100644 --- a/contrib/Dockerfile +++ b/contrib/Dockerfile @@ -14,7 +14,7 @@ RUN apt-get update && \ RUN cd /electrumx* && python3 setup.py install ENV SERVICES="tcp://:50001" -ENV COIN=BitcoinSV +ENV COIN=Bitcoin ENV DB_DIRECTORY=/db ENV DAEMON_URL="http://username:password@hostname:port/" ENV ALLOW_ROOT=true diff --git a/contrib/raspberrypi3/run_electrumx.sh b/contrib/raspberrypi3/run_electrumx.sh index db23f0d..a022262 100644 --- a/contrib/raspberrypi3/run_electrumx.sh +++ b/contrib/raspberrypi3/run_electrumx.sh @@ -4,7 +4,7 @@ ############### # configure electrumx -export COIN=BitcoinSegwit +export COIN=Bitcoin export DAEMON_URL=http://rpcuser:rpcpassword@127.0.0.1 export NET=mainnet export CACHE_MB=400 diff --git a/contrib/systemd/electrumx.conf b/contrib/systemd/electrumx.conf index 9787037..48d66c7 100644 --- a/contrib/systemd/electrumx.conf +++ b/contrib/systemd/electrumx.conf @@ -5,7 +5,7 @@ DB_DIRECTORY = /db # Bitcoin Node RPC Credentials DAEMON_URL = http://username:password@hostname:port/ -# COIN = BitcoinSegwit +# COIN = Bitcoin # See http://electrumx.readthedocs.io/en/latest/environment.html for # information about other configuration settings you probably want to consider. diff --git a/docs/environment.rst b/docs/environment.rst index 5082af7..da10936 100644 --- a/docs/environment.rst +++ b/docs/environment.rst @@ -22,7 +22,7 @@ These environment variables are always required: .. envvar:: COIN Must be a :attr:`NAME` from one of the :class:`Coin` classes in - `lib/coins.py`_. + `lib/coins.py`_. For example, ``Bitcoin``. .. envvar:: DB_DIRECTORY diff --git a/docs/rpc-interface.rst b/docs/rpc-interface.rst index d6d84e0..9b90f8a 100644 --- a/docs/rpc-interface.rst +++ b/docs/rpc-interface.rst @@ -69,7 +69,7 @@ A typical result is as follows (with annotated comments):: $ electrumx_rpc getinfo { - "coin": "BitcoinSegwit", + "coin": "Bitcoin", "daemon": "127.0.0.1:9334/", "daemon height": 572154, # The daemon's height when last queried "db height": 572154, # The height to which the DB is flushed diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index f355736..31041e6 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -583,8 +583,8 @@ def warn_old_client_on_tx_broadcast(cls, client_ver): return False -class BitcoinSegwit(BitcoinMixin, Coin): - NAME = "BitcoinSegwit" +class Bitcoin(BitcoinMixin, Coin): + NAME = "Bitcoin" DESERIALIZER = lib_tx.DeserializerSegWit MEMPOOL_HISTOGRAM_REFRESH_SECS = 120 TX_COUNT = 318337769 @@ -622,6 +622,10 @@ def warn_old_client_on_tx_broadcast(cls, client_ver): return False +class BitcoinSegwit(Bitcoin): + NAME = "BitcoinSegwit" # support legacy name + + class BitcoinGold(EquihashMixin, BitcoinMixin, Coin): CHUNK_SIZE = 252 NAME = "BitcoinGold" @@ -688,7 +692,7 @@ class BitcoinGoldRegtest(BitcoinGold): PEERS = [] -class BitcoinDiamond(BitcoinSegwit, Coin): +class BitcoinDiamond(Bitcoin, Coin): NAME = "BitcoinDiamond" SHORTNAME = "BCD" TX_VERSION = 12 @@ -836,9 +840,9 @@ class BitcoinSVRegtest(BitcoinSVTestnet): GENESIS_ACTIVATION = 10_000 -class BitcoinSegwitTestnet(BitcoinTestnetMixin, Coin): +class BitcoinTestnet(BitcoinTestnetMixin, Coin): '''Bitcoin Testnet for Core bitcoind >= 0.13.1.''' - NAME = "BitcoinSegwit" + NAME = "Bitcoin" DESERIALIZER = lib_tx.DeserializerSegWit CRASH_CLIENT_VER = (3, 2, 3) PEERS = [ @@ -862,8 +866,12 @@ def warn_old_client_on_tx_broadcast(cls, client_ver): return False -class BitcoinSegwitRegtest(BitcoinSegwitTestnet): - NAME = "BitcoinSegwit" +class BitcoinSegwitTestnet(BitcoinTestnet): + NAME = "BitcoinSegwit" # support legacy name + + +class BitcoinRegtest(BitcoinTestnet): + NAME = "Bitcoin" NET = "regtest" GENESIS_HASH = ('0f9188f13cb7b2c71f2a335e3a4fc328' 'bf5beb436012afca590b1a11466e2206') @@ -872,6 +880,10 @@ class BitcoinSegwitRegtest(BitcoinSegwitTestnet): TX_COUNT_HEIGHT = 1 +class BitcoinSegwitRegtest(BitcoinRegtest): + NAME = "BitcoinSegwit" # support legacy name + + class BitcoinNolnet(BitcoinCash): '''Bitcoin Unlimited nolimit testnet.''' NET = "nolnet" From 495d31e4f3c3a5c9dd3442fb10015e84c921f94e Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 2 Jun 2020 03:41:33 +0200 Subject: [PATCH 009/386] altcoins: rm "GINCoin", "Sparks"; as neoscrypt segfaults on ubuntu1804 if you want them re-added, please fix the segfault and open a PR --- .travis.yml | 1 - electrumx/lib/coins.py | 60 ------------------------- tests/blocks/gincoin_mainnet_30000.json | 14 ------ tests/blocks/sparks_mainnet_1000.json | 14 ------ 4 files changed, 89 deletions(-) delete mode 100644 tests/blocks/gincoin_mainnet_30000.json delete mode 100644 tests/blocks/sparks_mainnet_1000.json diff --git a/.travis.yml b/.travis.yml index 8ce8d6e..4b3551d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,6 @@ install: - pip install xevan_hash - pip install quark_hash - pip install groestlcoin_hash - - pip install neoscrypt - pip install x16r_hash - pip install pycryptodomex - pip install git+https://github.com/Electra-project/nist5_hash diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 1720ee8..02211ad 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -2530,42 +2530,6 @@ class ZcoinTestnet(Zcoin): RPC_PORT = 18888 -class GINCoin(Coin): - NAME = "GINCoin" - SHORTNAME = "GIN" - NET = "mainnet" - XPUB_VERBYTES = bytes.fromhex("0488B21E") - XPRV_VERBYTES = bytes.fromhex("0488ADE4") - GENESIS_HASH = ('00000cd6bde619b2c3b23ad2e384328a' - '450a37fa28731debf748c3b17f91f97d') - P2PKH_VERBYTE = bytes.fromhex("37") - P2SH_VERBYTES = [bytes.fromhex("38")] - WIF_BYTE = bytes.fromhex("3c") - TX_COUNT_HEIGHT = 225000 - TX_COUNT = 470784 - TX_PER_BLOCK = 4 - RPC_PORT = 10211 - PEERS = [ - 'electrum.polispay.com' - ] - SESSIONCLS = DashElectrumX - DAEMON = daemon.DashDaemon - - # Seems that the main lyra2z_hash python package doesn't works. - # Tested and working with: https://github.com/LapoLab/lyra2z-py - @classmethod - def header_hash(cls, header): - timestamp = util.unpack_le_uint32_from(header, 68)[0] - if timestamp > 1550246400: - import x16rt_hash - return x16rt_hash.getPoWHash(header) - elif timestamp > 1525651200: - import lyra2z_hash - return lyra2z_hash.getPoWHash(header) - import neoscrypt - return neoscrypt.getPoWHash(header) - - class Polis(Coin): NAME = "Polis" SHORTNAME = "POLIS" @@ -3024,30 +2988,6 @@ class MyriadcoinTestnet(Myriadcoin): '2b20678c354b34085f62b762084b9788') -class Sparks(Coin): - NAME = "Sparks" - SHORTNAME = "SPK" - NET = "mainnet" - XPUB_VERBYTES = bytes.fromhex("0488B21E") - XPRV_VERBYTES = bytes.fromhex("0488ADE4") - GENESIS_HASH = ('00000a5c6ddfaac5097218560d5b92d4' - '16931cfeba1abf10c81d1d6a232fc8ea') - P2PKH_VERBYTE = bytes.fromhex("26") - P2SH_VERBYTES = [bytes.fromhex("0A")] - WIF_BYTE = bytes.fromhex("C6") - TX_COUNT_HEIGHT = 117400 - TX_COUNT = 162310 - TX_PER_BLOCK = 4 - RPC_PORT = 8818 - SESSIONCLS = DashElectrumX - DAEMON = daemon.DashDaemon - - @classmethod - def header_hash(cls, header): - import neoscrypt - return neoscrypt.getPoWHash(header) - - # Source: https://github.com/LIMXTEC/BitSend class Bitsend(Coin): NAME = "Bitsend" diff --git a/tests/blocks/gincoin_mainnet_30000.json b/tests/blocks/gincoin_mainnet_30000.json deleted file mode 100644 index 0d57531..0000000 --- a/tests/blocks/gincoin_mainnet_30000.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "hash": "000000000014475c6019e0702f7ed76a33215236cd5bce6620f66e4d6a4737ce", - "size": 223, - "height": 30000, - "merkleroot": "90150c9db0646dbb498e2389a002b6f31812103010a8cdfa22d766454c43d1fa", - "tx": [ - "90150c9db0646dbb498e2389a002b6f31812103010a8cdfa22d766454c43d1fa" - ], - "time": 1523881569, - "nonce": 2358339072, - "bits": "1b6eb5bc", - "previousblockhash": "0000000000679de3a18bc02ddb554a123d99fad96dbc6a97746eb16efddb8265", - "block": "000000206582dbfd6eb16e74976abc6dd9fa993d124a55db2dc08ba1e39d670000000000fad1434c4566d722facda81030101218f3b602a089238e49bb6d64b09d0c15906196d45abcb56e1b0066918c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff17023075046196d45a0881000be75c0300007969696d7000000000000200ca9a3b000000001976a914c82d962c67815f3df3c1dfa8ad485ac2c4a9aaa888ac00ca9a3b000000001976a91450dacc9eceebb94d12219fed9f7250822a2ae5b688ac00000000" -} diff --git a/tests/blocks/sparks_mainnet_1000.json b/tests/blocks/sparks_mainnet_1000.json deleted file mode 100644 index 2f1e11a..0000000 --- a/tests/blocks/sparks_mainnet_1000.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "hash": "00000000020ff055aa61a4d11d1d66730d7e0534dc8570dc2d40c45b675b2582", - "size": 196, - "height": 1000, - "merkleroot": "665fabda52881b52894d5b30feead9755d0679ee8fa832f26a82bba039c25745", - "tx": [ - "665fabda52881b52894d5b30feead9755d0679ee8fa832f26a82bba039c25745" - ], - "time": 1514021201, - "nonce": 3752529043, - "bits": "1c027039", - "previousblockhash": "00000000022df0e0facb9d2c6f64636cf1d8452ccfd8d3243fa83caa8a6d9259", - "block": "0000002059926d8aaa3ca83f24d3d8cf2c45d8f16c63646f2c9dcbfae0f02d02000000004557c239a0bb826af232a88fee79065d75d9eafe305b4d89521b8852daab5f6651213e5a3970021c930cabdf0102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1e02e8030451213e5a0881000638d2000000416c744d696e65722e4e657400000000000100d2496b000000001976a9143d619fd153f2872791be572f222c0b896517f91288ac00000000" -} From 311a32553b5ec5dc3dbb26f295f71ae2466d5c93 Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Tue, 2 Jun 2020 01:15:39 -0500 Subject: [PATCH 010/386] Test on production or near-production Pythons only for now. --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4b3551d..604b389 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,8 @@ before_install: - sudo apt-get install -yq libsnappy-dev zlib1g-dev libbz2-dev libgflags-dev liblz4-dev librocksdb-dev libleveldb-dev python: - "3.7-dev" - - "nightly" + - "3.8-dev" + - "3.9-dev" # command to install dependencies install: - pip install aiohttp From 860ff53f6563dbabed07cf4ac336def42a1c1607 Mon Sep 17 00:00:00 2001 From: Michal Taborsky Date: Fri, 5 Jun 2020 12:41:39 +0200 Subject: [PATCH 011/386] Add option to deny serving unknown clients --- docs/environment.rst | 8 ++++++++ electrumx/server/env.py | 1 + electrumx/server/session.py | 8 ++++++++ 3 files changed, 17 insertions(+) diff --git a/docs/environment.rst b/docs/environment.rst index da10936..85d1ed7 100644 --- a/docs/environment.rst +++ b/docs/environment.rst @@ -281,6 +281,14 @@ These environment variables are optional: version string. For example to drop versions from 1.0 to 1.2 use the regex ``1\.[0-2]\.\d+``. +.. envvar:: DROP_CLIENT_UNKNOWN + + Set to anything non-empty to deny serving clients, which do not + identify themselves first by issuing the server.version method + call with a non-empty client identifier. The connection is dropped + on first actual method call. This might help to filter out simple + robots. This behavior is off by default. + Resource Usage Limits ===================== diff --git a/electrumx/server/env.py b/electrumx/server/env.py index c45ded0..dc54f4f 100644 --- a/electrumx/server/env.py +++ b/electrumx/server/env.py @@ -69,6 +69,7 @@ def __init__(self, coin=None): self.log_level = self.default('LOG_LEVEL', 'info').upper() self.donation_address = self.default('DONATION_ADDRESS', '') self.drop_client = self.custom("DROP_CLIENT", None, re.compile) + self.drop_client_unknown = self.boolean('DROP_CLIENT_UNKNOWN', False) self.blacklist_url = self.default('BLACKLIST_URL', self.coin.BLACKLIST_URL) self.cache_MB = self.integer('CACHE_MB', 1200) self.reorg_limit = self.integer('REORG_LIMIT', self.coin.REORG_LIMIT) diff --git a/electrumx/server/session.py b/electrumx/server/session.py index 097d41e..030d1f6 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -886,6 +886,14 @@ async def handle_request(self, request): else: handler = None method = 'invalid method' if handler is None else request.method + + # If DROP_CLIENT_UNKNOWN is enabled, check if the client identified + # by calling server.version previously. If not, disconnect the session + if self.env.drop_client_unknown and method != 'server.version' and self.client == 'unknown': + self.logger.info(f'disconnecting because client is unknown') + raise ReplyAndDisconnect( + BAD_REQUEST, f'use server.version to identify client') + self.session_mgr._method_counts[method] += 1 coro = handler_invocation(handler, request)() return await coro From 7e1a0288537c1cf1d202fbadff3a7f0f93b7565e Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Sun, 7 Jun 2020 19:28:59 -0500 Subject: [PATCH 012/386] Update Dockerfile to latest LTS, simpler install. --- contrib/Dockerfile | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/contrib/Dockerfile b/contrib/Dockerfile index 70a398c..f5803c0 100644 --- a/contrib/Dockerfile +++ b/contrib/Dockerfile @@ -1,17 +1,16 @@ # example of Dockerfile that builds release of electrumx-1.13.0 # ENV variables can be overrided on the `docker run` command -FROM ubuntu:18.04 +FROM ubuntu:20.04 WORKDIR / -ADD https://github.com/spesmilo/electrumx/archive/1.13.0.tar.gz / +ADD https://github.com/spesmilo/electrumx/archive/1.15.0.tar.gz / RUN tar zxvf *.tar.gz RUN apt-get update && \ - apt-get -y install python3.7 python3-pip librocksdb-dev libsnappy-dev libbz2-dev libz-dev liblz4-dev && \ - pip3 install aiohttp pylru python-rocksdb + apt-get -y install python3.8 python3-pip librocksdb-dev libsnappy-dev libbz2-dev libz-dev liblz4-dev -RUN cd /electrumx* && python3 setup.py install +RUN cd /electrumx* && pip3 install .[rocksdb] ENV SERVICES="tcp://:50001" ENV COIN=Bitcoin From d128aaf641ac2a048c343734c57f300839771f24 Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Sun, 7 Jun 2020 16:05:51 -0500 Subject: [PATCH 013/386] Support rapidjson and ujson libs for better sync performance. --- docs/HOWTO.rst | 24 ++++++++++++++++++++---- docs/PERFORMANCE-NOTES | 11 ++++++++++- electrumx/lib/util.py | 12 ++++++++++++ electrumx/server/daemon.py | 12 ++++++------ electrumx/server/peers.py | 15 +++++++-------- electrumx/server/session.py | 17 +++++++---------- setup.py | 2 ++ tests/server/test_daemon.py | 4 ++-- 8 files changed, 66 insertions(+), 31 deletions(-) diff --git a/docs/HOWTO.rst b/docs/HOWTO.rst index 276a32e..b9a22eb 100644 --- a/docs/HOWTO.rst +++ b/docs/HOWTO.rst @@ -69,8 +69,12 @@ was much worse. You will need to install one of: -+ `plyvel `_ for LevelDB -+ `python-rocksdb `_ for RocksDB (`pip3 install python-rocksdb`) ++ `plyvel `_ for LevelDB. + + Included as part of a regular pip or ``setup.py`` installation of ElectrumX. ++ `python-rocksdb `_ for RocksDB + + ``pip3 install python-rocksdb`` or use the rocksdb extra install option to ElectrumX. + `pyrocksdb `_ for an unmaintained version that doesn't work with recent releases of RocksDB Running @@ -83,8 +87,20 @@ Check out the code from Github:: git clone https://github.com/spesmilo/electrumx.git cd electrumx -You can install with :file:`setup.py` or run the code from the source -tree or a copy of it. +You can install with:: + + pip3 install . + +There are many extra Python dependencies available to fit the needs of your +system or coins. For example, to install the RocksDB dependencies and a faster +JSON parsing library:: + + pip3 install .[rocksdb,ujson] + +see setup.py's ``extra_requires`` for a complete list. + +You can also run the code from the source tree or a copy of it. + You should create a standard user account to run the server under; your own is probably adequate unless paranoid. The paranoid might diff --git a/docs/PERFORMANCE-NOTES b/docs/PERFORMANCE-NOTES index 9335a7b..95b9226 100644 --- a/docs/PERFORMANCE-NOTES +++ b/docs/PERFORMANCE-NOTES @@ -33,4 +33,13 @@ account in the code. - multiple assignment is faster using tuples only for 3 or more items - retrieving a previously stored length of a bytes object can be over 200% - faster than a new call to len(b) \ No newline at end of file + faster than a new call to len(b) + +- The cpython and pypy stdlibs use a Python-based JSON serializer/deserializer, + which is stable and easy to use and install, but much slower than + system-compiled variants. ElectrumX now looks for either of the popular + C-based JSON libraries for Python, python-rapidjson or ujson. If it finds + one, it's used instead of the stdlib module for improved performance. + + To install a known-compatible system-compiled JSON lib, install ElectrumX + with either the "rapidjson" or "ujson" extras parameter. diff --git a/electrumx/lib/util.py b/electrumx/lib/util.py index 812bffe..549a21e 100644 --- a/electrumx/lib/util.py +++ b/electrumx/lib/util.py @@ -37,6 +37,18 @@ from struct import Struct +# Use system-compiled JSON lib if available, fallback to stdlib +try: + import rapidjson as json +except ImportError: + try: + import ujson as json + except ImportError: + import json + +json_deserialize = json.loads +json_serialize = json.dumps + # Logging utilities diff --git a/electrumx/server/daemon.py b/electrumx/server/daemon.py index 3cd99a0..18677bb 100644 --- a/electrumx/server/daemon.py +++ b/electrumx/server/daemon.py @@ -10,7 +10,6 @@ import asyncio import itertools -import json import time from calendar import timegm from struct import pack @@ -18,10 +17,11 @@ import aiohttp from aiorpcx import JSONRPC -from electrumx.lib.util import hex_to_bytes, class_logger,\ - unpack_le_uint16_from, pack_varint -from electrumx.lib.hash import hex_str_to_hash, hash_to_hex_str +from electrumx.lib.hash import hash_to_hex_str, hex_str_to_hash from electrumx.lib.tx import DeserializerDecred +from electrumx.lib.util import (class_logger, hex_to_bytes, json_deserialize, + json_serialize, pack_varint, + unpack_le_uint16_from) class DaemonError(Exception): @@ -105,7 +105,7 @@ async def _send_data(self, data): async with self.session.post(self.current_url(), data=data) as resp: kind = resp.headers.get('Content-Type', None) if kind == 'application/json': - return await resp.json() + return await resp.json(loads=json_deserialize) text = await resp.text() text = text.strip() or resp.reason raise ServiceRefusedError(text) @@ -127,7 +127,7 @@ def log_error(error): on_good_message = None last_error_log = 0 - data = json.dumps(payload) + data = json_serialize(payload) retry = self.init_retry while True: try: diff --git a/electrumx/server/peers.py b/electrumx/server/peers.py index eeda665..5d8c0e7 100644 --- a/electrumx/server/peers.py +++ b/electrumx/server/peers.py @@ -8,21 +8,20 @@ '''Peer management.''' import asyncio -from ipaddress import IPv4Address, IPv6Address -import json import random import socket import ssl import time -from collections import defaultdict, Counter +from collections import Counter, defaultdict +from ipaddress import IPv4Address, IPv6Address import aiohttp -from aiorpcx import (connect_rs, RPCSession, SOCKSProxy, Notification, handler_invocation, - SOCKSError, RPCError, TaskTimeout, TaskGroup, Event, - sleep, ignore_after) +from aiorpcx import (Event, Notification, RPCError, RPCSession, SOCKSError, + SOCKSProxy, TaskGroup, TaskTimeout, connect_rs, + handler_invocation, ignore_after, sleep) from electrumx.lib.peer import Peer -from electrumx.lib.util import class_logger +from electrumx.lib.util import class_logger, json_deserialize PEER_GOOD, PEER_STALE, PEER_NEVER, PEER_BAD = range(4) STALE_SECS = 3 * 3600 @@ -143,7 +142,7 @@ async def read_blacklist(): async with aiohttp.ClientSession() as session: async with session.get(url) as response: text = await response.text() - return set(entry.lower() for entry in json.loads(text)) + return set(entry.lower() for entry in json_deserialize(text)) while True: try: diff --git a/electrumx/server/session.py b/electrumx/server/session.py index 097d41e..f7e9a6a 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -10,7 +10,6 @@ import codecs import datetime import itertools -import json import math import os import ssl @@ -20,22 +19,20 @@ from ipaddress import IPv4Address, IPv6Address import attr -from aiorpcx import ( - RPCSession, JSONRPCAutoDetect, JSONRPCConnection, serve_rs, serve_ws, - TaskGroup, handler_invocation, RPCError, Request, sleep, Event, ReplyAndDisconnect -) import pylru +from aiorpcx import (Event, JSONRPCAutoDetect, JSONRPCConnection, + ReplyAndDisconnect, Request, RPCError, RPCSession, + TaskGroup, handler_invocation, serve_rs, serve_ws, sleep) import electrumx +import electrumx.lib.util as util +from electrumx.lib.hash import (HASHX_LEN, Base58Error, hash_to_hex_str, + hex_str_to_hash, sha256) from electrumx.lib.merkle import MerkleCache from electrumx.lib.text import sessions_lines -import electrumx.lib.util as util -from electrumx.lib.hash import (sha256, hash_to_hex_str, hex_str_to_hash, - HASHX_LEN, Base58Error) from electrumx.server.daemon import DaemonError from electrumx.server.peers import PeerManager - BAD_REQUEST = 1 DAEMON_ERROR = 2 @@ -234,7 +231,7 @@ async def _log_sessions(self): data = self._session_data(for_log=True) for line in sessions_lines(data): self.logger.info(line) - self.logger.info(json.dumps(self._get_info())) + self.logger.info(util.json_serialize(self._get_info())) async def _disconnect_sessions(self, sessions, reason, *, force_after=1.0): if sessions: diff --git a/setup.py b/setup.py index 0bb6fd6..0668e9e 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,9 @@ install_requires=['aiorpcX[ws]>=0.18.3,<0.19', 'attrs', 'plyvel', 'pylru', 'aiohttp>=3.3'], extras_require={ + 'rapidjson': ['python-rapidjson>=0.4.1,<1.0.0'], 'rocksdb': ['python-rocksdb>=0.6.9'], + 'ujson': ['ujson>=2.0.0,<4.0.0'], 'uvloop': ['uvloop>=0.14'], # For various coins 'blake256': ['blake256>=0.1.1'], diff --git a/tests/server/test_daemon.py b/tests/server/test_daemon.py index 74c701a..b45103f 100644 --- a/tests/server/test_daemon.py +++ b/tests/server/test_daemon.py @@ -54,14 +54,14 @@ def __init__(self, result, msg_id, status=200): self.result = result self.msg_id = msg_id - async def json(self): + async def json(self, loads=json.loads): if isinstance(self.msg_id, int): message = JSONRPCv1.response_message(self.result, self.msg_id) else: parts = [JSONRPCv1.response_message(item, msg_id) for item, msg_id in zip(self.result, self.msg_id)] message = JSONRPCv1.batch_message_from_parts(parts) - return json.loads(message.decode()) + return loads(message.decode()) class HTMLResponse(ResponseBase): From e58a785f9abe00abaa1476a7a341bf5a989886da Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Sun, 31 May 2020 04:06:05 -0500 Subject: [PATCH 014/386] Low-hanging fruit performance improvements. --- electrumx/lib/coins.py | 300 ++++++++++++++-------------- electrumx/lib/env_base.py | 16 +- electrumx/lib/hash.py | 12 +- electrumx/lib/peer.py | 8 +- electrumx/lib/script.py | 31 ++- electrumx/lib/server_base.py | 2 +- electrumx/lib/text.py | 38 ++-- electrumx/lib/tx.py | 21 +- electrumx/lib/util.py | 22 +- electrumx/server/block_processor.py | 52 ++--- electrumx/server/db.py | 48 ++--- electrumx/server/env.py | 8 +- electrumx/server/history.py | 33 +-- electrumx/server/mempool.py | 8 +- electrumx/server/peers.py | 13 +- electrumx/server/session.py | 52 ++--- electrumx/server/storage.py | 2 +- tests/server/test_compaction.py | 3 +- 18 files changed, 338 insertions(+), 331 deletions(-) diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 58ad4fe..877168b 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -78,7 +78,7 @@ class Coin(object): HEADER_UNPACK = struct.Struct('< I 32s 32s I I I').unpack_from MEMPOOL_HISTOGRAM_REFRESH_SECS = 500 P2PKH_VERBYTE = bytes.fromhex("00") - P2SH_VERBYTES = [bytes.fromhex("05")] + P2SH_VERBYTES = (bytes.fromhex("05"),) XPUB_VERBYTES = bytes('????', 'utf-8') XPRV_VERBYTES = bytes('????', 'utf-8') WIF_BYTE = bytes.fromhex("80") @@ -98,19 +98,21 @@ def lookup_coin_class(cls, name, net): '''Return a coin class given name and network. Raise an exception if unrecognised.''' - req_attrs = ['TX_COUNT', 'TX_COUNT_HEIGHT', 'TX_PER_BLOCK'] + req_attrs = ('TX_COUNT', 'TX_COUNT_HEIGHT', 'TX_PER_BLOCK') for coin in util.subclasses(Coin): if (coin.NAME.lower() == name.lower() and coin.NET.lower() == net.lower()): - coin_req_attrs = req_attrs.copy() - missing = [attr for attr in coin_req_attrs - if not hasattr(coin, attr)] + missing = [ + attr + for attr in req_attrs + if not hasattr(coin, attr) + ] if missing: - raise CoinError('coin {} missing {} attributes' - .format(name, missing)) + raise CoinError( + f'coin {name} missing {missing} attributes' + ) return coin - raise CoinError('unknown coin {} and network {} combination' - .format(name, net)) + raise CoinError(f'unknown coin {name} and network {net} combination') @classmethod def sanitize_url(cls, url): @@ -118,11 +120,11 @@ def sanitize_url(cls, url): url = url.strip().rstrip('/') match = cls.RPC_URL_REGEX.match(url) if not match: - raise CoinError('invalid daemon URL: "{}"'.format(url)) + raise CoinError(f'invalid daemon URL: "{url}"') if match.groups()[1] is None: - url += ':{:d}'.format(cls.RPC_PORT) - if not url.startswith('http://') and not url.startswith('https://'): - url = 'http://' + url + url = f'{url}:{cls.RPC_PORT:d}' + if not url.startswith(('http://', 'https://')): + url = f'http://{url}' return url + '/' @classmethod @@ -140,10 +142,10 @@ def genesis_block(cls, block): header = cls.block_header(block, 0) header_hex_hash = hash_to_hex_str(cls.header_hash(header)) if header_hex_hash != cls.GENESIS_HASH: - raise CoinError('genesis block has hash {} expected {}' - .format(header_hex_hash, cls.GENESIS_HASH)) + raise CoinError(f'genesis block has hash {header_hex_hash} ' + f'expected {cls.GENESIS_HASH}') - return header + bytes(1) + return header + b'\0' @classmethod def hashX_from_script(cls, script): @@ -210,12 +212,12 @@ def pay_to_address_script(cls, address): if verbyte in cls.P2SH_VERBYTES: return ScriptPubKey.P2SH_script(hash160) - raise CoinError('invalid address: {}'.format(address)) + raise CoinError(f'invalid address: {address}') @classmethod def privkey_WIF(cls, privkey_bytes, compressed): '''Return the private key encoded in Wallet Import Format.''' - payload = bytearray(cls.WIF_BYTE) + privkey_bytes + payload = bytearray(cls.WIF_BYTE + privkey_bytes) if compressed: payload.append(0x01) return cls.ENCODE_CHECK(payload) @@ -330,7 +332,7 @@ def header_hash(cls, header): class KomodoMixin(object): P2PKH_VERBYTE = bytes.fromhex("3C") - P2SH_VERBYTES = [bytes.fromhex("55")] + P2SH_VERBYTES = (bytes.fromhex("55"),) WIF_BYTE = bytes.fromhex("BC") GENESIS_HASH = ('027e3758c3a65b12aa1046462b486d0a' '63bfa1beae327897f56c5cfb7daaae71') @@ -412,11 +414,11 @@ def _add_data_placeholders_to_template(cls, opcodes, template): num_2drop = num_dp // 2 num_drop = num_dp % 2 - two_drops = [OpCodes.OP_2DROP for _ in range(num_2drop)] - one_drops = [OpCodes.OP_DROP for _ in range(num_drop)] + two_drops = [OpCodes.OP_2DROP] * num_2drop + one_drops = [OpCodes.OP_DROP] * num_drop elements_added = num_dp + num_2drop + num_drop - placeholders = [-1 for _ in range(num_dp)] + placeholders = [-1] * num_dp drops = two_drops + one_drops return elements_added, template + placeholders + drops @@ -481,8 +483,8 @@ def build_name_index_script(cls, name): res = bytearray() res.append(cls.OP_NAME_UPDATE) - res.extend(Script.push_data(name)) - res.extend(Script.push_data(bytes([]))) + res += Script.push_data(name) + res += Script.push_data(b'') res.append(OpCodes.OP_2DROP) res.append(OpCodes.OP_DROP) res.append(OpCodes.OP_RETURN) @@ -633,7 +635,7 @@ class BitcoinGold(EquihashMixin, BitcoinMixin, Coin): SHORTNAME = "BTG" FORK_HEIGHT = 491407 P2PKH_VERBYTE = bytes.fromhex("26") - P2SH_VERBYTES = [bytes.fromhex("17")] + P2SH_VERBYTES = (bytes.fromhex("17"),) DESERIALIZER = lib_tx.DeserializerEquihashSegWit TX_COUNT = 265026255 TX_COUNT_HEIGHT = 499923 @@ -661,7 +663,7 @@ class BitcoinGoldTestnet(BitcoinGold): XPUB_VERBYTES = bytes.fromhex("043587CF") XPRV_VERBYTES = bytes.fromhex("04358394") P2PKH_VERBYTE = bytes.fromhex("6F") - P2SH_VERBYTES = [bytes.fromhex("C4")] + P2SH_VERBYTES = (bytes.fromhex("C4"),) WIF_BYTE = bytes.fromhex("EF") TX_COUNT = 0 TX_COUNT_HEIGHT = 1 @@ -682,7 +684,7 @@ class BitcoinGoldRegtest(BitcoinGold): XPUB_VERBYTES = bytes.fromhex("043587CF") XPRV_VERBYTES = bytes.fromhex("04358394") P2PKH_VERBYTE = bytes.fromhex("6F") - P2SH_VERBYTES = [bytes.fromhex("C4")] + P2SH_VERBYTES = (bytes.fromhex("C4"),) WIF_BYTE = bytes.fromhex("EF") TX_COUNT = 0 TX_COUNT_HEIGHT = 1 @@ -713,7 +715,7 @@ class Emercoin(NameMixin, Coin): XPUB_VERBYTES = bytes.fromhex("0488b21e") XPRV_VERBYTES = bytes.fromhex("0488ade4") P2PKH_VERBYTE = bytes.fromhex("21") - P2SH_VERBYTES = [bytes.fromhex("5c")] + P2SH_VERBYTES = (bytes.fromhex("5c"),) GENESIS_HASH = ('00000000bcccd459d036a588d1008fce' '8da3754b205736f32ddfd35350e84c2d') TX_COUNT = 217380620 @@ -732,17 +734,17 @@ class Emercoin(NameMixin, Coin): OP_NAME_DELETE = OpCodes.OP_3 # Valid name prefixes. - NAME_NEW_OPS = [OP_NAME_NEW, OpCodes.OP_DROP, "name", "days", - OpCodes.OP_2DROP, NameMixin.DATA_PUSH_MULTIPLE] - NAME_UPDATE_OPS = [OP_NAME_UPDATE, OpCodes.OP_DROP, "name", "days", - OpCodes.OP_2DROP, NameMixin.DATA_PUSH_MULTIPLE] - NAME_DELETE_OPS = [OP_NAME_DELETE, OpCodes.OP_DROP, "name", - OpCodes.OP_DROP] - NAME_OPERATIONS = [ + NAME_NEW_OPS = (OP_NAME_NEW, OpCodes.OP_DROP, "name", "days", + OpCodes.OP_2DROP, NameMixin.DATA_PUSH_MULTIPLE) + NAME_UPDATE_OPS = (OP_NAME_UPDATE, OpCodes.OP_DROP, "name", "days", + OpCodes.OP_2DROP, NameMixin.DATA_PUSH_MULTIPLE) + NAME_DELETE_OPS = (OP_NAME_DELETE, OpCodes.OP_DROP, "name", + OpCodes.OP_DROP) + NAME_OPERATIONS = ( NAME_NEW_OPS, NAME_UPDATE_OPS, NAME_DELETE_OPS, - ] + ) @classmethod def block_header(cls, block, height): @@ -771,7 +773,7 @@ class BitcoinTestnetMixin(object): XPUB_VERBYTES = bytes.fromhex("043587cf") XPRV_VERBYTES = bytes.fromhex("04358394") P2PKH_VERBYTE = bytes.fromhex("6f") - P2SH_VERBYTES = [bytes.fromhex("c4")] + P2SH_VERBYTES = (bytes.fromhex("c4"),) WIF_BYTE = bytes.fromhex("ef") GENESIS_HASH = ('000000000933ea01ad0ee984209779ba' 'aec3ced90fa3f408719526f8d77f4943') @@ -907,7 +909,7 @@ class Sumcoin(Coin): XPUB_VERBYTES = bytes.fromhex("0488b41c") XPRV_VERBYTES = bytes.fromhex("0488abe6") P2PKH_VERBYTE = bytes.fromhex("3f") - P2SH_VERBYTES = [bytes.fromhex("c8"), bytes.fromhex("05")] + P2SH_VERBYTES = (bytes.fromhex("c8"), bytes.fromhex("05")) WIF_BYTE = bytes.fromhex("bf") GENESIS_HASH = ('37d4696c5072cd012f3b7c651e5ce56a' '1383577e4edacc2d289ec9b25eebfd5e') @@ -928,7 +930,7 @@ class Litecoin(Coin): XPUB_VERBYTES = bytes.fromhex("0488b21e") XPRV_VERBYTES = bytes.fromhex("0488ade4") P2PKH_VERBYTE = bytes.fromhex("30") - P2SH_VERBYTES = [bytes.fromhex("32"), bytes.fromhex("05")] + P2SH_VERBYTES = (bytes.fromhex("32"), bytes.fromhex("05")) WIF_BYTE = bytes.fromhex("b0") GENESIS_HASH = ('12a765e31ffd4059bada1e25190f6e98' 'c99d9714d334efa41a195a7e7e04bfe2') @@ -955,7 +957,7 @@ class LitecoinTestnet(Litecoin): XPUB_VERBYTES = bytes.fromhex("043587cf") XPRV_VERBYTES = bytes.fromhex("04358394") P2PKH_VERBYTE = bytes.fromhex("6f") - P2SH_VERBYTES = [bytes.fromhex("3a"), bytes.fromhex("c4")] + P2SH_VERBYTES = (bytes.fromhex("3a"), bytes.fromhex("c4")) WIF_BYTE = bytes.fromhex("ef") GENESIS_HASH = ('4966625a4b2851d9fdee139e56211a0d' '88575f59ed816ff5e6a63deb4e3e29a0') @@ -996,7 +998,7 @@ class Viacoin(AuxPowMixin, Coin): SHORTNAME = "VIA" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("47") - P2SH_VERBYTES = [bytes.fromhex("21")] + P2SH_VERBYTES = (bytes.fromhex("21"),) WIF_BYTE = bytes.fromhex("c7") GENESIS_HASH = ('4e9b54001f9976049830128ec0331515' 'eaabe35a70970d79971da1539a400ba1') @@ -1018,7 +1020,7 @@ class ViacoinTestnet(Viacoin): SHORTNAME = "TVI" NET = "testnet" P2PKH_VERBYTE = bytes.fromhex("7f") - P2SH_VERBYTES = [bytes.fromhex("c4")] + P2SH_VERBYTES = (bytes.fromhex("c4"),) WIF_BYTE = bytes.fromhex("ff") GENESIS_HASH = ('00000007199508e34a9ff81e6ec0c477' 'a4cccff2a4767a8eee39c11db367b008') @@ -1043,7 +1045,7 @@ class GravityCoin(Coin): XPUB_VERBYTES = bytes.fromhex("0488b21e") XPRV_VERBYTES = bytes.fromhex("0488ade4") P2PKH_VERBYTE = bytes.fromhex("28") - P2SH_VERBYTES = [bytes.fromhex("0a")] + P2SH_VERBYTES = (bytes.fromhex("0a"),) WIF_BYTE = bytes.fromhex("d2") GENESIS_HASH = ('322bad477efb4b33fa4b1f0b2861eaf543c61068da9898a95062fdb02ada486f') TX_COUNT = 446050 @@ -1067,7 +1069,7 @@ class Bitcoinzero(Coin): XPUB_VERBYTES = bytes.fromhex("0488b21e") XPRV_VERBYTES = bytes.fromhex("0488ade4") P2PKH_VERBYTE = bytes.fromhex("4b") - P2SH_VERBYTES = [bytes.fromhex("22")] + P2SH_VERBYTES = (bytes.fromhex("22"),) WIF_BYTE = bytes.fromhex("d2") RPC_PORT = 29202 REORG_LIMIT = 5000 @@ -1081,7 +1083,7 @@ class Unitus(Coin): XPUB_VERBYTES = bytes.fromhex("0488B21E") XPRV_VERBYTES = bytes.fromhex("0488ADE4") P2PKH_VERBYTE = bytes.fromhex("44") - P2SH_VERBYTES = [bytes.fromhex("0A")] + P2SH_VERBYTES = (bytes.fromhex("0A"),) WIF_BYTE = bytes.fromhex("84") GENESIS_HASH = ('d8a2b2439d013a59f3bfc626a33487a3' 'd7d27e42a3c9e0b81af814cd8e592f31') @@ -1104,7 +1106,7 @@ class Namecoin(NameIndexMixin, AuxPowMixin, Coin): XPUB_VERBYTES = bytes.fromhex("d7dd6370") XPRV_VERBYTES = bytes.fromhex("d7dc6e31") P2PKH_VERBYTE = bytes.fromhex("34") - P2SH_VERBYTES = [bytes.fromhex("0d")] + P2SH_VERBYTES = (bytes.fromhex("0d"),) WIF_BYTE = bytes.fromhex("e4") GENESIS_HASH = ('000000000062b72c5e2ceb45fbc8587e' '807c155b0da735e6483dfba2f0a9c770') @@ -1133,11 +1135,11 @@ class Namecoin(NameIndexMixin, AuxPowMixin, Coin): OpCodes.OP_2DROP, OpCodes.OP_2DROP] NAME_UPDATE_OPS = [OP_NAME_UPDATE, "name", -1, OpCodes.OP_2DROP, OpCodes.OP_DROP] - NAME_OPERATIONS = [ + NAME_OPERATIONS = ( NAME_NEW_OPS, NAME_FIRSTUPDATE_OPS, NAME_UPDATE_OPS, - ] + ) class NamecoinTestnet(Namecoin): @@ -1145,7 +1147,7 @@ class NamecoinTestnet(Namecoin): SHORTNAME = "XNM" NET = "testnet" P2PKH_VERBYTE = bytes.fromhex("6f") - P2SH_VERBYTES = [bytes.fromhex("c4")] + P2SH_VERBYTES = (bytes.fromhex("c4"),) WIF_BYTE = bytes.fromhex("ef") GENESIS_HASH = ('00000007199508e34a9ff81e6ec0c477' 'a4cccff2a4767a8eee39c11db367b008') @@ -1168,7 +1170,7 @@ class Dogecoin(AuxPowMixin, Coin): XPUB_VERBYTES = bytes.fromhex("02facafd") XPRV_VERBYTES = bytes.fromhex("02fac398") P2PKH_VERBYTE = bytes.fromhex("1e") - P2SH_VERBYTES = [bytes.fromhex("16")] + P2SH_VERBYTES = (bytes.fromhex("16"),) WIF_BYTE = bytes.fromhex("9e") GENESIS_HASH = ('1a91e3dace36e2be3bf030a65679fe82' '1aa1d6ef92e7c9902eb318182c355691') @@ -1183,7 +1185,7 @@ class DogecoinTestnet(Dogecoin): SHORTNAME = "XDT" NET = "testnet" P2PKH_VERBYTE = bytes.fromhex("71") - P2SH_VERBYTES = [bytes.fromhex("c4")] + P2SH_VERBYTES = (bytes.fromhex("c4"),) WIF_BYTE = bytes.fromhex("f1") GENESIS_HASH = ('bb0a78264637406b6360aad926284d54' '4d7049f45189db5664f3c4d07350559e') @@ -1199,7 +1201,7 @@ class Motion(Coin): GENESIS_HASH = ('000001e9dc60dd2618e91f7b90141349' '22c374496b61c1a272519b1c39979d78') P2PKH_VERBYTE = bytes.fromhex("32") - P2SH_VERBYTES = [bytes.fromhex("12")] + P2SH_VERBYTES = (bytes.fromhex("12"),) TX_COUNT_HEIGHT = 54353 TX_COUNT = 92701 TX_PER_BLOCK = 4 @@ -1224,7 +1226,7 @@ class Dash(Coin): GENESIS_HASH = ('00000ffd590b1485b3caadc19b22e637' '9c733355108f107a430458cdf3407ab6') P2PKH_VERBYTE = bytes.fromhex("4c") - P2SH_VERBYTES = [bytes.fromhex("10")] + P2SH_VERBYTES = (bytes.fromhex("10"),) WIF_BYTE = bytes.fromhex("cc") TX_COUNT_HEIGHT = 569399 TX_COUNT = 2157510 @@ -1257,7 +1259,7 @@ class DashTestnet(Dash): GENESIS_HASH = ('00000bafbc94add76cb75e2ec9289483' '7288a481e5c005f6563d91623bf8bc2c') P2PKH_VERBYTE = bytes.fromhex("8c") - P2SH_VERBYTES = [bytes.fromhex("13")] + P2SH_VERBYTES = (bytes.fromhex("13"),) WIF_BYTE = bytes.fromhex("ef") TX_COUNT_HEIGHT = 101619 TX_COUNT = 132681 @@ -1297,7 +1299,7 @@ class ArgentumTestnet(Argentum): SHORTNAME = "XRG" NET = "testnet" P2PKH_VERBYTE = bytes.fromhex("6f") - P2SH_VERBYTES = [bytes.fromhex("c4")] + P2SH_VERBYTES = (bytes.fromhex("c4"),) WIF_BYTE = bytes.fromhex("ef") REORG_LIMIT = 2000 @@ -1319,7 +1321,7 @@ class DigiByte(Coin): class DigiByteTestnet(DigiByte): NET = "testnet" P2PKH_VERBYTE = bytes.fromhex("6f") - P2SH_VERBYTES = [bytes.fromhex("c4")] + P2SH_VERBYTES = (bytes.fromhex("c4"),) WIF_BYTE = bytes.fromhex("ef") GENESIS_HASH = ('b5dca8039e300198e5fe7cd23bdd1728' 'e2a444af34c447dbd0916fa3430a68c2') @@ -1332,7 +1334,7 @@ class FairCoin(Coin): SHORTNAME = "FAIR" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("5f") - P2SH_VERBYTES = [bytes.fromhex("24")] + P2SH_VERBYTES = (bytes.fromhex("24"),) WIF_BYTE = bytes.fromhex("df") GENESIS_HASH = ('beed44fa5e96150d95d56ebd5d262578' '1825a9407a5215dd7eda723373a0a1d7') @@ -1364,7 +1366,7 @@ class Zcash(EquihashMixin, Coin): SHORTNAME = "ZEC" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("1CB8") - P2SH_VERBYTES = [bytes.fromhex("1CBD")] + P2SH_VERBYTES = (bytes.fromhex("1CBD"),) GENESIS_HASH = ('00040fe8ec8471911baa1db1266ea15d' 'd06b4a8a5c453883c000b031973dce08') DESERIALIZER = lib_tx.DeserializerZcash @@ -1379,7 +1381,7 @@ class ZcashTestnet(Zcash): SHORTNAME = "TAZ" NET = "testnet" P2PKH_VERBYTE = bytes.fromhex("1D25") - P2SH_VERBYTES = [bytes.fromhex("1CBA")] + P2SH_VERBYTES = (bytes.fromhex("1CBA"),) WIF_BYTE = bytes.fromhex("EF") GENESIS_HASH = ('05a60a92d99d85997cce3b87616c089f' '6124d7342af37106edc76126334a2c38') @@ -1394,7 +1396,7 @@ class SnowGem(EquihashMixin, Coin): SHORTNAME = "XSG" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("1C28") - P2SH_VERBYTES = [bytes.fromhex("1C2D")] + P2SH_VERBYTES = (bytes.fromhex("1C2D"),) GENESIS_HASH = ('00068b35729d9d2b0c294ff1fe9af009' '4740524311a131de40e7f705e4c29a5b') DESERIALIZER = lib_tx.DeserializerZcash @@ -1411,7 +1413,7 @@ class Zero(EquihashMixin, Coin): SHORTNAME = "ZER" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("1CB8") - P2SH_VERBYTES = [bytes.fromhex("1CBD")] + P2SH_VERBYTES = (bytes.fromhex("1CBD"),) GENESIS_HASH = ('068cbb5db6bc11be5b93479ea4df41fa' '7e012e92ca8603c315f9b1a2202205c6') DESERIALIZER = lib_tx.DeserializerZcash @@ -1427,7 +1429,7 @@ class BitcoinZ(EquihashMixin, Coin): SHORTNAME = "BTCZ" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("1CB8") - P2SH_VERBYTES = [bytes.fromhex("1CBD")] + P2SH_VERBYTES = (bytes.fromhex("1CBD"),) GENESIS_HASH = ('f499ee3d498b4298ac6a64205b8addb7' 'c43197e2a660229be65db8a4534d75c1') DESERIALIZER = lib_tx.DeserializerZcash @@ -1443,7 +1445,7 @@ class ZelCash(EquihashMixin, Coin): SHORTNAME = "ZEL" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("1CB8") - P2SH_VERBYTES = [bytes.fromhex("1CBD")] + P2SH_VERBYTES = (bytes.fromhex("1CBD"),) GENESIS_HASH = ('00052461a5006c2e3b74ce48992a0869' '5607912d5604c3eb8da25749b0900444') DESERIALIZER = lib_tx.DeserializerZcash @@ -1459,7 +1461,7 @@ class Zclassic(EquihashMixin, Coin): SHORTNAME = "ZCL" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("1CB8") - P2SH_VERBYTES = [bytes.fromhex("1CBD")] + P2SH_VERBYTES = (bytes.fromhex("1CBD"),) GENESIS_HASH = ('0007104ccda289427919efc39dc9e4d4' '99804b7bebc22df55f8b834301260602') DESERIALIZER = lib_tx.DeserializerZcash @@ -1475,7 +1477,7 @@ class Koto(Coin): SHORTNAME = "KOTO" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("1836") - P2SH_VERBYTES = [bytes.fromhex("183B")] + P2SH_VERBYTES = (bytes.fromhex("183B"),) GENESIS_HASH = ('6d424c350729ae633275d51dc3496e16' 'cd1b1d195c164da00f39c499a2e9959e') DESERIALIZER = lib_tx.DeserializerZcash @@ -1494,7 +1496,7 @@ class KotoTestnet(Koto): SHORTNAME = "TOKO" NET = "testnet" P2PKH_VERBYTE = bytes.fromhex("18A4") - P2SH_VERBYTES = [bytes.fromhex("1839")] + P2SH_VERBYTES = (bytes.fromhex("1839"),) WIF_BYTE = bytes.fromhex("EF") GENESIS_HASH = ('bf84afbde20c2d213b68b231ddb585ab' '616ef7567226820f00d9b397d774d2f0') @@ -1559,7 +1561,7 @@ def header_hash(cls, header): '''Given a header return hash''' import verushash # if this may be the genesis block, use sha256, otherwise, VerusHash - if cls.header_prevhash(header) == bytes([0] * 32): + if cls.header_prevhash(header) == bytes(32): return double_sha256(header) else: if (header[0] == 4 and header[2] >= 1): @@ -1592,7 +1594,7 @@ class Blackcoin(ScryptMixin, Coin): SHORTNAME = "BLK" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("19") - P2SH_VERBYTES = [bytes.fromhex("55")] + P2SH_VERBYTES = (bytes.fromhex("55"),) WIF_BYTE = bytes.fromhex("99") GENESIS_HASH = ('000001faef25dec4fbcf906e6242621d' 'f2c183bf232f263d0ba5b101911e4563') @@ -1609,7 +1611,7 @@ class Bitbay(ScryptMixin, Coin): SHORTNAME = "BAY" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("19") - P2SH_VERBYTES = [bytes.fromhex("55")] + P2SH_VERBYTES = (bytes.fromhex("55"),) WIF_BYTE = bytes.fromhex("99") GENESIS_HASH = ('0000075685d3be1f253ce777174b1594' '354e79954d2a32a6f77fe9cba00e6467') @@ -1625,7 +1627,7 @@ class DeepOnion(Coin): SHORTNAME = "ONION" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("1F") - P2SH_VERBYTES = [bytes.fromhex("4E")] + P2SH_VERBYTES = (bytes.fromhex("4E"),) WIF_BYTE = bytes.fromhex("9f") GENESIS_HASH = ('000004e29458ef4f2e0abab544737b07' '344e6ff13718f7c2d12926166db07b5e') @@ -1656,7 +1658,7 @@ class Peercoin(Coin): SHORTNAME = "PPC" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("37") - P2SH_VERBYTES = [bytes.fromhex("75")] + P2SH_VERBYTES = (bytes.fromhex("75"),) WIF_BYTE = bytes.fromhex("b7") GENESIS_HASH = ('0000000032fe677166d54963b62a4677' 'd8957e87c508eaa4fd7eb1c880cd27e3') @@ -1682,7 +1684,7 @@ class PeercoinTestnet(Peercoin): SHORTNAME = "tPPC" NET = "testnet" P2PKH_VERBYTE = bytes.fromhex("6f") - P2SH_VERBYTES = [bytes.fromhex("c4")] + P2SH_VERBYTES = (bytes.fromhex("c4"),) WIF_BYTE = bytes.fromhex("ef") GENESIS_HASH = ('00000001f757bb737f6596503e17cd17' 'b0658ce630cc727c0cca81aec47c9f06') @@ -1697,7 +1699,7 @@ class Trezarcoin(Coin): XPUB_VERBYTES = bytes.fromhex("0488B21E") XPRV_VERBYTES = bytes.fromhex("0488ADE4") P2PKH_VERBYTE = bytes.fromhex("42") - P2SH_VERBYTES = [bytes.fromhex("08")] + P2SH_VERBYTES = (bytes.fromhex("08"),) WIF_BYTE = bytes.fromhex("c2") GENESIS_HASH = ('24502ba55d673d2ee9170d83dae2d1ad' 'b3bfb4718e4f200db9951382cc4f6ee6') @@ -1723,9 +1725,9 @@ def genesis_block(cls, block): header = cls.block_header(block, 0) header_hex_hash = cls.HEADER_HASH_GEN(header) if header_hex_hash != cls.GENESIS_HASH: - raise CoinError('genesis block has hash {} expected {}' - .format(header_hex_hash, cls.GENESIS_HASH)) - return header + bytes(1) + raise CoinError(f'genesis block has hash {header_hex_hash} ' + f'expected {cls.GENESIS_HASH}') + return header + b'\0' @classmethod def header_hash(cls, header): @@ -1753,7 +1755,7 @@ class TokenPay(ScryptMixin, Coin): SHORTNAME = "TPAY" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("41") - P2SH_VERBYTES = [bytes.fromhex("7e")] + P2SH_VERBYTES = (bytes.fromhex("7e"),) WIF_BYTE = bytes.fromhex("b3") GENESIS_HASH = ('000008b71ab32e585a23f0de642dc113' '740144e94c0ece047751e9781f953ae9') @@ -1797,7 +1799,7 @@ class Monacoin(Coin): XPUB_VERBYTES = bytes.fromhex("0488B21E") XPRV_VERBYTES = bytes.fromhex("0488ADE4") P2PKH_VERBYTE = bytes.fromhex("32") - P2SH_VERBYTES = [bytes.fromhex("37"), bytes.fromhex("05")] + P2SH_VERBYTES = (bytes.fromhex("37"), bytes.fromhex("05")) WIF_BYTE = bytes.fromhex("B0") GENESIS_HASH = ('ff9f1c0116d19de7c9963845e129f9ed' '1bfc0b376eb54fd7afa42e0d418c8bb6') @@ -1824,7 +1826,7 @@ class MonacoinTestnet(Monacoin): XPUB_VERBYTES = bytes.fromhex("043587CF") XPRV_VERBYTES = bytes.fromhex("04358394") P2PKH_VERBYTE = bytes.fromhex("6F") - P2SH_VERBYTES = [bytes.fromhex("75"), bytes.fromhex("C4")] + P2SH_VERBYTES = (bytes.fromhex("75"), bytes.fromhex("C4")) WIF_BYTE = bytes.fromhex("EF") GENESIS_HASH = ('a2b106ceba3be0c6d097b2a6a6aacf9d' '638ba8258ae478158f449c321061e0b2') @@ -1855,7 +1857,7 @@ class Crown(AuxPowMixin, Coin): NET = "mainnet" XPUB_VERBYTES = bytes.fromhex("0488b21e") XPRV_VERBYTES = bytes.fromhex("0488ade4") - P2SH_VERBYTES = [bytes.fromhex("1c")] + P2SH_VERBYTES = (bytes.fromhex("1c"),) GENESIS_HASH = ('0000000085370d5e122f64f4ab19c686' '14ff3df78c8d13cb814fd7e69a1dc6da') TX_COUNT = 13336629 @@ -1882,7 +1884,7 @@ class Fujicoin(Coin): XPUB_VERBYTES = bytes.fromhex("0488b21e") XPRV_VERBYTES = bytes.fromhex("0488ade4") P2PKH_VERBYTE = bytes.fromhex("24") - P2SH_VERBYTES = [bytes.fromhex("10")] + P2SH_VERBYTES = (bytes.fromhex("10"),) WIF_BYTE = bytes.fromhex("a4") GENESIS_HASH = ('adb6d9cfd74075e7f91608add4bd2a2e' 'a636f70856183086842667a1597714a0') @@ -1901,7 +1903,7 @@ class Neblio(ScryptMixin, Coin): XPUB_VERBYTES = bytes.fromhex("0488b21e") XPRV_VERBYTES = bytes.fromhex("0488ade4") P2PKH_VERBYTE = bytes.fromhex("35") - P2SH_VERBYTES = [bytes.fromhex("70")] + P2SH_VERBYTES = (bytes.fromhex("70"),) GENESIS_HASH = ('7286972be4dbc1463d256049b7471c25' '2e6557e222cab9be73181d359cd28bcc') TX_COUNT = 23675 @@ -1961,7 +1963,7 @@ class Denarius(Coin): XPUB_VERBYTES = bytes.fromhex("0488b21e") XPRV_VERBYTES = bytes.fromhex("0488ade4") P2PKH_VERBYTE = bytes.fromhex("1E") # Address starts with a D - P2SH_VERBYTES = [bytes.fromhex("5A")] + P2SH_VERBYTES = (bytes.fromhex("5A"),) WIF_BYTE = bytes.fromhex("9E") # WIF starts with a 6 GENESIS_HASH = ('00000d5dbbda01621cfc16bbc1f9bf32' '64d641a5dbf0de89fd0182c2c4828fcd') @@ -1986,7 +1988,7 @@ class DenariusTestnet(Denarius): XPUB_VERBYTES = bytes.fromhex("043587cf") XPRV_VERBYTES = bytes.fromhex("04358394") P2PKH_VERBYTE = bytes.fromhex("12") - P2SH_VERBYTES = [bytes.fromhex("74")] + P2SH_VERBYTES = (bytes.fromhex("74"),) WIF_BYTE = bytes.fromhex("ef") GENESIS_HASH = ('000086bfe8264d241f7f8e5393f74778' '4b8ca2aa98bdd066278d590462a4fdb4') @@ -2001,7 +2003,7 @@ class Sibcoin(Dash): XPUB_VERBYTES = bytes.fromhex("0488b21e") XPRV_VERBYTES = bytes.fromhex("0488ade4") P2PKH_VERBYTE = bytes.fromhex("3F") - P2SH_VERBYTES = [bytes.fromhex("28")] + P2SH_VERBYTES = (bytes.fromhex("28"),) WIF_BYTE = bytes.fromhex("80") GENESIS_HASH = ('00000c492bf73490420868bc577680bf' 'c4c60116e7e85343bc624787c21efa4c') @@ -2040,7 +2042,7 @@ class Chips(Coin): SHORTNAME = "CHIPS" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("3c") - P2SH_VERBYTES = [bytes.fromhex("55")] + P2SH_VERBYTES = (bytes.fromhex("55"),) WIF_BYTE = bytes.fromhex("bc") GENESIS_HASH = ('0000006e75f6aa0efdbf7db03132aa4e' '4d0c84951537a6f5a7c39a0a9d30e1e7') @@ -2082,7 +2084,7 @@ class UFO(Coin): XPUB_VERBYTES = bytes.fromhex("0488B21E") XPRV_VERBYTES = bytes.fromhex("0488ADE4") P2PKH_VERBYTE = bytes.fromhex("1B") - P2SH_VERBYTES = [bytes.fromhex("44")] + P2SH_VERBYTES = (bytes.fromhex("44"),) WIF_BYTE = bytes.fromhex("9B") GENESIS_HASH = ('ba1d39b4928ab03d813d952daf65fb77' '97fcf538a9c1b8274f4edc8557722d13') @@ -2102,7 +2104,7 @@ class Newyorkcoin(AuxPowMixin, Coin): SHORTNAME = "NYC" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("3c") - P2SH_VERBYTES = [bytes.fromhex("16")] + P2SH_VERBYTES = (bytes.fromhex("16"),) WIF_BYTE = bytes.fromhex("bc") GENESIS_HASH = ('5597f25c062a3038c7fd815fe46c67de' 'dfcb3c839fbc8e01ed4044540d08fe48') @@ -2116,7 +2118,7 @@ class NewyorkcoinTestnet(Newyorkcoin): SHORTNAME = "tNYC" NET = "testnet" P2PKH_VERBYTE = bytes.fromhex("71") - P2SH_VERBYTES = [bytes.fromhex("c4")] + P2SH_VERBYTES = (bytes.fromhex("c4"),) WIF_BYTE = bytes.fromhex("f1") GENESIS_HASH = ('24463e4d3c625b0a9059f309044c2cf0' 'd7e196cf2a6ecce901f24f681be33c8f') @@ -2130,7 +2132,7 @@ class Bitcore(BitcoinMixin, Coin): NAME = "Bitcore" SHORTNAME = "BTX" P2PKH_VERBYTE = bytes.fromhex("03") - P2SH_VERBYTES = [bytes.fromhex("7D")] + P2SH_VERBYTES = (bytes.fromhex("7D"),) DESERIALIZER = lib_tx.DeserializerSegWit GENESIS_HASH = ('604148281e5c4b7f2487e5d03cd60d8e' '6f69411d613f6448034508cea52e9574') @@ -2169,7 +2171,7 @@ class Machinecoin(Coin): XPUB_VERBYTES = bytes.fromhex("0488b21e") XPRV_VERBYTES = bytes.fromhex("0488ade4") P2PKH_VERBYTE = bytes.fromhex("32") - P2SH_VERBYTES = [bytes.fromhex("26"), bytes.fromhex("05")] + P2SH_VERBYTES = (bytes.fromhex("26"), bytes.fromhex("05")) WIF_BYTE = bytes.fromhex("b2") GENESIS_HASH = ('6a1f879bcea5471cbfdee1fd0cb2ddcc' '4fed569a500e352d41de967703e83172') @@ -2186,7 +2188,7 @@ class BitcoinAtom(Coin): SHORTNAME = "BCA" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("17") - P2SH_VERBYTES = [bytes.fromhex("0a")] + P2SH_VERBYTES = (bytes.fromhex("0a"),) STATIC_BLOCK_HEADERS = False DESERIALIZER = lib_tx.DeserializerBitcoinAtom HEADER_SIZE_POST_FORK = 84 @@ -2225,7 +2227,7 @@ class Decred(Coin): XPUB_VERBYTES = bytes.fromhex("02fda926") XPRV_VERBYTES = bytes.fromhex("02fda4e8") P2PKH_VERBYTE = bytes.fromhex("073f") - P2SH_VERBYTES = [bytes.fromhex("071a")] + P2SH_VERBYTES = (bytes.fromhex("071a"),) WIF_BYTE = bytes.fromhex("22de") GENESIS_HASH = ('298e5cc3d985bfe7f81dc135f360abe0' '89edd4396b86d2de66b0cef42b21d980') @@ -2271,7 +2273,7 @@ class DecredTestnet(Decred): XPUB_VERBYTES = bytes.fromhex("043587d1") XPRV_VERBYTES = bytes.fromhex("04358397") P2PKH_VERBYTE = bytes.fromhex("0f21") - P2SH_VERBYTES = [bytes.fromhex("0efc")] + P2SH_VERBYTES = (bytes.fromhex("0efc"),) WIF_BYTE = bytes.fromhex("230e") GENESIS_HASH = ( 'a649dce53918caf422e9c711c858837e08d626ecfcd198969b24f7b634a49bac') @@ -2291,7 +2293,7 @@ class Axe(Dash): XPUB_VERBYTES = bytes.fromhex("02fe52cc") XPRV_VERBYTES = bytes.fromhex("02fe52f8") P2PKH_VERBYTE = bytes.fromhex("37") - P2SH_VERBYTES = [bytes.fromhex("10")] + P2SH_VERBYTES = (bytes.fromhex("10"),) WIF_BYTE = bytes.fromhex("cc") GENESIS_HASH = ('00000c33631ca6f2f61368991ce2dc03' '306b5bb50bf7cede5cfbba6db38e52e6') @@ -2324,7 +2326,7 @@ class AxeTestnet(Axe): GENESIS_HASH = ('000005b709662e7bc5e89c71d3aba6c9' 'd4623b4bbf44ac205caec55f4cefb483') P2PKH_VERBYTE = bytes.fromhex("8c") - P2SH_VERBYTES = [bytes.fromhex("13")] + P2SH_VERBYTES = (bytes.fromhex("13"),) WIF_BYTE = bytes.fromhex("ef") TX_COUNT_HEIGHT = 101619 TX_COUNT = 132681 @@ -2351,7 +2353,7 @@ class Xuez(Coin): XPUB_VERBYTES = bytes.fromhex("022d2533") XPRV_VERBYTES = bytes.fromhex("0221312b") P2PKH_VERBYTE = bytes.fromhex("48") - P2SH_VERBYTES = [bytes.fromhex("12")] + P2SH_VERBYTES = (bytes.fromhex("12"),) WIF_BYTE = bytes.fromhex("d4") GENESIS_HASH = ('000000e1febc39965b055e8e0117179a' '4d18e24e7aaa0c69864c4054b4f29445') @@ -2388,7 +2390,7 @@ class Odin(Coin): XPUB_VERBYTES = bytes.fromhex("27561872") XPRV_VERBYTES = bytes.fromhex("27256746") P2PKH_VERBYTE = bytes.fromhex("73") - P2SH_VERBYTES = [bytes.fromhex("39")] + P2SH_VERBYTES = (bytes.fromhex("39"),) WIF_BYTE = bytes.fromhex("8a") GENESIS_HASH = ('31ca29566549e444cf227a0e2e067aed' '847c2acc541d3bbf9ca1ae89f4fd57d7') @@ -2436,7 +2438,7 @@ class Pac(Coin): GENESIS_HASH = ('00000354655ff039a51273fe61d3b493' 'bd2897fe6c16f732dbc4ae19f04b789e') P2PKH_VERBYTE = bytes.fromhex("37") - P2SH_VERBYTES = [bytes.fromhex("0A")] + P2SH_VERBYTES = (bytes.fromhex("0A"),) WIF_BYTE = bytes.fromhex("CC") TX_COUNT_HEIGHT = 14939 TX_COUNT = 23708 @@ -2466,7 +2468,7 @@ class PacTestnet(Pac): GENESIS_HASH = ('00000da63bd9478b655ef6bf1bf76cd9' 'af05202ab68643f9091e049b2b5280ed') P2PKH_VERBYTE = bytes.fromhex("78") - P2SH_VERBYTES = [bytes.fromhex("0E")] + P2SH_VERBYTES = (bytes.fromhex("0E"),) WIF_BYTE = bytes.fromhex("EF") TX_COUNT_HEIGHT = 16275 TX_COUNT = 16275 @@ -2479,7 +2481,7 @@ class Zcoin(Coin): SHORTNAME = "XZC" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("52") - P2SH_VERBYTES = [bytes.fromhex("07")] + P2SH_VERBYTES = (bytes.fromhex("07"),) WIF_BYTE = bytes.fromhex("d2") GENESIS_HASH = ('4381deb85b1b2c9843c222944b616d99' '7516dcbd6a964e1eaf0def0830695233') @@ -2534,7 +2536,7 @@ class ZcoinTestnet(Zcoin): XPUB_VERBYTES = bytes.fromhex("043587cf") XPRV_VERBYTES = bytes.fromhex("04358394") P2PKH_VERBYTE = bytes.fromhex("41") - P2SH_VERBYTES = [bytes.fromhex("b2")] + P2SH_VERBYTES = (bytes.fromhex("b2"),) WIF_BYTE = bytes.fromhex("b9") GENESIS_HASH = '1e3487fdb1a7d46dac3e8f3e58339c6e' \ 'ff54abf6aef353485f3ed64250a35e89' @@ -2551,7 +2553,7 @@ class Polis(Coin): GENESIS_HASH = ('000009701eb781a8113b1af1d814e2f0' '60f6408a2c990db291bc5108a1345c1e') P2PKH_VERBYTE = bytes.fromhex("37") - P2SH_VERBYTES = [bytes.fromhex("38")] + P2SH_VERBYTES = (bytes.fromhex("38"),) WIF_BYTE = bytes.fromhex("3c") TX_COUNT_HEIGHT = 280600 TX_COUNT = 635415 @@ -2579,7 +2581,7 @@ class MNPCoin(Coin): GENESIS_HASH = ('00000924036c67d803ce606ded814312' '7e62fa2111dd3b063880a1067c69ccb1') P2PKH_VERBYTE = bytes.fromhex("32") - P2SH_VERBYTES = [bytes.fromhex("35")] + P2SH_VERBYTES = (bytes.fromhex("35"),) WIF_BYTE = bytes.fromhex("37") TX_COUNT_HEIGHT = 248000 TX_COUNT = 506447 @@ -2607,7 +2609,7 @@ class ColossusXT(Coin): GENESIS_HASH = ('a0ce8206c908357008c1b9a8ba2813af' 'f0989ca7f72d62b14e652c55f02b4f5c') P2PKH_VERBYTE = bytes.fromhex("1E") - P2SH_VERBYTES = [bytes.fromhex("0D")] + P2SH_VERBYTES = (bytes.fromhex("0D"),) WIF_BYTE = bytes.fromhex("D4") TX_COUNT_HEIGHT = 356500 BASIC_HEADER_SIZE = 80 @@ -2711,7 +2713,7 @@ class GroestlcoinTestnet(Groestlcoin): XPUB_VERBYTES = bytes.fromhex("043587cf") XPRV_VERBYTES = bytes.fromhex("04358394") P2PKH_VERBYTE = bytes.fromhex("6f") - P2SH_VERBYTES = [bytes.fromhex("c4")] + P2SH_VERBYTES = (bytes.fromhex("c4"),) WIF_BYTE = bytes.fromhex("ef") GENESIS_HASH = ('000000ffbb50fc9898cdd36ec163e6ba' '23230164c0052a28876255b7dcf2cd36') @@ -2783,7 +2785,7 @@ class Bitg(Coin): XPUB_VERBYTES = bytes.fromhex("0488b21e") XPRV_VERBYTES = bytes.fromhex("0488ade4") P2PKH_VERBYTE = bytes.fromhex("26") - P2SH_VERBYTES = [bytes.fromhex("06")] + P2SH_VERBYTES = (bytes.fromhex("06"),) WIF_BYTE = bytes.fromhex("2e") GENESIS_HASH = ( '000008467c3a9c587533dea06ad9380cded3ed32f9742a6c0c1aebc21bf2bc9b') @@ -2809,7 +2811,7 @@ class tBitg(Bitg): XPUB_VERBYTES = bytes.fromhex("043587cf") XPRV_VERBYTES = bytes.fromhex("04358394") P2PKH_VERBYTE = bytes.fromhex("62") - P2SH_VERBYTES = [bytes.fromhex("0c")] + P2SH_VERBYTES = (bytes.fromhex("0c"),) WIF_BYTE = bytes.fromhex("6c") GENESIS_HASH = ( '000008467c3a9c587533dea06ad9380cded3ed32f9742a6c0c1aebc21bf2bc9b') @@ -2871,7 +2873,7 @@ class SmartCash(Coin): SHORTNAME = "SMART" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("3f") - P2SH_VERBYTES = [bytes.fromhex("12")] + P2SH_VERBYTES = (bytes.fromhex("12"),) WIF_BYTE = bytes.fromhex("bf") GENESIS_HASH = ('000007acc6970b812948d14ea5a0a13d' 'b0fdd07d5047c7e69101fa8b361e05a4') @@ -2902,7 +2904,7 @@ class NIX(Coin): XPUB_VERBYTES = bytes.fromhex("0488b21e") XPRV_VERBYTES = bytes.fromhex("0488ade4") P2PKH_VERBYTE = bytes.fromhex("26") - P2SH_VERBYTES = [bytes.fromhex("35")] + P2SH_VERBYTES = (bytes.fromhex("35"),) GENESIS_HASH = ('dd28ad86def767c3cfc34267a950d871' 'fc7462bc57ea4a929fc3596d9b598e41') DESERIALIZER = lib_tx.DeserializerSegWit @@ -2932,7 +2934,7 @@ class Noir(Coin): NET = "mainnet" XPUB_VERBYTES = bytes.fromhex("0488b21e") XPRV_VERBYTES = bytes.fromhex("0488ade4") - P2SH_VERBYTES = [bytes.fromhex("07")] + P2SH_VERBYTES = (bytes.fromhex("07"),) WIF_BYTE = bytes.fromhex("D0") GENESIS_HASH = ('23911212a525e3d149fcad6c559c8b17' 'f1e8326a272a75ff9bb315c8d96433ef') @@ -2949,7 +2951,7 @@ class BitcoinPlus(Coin): XPUB_VERBYTES = bytes.fromhex("0488B21E") XPRV_VERBYTES = bytes.fromhex("0488ADE4") P2PKH_VERBYTE = bytes.fromhex("19") - P2SH_VERBYTES = [bytes.fromhex("55")] + P2SH_VERBYTES = (bytes.fromhex("55"),) WIF_BYTE = bytes.fromhex("99") GENESIS_HASH = ('0000005f6a28e686f641c616e56182d1' 'b43afbe08a223f23bda23cdf9d55b882') @@ -2975,7 +2977,7 @@ class Myriadcoin(AuxPowMixin, Coin): XPUB_VERBYTES = bytes.fromhex("0488b21e") XPRV_VERBYTES = bytes.fromhex("0488ade4") P2PKH_VERBYTE = bytes.fromhex("32") - P2SH_VERBYTES = [bytes.fromhex("09")] + P2SH_VERBYTES = (bytes.fromhex("09"),) WIF_BYTE = bytes.fromhex("b2") GENESIS_HASH = ('00000ffde4c020b5938441a0ea3d314b' 'f619eff0b38f32f78f7583cffa1ea485') @@ -2994,7 +2996,7 @@ class MyriadcoinTestnet(Myriadcoin): XPUB_VERBYTES = bytes.fromhex("043587cf") XPRV_VERBYTES = bytes.fromhex("04358394") P2PKH_VERBYTE = bytes.fromhex("58") - P2SH_VERBYTES = [bytes.fromhex("bc")] + P2SH_VERBYTES = (bytes.fromhex("bc"),) WIF_BYTE = bytes.fromhex("ef") GENESIS_HASH = ('0000017ce2a79c8bddafbbe47c004aa9' '2b20678c354b34085f62b762084b9788') @@ -3038,9 +3040,9 @@ def genesis_block(cls, block): header = cls.block_header(block, 0) header_hex_hash = hash_to_hex_str(cls.header_hash(header)) if header_hex_hash != cls.GENESIS_HASH: - raise CoinError('genesis block has hash {} expected {}' - .format(header_hex_hash, cls.GENESIS_HASH)) - return header + bytes(1) + raise CoinError(f'genesis block has hash {header_hex_hash} ' + f'expected {cls.GENESIS_HASH}') + return header + b'\0' class Ritocoin(Coin): @@ -3050,7 +3052,7 @@ class Ritocoin(Coin): XPUB_VERBYTES = bytes.fromhex("0534E7CA") XPRV_VERBYTES = bytes.fromhex("05347EAC") P2PKH_VERBYTE = bytes.fromhex("19") - P2SH_VERBYTES = [bytes.fromhex("69")] + P2SH_VERBYTES = (bytes.fromhex("69"),) GENESIS_HASH = ('00000075e344bdf1c0e433f453764b18' '30a7aa19b2a5213e707502a22b779c1b') DESERIALIZER = lib_tx.DeserializerSegWit @@ -3077,7 +3079,7 @@ class Ravencoin(Coin): XPUB_VERBYTES = bytes.fromhex("0488B21E") XPRV_VERBYTES = bytes.fromhex("0488ADE4") P2PKH_VERBYTE = bytes.fromhex("3C") - P2SH_VERBYTES = [bytes.fromhex("7A")] + P2SH_VERBYTES = (bytes.fromhex("7A"),) GENESIS_HASH = ('0000006b444bc2f2ffe627be9d9e7e7a' '0730000870ef6eb6da46c8eae389df90') DESERIALIZER = lib_tx.DeserializerSegWit @@ -3137,7 +3139,7 @@ class RavencoinTestnet(Ravencoin): XPUB_VERBYTES = bytes.fromhex("043587CF") XPRV_VERBYTES = bytes.fromhex("04358394") P2PKH_VERBYTE = bytes.fromhex("6F") - P2SH_VERBYTES = [bytes.fromhex("C4")] + P2SH_VERBYTES = (bytes.fromhex("C4"),) WIF_BYTE = bytes.fromhex("EF") GENESIS_HASH = ('000000ecfc5e6324a079542221d00e10' '362bdc894d56500c414060eea8a3ad5a') @@ -3161,7 +3163,7 @@ class Bolivarcoin(Coin): XPUB_VERBYTES = bytes.fromhex("0488B21E") XPRV_VERBYTES = bytes.fromhex("0488ADE4") P2PKH_VERBYTE = bytes.fromhex("55") - P2SH_VERBYTES = [bytes.fromhex("05")] + P2SH_VERBYTES = (bytes.fromhex("05"),) WIF_BYTE = bytes.fromhex("D5") GENESIS_HASH = ('00000e4fc293a1912b9d73cbb8d8f727' '0007a7d84382f1370661e65d5d57b1f6') @@ -3213,7 +3215,7 @@ class Electra(Coin): XPUB_VERBYTES = bytes.fromhex("0488b21e") XPRV_VERBYTES = bytes.fromhex("0488ade4") P2PKH_VERBYTE = bytes.fromhex("21") - P2SH_VERBYTES = [bytes.fromhex("28")] + P2SH_VERBYTES = (bytes.fromhex("28"),) WIF_BYTE = bytes.fromhex("A1") GENESIS_HASH = ('00000f98da995de0ef1665c7d3338687' '923c1199230a44ecbdb5cec9306e4f4e') @@ -3244,7 +3246,7 @@ class ECCoin(Coin): XPUB_VERBYTES = bytes.fromhex("0488b21e") XPRV_VERBYTES = bytes.fromhex("0488ade4") P2PKH_VERBYTE = bytes.fromhex("21") - P2SH_VERBYTES = [bytes.fromhex("08")] + P2SH_VERBYTES = (bytes.fromhex("08"),) GENESIS_HASH = ('a60ac43c88dbc44b826cf315352a8a7b373d2af8b6e1c4c4a0638859c5e9ecd1') TX_COUNT = 4661197 TX_COUNT_HEIGHT = 2114846 @@ -3266,7 +3268,7 @@ class Bellcoin(Coin): XPUB_VERBYTES = bytes.fromhex("0488b21e") XPRV_VERBYTES = bytes.fromhex("0488ade4") P2PKH_VERBYTE = bytes.fromhex("19") - P2SH_VERBYTES = [bytes.fromhex("55")] + P2SH_VERBYTES = (bytes.fromhex("55"),) WIF_BYTE = bytes.fromhex("80") GENESIS_HASH = ('000008f3b6bd10c2d03b06674a006b8d' '9731f6cb58179ef1eee008cee2209603') @@ -3293,7 +3295,7 @@ class CPUchain(Coin): SHORTNAME = "CPU" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("1C") - P2SH_VERBYTES = [bytes.fromhex("1E")] + P2SH_VERBYTES = (bytes.fromhex("1E"),) GENESIS_HASH = ('000024d8766043ea0e1c9ad42e7ea4b5' 'fdb459887bd80b8f9756f3d87e128f12') DESERIALIZER = lib_tx.DeserializerSegWit @@ -3320,7 +3322,7 @@ class Xaya(NameIndexMixin, AuxPowMixin, Coin): XPUB_VERBYTES = bytes.fromhex("0488b21e") XPRV_VERBYTES = bytes.fromhex("0488ade4") P2PKH_VERBYTE = bytes.fromhex("1c") - P2SH_VERBYTES = [bytes.fromhex("1e")] + P2SH_VERBYTES = (bytes.fromhex("1e"),) WIF_BYTE = bytes.fromhex("82") GENESIS_HASH = ('e5062d76e5f50c42f493826ac9920b63' 'a8def2626fd70a5cec707ec47a4c4651') @@ -3344,10 +3346,10 @@ class Xaya(NameIndexMixin, AuxPowMixin, Coin): OpCodes.OP_DROP] NAME_UPDATE_OPS = [OP_NAME_UPDATE, "name", -1, OpCodes.OP_2DROP, OpCodes.OP_DROP] - NAME_OPERATIONS = [ + NAME_OPERATIONS = ( NAME_REGISTER_OPS, NAME_UPDATE_OPS, - ] + ) @classmethod def genesis_block(cls, block): @@ -3362,7 +3364,7 @@ class XayaTestnet(Xaya): SHORTNAME = "XCH" NET = "testnet" P2PKH_VERBYTE = bytes.fromhex("58") - P2SH_VERBYTES = [bytes.fromhex("5a")] + P2SH_VERBYTES = (bytes.fromhex("5a"),) WIF_BYTE = bytes.fromhex("e6") GENESIS_HASH = ('5195fc01d0e23d70d1f929f21ec55f47' 'e1c6ea1e66fae98ee44cbbc994509bba') @@ -3464,7 +3466,7 @@ class Navcoin(Coin): XPUB_VERBYTES = bytes.fromhex("0488b21e") XPRV_VERBYTES = bytes.fromhex("0488ade4") P2PKH_VERBYTE = bytes.fromhex("35") - P2SH_VERBYTES = [bytes.fromhex("55")] + P2SH_VERBYTES = (bytes.fromhex("55"),) WIF_BYTE = bytes.fromhex("96") GENESIS_HASH = ('00006a4e3e18c71c6d48ad6c261e2254' 'fa764cf29607a4357c99b712dfbb8e6a') @@ -3489,7 +3491,7 @@ class Primecoin(PrimeChainPowMixin, Coin): SHORTNAME = "XPM" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("17") - P2SH_VERBYTES = [bytes.fromhex("53")] + P2SH_VERBYTES = (bytes.fromhex("53"),) WIF_BYTE = bytes.fromhex("97") GENESIS_HASH = ('963d17ba4dc753138078a2f56afb3af9' '674e2546822badff26837db9a0152106') @@ -3510,7 +3512,7 @@ class PrimecoinTestnet(Primecoin): SHORTNAME = "tXPM" NET = "testnet" P2PKH_VERBYTE = bytes.fromhex("6f") - P2SH_VERBYTES = [bytes.fromhex("c4")] + P2SH_VERBYTES = (bytes.fromhex("c4"),) WIF_BYTE = bytes.fromhex("ef") GENESIS_HASH = ('221156cf301bc3585e72de34fe1efdb6' 'fbd703bc27cfc468faa1cdd889d0efa0') @@ -3527,7 +3529,7 @@ class Unobtanium(AuxPowMixin, Coin): XPUB_VERBYTES = bytes.fromhex("0488B21E") XPRV_VERBYTES = bytes.fromhex("0488ADE4") P2PKH_VERBYTE = bytes.fromhex("82") - P2SH_VERBYTES = [bytes.fromhex("1e")] + P2SH_VERBYTES = (bytes.fromhex("1e"),) WIF_BYTE = bytes.fromhex("e0") GENESIS_HASH = ('000004c2fc5fffb810dccc197d603690' '099a68305232e552d96ccbe8e2c52b75') @@ -3543,7 +3545,7 @@ class Linx(Coin): SHORTNAME = "LINX" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("4b") - P2SH_VERBYTES = [bytes.fromhex("05")] + P2SH_VERBYTES = (bytes.fromhex("05"),) WIF_BYTE = bytes.fromhex("cb") GENESIS_HASH = ('3bafea350a70f75e7a1cd279999faed7' '1a51852aae88fed3c38553cecc810a92') @@ -3559,7 +3561,7 @@ class Flashcoin(Coin): SHORTNAME = "FLASH" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("44") - P2SH_VERBYTES = [bytes.fromhex("82")] + P2SH_VERBYTES = (bytes.fromhex("82"),) WIF_BYTE = bytes.fromhex("c4") GENESIS_HASH = ('aa0cf4f5ce0a3c550ce5674c1e808c41' '7cf5077b4e95bda1d6fbaeaf4258972b') @@ -3607,7 +3609,7 @@ class Iop(Coin): SHORTNAME = "IOP" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("75") - P2SH_VERBYTES = [bytes.fromhex("AE")] + P2SH_VERBYTES = (bytes.fromhex("AE"),) WIF_BYTE = bytes.fromhex("31") GENESIS_HASH = ('00000000bf5f2ee556cb9be8be64e077' '6af14933438dbb1af72c41bfb6c82db3') @@ -3624,7 +3626,7 @@ class Egulden(Coin): SHORTNAME = "EFL" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("30") - P2SH_VERBYTES = [bytes.fromhex("05")] + P2SH_VERBYTES = (bytes.fromhex("05"),) WIF_BYTE = bytes.fromhex("b0") GENESIS_HASH = ('6d39f28ad01a7edd3e2374b355cf8c7f' '8dbc1c5e4596ad3642fa6d10c2599217') @@ -3640,7 +3642,7 @@ class Ixcoin(AuxPowMixin, Coin): SHORTNAME = "IXC" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("8a") - P2SH_VERBYTES = [bytes.fromhex("05")] + P2SH_VERBYTES = (bytes.fromhex("05"),) WIF_BYTE = bytes.fromhex("80") GENESIS_HASH = ('0000000001534ef8893b025b9c1da672' '50285e35c9f76cae36a4904fdf72c591') @@ -3656,7 +3658,7 @@ class Batacoin(Coin): SHORTNAME = "BTA" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("19") - P2SH_VERBYTES = [bytes.fromhex("05")] + P2SH_VERBYTES = (bytes.fromhex("05"),) WIF_BYTE = bytes.fromhex("99") GENESIS_HASH = ('b4bee36fd54a6176fd832f462641415c' '142d50e4b378f71c041870c2b1186bc8') @@ -3673,7 +3675,7 @@ class Digitalcoin(Coin): SHORTNAME = "DGC" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("1e") - P2SH_VERBYTES = [bytes.fromhex("05")] + P2SH_VERBYTES = (bytes.fromhex("05"),) WIF_BYTE = bytes.fromhex("9e") GENESIS_HASH = ('5e039e1ca1dbf128973bf6cff98169e4' '0a1b194c3b91463ab74956f413b2f9c8') @@ -3689,7 +3691,7 @@ class Cannacoin(Coin): SHORTNAME = "CCN" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("1C") - P2SH_VERBYTES = [bytes.fromhex("05")] + P2SH_VERBYTES = (bytes.fromhex("05"),) WIF_BYTE = bytes.fromhex("bd") GENESIS_HASH = ('f1b4cdf03c86099a0758f1c018d1a10b' 'f05afab436c92b93b42bb88970de9821') @@ -3706,7 +3708,7 @@ class Europecoin(Coin): SHORTNAME = "ERC" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("21") - P2SH_VERBYTES = [bytes.fromhex("05")] + P2SH_VERBYTES = (bytes.fromhex("05"),) WIF_BYTE = bytes.fromhex("a8") GENESIS_HASH = ('000d0da26987ead011c5d568e627f7e3' 'd4a4f83a0b280b1134d8e7e366377f9a') @@ -3723,7 +3725,7 @@ class Adcoin(Coin): SHORTNAME = "ACC" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("1e") - P2SH_VERBYTES = [bytes.fromhex("0d")] + P2SH_VERBYTES = (bytes.fromhex("0d"),) WIF_BYTE = bytes.fromhex("97") GENESIS_HASH = ('000000fc5276647fd959f718c9526f87' 'f4858c4ef62f2e29d3772e4e37040a25') @@ -3741,7 +3743,7 @@ class Lynx(Coin): SHORTNAME = "LYNX" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("2d") - P2SH_VERBYTES = [bytes.fromhex("16")] + P2SH_VERBYTES = (bytes.fromhex("16"),) WIF_BYTE = bytes.fromhex("ad") GENESIS_HASH = ('984b30fc9bb5e5ff424ad7f4ec193053' '8a7b14a2d93e58ad7976c23154ea4a76') @@ -3758,7 +3760,7 @@ class LitecoinCash(Coin): SHORTNAME = "LCC" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("1c") - P2SH_VERBYTES = [bytes.fromhex("32"), bytes.fromhex("05")] + P2SH_VERBYTES = (bytes.fromhex("32"), bytes.fromhex("05")) WIF_BYTE = bytes.fromhex("b0") GENESIS_HASH = ('12a765e31ffd4059bada1e25190f6e98' 'c99d9714d334efa41a195a7e7e04bfe2') @@ -3775,7 +3777,7 @@ class BitcoinPrivate(EquihashMixin, Coin): SHORTNAME = "BTCP" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("1325") - P2SH_VERBYTES = [bytes.fromhex("13AF")] + P2SH_VERBYTES = (bytes.fromhex("13AF"),) WIF_BYTE = bytes.fromhex("80") GENESIS_HASH = ('0007104ccda289427919efc39dc9e4d4' '99804b7bebc22df55f8b834301260602') @@ -3794,7 +3796,7 @@ class Aryacoin(Coin): XPUB_VERBYTES = bytes.fromhex("019d9cfe") XPRV_VERBYTES = bytes.fromhex("019da462") P2PKH_VERBYTE = bytes.fromhex("17") - P2SH_VERBYTES = [bytes.fromhex("6f")] + P2SH_VERBYTES = (bytes.fromhex("6f"),) WIF_BYTE = bytes.fromhex("b0") GENESIS_HASH = ('b553727635006d7faade229d152482df' 'b9da7822d41cf0cad9ffa82a54f67803') @@ -3811,7 +3813,7 @@ class Donu(Coin): SHORTNAME = "DONU" NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("35") - P2SH_VERBYTES = [bytes.fromhex("05")] + P2SH_VERBYTES = (bytes.fromhex("05"),) WIF_BYTE = bytes.fromhex("b1") XPUB_VERBYTES = bytes.fromhex("0488B21E") XPRV_VERBYTES = bytes.fromhex("0488ADE4") @@ -3832,7 +3834,7 @@ class Quebecoin(AuxPowMixin, Coin): XPUB_VERBYTES = bytes.fromhex("0488b21e") XPRV_VERBYTES = bytes.fromhex("0488ade4") P2PKH_VERBYTE = bytes.fromhex("3a") - P2SH_VERBYTES = [bytes.fromhex("05")] + P2SH_VERBYTES = (bytes.fromhex("05"),) WIF_BYTE = bytes.fromhex("ba") GENESIS_HASH = ('000008c2d57759af6462352ee9f4923d' '97401cb599a9318e6595a2a74c26ea74') diff --git a/electrumx/lib/env_base.py b/electrumx/lib/env_base.py index 3e16c0b..bcab47a 100644 --- a/electrumx/lib/env_base.py +++ b/electrumx/lib/env_base.py @@ -37,7 +37,7 @@ def boolean(cls, envvar, default): def required(cls, envvar): value = environ.get(envvar) if value is None: - raise cls.Error('required envvar {} not set'.format(envvar)) + raise cls.Error(f'required envvar {envvar} not set') return value @classmethod @@ -48,8 +48,8 @@ def integer(cls, envvar, default): try: return int(value) except Exception: - raise cls.Error('cannot convert envvar {} value {} to an integer' - .format(envvar, value)) + raise cls.Error(f'cannot convert envvar {envvar} value {value} to ' + f'an integer') @classmethod def custom(cls, envvar, default, parse): @@ -59,15 +59,15 @@ def custom(cls, envvar, default, parse): try: return parse(value) except Exception as e: - raise cls.Error('cannot parse envvar {} value {}' - .format(envvar, value)) from e + raise cls.Error( + f'cannot parse envvar {envvar} value {value}' + ) from e @classmethod def obsolete(cls, envvars): bad = [envvar for envvar in envvars if environ.get(envvar)] if bad: - raise cls.Error('remove obsolete environment variables {}' - .format(bad)) + raise cls.Error(f'remove obsolete environment variables {bad}') def event_loop_policy(self): policy = self.default('EVENT_LOOP_POLICY', None) @@ -76,4 +76,4 @@ def event_loop_policy(self): if policy == 'uvloop': import uvloop return uvloop.EventLoopPolicy() - raise self.Error('unknown event loop policy "{}"'.format(policy)) + raise self.Error(f'unknown event loop policy "{policy}"') diff --git a/electrumx/lib/hash.py b/electrumx/lib/hash.py index b6c9ed2..bddd1b2 100644 --- a/electrumx/lib/hash.py +++ b/electrumx/lib/hash.py @@ -34,7 +34,7 @@ _sha256 = hashlib.sha256 _sha512 = hashlib.sha512 _new_hash = hashlib.new -_new_hmac = hmac.new +_hmac_digest = hmac.digest HASHX_LEN = 11 @@ -45,9 +45,7 @@ def sha256(x): def ripemd160(x): '''Simple wrapper of hashlib ripemd160.''' - h = _new_hash('ripemd160') - h.update(x) - return h.digest() + return _new_hash('ripemd160', x).digest() def double_sha256(x): @@ -57,7 +55,7 @@ def double_sha256(x): def hmac_sha512(key, msg): '''Use SHA-512 to provide an HMAC.''' - return _new_hmac(key, msg, _sha512).digest() + return _hmac_digest(key, msg, _sha512) def hash160(x): @@ -95,7 +93,7 @@ class Base58(object): def char_value(c): val = Base58.cmap.get(c) if val is None: - raise Base58Error('invalid base 58 character "{}"'.format(c)) + raise Base58Error(f'invalid base 58 character "{c}"') return val @staticmethod @@ -148,7 +146,7 @@ def decode_check(txt, *, hash_fn=double_sha256): be_bytes = Base58.decode(txt) result, check = be_bytes[:-4], be_bytes[-4:] if check != hash_fn(result)[:4]: - raise Base58Error('invalid base 58 checksum for {}'.format(txt)) + raise Base58Error(f'invalid base 58 checksum for {txt}') return result @staticmethod diff --git a/electrumx/lib/peer.py b/electrumx/lib/peer.py index d17bd25..41c6557 100644 --- a/electrumx/lib/peer.py +++ b/electrumx/lib/peer.py @@ -123,11 +123,11 @@ def connection_tuples(self): while self.other_port_pairs: pairs.append(self.other_port_pairs.pop()) if isinstance(self.ip_address, IPv4Address): - families = [AF_INET] + families = (AF_INET,) elif isinstance(self.ip_address, IPv6Address): - families = [AF_INET6] + families = (AF_INET6,) else: - families = [AF_INET, AF_INET6] + families = (AF_INET, AF_INET6) return [(kind, port, family) for kind, port in pairs if port for family in families] @@ -294,7 +294,7 @@ def port_text(letter, port): parts = [self.host, 'v' + self.protocol_max] if self.pruning: - parts.append('p{:d}'.format(self.pruning)) + parts.append(f'p{self.pruning:d}') for letter, port in (('s', self.ssl_port), ('t', self.tcp_port)): if port: parts.append(port_text(letter, port)) diff --git a/electrumx/lib/script.py b/electrumx/lib/script.py index 2e7687c..5022202 100644 --- a/electrumx/lib/script.py +++ b/electrumx/lib/script.py @@ -104,10 +104,10 @@ class ScriptPubKey(object): necessary for spending. ''' - TO_ADDRESS_OPS = [OpCodes.OP_DUP, OpCodes.OP_HASH160, -1, - OpCodes.OP_EQUALVERIFY, OpCodes.OP_CHECKSIG] - TO_P2SH_OPS = [OpCodes.OP_HASH160, -1, OpCodes.OP_EQUAL] - TO_PUBKEY_OPS = [-1, OpCodes.OP_CHECKSIG] + TO_ADDRESS_OPS = (OpCodes.OP_DUP, OpCodes.OP_HASH160, -1, + OpCodes.OP_EQUALVERIFY, OpCodes.OP_CHECKSIG) + TO_P2SH_OPS = (OpCodes.OP_HASH160, -1, OpCodes.OP_EQUAL) + TO_PUBKEY_OPS = (-1, OpCodes.OP_CHECKSIG) PayToHandlers = namedtuple('PayToHandlers', 'address script_hash pubkey ' 'unspendable strange') @@ -143,15 +143,15 @@ def pay_to(cls, handlers, script): @classmethod def P2SH_script(cls, hash160): - return (bytes([OpCodes.OP_HASH160]) + return (bytes((OpCodes.OP_HASH160,)) + Script.push_data(hash160) - + bytes([OpCodes.OP_EQUAL])) + + bytes((OpCodes.OP_EQUAL,))) @classmethod def P2PKH_script(cls, hash160): - return (bytes([OpCodes.OP_DUP, OpCodes.OP_HASH160]) + return (bytes((OpCodes.OP_DUP, OpCodes.OP_HASH160)) + Script.push_data(hash160) - + bytes([OpCodes.OP_EQUALVERIFY, OpCodes.OP_CHECKSIG])) + + bytes((OpCodes.OP_EQUALVERIFY, OpCodes.OP_CHECKSIG))) class Script(object): @@ -200,21 +200,21 @@ def push_data(cls, data): n = len(data) if n < OpCodes.OP_PUSHDATA1: - return bytes([n]) + data + return bytes((n,)) + data if n < 256: - return bytes([OpCodes.OP_PUSHDATA1, n]) + data + return bytes((OpCodes.OP_PUSHDATA1, n)) + data if n < 65536: - return bytes([OpCodes.OP_PUSHDATA2]) + pack_le_uint16(n) + data - return bytes([OpCodes.OP_PUSHDATA4]) + pack_le_uint32(n) + data + return bytes((OpCodes.OP_PUSHDATA2,)) + pack_le_uint16(n) + data + return bytes((OpCodes.OP_PUSHDATA4,)) + pack_le_uint32(n) + data @classmethod def opcode_name(cls, opcode): if OpCodes.OP_0 < opcode < OpCodes.OP_PUSHDATA1: - return 'OP_{:d}'.format(opcode) + return f'OP_{opcode:d}' try: return OpCodes.whatis(opcode) except KeyError: - return 'OP_UNKNOWN:{:d}'.format(opcode) + return f'OP_UNKNOWN:{opcode:d}' @classmethod def dump(cls, script): @@ -224,5 +224,4 @@ def dump(cls, script): if data is None: print(name) else: - print('{} {} ({:d} bytes)' - .format(name, data.hex(), len(data))) + print(f'{name} {data.hex()} ({len(data):d} bytes)') diff --git a/electrumx/lib/server_base.py b/electrumx/lib/server_base.py index 96f7d49..79cacf3 100644 --- a/electrumx/lib/server_base.py +++ b/electrumx/lib/server_base.py @@ -54,7 +54,7 @@ def __init__(self, env): # Sanity checks if sys.version_info < self.PYTHON_MIN_VERSION: mvs = '.'.join(str(part) for part in self.PYTHON_MIN_VERSION) - raise RuntimeError('Python version >= {} is required'.format(mvs)) + raise RuntimeError(f'Python version >= {mvs} is required') if platform.system() == 'Windows': pass diff --git a/electrumx/lib/text.py b/electrumx/lib/text.py index df2b949..665411e 100644 --- a/electrumx/lib/text.py +++ b/electrumx/lib/text.py @@ -16,15 +16,15 @@ def sessions_lines(data): for (id_, flags, peer, client, proto, cost, extra_cost, reqs, txs_sent, subs, recv_count, recv_size, send_count, send_size, conn_time) in data: yield fmt.format(id_, flags, client, proto, - '{:,d}'.format(int(cost)), - '{:,d}'.format(int(extra_cost)), - '{:,d}'.format(reqs), - '{:,d}'.format(txs_sent), - '{:,d}'.format(subs), - '{:,d}'.format(recv_count), - '{:,d}'.format(recv_size // 1024), - '{:,d}'.format(send_count), - '{:,d}'.format(send_size // 1024), + f'{int(cost):,d}', + f'{int(extra_cost):,d}', + f'{reqs:,d}', + f'{txs_sent:,d}', + f'{subs:,d}', + f'{recv_count:,d}', + f'{recv_size // 1024:,d}', + f'{send_count:,d}', + f'{send_size // 1024:,d}', util.formatted_time(conn_time, sep=''), peer) @@ -40,16 +40,16 @@ def groups_lines(data): for (name, session_count, cost, retained_cost, reqs, txs_sent, subs, recv_count, recv_size, send_count, send_size) in data: yield fmt.format(name, - '{:,d}'.format(session_count), - '{:,d}'.format(int(cost)), - '{:,d}'.format(int(retained_cost)), - '{:,d}'.format(reqs), - '{:,d}'.format(txs_sent), - '{:,d}'.format(subs), - '{:,d}'.format(recv_count), - '{:,d}'.format(recv_size // 1024), - '{:,d}'.format(send_count), - '{:,d}'.format(send_size // 1024)) + f'{session_count:,d}', + f'{int(cost):,d}', + f'{int(retained_cost):,d}', + f'{reqs:,d}', + f'{txs_sent:,d}', + f'{subs:,d}', + f'{recv_count:,d}', + f'{recv_size // 1024:,d}', + f'{send_count:,d}', + f'{send_size // 1024:,d}') def peers_lines(data): diff --git a/electrumx/lib/tx.py b/electrumx/lib/tx.py index eb046b8..e4b956b 100644 --- a/electrumx/lib/tx.py +++ b/electrumx/lib/tx.py @@ -62,8 +62,8 @@ class TxInput(namedtuple("TxInput", "prev_hash prev_idx script sequence")): def __str__(self): script = self.script.hex() prev_hash = hash_to_hex_str(self.prev_hash) - return ("Input({}, {:d}, script={}, sequence={:d})" - .format(prev_hash, self.prev_idx, script, self.sequence)) + return (f"Input({prev_hash}, {self.prev_idx:d}, script={script}, " + f"sequence={self.sequence:d})") def is_generation(self): '''Test if an input is generation/coinbase like''' @@ -568,8 +568,7 @@ def blake2s_gen(data): _input112 = data + _full_merkle _key = keyTwo + ntime + _nBits + _nonce + keyOne # Prepare 112Byte Header - blake2s_hash = blake2s(key=_key, digest_size=32) - blake2s_hash.update(_input112) + blake2s_hash = blake2s(_input112, digest_size=32, key=_key) # TrezarFlips - Only for Genesis return ''.join(map(str.__add__, blake2s_hash.hexdigest()[-2::-2], blake2s_hash.hexdigest()[-1::-2])) @@ -585,8 +584,7 @@ def blake2s(data): _input112 = data + _full_merkle _key = keyTwo + ntime + _nBits + _nonce + keyOne # Prepare 112Byte Header - blake2s_hash = blake2s(key=_key, digest_size=32) - blake2s_hash.update(_input112) + blake2s_hash = blake2s(_input112, digest_size=32, key=_key) # TrezarFlips return blake2s_hash.digest() @@ -685,8 +683,8 @@ class TxInputTokenPayStealth( def __str__(self): script = self.script.hex() keyimage = bytes(self.keyimage).hex() - return ("Input({}, {:d}, script={}, sequence={:d})" - .format(keyimage, self.ringsize[1], script, self.sequence)) + return (f"Input({keyimage}, {self.ringsize[1]:d}, script={script}, " + f"sequence={self.sequence:d})") def is_generation(self): return True @@ -730,8 +728,8 @@ class TxInputDcr(namedtuple("TxInput", "prev_hash prev_idx tree sequence")): def __str__(self): prev_hash = hash_to_hex_str(self.prev_hash) - return ("Input({}, {:d}, tree={}, sequence={:d})" - .format(prev_hash, self.prev_idx, self.tree, self.sequence)) + return (f"Input({prev_hash}, {self.prev_idx:d}, tree={self.tree}, " + f"sequence={self.sequence:d})") def is_generation(self): '''Test if an input is generation/coinbase like''' @@ -841,8 +839,7 @@ class DeserializerSmartCash(Deserializer): @staticmethod def keccak(data): from Cryptodome.Hash import keccak - keccak_hash = keccak.new(digest_bits=256) - keccak_hash.update(data) + keccak_hash = keccak.new(data=data, digest_bits=256) return keccak_hash.digest() def read_tx_and_hash(self): diff --git a/electrumx/lib/util.py b/electrumx/lib/util.py index 812bffe..b165fd4 100644 --- a/electrumx/lib/util.py +++ b/electrumx/lib/util.py @@ -27,7 +27,7 @@ '''Miscellaneous utility classes and functions.''' -import array +from array import array import inspect from ipaddress import ip_address import logging @@ -95,7 +95,7 @@ def formatted_time(t, sep=' '): parts.append(fmt.format(val)) t %= n if len(parts) < 3: - parts.append('{:02d}s'.format(t)) + parts.append(f'{t:02d}s') return sep.join(parts) @@ -122,7 +122,7 @@ def size(o): r = sys.getsizeof(o) ids.add(id(o)) - if isinstance(o, (str, bytes, bytearray, array.array)): + if isinstance(o, (str, bytes, bytearray, array)): return r if isinstance(o, Mapping): @@ -173,18 +173,17 @@ def increment_byte_string(bs): '''Return the lexicographically next byte string of the same length. Return None if there is none (when the input is all 0xff bytes).''' - for n in range(1, len(bs) + 1): - if bs[-n] != 0xff: - return bs[:-n] + bytes([bs[-n] + 1]) + bytes(n - 1) - return None + try: + return (int.from_bytes(bs, 'big') + 1).to_bytes(len(bs), 'big') + except OverflowError: + return None class LogicalFile(object): '''A logical binary file split across several separate files on disk.''' def __init__(self, prefix, digits, file_size): - digit_fmt = '{' + ':0{:d}d'.format(digits) + '}' - self.filename_fmt = prefix + digit_fmt + self.filename_fmt = f'{prefix}{{:0{digits:d}d}}' self.file_size = file_size def read(self, start, size=-1): @@ -245,7 +244,6 @@ def open_truncate(filename): def address_string(address): '''Return an address as a correctly formatted string.''' - fmt = '{}:{:d}' host, port = address try: host = ip_address(host) @@ -253,8 +251,8 @@ def address_string(address): pass else: if host.version == 6: - fmt = '[{}]:{:d}' - return fmt.format(host, port) + return f'[{host}]:{port:d}' + return f'{host}:{port:d}' def protocol_tuple(s): diff --git a/electrumx/server/block_processor.py b/electrumx/server/block_processor.py index b88604c..74fd40b 100644 --- a/electrumx/server/block_processor.py +++ b/electrumx/server/block_processor.py @@ -87,12 +87,12 @@ async def reset_height(self, height): daemon_height = await self.daemon.height() behind = daemon_height - height if behind > 0: - self.logger.info('catching up to daemon height {:,d} ' - '({:,d} blocks behind)' - .format(daemon_height, behind)) + self.logger.info( + f'catching up to daemon height {daemon_height:,d} ({behind:,d} ' + f'blocks behind)' + ) else: - self.logger.info('caught up to daemon height {:,d}' - .format(daemon_height)) + self.logger.info(f'caught up to daemon height {daemon_height:,d}') async def _prefetch_blocks(self): '''Prefetch some blocks and put them on the queue. @@ -115,8 +115,8 @@ async def _prefetch_blocks(self): hex_hashes = await daemon.block_hex_hashes(first, count) if self.caught_up: - self.logger.info('new block height {:,d} hash {}' - .format(first + count-1, hex_hashes[-1])) + self.logger.info(f'new block height {first + count-1:,d} ' + f'hash {hex_hashes[-1]}') blocks = await daemon.raw_blocks(hex_hashes) assert count == len(blocks) @@ -124,8 +124,8 @@ async def _prefetch_blocks(self): # Special handling for genesis block if first == 0: blocks[0] = self.coin.genesis_block(blocks[0]) - self.logger.info('verified genesis block with hash {}' - .format(hex_hashes[0])) + self.logger.info(f'verified genesis block with hash ' + f'{hex_hashes[0]}') # Update our recent average block size estimate size = sum(len(block) for block in blocks) @@ -372,10 +372,9 @@ def check_cache_size(self): utxo_MB = (db_deletes_size + utxo_cache_size) // one_MB hist_MB = (hist_cache_size + tx_hash_size) // one_MB - self.logger.info('our height: {:,d} daemon: {:,d} ' - 'UTXOs {:,d}MB hist {:,d}MB' - .format(self.height, self.daemon.cached_height(), - utxo_MB, hist_MB)) + self.logger.info(f'our height: {self.height:,d} daemon: ' + f'{self.daemon.cached_height():,d} ' + f'UTXOs {utxo_MB:,d}MB hist {hist_MB:,d}MB') # Flush history if it takes up over 20% of cache memory. # Flush UTXOs once they take up 80% of cache memory. @@ -404,7 +403,7 @@ def advance_blocks(self, blocks): headers = [block.header for block in blocks] self.height = height - self.headers.extend(headers) + self.headers += headers self.tip = self.coin.header_hash(headers[-1]) def advance_txs(self, txs, is_unspendable): @@ -475,10 +474,10 @@ def backup_blocks(self, raw_blocks): block = coin.block(raw_block, self.height) header_hash = coin.header_hash(block.header) if header_hash != self.tip: - raise ChainError('backup block {} not tip {} at height {:,d}' - .format(hash_to_hex_str(header_hash), - hash_to_hex_str(self.tip), - self.height)) + raise ChainError( + f'backup block {hash_to_hex_str(header_hash)} not tip ' + f'{hash_to_hex_str(self.tip)} at height {self.height:,d}' + ) self.tip = coin.header_prevhash(block.header) is_unspendable = (is_unspendable_genesis if self.height >= genesis_activation else is_unspendable_legacy) @@ -486,15 +485,15 @@ def backup_blocks(self, raw_blocks): self.height -= 1 self.db.tx_counts.pop() - self.logger.info('backed up to height {:,d}'.format(self.height)) + self.logger.info(f'backed up to height {self.height:,d}') def backup_txs(self, txs, is_unspendable): # Prevout values, in order down the block (coinbase first if present) # undo_info is in reverse block order undo_info = self.db.read_undo_info(self.height) if undo_info is None: - raise ChainError('no undo information found for height {:,d}' - .format(self.height)) + raise ChainError(f'no undo information found for height ' + f'{self.height:,d}') n = len(undo_info) # Use local vars for speed in the loops @@ -607,7 +606,7 @@ def spend_utxo(self, tx_hash, tx_idx): tx_num_packed = hdb_key[-5:] if len(candidates) > 1: - tx_num, = unpack_le_uint64(tx_num_packed + bytes(3)) + tx_num, = unpack_le_uint64(tx_num_packed + b'\0\0\0') hash, _height = self.db.fs_tx_hash(tx_num) if hash != tx_hash: assert hash is not None # Should always be found @@ -623,8 +622,8 @@ def spend_utxo(self, tx_hash, tx_idx): self.db_deletes.append(udb_key) return hashX + tx_num_packed + utxo_value_packed - raise ChainError('UTXO {} / {:,d} not found in "h" table' - .format(hash_to_hex_str(tx_hash), tx_idx)) + raise ChainError(f'UTXO {hash_to_hex_str(tx_hash)} / {tx_idx:,d} not ' + f'found in "h" table') async def _process_prefetched_blocks(self): '''Loop forever processing blocks as they arrive.''' @@ -799,8 +798,9 @@ def advance_txs(self, txs, is_unspendable): def backup_txs(self, txs, is_unspendable): undo_info = self.db.read_undo_info(self.height) if undo_info is None: - raise ChainError('no undo information found for height {:,d}' - .format(self.height)) + raise ChainError( + f'no undo information found for height {self.height:,d}' + ) # Use local vars for speed in the loops put_utxo = self.utxo_cache.__setitem__ diff --git a/electrumx/server/db.py b/electrumx/server/db.py index 45de175..a34bcff 100644 --- a/electrumx/server/db.py +++ b/electrumx/server/db.py @@ -9,7 +9,7 @@ '''Interface to the blockchain database.''' -import array +from array import array import ast import os import time @@ -55,7 +55,7 @@ class DB(object): it was shutdown uncleanly. ''' - DB_VERSIONS = [6, 7, 8] + DB_VERSIONS = (6, 7, 8) class DBError(Exception): '''Raised on general DB errors generally indicating corruption.''' @@ -113,7 +113,7 @@ async def _read_tx_counts(self): size = (self.db_height + 1) * 8 tx_counts = self.tx_counts_file.read(0, size) assert len(tx_counts) == size - self.tx_counts = array.array('Q', tx_counts) + self.tx_counts = array('Q', tx_counts) if self.tx_counts: assert self.db_tx_count == self.tx_counts[-1] else: @@ -132,7 +132,7 @@ async def _open_dbs(self, for_sync, compacting): f.write(f'ElectrumX databases and metadata for ' f'{self.coin.NAME} {self.coin.NET}'.encode()) if not self.coin.STATIC_BLOCK_HEADERS: - self.headers_offsets_file.write(0, bytes(8)) + self.headers_offsets_file.write(0, b'\0\0\0\0\0\0\0\0') else: self.logger.info(f'opened UTXO DB (for sync: {for_sync})') self.read_utxo_state() @@ -447,8 +447,8 @@ async def tx_hashes_at_blockheight(self, block_height): async def fs_block_hashes(self, height, count): headers_concat, headers_count = await self.read_headers(height, count) if headers_count != count: - raise self.DBError('only got {:,d} headers starting at {:,d}, not ' - '{:,d}'.format(headers_count, height, count)) + raise self.DBError(f'only got {headers_count:,d} headers starting ' + f'at {height:,d}, not {count:,d}') offset = 0 headers = [] for n in range(count): @@ -568,17 +568,16 @@ def read_utxo_state(self): raise self.DBError('failed reading state from DB') self.db_version = state['db_version'] if self.db_version not in self.DB_VERSIONS: - raise self.DBError('your UTXO DB version is {} but this ' - 'software only handles versions {}' - .format(self.db_version, self.DB_VERSIONS)) + raise self.DBError(f'your UTXO DB version is {self.db_version} ' + f'but this software only handles versions ' + f'{self.DB_VERSIONS}') # backwards compat genesis_hash = state['genesis'] if isinstance(genesis_hash, bytes): genesis_hash = genesis_hash.decode() if genesis_hash != self.coin.GENESIS_HASH: - raise self.DBError('DB genesis hash {} does not match coin {}' - .format(genesis_hash, - self.coin.GENESIS_HASH)) + raise self.DBError(f'DB genesis hash {genesis_hash} does not ' + f'match coin {self.coin.GENESIS_HASH}') self.db_height = state['height'] self.db_tx_count = state['tx_count'] self.db_tip = state['tip'] @@ -596,17 +595,18 @@ def read_utxo_state(self): self.upgrade_db() # Log some stats - self.logger.info('UTXO DB version: {:d}'.format(self.db_version)) - self.logger.info('coin: {}'.format(self.coin.NAME)) - self.logger.info('network: {}'.format(self.coin.NET)) - self.logger.info('height: {:,d}'.format(self.db_height)) - self.logger.info('tip: {}'.format(hash_to_hex_str(self.db_tip))) - self.logger.info('tx count: {:,d}'.format(self.db_tx_count)) + self.logger.info(f'UTXO DB version: {self.db_version:d}') + self.logger.info(f'coin: {self.coin.NAME}') + self.logger.info(f'network: {self.coin.NET}') + self.logger.info(f'height: {self.db_height:,d}') + self.logger.info(f'tip: {hash_to_hex_str(self.db_tip)}') + self.logger.info(f'tx count: {self.db_tx_count:,d}') if self.utxo_db.for_sync: self.logger.info(f'flushing DB cache at {self.env.cache_MB:,d} MB') if self.first_sync: - self.logger.info('sync time so far: {}' - .format(util.formatted_time(self.wall_time))) + self.logger.info( + f'sync time so far: {util.formatted_time(self.wall_time)}' + ) def upgrade_db(self): self.logger.info(f'UTXO DB version: {self.db_version}') @@ -683,8 +683,8 @@ def upgrade_h_prefix(prefix): size = (self.db_height + 1) * 8 tx_counts = self.tx_counts_file.read(0, size) if len(tx_counts) == (self.db_height + 1) * 4: - tx_counts = array.array('I', tx_counts) - tx_counts = array.array('Q', tx_counts) + tx_counts = array('I', tx_counts) + tx_counts = array('Q', tx_counts) self.tx_counts_file.write(0, tx_counts.tobytes()) self.db_version = max(self.DB_VERSIONS) @@ -721,7 +721,7 @@ def read_utxos(): prefix = b'u' + hashX for db_key, db_value in self.utxo_db.iterator(prefix=prefix): tx_pos, = unpack_le_uint32(db_key[-9:-5]) - tx_num, = unpack_le_uint64(db_key[-5:] + bytes(3)) + tx_num, = unpack_le_uint64(db_key[-5:] + b'\0\0\0') value, = unpack_le_uint64(db_value) tx_hash, height = self.fs_tx_hash(tx_num) utxos_append(UTXO(tx_num, tx_pos, tx_hash, height, value)) @@ -755,7 +755,7 @@ def lookup_hashX(tx_hash, tx_idx): # Find which entry, if any, the TX_HASH matches. for db_key, hashX in self.utxo_db.iterator(prefix=prefix): tx_num_packed = db_key[-5:] - tx_num, = unpack_le_uint64(tx_num_packed + bytes(3)) + tx_num, = unpack_le_uint64(tx_num_packed + b'\0\0\0') hash, _height = self.fs_tx_hash(tx_num) if hash == tx_hash: return hashX, idx_packed + tx_num_packed diff --git a/electrumx/server/env.py b/electrumx/server/env.py index c45ded0..24d5dcd 100644 --- a/electrumx/server/env.py +++ b/electrumx/server/env.py @@ -105,9 +105,11 @@ def sane_max_sessions(self): # We give the DB 250 files; allow ElectrumX 100 for itself value = max(0, min(env_value, nofile_limit - 350)) if value < env_value: - self.logger.warning('lowered maximum sessions from {:,d} to {:,d} ' - 'because your open file limit is {:,d}' - .format(env_value, value, nofile_limit)) + self.logger.warning( + f'lowered maximum sessions from {env_value:,d} to ' + f'{value:,d} because your open file limit is ' + f'{nofile_limit:,d}' + ) except ImportError: value = 512 # that is what returned by stdio's _getmaxstdio() return value diff --git a/electrumx/server/history.py b/electrumx/server/history.py index 3988440..fced542 100644 --- a/electrumx/server/history.py +++ b/electrumx/server/history.py @@ -8,7 +8,7 @@ '''History by script hash (address).''' -import array +from array import array import ast import bisect import time @@ -24,7 +24,7 @@ class History(object): - DB_VERSIONS = [0, 1] + DB_VERSIONS = (0, 1) def __init__(self): self.logger = util.class_logger(__name__, self.__class__.__name__) @@ -127,7 +127,7 @@ def add_unflushed(self, hashXs_by_tx, first_tx_num): tx_numb = pack_le_uint64(tx_num)[:5] hashXs = set(hashXs) for hashX in hashXs: - unflushed[hashX].extend(tx_numb) + unflushed[hashX] += tx_numb count += len(hashXs) self.unflushed_count += count @@ -170,8 +170,10 @@ def backup(self, hashXs, tx_count): deletes = [] puts = {} for key, hist in self.db.iterator(prefix=hashX, reverse=True): - a = array.array('Q') - a.frombytes(b''.join(item + bytes(3) for item in chunks(hist, 5))) + a = array( + 'Q', + b''.join(item + b'\0\0\0' for item in chunks(hist, 5)) + ) # Remove all history entries >= tx_count idx = bisect_left(a, tx_count) nremoves += len(a) - idx @@ -199,7 +201,7 @@ def get_txnums(self, hashX, limit=1000): for tx_numb in chunks(hist, 5): if limit == 0: return - tx_num, = unpack_le_uint64(tx_numb + bytes(3)) + tx_num, = unpack_le_uint64(tx_numb + b'\0\0\0') yield tx_num limit -= 1 @@ -253,10 +255,10 @@ def _compact_hashX(self, hashX, hist_map, hist_list, full_hist = b''.join(hist_list) nrows = (len(full_hist) + max_row_size - 1) // max_row_size if nrows > 4: - self.logger.info('hashX {} is large: {:,d} entries across ' - '{:,d} rows' - .format(hash_to_hex_str(hashX), - len(full_hist) // 5, nrows)) + self.logger.info( + f'hashX {hash_to_hex_str(hashX)} is large: ' + f'{len(full_hist) // 5:,d} entries across {nrows:,d} rows' + ) # Find what history needs to be written, and what keys need to # be deleted. Start by assuming all keys are to be deleted, @@ -325,11 +327,12 @@ def _compact_history(self, limit): max_rows = self.comp_flush_count + 1 self._flush_compaction(cursor, write_items, keys_to_delete) - self.logger.info('history compaction: wrote {:,d} rows ({:.1f} MB), ' - 'removed {:,d} rows, largest: {:,d}, {:.1f}% complete' - .format(len(write_items), write_size / 1000000, - len(keys_to_delete), max_rows, - 100 * cursor / 65536)) + self.logger.info( + f'history compaction: wrote {len(write_items):,d} rows ' + f'({write_size / 1000000:.1f} MB), removed ' + f'{len(keys_to_delete):,d} rows, largest: {max_rows:,d}, ' + f'{100 * cursor / 65536:.1f}% complete' + ) return write_size def _cancel_compaction(self): diff --git a/electrumx/server/mempool.py b/electrumx/server/mempool.py index 8334a9b..f88a925 100644 --- a/electrumx/server/mempool.py +++ b/electrumx/server/mempool.py @@ -218,7 +218,7 @@ async def _refresh_hashes(self, synchronized_event): hex_hashes = await self.api.mempool_hashes() if height != await self.api.height(): continue - hashes = set(hex_str_to_hash(hh) for hh in hex_hashes) + hashes = {hex_str_to_hash(hh) for hh in hex_hashes} try: async with self.lock: await self._process_mempool(hashes, touched, height) @@ -242,15 +242,15 @@ async def _process_mempool(self, all_hashes, touched, mempool_height): raise DBSyncError # First handle txs that have disappeared - for tx_hash in set(txs).difference(all_hashes): + for tx_hash in (set(txs) - all_hashes): tx = txs.pop(tx_hash) - tx_hashXs = set(hashX for hashX, value in tx.in_pairs) + tx_hashXs = {hashX for hashX, value in tx.in_pairs} tx_hashXs.update(hashX for hashX, value in tx.out_pairs) for hashX in tx_hashXs: hashXs[hashX].remove(tx_hash) if not hashXs[hashX]: del hashXs[hashX] - touched.update(tx_hashXs) + touched |= tx_hashXs # Process new transactions new_hashes = list(all_hashes.difference(txs)) diff --git a/electrumx/server/peers.py b/electrumx/server/peers.py index eeda665..00bebe4 100644 --- a/electrumx/server/peers.py +++ b/electrumx/server/peers.py @@ -25,6 +25,7 @@ from electrumx.lib.util import class_logger PEER_GOOD, PEER_STALE, PEER_NEVER, PEER_BAD = range(4) +STATUS_DESCS = ('good', 'stale', 'never', 'bad') STALE_SECS = 3 * 3600 WAKEUP_SECS = 300 PEER_ADD_PAUSE = 600 @@ -143,7 +144,7 @@ async def read_blacklist(): async with aiohttp.ClientSession() as session: async with session.get(url) as response: text = await response.text() - return set(entry.lower() for entry in json.loads(text)) + return {entry.lower() for entry in json.loads(text)} while True: try: @@ -536,8 +537,11 @@ def on_peers_subscribe(self, is_tor): # Always report ourselves if valid (even if not public) cutoff = time.time() - STALE_SECS - peers = set(myself for myself in self.myselves - if myself.last_good > cutoff) + peers = { + myself + for myself in self.myselves + if myself.last_good > cutoff + } # Bucket the clearnet peers and select up to two from each onion_peers = [] @@ -567,11 +571,10 @@ def proxy_address(self): def rpc_data(self): '''Peer data for the peers RPC method.''' self._set_peer_statuses() - descs = ['good', 'stale', 'never', 'bad'] def peer_data(peer): data = peer.serialize() - data['status'] = descs[peer.status] + data['status'] = STATUS_DESCS[peer.status] return data def peer_key(peer): diff --git a/electrumx/server/session.py b/electrumx/server/session.py index 097d41e..3790f7f 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -411,7 +411,7 @@ async def rpc_add_peer(self, real_name): real_name: "bch.electrumx.cash t50001 s50002" for example ''' await self.peer_mgr.add_localRPC_peer(real_name) - return "peer '{}' added".format(real_name) + return f"peer '{real_name}' added" async def rpc_disconnect(self, session_ids): '''Disconnect sesssions. @@ -603,8 +603,9 @@ async def serve(self, notifications, event): self.logger.info(f'max response size {self.env.max_send:,d} bytes') if self.env.drop_client is not None: - self.logger.info('drop clients matching: {}' - .format(self.env.drop_client.pattern)) + self.logger.info( + f'drop clients matching: {self.env.drop_client.pattern}' + ) for service in self.env.report_services: self.logger.info(f'advertising service {service}') # Start notifications; initialize hsub_results @@ -1197,7 +1198,7 @@ async def replaced_banner(self, banner): major, minor = divmod(ni_version, 1000000) minor, revision = divmod(minor, 10000) revision //= 100 - daemon_version = '{:d}.{:d}.{:d}'.format(major, minor, revision) + daemon_version = f'{major:d}.{minor:d}.{revision:d}' for pair in [ ('$SERVER_VERSION', electrumx.version_short), ('$SERVER_SUBVERSION', electrumx.version), @@ -1464,9 +1465,9 @@ async def notify(self, touched, height_changed): await super().notify(touched, height_changed) for mn in self.mns.copy(): status = await self.daemon_request('masternode_list', - ['status', mn]) + ('status', mn)) await self.send_notification('masternode.subscribe', - [mn, status.get(mn)]) + (mn, status.get(mn))) # Masternode command handlers async def masternode_announce_broadcast(self, signmnb): @@ -1476,7 +1477,7 @@ async def masternode_announce_broadcast(self, signmnb): signmnb: signed masternode broadcast message.''' try: return await self.daemon_request('masternode_broadcast', - ['relay', signmnb]) + ('relay', signmnb)) except DaemonError as e: error, = e.args message = error['message'] @@ -1490,7 +1491,7 @@ async def masternode_subscribe(self, collateral): collateral: masternode collateral. ''' result = await self.daemon_request('masternode_list', - ['status', collateral]) + ('status', collateral)) if result is not None: self.mns.add(collateral) return result.get(collateral) @@ -1555,26 +1556,29 @@ def get_payment_position(payment_queue, address): cache = self.session_mgr.mn_cache if not cache or self.session_mgr.mn_cache_height != self.db.db_height: full_mn_list = await self.daemon_request('masternode_list', - ['full']) + ('full',)) mn_payment_queue = get_masternode_payment_queue(full_mn_list) mn_payment_count = len(mn_payment_queue) mn_list = [] for key, value in full_mn_list.items(): mn_data = value.split() - mn_info = {} - mn_info['vin'] = key - mn_info['status'] = mn_data[0] - mn_info['protocol'] = mn_data[1] - mn_info['payee'] = mn_data[2] - mn_info['lastseen'] = mn_data[3] - mn_info['activeseconds'] = mn_data[4] - mn_info['lastpaidtime'] = mn_data[5] - mn_info['lastpaidblock'] = mn_data[6] - mn_info['ip'] = mn_data[7] + mn_info = { + 'vin': key, + 'status': mn_data[0], + 'protocol': mn_data[1], + 'payee': mn_data[2], + 'lastseen': mn_data[3], + 'activeseconds': mn_data[4], + 'lastpaidtime': mn_data[5], + 'lastpaidblock': mn_data[6], + 'ip': mn_data[7] + } mn_info['paymentposition'] = get_payment_position( - mn_payment_queue, mn_info['payee']) + mn_payment_queue, mn_info['payee'] + ) mn_info['inselection'] = ( - mn_info['paymentposition'] < mn_payment_count // 10) + mn_info['paymentposition'] < mn_payment_count // 10 + ) hashX = self.coin.address_to_hashX(mn_info['payee']) balance = await self.get_balance(hashX) mn_info['balance'] = (sum(balance.values()) @@ -1639,7 +1643,7 @@ def set_request_handlers(self, ptuple): async def smartrewards_current(self): '''Returns the current smartrewards info.''' - result = await self.daemon_request('smartrewards', ['current']) + result = await self.daemon_request('smartrewards', ('current',)) if result is not None: return result return None @@ -1650,7 +1654,7 @@ async def smartrewards_check(self, addr): addr: a single smartcash address ''' - result = await self.daemon_request('smartrewards', ['check', addr]) + result = await self.daemon_request('smartrewards', ('check', addr)) if result is not None: return result return None @@ -1694,7 +1698,7 @@ def truncate_auxpow(self, headers_full_hex, start_height): headers = bytearray() while cursor < len(headers_full): - headers.extend(headers_full[cursor:cursor+self.coin.TRUNCATED_HEADER_SIZE]) + headers += headers_full[cursor:cursor+self.coin.TRUNCATED_HEADER_SIZE] cursor += self.db.dynamic_header_len(height) height += 1 diff --git a/electrumx/server/storage.py b/electrumx/server/storage.py index 4f0b251..1272eac 100644 --- a/electrumx/server/storage.py +++ b/electrumx/server/storage.py @@ -19,7 +19,7 @@ def db_class(name): if db_class.__name__.lower() == name.lower(): db_class.import_module() return db_class - raise RuntimeError('unrecognised DB engine "{}"'.format(name)) + raise RuntimeError(f'unrecognised DB engine "{name}"') class Storage(object): diff --git a/tests/server/test_compaction.py b/tests/server/test_compaction.py index 08ecaed..ad6d21b 100644 --- a/tests/server/test_compaction.py +++ b/tests/server/test_compaction.py @@ -127,8 +127,9 @@ async def run_test(db_dir): compact_history(history) check_written(history, histories) + def test_compaction(tmpdir): db_dir = str(tmpdir) - print('Temp dir: {}'.format(db_dir)) + print(f'Temp dir: {db_dir}') loop = asyncio.get_event_loop() loop.run_until_complete(run_test(db_dir)) From 6607ea84470a2f3dc0b6c05838693f3191898357 Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Sun, 7 Jun 2020 23:16:50 -0500 Subject: [PATCH 015/386] Avoid unnecessary stack depth in Bitcoin tx serialization. --- electrumx/lib/tx.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/electrumx/lib/tx.py b/electrumx/lib/tx.py index e4b956b..5ae881b 100644 --- a/electrumx/lib/tx.py +++ b/electrumx/lib/tx.py @@ -233,11 +233,18 @@ def _read_tx_parts(self): start = self.cursor marker = self.binary[self.cursor + 4] if marker: - tx = super().read_tx() + # We could call super().read_tx here but the call stack is + # expensive when executed millions of times. + tx = Tx( + self._read_le_int32(), # version + self._read_inputs(), # inputs + self._read_outputs(), # outputs + self._read_le_uint32() # locktime + ) tx_hash = self.TX_HASH_FN(self.binary[start:self.cursor]) return tx, tx_hash, self.binary_length - # Ugh, this is nasty. + # Ugh, this is tasty. version = self._read_le_int32() orig_ser = self.binary[start:self.cursor] From 31ef8873f8c24995e50f024670610869f17385a0 Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Thu, 11 Jun 2020 22:41:03 -0500 Subject: [PATCH 016/386] Try slotted dataclasses instead of named tuples. --- electrumx/lib/coins.py | 15 ++-- electrumx/lib/tx.py | 141 ++++++++++++++++++++++++++++------- electrumx/server/db.py | 11 ++- tests/server/test_mempool.py | 6 +- 4 files changed, 137 insertions(+), 36 deletions(-) diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 58ad4fe..1a791f5 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -29,13 +29,13 @@ Anything coin-specific should go in this file and be subclassed where necessary for appropriate handling. ''' - -from collections import namedtuple import re import struct +from dataclasses import dataclass from decimal import Decimal -from hashlib import sha256 from functools import partial +from hashlib import sha256 +from typing import Sequence import electrumx.lib.util as util from electrumx.lib.hash import Base58, hash160, double_sha256, hash_to_hex_str @@ -51,7 +51,12 @@ SmartCashElectrumX, AuxPoWElectrumX) -Block = namedtuple("Block", "raw header transactions") +@dataclass +class Block: + __slots__ = "raw", "header", "transactions" + raw: bytes + header: bytes + transactions: Sequence class CoinError(Exception): @@ -1352,7 +1357,7 @@ class FairCoin(Coin): @classmethod def block(cls, raw_block, height): - '''Return a Block namedtuple given a raw block and its height.''' + '''Return a Block given a raw block and its height.''' if height > 0: return super().block(raw_block, height) else: diff --git a/electrumx/lib/tx.py b/electrumx/lib/tx.py index eb046b8..d91cff9 100644 --- a/electrumx/lib/tx.py +++ b/electrumx/lib/tx.py @@ -27,8 +27,9 @@ '''Transaction-related classes and functions.''' -from collections import namedtuple +from dataclasses import dataclass from hashlib import blake2s +from typing import Sequence from electrumx.lib.hash import sha256, double_sha256, hash_to_hex_str from electrumx.lib.script import OpCodes @@ -43,8 +44,14 @@ MINUS_1 = 4294967295 -class Tx(namedtuple("Tx", "version inputs outputs locktime")): +@dataclass +class Tx: '''Class representing a transaction.''' + __slots__ = 'version', 'inputs', 'outputs', 'locktime' + version: int + inputs: Sequence + outputs: Sequence + locktime: int def serialize(self): return b''.join(( @@ -57,8 +64,15 @@ def serialize(self): )) -class TxInput(namedtuple("TxInput", "prev_hash prev_idx script sequence")): +@dataclass +class TxInput: '''Class representing a transaction input.''' + __slots__ = 'prev_hash', 'prev_idx', 'script', 'sequence' + prev_hash: bytes + prev_idx: int + script: bytes + sequence: int + def __str__(self): script = self.script.hex() prev_hash = hash_to_hex_str(self.prev_hash) @@ -78,7 +92,11 @@ def serialize(self): )) -class TxOutput(namedtuple("TxOutput", "value pk_script")): +@dataclass +class TxOutput: + __slots__ = 'value', 'pk_script' + value: int + pk_script: bytes def serialize(self): return b''.join(( @@ -211,9 +229,18 @@ def _read_le_uint64(self): return result -class TxSegWit(namedtuple("Tx", "version marker flag inputs outputs " - "witness locktime")): +@dataclass +class TxSegWit: '''Class representing a SegWit transaction.''' + __slots__ = ('version', 'marker', 'flag', 'inputs', 'outputs', 'witness', + 'locktime') + version: int + marker: int + flag: int + inputs: Sequence + outputs: Sequence + witness: Sequence + locktime: int class DeserializerSegWit(Deserializer): @@ -336,10 +363,6 @@ class DeserializerEquihashSegWit(DeserializerSegWit, DeserializerEquihash): pass -class TxJoinSplit(namedtuple("Tx", "version inputs outputs locktime")): - '''Class representing a JoinSplit transaction.''' - - class DeserializerZcash(DeserializerEquihash): def read_tx(self): header = self._read_le_uint32() @@ -353,7 +376,7 @@ def read_tx(self): is_overwinter_v3 = version == 3 is_sapling_v4 = version == 4 - base_tx = TxJoinSplit( + base_tx = Tx( version, self._read_inputs(), # inputs self._read_outputs(), # outputs @@ -387,8 +410,15 @@ def read_tx(self): return base_tx -class TxTime(namedtuple("Tx", "version time inputs outputs locktime")): +@dataclass +class TxTime: '''Class representing transaction that has a time field.''' + __slots__ = 'version', 'time', 'inputs', 'outputs', 'locktime' + version: int + time: int + inputs: Sequence + outputs: Sequence + locktime: int class DeserializerTxTime(Deserializer): @@ -402,9 +432,19 @@ def read_tx(self): ) -class TxTimeSegWit(namedtuple( - "Tx", "version time marker flag inputs outputs witness locktime")): +@dataclass +class TxTimeSegWit: '''Class representing a SegWit transaction with time.''' + __slots__ = ('version', 'time', 'marker', 'flag', 'inputs', 'outputs', + 'witness', 'locktime') + version: int + time: int + marker: int + flag: int + inputs: Sequence + outputs: Sequence + witness: Sequence + locktime: int class DeserializerTxTimeSegWit(DeserializerTxTime): @@ -537,9 +577,17 @@ def read_tx_and_vsize(self): return tx, vsize -class TxTrezarcoin( - namedtuple("Tx", "version time inputs outputs locktime txcomment")): +@dataclass +class TxTrezarcoin: '''Class representing transaction that has a time and txcomment field.''' + __slots__ = ('version', 'time', 'inputs', 'outputs', 'locktime', + 'txcomment') + version: int + time: int + inputs: Sequence + outputs: Sequence + locktime: int + txcomment: bytes class DeserializerTrezarcoin(Deserializer): @@ -678,9 +726,14 @@ def is_generation(self): return super(TxInputTokenPay, self).is_generation() -class TxInputTokenPayStealth( - namedtuple("TxInput", "keyimage ringsize script sequence")): +@dataclass +class TxInputTokenPayStealth: '''Class representing a TokenPay stealth transaction input.''' + __slots__ = 'keyimage', 'ringsize', 'script', 'sequence' + keyimage: bytes + ringsize: bytes + script: bytes + sequence: int def __str__(self): script = self.script.hex() @@ -725,8 +778,14 @@ def _read_input(self): # Decred -class TxInputDcr(namedtuple("TxInput", "prev_hash prev_idx tree sequence")): +@dataclass +class TxInputDcr: '''Class representing a Decred transaction input.''' + __slots__ = 'prev_hash', 'prev_idx', 'tree', 'sequence' + prev_hash: bytes + prev_idx: int + tree: int + sequence: int def __str__(self): prev_hash = hash_to_hex_str(self.prev_hash) @@ -738,13 +797,25 @@ def is_generation(self): return self.prev_idx == MINUS_1 and self.prev_hash == ZERO -class TxOutputDcr(namedtuple("TxOutput", "value version pk_script")): +@dataclass +class TxOutputDcr: '''Class representing a Decred transaction output.''' + __slots__ = 'value', 'version', 'pk_script' + value: int + version: int + pk_script: bytes -class TxDcr(namedtuple("Tx", "version inputs outputs locktime expiry " - "witness")): +@dataclass +class TxDcr: '''Class representing a Decred transaction.''' + __slots__ = 'version', 'inputs', 'outputs', 'locktime', 'expiry', 'witness' + version: int + inputs: Sequence + outputs: Sequence + locktime: int + expiry: int + witness: Sequence class DeserializerDecred(Deserializer): @@ -850,10 +921,15 @@ def read_tx_and_hash(self): return self.read_tx(), sha256(self.binary[start:self.cursor]) -class TxBitcoinDiamond(namedtuple("Tx", - "version preblockhash inputs outputs " - "locktime")): +@dataclass +class TxBitcoinDiamond: '''Class representing a transaction.''' + __slots__ = 'version', 'preblockhash', 'inputs', 'outputs', 'locktime' + version: int + preblockhash: str + inputs: Sequence + outputs: Sequence + locktime: int class DeserializerBitcoinDiamond(Deserializer): @@ -883,10 +959,19 @@ def _get_version(self): return result -class TxBitcoinDiamondSegWit(namedtuple("Tx", - "version preblockhash marker flag " - "inputs outputs witness locktime")): +@dataclass +class TxBitcoinDiamondSegWit: '''Class representing a SegWit transaction.''' + __slots__ = ('version', 'preblockhash', 'marker', 'flag', 'inputs', + 'outputs', 'witness', 'locktime') + version: int + preblockhash: str + marker: int + flag: int + inputs: Sequence + outputs: Sequence + witness: Sequence + locktime: int class DeserializerBitcoinDiamondSegWit(DeserializerBitcoinDiamond, diff --git a/electrumx/server/db.py b/electrumx/server/db.py index 45de175..ea8733c 100644 --- a/electrumx/server/db.py +++ b/electrumx/server/db.py @@ -14,7 +14,7 @@ import os import time from bisect import bisect_right -from collections import namedtuple +from dataclasses import dataclass from glob import glob from struct import Struct @@ -32,7 +32,14 @@ from electrumx.server.history import History -UTXO = namedtuple("UTXO", "tx_num tx_pos tx_hash height value") +@dataclass +class UTXO: + __slots__ = 'tx_num', 'tx_pos', 'tx_hash', 'height', 'height', 'value' + tx_num: int + tx_pos: int + tx_hash: bytes + height: int + value: int @attr.s(slots=True) diff --git a/tests/server/test_mempool.py b/tests/server/test_mempool.py index 91189e9..0c42855 100644 --- a/tests/server/test_mempool.py +++ b/tests/server/test_mempool.py @@ -1,3 +1,4 @@ +import dataclasses import datetime import logging import os @@ -400,7 +401,10 @@ async def test_unordered_UTXOs(): for hashX in api.hashXs: mempool_result = await mempool.unordered_UTXOs(hashX) our_result = utxos.get(hashX, []) - assert set(our_result) == set(mempool_result) + assert set(our_result) == { + dataclasses.astuple(mr) + for mr in mempool_result + } @pytest.mark.asyncio From b28f73d176135fa04ed54875dd3ff7787ba30e06 Mon Sep 17 00:00:00 2001 From: Michal Taborsky Date: Sun, 14 Jun 2020 09:51:49 +0200 Subject: [PATCH 017/386] Add tests for DROP_CLIENT_UNKNOWN --- tests/server/test_env.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/server/test_env.py b/tests/server/test_env.py index f69a1cd..0a6f98f 100644 --- a/tests/server/test_env.py +++ b/tests/server/test_env.py @@ -406,3 +406,16 @@ def test_ban_versions(): def test_coin_class_provided(): e = Env(lib_coins.BitcoinSV) assert e.coin == lib_coins.BitcoinSV + +def test_drop_unknown_clients(): + e = Env() + assert e.drop_client_unknown is False + os.environ['DROP_CLIENT_UNKNOWN'] = "" + e = Env() + assert e.drop_client_unknown is False + os.environ['DROP_CLIENT_UNKNOWN'] = "1" + e = Env() + assert e.drop_client_unknown is True + os.environ['DROP_CLIENT_UNKNOWN'] = "whatever" + e = Env() + assert e.drop_client_unknown is True From 9ae83cab59a5ff7dedaed1ac954c81bfdd5286fe Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Mon, 15 Jun 2020 01:00:38 -0500 Subject: [PATCH 018/386] Remove unused or redundant code. --- contrib/query.py | 4 +- electrumx/lib/coins.py | 58 ++++++++++------------------- electrumx/lib/env_base.py | 2 +- electrumx/lib/hash.py | 20 +--------- electrumx/lib/merkle.py | 4 +- electrumx/lib/peer.py | 2 +- electrumx/lib/script.py | 40 +------------------- electrumx/lib/tx.py | 2 +- electrumx/lib/util.py | 6 +-- electrumx/server/block_processor.py | 4 +- electrumx/server/controller.py | 2 +- electrumx/server/daemon.py | 2 +- electrumx/server/db.py | 5 +-- electrumx/server/history.py | 12 +++--- electrumx/server/mempool.py | 6 +-- electrumx/server/peers.py | 2 +- electrumx/server/session.py | 6 +-- electrumx/server/storage.py | 6 +-- electrumx_rpc | 3 +- tests/lib/test_addresses.py | 15 -------- tests/lib/test_coins.py | 2 +- tests/lib/test_hash.py | 11 ------ tests/lib/test_merkle.py | 2 +- tests/server/test_compaction.py | 22 +++++------ tests/server/test_daemon.py | 18 ++------- tests/server/test_mempool.py | 3 +- tests/server/test_notifications.py | 2 - tests/test_blocks.py | 1 - 28 files changed, 69 insertions(+), 193 deletions(-) diff --git a/contrib/query.py b/contrib/query.py index 56dbda8..c27ddbd 100755 --- a/contrib/query.py +++ b/contrib/query.py @@ -14,7 +14,6 @@ import argparse import asyncio -import sys from electrumx import Env from electrumx.server.db import DB @@ -106,8 +105,7 @@ def main(): parser.add_argument('scripts', nargs='*', default=[], type=str, help='hex scripts to query') args = parser.parse_args() - loop = asyncio.get_event_loop() - loop.run_until_complete(query(args)) + asyncio.run(query(args)) if __name__ == '__main__': main() diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index aba2e2c..3da3025 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -38,7 +38,7 @@ from typing import Sequence import electrumx.lib.util as util -from electrumx.lib.hash import Base58, hash160, double_sha256, hash_to_hex_str +from electrumx.lib.hash import Base58, double_sha256, hash_to_hex_str from electrumx.lib.hash import HASHX_LEN, hex_str_to_hash from electrumx.lib.script import (_match_ops, Script, ScriptError, ScriptPubKey, OpCodes) @@ -63,7 +63,7 @@ class CoinError(Exception): '''Exception raised for coin-related errors.''' -class Coin(object): +class Coin: '''Base class of coin hierarchy.''' REORG_LIMIT = 200 @@ -173,23 +173,6 @@ def address_to_hashX(cls, address): '''Return a hashX given a coin address.''' return cls.hashX_from_script(cls.pay_to_address_script(address)) - @classmethod - def P2PKH_address_from_hash160(cls, hash160): - '''Return a P2PKH address given a public key.''' - assert len(hash160) == 20 - return cls.ENCODE_CHECK(cls.P2PKH_VERBYTE + hash160) - - @classmethod - def P2PKH_address_from_pubkey(cls, pubkey): - '''Return a coin address given a public key.''' - return cls.P2PKH_address_from_hash160(hash160(pubkey)) - - @classmethod - def P2SH_address_from_hash160(cls, hash160): - '''Return a coin address given a hash160.''' - assert len(hash160) == 20 - return cls.ENCODE_CHECK(cls.P2SH_VERBYTES[0] + hash160) - @classmethod def hash160_to_P2PKH_script(cls, hash160): return ScriptPubKey.P2PKH_script(hash160) @@ -278,7 +261,7 @@ def warn_old_client_on_tx_broadcast(cls, _client_ver): return False -class AuxPowMixin(object): +class AuxPowMixin: STATIC_BLOCK_HEADERS = False DESERIALIZER = lib_tx.DeserializerAuxPow SESSIONCLS = AuxPoWElectrumX @@ -300,7 +283,7 @@ def block_header(cls, block, height): return deserializer.read_header(cls.BASIC_HEADER_SIZE) -class EquihashMixin(object): +class EquihashMixin: STATIC_BLOCK_HEADERS = False BASIC_HEADER_SIZE = 140 # Excluding Equihash solution DESERIALIZER = lib_tx.DeserializerEquihash @@ -315,7 +298,7 @@ def block_header(cls, block, height): return deserializer.read_header(cls.BASIC_HEADER_SIZE) -class ScryptMixin(object): +class ScryptMixin: DESERIALIZER = lib_tx.DeserializerTxTime HEADER_HASH = None @@ -335,7 +318,7 @@ def header_hash(cls, header): return cls.HEADER_HASH(header) -class KomodoMixin(object): +class KomodoMixin: P2PKH_VERBYTE = bytes.fromhex("3C") P2SH_VERBYTES = (bytes.fromhex("55"),) WIF_BYTE = bytes.fromhex("BC") @@ -344,7 +327,7 @@ class KomodoMixin(object): DESERIALIZER = lib_tx.DeserializerZcash -class BitcoinMixin(object): +class BitcoinMixin: SHORTNAME = "BTC" NET = "mainnet" XPUB_VERBYTES = bytes.fromhex("0488b21e") @@ -352,7 +335,7 @@ class BitcoinMixin(object): RPC_PORT = 8332 -class NameMixin(object): +class NameMixin: DATA_PUSH_MULTIPLE = -2 @classmethod @@ -524,7 +507,7 @@ def name_hashX_from_script(cls, script): return super().hashX_from_script(name_index_script) -class PrimeChainPowMixin(object): +class PrimeChainPowMixin: STATIC_BLOCK_HEADERS = False DESERIALIZER = lib_tx.DeserializerPrimecoin @@ -772,7 +755,7 @@ def hashX_from_script(cls, script): return super().hashX_from_script(address_script) -class BitcoinTestnetMixin(object): +class BitcoinTestnetMixin: SHORTNAME = "XTN" NET = "testnet" XPUB_VERBYTES = bytes.fromhex("043587cf") @@ -1052,7 +1035,7 @@ class GravityCoin(Coin): P2PKH_VERBYTE = bytes.fromhex("28") P2SH_VERBYTES = (bytes.fromhex("0a"),) WIF_BYTE = bytes.fromhex("d2") - GENESIS_HASH = ('322bad477efb4b33fa4b1f0b2861eaf543c61068da9898a95062fdb02ada486f') + GENESIS_HASH = '322bad477efb4b33fa4b1f0b2861eaf543c61068da9898a95062fdb02ada486f' TX_COUNT = 446050 TX_COUNT_HEIGHT = 547346 TX_PER_BLOCK = 2 @@ -1569,8 +1552,8 @@ def header_hash(cls, header): if cls.header_prevhash(header) == bytes(32): return double_sha256(header) else: - if (header[0] == 4 and header[2] >= 1): - if (len(header) < 144 or header[143] < 3): + if header[0] == 4 and header[2] >= 1: + if len(header) < 144 or header[143] < 3: return verushash.verushash_v2b(header) else: return verushash.verushash_v2b1(header) @@ -2737,7 +2720,7 @@ class Pivx(Coin): NET = "mainnet" XPUB_VERBYTES = bytes.fromhex("022D2533") XPRV_VERBYTES = bytes.fromhex("0221312B") - GENESIS_HASH = ('0000041e482b9b9691d98eefb48473405c0b8ec31b76df3797c74a78680ef818') + GENESIS_HASH = '0000041e482b9b9691d98eefb48473405c0b8ec31b76df3797c74a78680ef818' P2PKH_VERBYTE = bytes.fromhex("1e") P2SH_VERBYTE = bytes.fromhex("0d") WIF_BYTE = bytes.fromhex("d4") @@ -2753,7 +2736,7 @@ class Pivx(Coin): @classmethod def static_header_len(cls, height): '''Given a header height return its length.''' - if (height >= cls.ZEROCOIN_START_HEIGHT): + if height >= cls.ZEROCOIN_START_HEIGHT: return cls.ZEROCOIN_HEADER else: return cls.BASIC_HEADER_SIZE @@ -2773,7 +2756,7 @@ class PivxTestnet(Pivx): NET = "testnet" XPUB_VERBYTES = bytes.fromhex("3a8061a0") XPRV_VERBYTES = bytes.fromhex("3a805837") - GENESIS_HASH = ('0000041e482b9b9691d98eefb48473405c0b8ec31b76df3797c74a78680ef818') + GENESIS_HASH = '0000041e482b9b9691d98eefb48473405c0b8ec31b76df3797c74a78680ef818' P2PKH_VERBYTE = bytes.fromhex("8B") P2SH_VERBYTE = bytes.fromhex("13") WIF_BYTE = bytes.fromhex("EF") @@ -2801,7 +2784,6 @@ class Bitg(Coin): RPC_PORT = 9332 REORG_LIMIT = 1000 SESSIONCLS = DashElectrumX - DAEMON = daemon.DashDaemon @classmethod def header_hash(cls, header): @@ -3252,7 +3234,7 @@ class ECCoin(Coin): XPRV_VERBYTES = bytes.fromhex("0488ade4") P2PKH_VERBYTE = bytes.fromhex("21") P2SH_VERBYTES = (bytes.fromhex("08"),) - GENESIS_HASH = ('a60ac43c88dbc44b826cf315352a8a7b373d2af8b6e1c4c4a0638859c5e9ecd1') + GENESIS_HASH = 'a60ac43c88dbc44b826cf315352a8a7b373d2af8b6e1c4c4a0638859c5e9ecd1' TX_COUNT = 4661197 TX_COUNT_HEIGHT = 2114846 TX_PER_BLOCK = 10 @@ -3395,7 +3377,7 @@ class GravityZeroCoin(ScryptMixin, Coin): NET = "mainnet" P2PKH_VERBYTE = bytes.fromhex("26") WIF_BYTE = bytes.fromhex("26") - GENESIS_HASH = ('0000028bfbf9ccaed8f28b3ca6b3ffe6b65e29490ab0e4430679bf41cc7c164f') + GENESIS_HASH = '0000028bfbf9ccaed8f28b3ca6b3ffe6b65e29490ab0e4430679bf41cc7c164f' DAEMON = daemon.FakeEstimateLegacyRPCDaemon TX_COUNT = 100 TX_COUNT_HEIGHT = 747635 @@ -3414,7 +3396,7 @@ class Simplicity(Coin): P2PKH_VERBYTE = bytes.fromhex("12") P2SH_VERBYTE = bytes.fromhex("3b") WIF_BYTE = bytes.fromhex("5d") - GENESIS_HASH = ('f4bbfc518aa3622dbeb8d2818a606b82c2b8b1ac2f28553ebdb6fc04d7abaccf') + GENESIS_HASH = 'f4bbfc518aa3622dbeb8d2818a606b82c2b8b1ac2f28553ebdb6fc04d7abaccf' RPC_PORT = 11958 TX_COUNT = 1726548 TX_COUNT_HEIGHT = 1040000 @@ -3443,7 +3425,7 @@ class Myce(Coin): P2PKH_VERBYTE = bytes.fromhex("32") P2SH_VERBYTE = bytes.fromhex("55") WIF_BYTE = bytes.fromhex("99") - GENESIS_HASH = ('0000c74cc66c72cb1a327c5c1d4893ae5276aa50be49fb23cec21df1a2f20d87') + GENESIS_HASH = '0000c74cc66c72cb1a327c5c1d4893ae5276aa50be49fb23cec21df1a2f20d87' RPC_PORT = 23512 TX_COUNT = 1568977 TX_COUNT_HEIGHT = 774450 diff --git a/electrumx/lib/env_base.py b/electrumx/lib/env_base.py index bcab47a..6f5d64c 100644 --- a/electrumx/lib/env_base.py +++ b/electrumx/lib/env_base.py @@ -13,7 +13,7 @@ from electrumx.lib.util import class_logger -class EnvBase(object): +class EnvBase: '''Wraps environment configuration.''' class Error(Exception): diff --git a/electrumx/lib/hash.py b/electrumx/lib/hash.py index bddd1b2..fe11d91 100644 --- a/electrumx/lib/hash.py +++ b/electrumx/lib/hash.py @@ -32,7 +32,6 @@ from electrumx.lib.util import bytes_to_int, int_to_bytes, hex_to_bytes _sha256 = hashlib.sha256 -_sha512 = hashlib.sha512 _new_hash = hashlib.new _hmac_digest = hmac.digest HASHX_LEN = 11 @@ -43,28 +42,11 @@ def sha256(x): return _sha256(x).digest() -def ripemd160(x): - '''Simple wrapper of hashlib ripemd160.''' - return _new_hash('ripemd160', x).digest() - - def double_sha256(x): '''SHA-256 of SHA-256, as used extensively in bitcoin.''' return sha256(sha256(x)) -def hmac_sha512(key, msg): - '''Use SHA-512 to provide an HMAC.''' - return _hmac_digest(key, msg, _sha512) - - -def hash160(x): - '''RIPEMD-160 of SHA-256. - - Used to make bitcoin addresses from pubkeys.''' - return ripemd160(sha256(x)) - - def hash_to_hex_str(x): '''Convert a big-endian binary hash to displayed hex string. @@ -82,7 +64,7 @@ class Base58Error(Exception): '''Exception used for Base58 errors.''' -class Base58(object): +class Base58: '''Class providing base 58 functionality.''' chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' diff --git a/electrumx/lib/merkle.py b/electrumx/lib/merkle.py index 20db739..5f86623 100644 --- a/electrumx/lib/merkle.py +++ b/electrumx/lib/merkle.py @@ -33,7 +33,7 @@ from electrumx.lib.hash import double_sha256 -class Merkle(object): +class Merkle: '''Perform merkle tree calculations on binary hashes using a given hash function. @@ -157,7 +157,7 @@ def branch_and_root_from_level(self, level, leaf_hashes, index, return leaf_branch + level_branch, root -class MerkleCache(object): +class MerkleCache: '''A cache to calculate merkle branches efficiently.''' def __init__(self, merkle, source_func): diff --git a/electrumx/lib/peer.py b/electrumx/lib/peer.py index 41c6557..bc1391d 100644 --- a/electrumx/lib/peer.py +++ b/electrumx/lib/peer.py @@ -32,7 +32,7 @@ from electrumx.lib.util import cachedproperty, protocol_tuple, version_string -class Peer(object): +class Peer: # Protocol version ATTRS = ('host', 'features', diff --git a/electrumx/lib/script.py b/electrumx/lib/script.py index 5022202..7a1e3a4 100644 --- a/electrumx/lib/script.py +++ b/electrumx/lib/script.py @@ -25,10 +25,6 @@ # and warranty status of this software. '''Script-related classes and functions.''' - - -from collections import namedtuple - from electrumx.lib.enum import Enumeration from electrumx.lib.util import unpack_le_uint16_from, unpack_le_uint32_from, \ pack_le_uint16, pack_le_uint32 @@ -99,7 +95,7 @@ def _match_ops(ops, pattern): return True -class ScriptPubKey(object): +class ScriptPubKey: '''A class for handling a tx output script that gives conditions necessary for spending. ''' @@ -109,38 +105,6 @@ class ScriptPubKey(object): TO_P2SH_OPS = (OpCodes.OP_HASH160, -1, OpCodes.OP_EQUAL) TO_PUBKEY_OPS = (-1, OpCodes.OP_CHECKSIG) - PayToHandlers = namedtuple('PayToHandlers', 'address script_hash pubkey ' - 'unspendable strange') - - @classmethod - def pay_to(cls, handlers, script): - '''Parse a script, invoke the appropriate handler and - return the result. - - One of the following handlers is invoked: - handlers.address(hash160) - handlers.script_hash(hash160) - handlers.pubkey(pubkey) - handlers.unspendable() - handlers.strange(script) - ''' - try: - ops = Script.get_ops(script) - except ScriptError: - return handlers.unspendable() - - match = _match_ops - - if match(ops, cls.TO_ADDRESS_OPS): - return handlers.address(ops[2][-1]) - if match(ops, cls.TO_P2SH_OPS): - return handlers.script_hash(ops[1][-1]) - if match(ops, cls.TO_PUBKEY_OPS): - return handlers.pubkey(ops[0][-1]) - if ops and ops[0] == OpCodes.OP_RETURN: - return handlers.unspendable() - return handlers.strange(script) - @classmethod def P2SH_script(cls, hash160): return (bytes((OpCodes.OP_HASH160,)) @@ -154,7 +118,7 @@ def P2PKH_script(cls, hash160): + bytes((OpCodes.OP_EQUALVERIFY, OpCodes.OP_CHECKSIG))) -class Script(object): +class Script: @classmethod def get_ops(cls, script): diff --git a/electrumx/lib/tx.py b/electrumx/lib/tx.py index ff98cbb..a579f30 100644 --- a/electrumx/lib/tx.py +++ b/electrumx/lib/tx.py @@ -105,7 +105,7 @@ def serialize(self): )) -class Deserializer(object): +class Deserializer: '''Deserializes blocks into transactions. External entry points are read_tx(), read_tx_and_hash(), diff --git a/electrumx/lib/util.py b/electrumx/lib/util.py index 169fe50..82a7daf 100644 --- a/electrumx/lib/util.py +++ b/electrumx/lib/util.py @@ -31,7 +31,6 @@ import inspect from ipaddress import ip_address import logging -import re import sys from collections.abc import Container, Mapping from struct import Struct @@ -83,8 +82,7 @@ def class_logger(path, classname): # Method decorator. To be used for calculations that will always # deliver the same result. The method cannot take any arguments # and should be accessed as an attribute. -class cachedproperty(object): - +class cachedproperty: def __init__(self, f): self.f = f @@ -191,7 +189,7 @@ def increment_byte_string(bs): return None -class LogicalFile(object): +class LogicalFile: '''A logical binary file split across several separate files on disk.''' def __init__(self, prefix, digits, file_size): diff --git a/electrumx/server/block_processor.py b/electrumx/server/block_processor.py index 74fd40b..5d731ff 100644 --- a/electrumx/server/block_processor.py +++ b/electrumx/server/block_processor.py @@ -24,7 +24,7 @@ from electrumx.server.db import FlushData -class Prefetcher(object): +class Prefetcher: '''Prefetches blocks (in the forward direction only).''' def __init__(self, daemon, coin, blocks_event): @@ -147,7 +147,7 @@ class ChainError(Exception): '''Raised on error processing blocks.''' -class BlockProcessor(object): +class BlockProcessor: '''Process blocks and update the DB state to match. Employ a prefetcher to prefetch blocks in batches for processing. diff --git a/electrumx/server/controller.py b/electrumx/server/controller.py index 3581409..79cf126 100644 --- a/electrumx/server/controller.py +++ b/electrumx/server/controller.py @@ -17,7 +17,7 @@ from electrumx.server.session import SessionManager -class Notifications(object): +class Notifications: # hashX notifications come from two sources: new blocks and # mempool refreshes. # diff --git a/electrumx/server/daemon.py b/electrumx/server/daemon.py index 18677bb..16b3d74 100644 --- a/electrumx/server/daemon.py +++ b/electrumx/server/daemon.py @@ -37,7 +37,7 @@ class ServiceRefusedError(Exception): some reason.''' -class Daemon(object): +class Daemon: '''Handles connections to a daemon at the given URL.''' WARMING_UP = -28 diff --git a/electrumx/server/db.py b/electrumx/server/db.py index 7272c8e..b6d9958 100644 --- a/electrumx/server/db.py +++ b/electrumx/server/db.py @@ -16,7 +16,6 @@ from bisect import bisect_right from dataclasses import dataclass from glob import glob -from struct import Struct import attr from aiorpcx import run_in_thread, sleep @@ -43,7 +42,7 @@ class UTXO: @attr.s(slots=True) -class FlushData(object): +class FlushData: height = attr.ib() tx_count = attr.ib() headers = attr.ib() @@ -55,7 +54,7 @@ class FlushData(object): tip = attr.ib() -class DB(object): +class DB: '''Simple wrapper of the backend database for querying. Performs no DB update, though the DB will be cleaned on opening if diff --git a/electrumx/server/history.py b/electrumx/server/history.py index fced542..1974086 100644 --- a/electrumx/server/history.py +++ b/electrumx/server/history.py @@ -8,21 +8,19 @@ '''History by script hash (address).''' -from array import array import ast import bisect import time +from array import array from collections import defaultdict -from functools import partial import electrumx.lib.util as util -from electrumx.lib.util import ( - pack_be_uint16, pack_le_uint64, unpack_be_uint16_from, unpack_le_uint64, -) -from electrumx.lib.hash import hash_to_hex_str, HASHX_LEN +from electrumx.lib.hash import HASHX_LEN, hash_to_hex_str +from electrumx.lib.util import (pack_be_uint16, pack_le_uint64, + unpack_be_uint16_from, unpack_le_uint64) -class History(object): +class History: DB_VERSIONS = (0, 1) diff --git a/electrumx/server/mempool.py b/electrumx/server/mempool.py index f88a925..208b209 100644 --- a/electrumx/server/mempool.py +++ b/electrumx/server/mempool.py @@ -22,7 +22,7 @@ @attr.s(slots=True) -class MemPoolTx(object): +class MemPoolTx: prevouts = attr.ib() # A pair is a (hashX, value) tuple in_pairs = attr.ib() @@ -32,7 +32,7 @@ class MemPoolTx(object): @attr.s(slots=True) -class MemPoolTxSummary(object): +class MemPoolTxSummary: hash = attr.ib() fee = attr.ib() has_unconfirmed_inputs = attr.ib() @@ -87,7 +87,7 @@ async def on_mempool(self, touched, height): daemon's height at the time the mempool was obtained.''' -class MemPool(object): +class MemPool: '''Representation of the daemon's mempool. coin - a coin class from coins.py diff --git a/electrumx/server/peers.py b/electrumx/server/peers.py index f035898..28eca22 100644 --- a/electrumx/server/peers.py +++ b/electrumx/server/peers.py @@ -577,6 +577,6 @@ def peer_data(peer): return data def peer_key(peer): - return (peer.bad, -peer.last_good) + return peer.bad, -peer.last_good return [peer_data(peer) for peer in sorted(self.peers, key=peer_key)] diff --git a/electrumx/server/session.py b/electrumx/server/session.py index db9fdc0..abedeae 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -1293,7 +1293,7 @@ async def server_version(self, client_name='', protocol_version=None): BAD_REQUEST, f'unsupported protocol version: {protocol_version}')) self.set_request_handlers(ptuple) - return (electrumx.version, self.protocol_version_string()) + return electrumx.version, self.protocol_version_string() async def crash_old_client(self, ptuple, crash_client_ver): if crash_client_ver: @@ -1342,7 +1342,7 @@ async def transaction_get(self, tx_hash, verbose=False): ''' assert_tx_hash(tx_hash) if verbose not in (True, False): - raise RPCError(BAD_REQUEST, f'"verbose" must be a boolean') + raise RPCError(BAD_REQUEST, '"verbose" must be a boolean') self.bump_cost(1.0) return await self.daemon_request('getrawtransaction', tx_hash, verbose) @@ -1370,7 +1370,7 @@ async def transaction_id_from_pos(self, height, tx_pos, merkle=False): tx_pos = non_negative_integer(tx_pos) height = non_negative_integer(height) if merkle not in (True, False): - raise RPCError(BAD_REQUEST, f'"merkle" must be a boolean') + raise RPCError(BAD_REQUEST, '"merkle" must be a boolean') if merkle: branch, tx_hash, cost = await self.session_mgr.merkle_branch_for_tx_pos( diff --git a/electrumx/server/storage.py b/electrumx/server/storage.py index 1272eac..4fab068 100644 --- a/electrumx/server/storage.py +++ b/electrumx/server/storage.py @@ -22,7 +22,7 @@ def db_class(name): raise RuntimeError(f'unrecognised DB engine "{name}"') -class Storage(object): +class Storage: '''Abstract base class of the DB backend abstraction.''' def __init__(self, name, for_sync): @@ -121,7 +121,7 @@ def iterator(self, prefix=b'', reverse=False): return RocksDBIterator(self.db, prefix, reverse) -class RocksDBWriteBatch(object): +class RocksDBWriteBatch: '''A write batch for RocksDB.''' def __init__(self, db): @@ -136,7 +136,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): self.db.write(self.batch) -class RocksDBIterator(object): +class RocksDBIterator: '''An iterator for RocksDB.''' def __init__(self, db, prefix, reverse): diff --git a/electrumx_rpc b/electrumx_rpc index 4be8ef4..e270397 100755 --- a/electrumx_rpc +++ b/electrumx_rpc @@ -136,8 +136,7 @@ def main(): print(f'error making request: {e}') return 1 - loop = asyncio.get_event_loop() - code = loop.run_until_complete(send_request()) + code = asyncio.run(send_request()) sys.exit(code) diff --git a/tests/lib/test_addresses.py b/tests/lib/test_addresses.py index 33fedc7..ea79299 100644 --- a/tests/lib/test_addresses.py +++ b/tests/lib/test_addresses.py @@ -86,18 +86,3 @@ def address(request): def test_address_to_hashX(address): coin, addr, _, hashX = address assert coin.address_to_hashX(addr).hex() == hashX - - -def test_address_from_hash160(address): - coin, addr, hash, _ = address - - raw = coin.DECODE_CHECK(addr) - verlen = len(raw) - 20 - assert verlen > 0 - verbyte, hash_bytes = raw[:verlen], raw[verlen:] - if coin.P2PKH_VERBYTE == verbyte: - assert coin.P2PKH_address_from_hash160(bytes.fromhex(hash)) == addr - elif verbyte in coin.P2SH_VERBYTES: - assert coin.P2SH_address_from_hash160(bytes.fromhex(hash)) == addr - else: - raise Exception("Unknown version byte") diff --git a/tests/lib/test_coins.py b/tests/lib/test_coins.py index f016fd4..dfee1c1 100644 --- a/tests/lib/test_coins.py +++ b/tests/lib/test_coins.py @@ -2,7 +2,7 @@ import pytest -from electrumx.lib.coins import BitcoinSV, NameMixin +from electrumx.lib.coins import NameMixin from electrumx.lib.script import OpCodes, Script diff --git a/tests/lib/test_hash.py b/tests/lib/test_hash.py index d4b6014..82750a8 100644 --- a/tests/lib/test_hash.py +++ b/tests/lib/test_hash.py @@ -13,20 +13,9 @@ def test_sha256(): with pytest.raises(TypeError): lib_hash.sha256('sha256') -def ripemd160(x): - assert lib_hash.ripemd160(b'ripemd160') == b'\x903\x91\xa1\xc0I\x9e\xc8\xdf\xb5\x1aSK\xa5VW\xf9|W\xd5' - with pytest.raises(TypeError): - lib_hash.ripemd160('ripemd160') - def test_double_sha256(): assert lib_hash.double_sha256(b'double_sha256') == b'ksn\x8e\xb7\xb9\x0f\xf6\xd9\xad\x88\xd9#\xa1\xbcU(j1Bx\xce\xd5;s\xectL\xe7\xc5\xb4\x00' -def test_hmac_sha512(): - assert lib_hash.hmac_sha512(b'key', b'message') == b"\xe4w8M|\xa2)\xdd\x14&\xe6Kc\xeb\xf2\xd3n\xbdm~f\x9ag5BNr\xeal\x01\xd3\xf8\xb5n\xb3\x9c6\xd8#/T'\x99\x9b\x8d\x1a?\x9c\xd1\x12\x8f\xc6\x9fMu\xb44!h\x10\xfa6~\x98" - -def test_hash160(): - assert lib_hash.hash160(b'hash_160') == b'\xb3\x96\x94\xfc\x978R\xa7)XqY\xbb\xdc\xeb\xac\xa7%\xb8$' - def test_hash_to_hex_str(): assert lib_hash.hash_to_hex_str(b'hash_to_str') == '7274735f6f745f68736168' diff --git a/tests/lib/test_merkle.py b/tests/lib/test_merkle.py index 6a139d6..4675ffa 100644 --- a/tests/lib/test_merkle.py +++ b/tests/lib/test_merkle.py @@ -144,7 +144,7 @@ def test_branch_and_root_from_level_bad(): merkle.branch_and_root_from_level(hashes, [hashes[0]], 1, 0) -class Source(object): +class Source: def __init__(self, length): self._hashes = [os.urandom(32) for _ in range(length)] diff --git a/tests/server/test_compaction.py b/tests/server/test_compaction.py index ad6d21b..ad6c96a 100644 --- a/tests/server/test_compaction.py +++ b/tests/server/test_compaction.py @@ -1,9 +1,9 @@ -# Test of compaction code in server/history.py - +'''Test of compaction code in server/history.py''' import array -import asyncio -from os import environ, urandom import random +from os import environ, urandom + +import pytest from electrumx.lib.hash import HASHX_LEN from electrumx.lib.util import pack_be_uint16, pack_le_uint64 @@ -98,6 +98,7 @@ def check_written(history, histories): db_hist = array.array('I', history.get_txnums(hashX, limit=None)) assert hist == db_hist + def compact_history(history): '''Synchronously compact the DB history.''' history.comp_cursor = 0 @@ -110,7 +111,11 @@ def compact_history(history): write_size += history._compact_history(limit) assert write_size != 0 -async def run_test(db_dir): + +@pytest.mark.asyncio +async def test_compaction(tmpdir): + db_dir = str(tmpdir) + print(f'Temp dir: {db_dir}') environ.clear() environ['DB_DIRECTORY'] = db_dir environ['DAEMON_URL'] = '' @@ -126,10 +131,3 @@ async def run_test(db_dir): check_written(history, histories) compact_history(history) check_written(history, histories) - - -def test_compaction(tmpdir): - db_dir = str(tmpdir) - print(f'Temp dir: {db_dir}') - loop = asyncio.get_event_loop() - loop.run_until_complete(run_test(db_dir)) diff --git a/tests/server/test_daemon.py b/tests/server/test_daemon.py index b45103f..8bb065d 100644 --- a/tests/server/test_daemon.py +++ b/tests/server/test_daemon.py @@ -5,14 +5,9 @@ import pytest -from aiorpcx import ( - JSONRPCv1, JSONRPCLoose, RPCError, ignore_after, - Request, Batch, -) +from aiorpcx import JSONRPCv1, JSONRPCLoose, RPCError, ignore_after, Request from electrumx.lib.coins import BitcoinCash, CoinError, Bitzeny, Dash -from electrumx.server.daemon import ( - Daemon, FakeEstimateFeeDaemon, DaemonError -) +from electrumx.server.daemon import Daemon, FakeEstimateFeeDaemon coin = BitcoinCash @@ -34,7 +29,7 @@ def dash_daemon(request): return coin.DAEMON(coin, ','.join(urls)) -class ResponseBase(object): +class ResponseBase: def __init__(self, headers, status): self.headers = headers @@ -243,13 +238,6 @@ async def test_broadcast_transaction(daemon): assert await daemon.broadcast_transaction(raw_tx) == tx_hash -@pytest.mark.asyncio -async def test_relayfee(daemon): - response = {"relayfee": sats, "other:": "cruft"} - daemon.session = ClientSessionGood(('getnetworkinfo', [], response)) - assert await daemon.getnetworkinfo() == response - - @pytest.mark.asyncio async def test_relayfee(daemon): if isinstance(daemon, FakeEstimateFeeDaemon): diff --git a/tests/server/test_mempool.py b/tests/server/test_mempool.py index 0c42855..3dda425 100644 --- a/tests/server/test_mempool.py +++ b/tests/server/test_mempool.py @@ -7,13 +7,12 @@ from random import randrange, choice, seed import pytest -from aiorpcx import Event, TaskGroup, sleep, spawn, ignore_after +from aiorpcx import Event, TaskGroup, sleep, ignore_after from electrumx.server.mempool import MemPool, MemPoolAPI from electrumx.lib.coins import BitcoinCash from electrumx.lib.hash import HASHX_LEN, hex_str_to_hash, hash_to_hex_str from electrumx.lib.tx import Tx, TxInput, TxOutput -from electrumx.lib.util import make_logger coin = BitcoinCash diff --git a/tests/server/test_notifications.py b/tests/server/test_notifications.py index af559a4..435a6c3 100644 --- a/tests/server/test_notifications.py +++ b/tests/server/test_notifications.py @@ -1,5 +1,3 @@ -import asyncio - import pytest from electrumx.server.controller import Notifications diff --git a/tests/test_blocks.py b/tests/test_blocks.py index df4a5d7..5ee06ba 100644 --- a/tests/test_blocks.py +++ b/tests/test_blocks.py @@ -32,7 +32,6 @@ from electrumx.lib.coins import Coin from electrumx.lib.hash import hex_str_to_hash -from electrumx.lib.util import pack_be_uint32 BLOCKS_DIR = os.path.join( os.path.dirname(os.path.realpath(__file__)), 'blocks') From 5b1fe780fae41667154ee17f9b40809c6889b667 Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Mon, 15 Jun 2020 02:08:45 -0500 Subject: [PATCH 019/386] Remove defunct Motion currency. Website is down and daemon git repo inactive. --- electrumx/lib/coins.py | 25 ------------------------- tests/blocks/motion_mainnet_52000.json | 14 -------------- 2 files changed, 39 deletions(-) delete mode 100644 tests/blocks/motion_mainnet_52000.json diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 3da3025..ee054bb 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -1179,31 +1179,6 @@ class DogecoinTestnet(Dogecoin): '4d7049f45189db5664f3c4d07350559e') -# Source: https://github.com/motioncrypto/motion -class Motion(Coin): - NAME = "Motion" - SHORTNAME = "XMN" - NET = "mainnet" - XPUB_VERBYTES = bytes.fromhex("0488B21E") - XPRV_VERBYTES = bytes.fromhex("0488ADE4") - GENESIS_HASH = ('000001e9dc60dd2618e91f7b90141349' - '22c374496b61c1a272519b1c39979d78') - P2PKH_VERBYTE = bytes.fromhex("32") - P2SH_VERBYTES = (bytes.fromhex("12"),) - TX_COUNT_HEIGHT = 54353 - TX_COUNT = 92701 - TX_PER_BLOCK = 4 - RPC_PORT = 3385 - SESSIONCLS = DashElectrumX - DAEMON = daemon.DashDaemon - - @classmethod - def header_hash(cls, header): - '''Given a header return the hash.''' - import x16r_hash - return x16r_hash.getPoWHash(header) - - # Source: https://github.com/dashpay/dash class Dash(Coin): NAME = "Dash" diff --git a/tests/blocks/motion_mainnet_52000.json b/tests/blocks/motion_mainnet_52000.json deleted file mode 100644 index babb6d9..0000000 --- a/tests/blocks/motion_mainnet_52000.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "hash": "000000000023070cbea0ce0ab9c999b97932f02354e8172c0eae02e2b5f95adf", - "size": 223, - "height": 52000, - "merkleroot": "7fd291107086805bb7f83168b0de609cbb3f0150f0bdf6faf2e724efa0601846", - "tx": [ - "7fd291107086805bb7f83168b0de609cbb3f0150f0bdf6faf2e724efa0601846" - ], - "time": 1531687551, - "nonce": 2901992536, - "bits": "1b337222", - "previousblockhash": "000000000015c6f58cb3f75edaf5e0fbaa0e5ef05ba777bd04f7935a812a36dc", - "block": "00000020dc362a815a93f704bd77a75bf05e0eaafbe0f5da5ef7b38cf5c6150000000000461860a0ef24e7f2faf6bdf050013fbb9c60deb06831f8b75b8086701091d27f7fb24b5b2272331b58e4f8ac0102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff170320cb00047fb24b5b088100005f000000007a657267000000000002008c8647000000001976a914c5656d08746687407e38afb22df2b1baf3b7caed88ac0008af2f000000001976a91494e11b4999b2e3cb539e90baa4fdbcf87c98df2288ac00000000" -} \ No newline at end of file From 667fd20f0c6f6cc6c8b62b082bd85c42a7f4ed58 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 16 Jun 2020 17:43:29 +0200 Subject: [PATCH 020/386] local RPC add_peer: retry if given different ports If already in peer list but with different ports, don't ignore. Also, add a comment to explain how changing ports are handled as it is really non-trivial... --- electrumx/server/peers.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/electrumx/server/peers.py b/electrumx/server/peers.py index f035898..3019a20 100644 --- a/electrumx/server/peers.py +++ b/electrumx/server/peers.py @@ -208,6 +208,11 @@ async def _note_peers(self, peers, limit=2, check_ports=False, source=None): for match in matches: if match.check_ports(peer): self.logger.info(f'ports changed for {peer}') + # Retry connecting to the peer. First we will try the existing + # ports and then try the new ports. Note that check_ports above + # had a side_effect to temporarily store the new ports. + # If we manage to connect, we will call 'server.features', + # and the ports for this peer will be updated to the return values. match.retry_event.set() else: match_set.add(peer) @@ -472,7 +477,7 @@ def info(self): async def add_localRPC_peer(self, real_name): '''Add a peer passed by the admin over LocalRPC.''' - await self._note_peers([Peer.from_real_name(real_name, 'RPC')]) + await self._note_peers([Peer.from_real_name(real_name, 'RPC')], check_ports=True) async def on_add_peer(self, features, source_addr): '''Add a peer (but only if the peer resolves to the source).''' From 419a16f69de568d5786a4e08cc720dd9d32a20c1 Mon Sep 17 00:00:00 2001 From: Michal Taborsky Date: Thu, 18 Jun 2020 08:25:21 +0200 Subject: [PATCH 021/386] Make environment tests PEP-8 compliant --- tests/server/test_env.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/server/test_env.py b/tests/server/test_env.py index 0a6f98f..5e60010 100644 --- a/tests/server/test_env.py +++ b/tests/server/test_env.py @@ -61,10 +61,10 @@ def assert_boolean(env_var, attr, default): assert getattr(e, attr) == default os.environ[env_var] = 'foo' e = Env() - assert getattr(e, attr) == True + assert getattr(e, attr) is True os.environ[env_var] = '' e = Env() - assert getattr(e, attr) == False + assert getattr(e, attr) is False def test_minimal(): @@ -141,6 +141,7 @@ def test_COIN_NET(): e = Env() assert e.coin == lib_coins.TokenPay + def test_CACHE_MB(): assert_integer('CACHE_MB', 'cache_MB', 1200) @@ -158,6 +159,7 @@ def test_SERVICES(): Service('rpc', NetAddress('::1', 700)), ] + def test_SERVICES_default_rpc(): # This has a blank entry between commas os.environ['SERVICES'] = 'rpc://foo.bar' @@ -178,7 +180,7 @@ def test_bad_SERVICES(): setup_base_env() os.environ['SERVICES'] = 'tcp:foo.bar:1234' with pytest.raises(ServiceError) as err: - Env() + Env() assert 'invalid service string' in str(err.value) os.environ['SERVICES'] = 'xxx://foo.com:50001' with pytest.raises(ServiceError) as err: @@ -197,7 +199,7 @@ def test_onion_SERVICES(): def test_duplicate_SERVICES(): setup_base_env() os.environ['SERVICES'] = 'tcp://foo.bar:1234,ws://foo.bar:1235' - e = Env() + Env() os.environ['SERVICES'] = 'tcp://foo.bar:1234,ws://foo.bar:1234' with pytest.raises(ServiceError) as err: Env() @@ -407,6 +409,7 @@ def test_coin_class_provided(): e = Env(lib_coins.BitcoinSV) assert e.coin == lib_coins.BitcoinSV + def test_drop_unknown_clients(): e = Env() assert e.drop_client_unknown is False From 6ae9bdec36ceca356bd342f07227a34a46a27da0 Mon Sep 17 00:00:00 2001 From: Michal Taborsky Date: Thu, 18 Jun 2020 08:26:38 +0200 Subject: [PATCH 022/386] Remove unnecessary comma --- docs/environment.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/environment.rst b/docs/environment.rst index 85d1ed7..3a9dd34 100644 --- a/docs/environment.rst +++ b/docs/environment.rst @@ -283,7 +283,7 @@ These environment variables are optional: .. envvar:: DROP_CLIENT_UNKNOWN - Set to anything non-empty to deny serving clients, which do not + Set to anything non-empty to deny serving clients which do not identify themselves first by issuing the server.version method call with a non-empty client identifier. The connection is dropped on first actual method call. This might help to filter out simple From 60e1043d37537525cc3d4cc78b2f35fd5a6e6564 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 19 Jun 2020 19:48:47 +0200 Subject: [PATCH 023/386] UTXO instances need ordering: fix #23 --- electrumx/server/db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electrumx/server/db.py b/electrumx/server/db.py index b6d9958..0e6d9f4 100644 --- a/electrumx/server/db.py +++ b/electrumx/server/db.py @@ -31,7 +31,7 @@ from electrumx.server.history import History -@dataclass +@dataclass(order=True) class UTXO: __slots__ = 'tx_num', 'tx_pos', 'tx_hash', 'height', 'height', 'value' tx_num: int From 76dca02061d2f70483092ce257455354f030ed3e Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 19 Jun 2020 19:34:53 +0200 Subject: [PATCH 024/386] fix a bug in peer discovery closes #22 --- electrumx/server/peers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/electrumx/server/peers.py b/electrumx/server/peers.py index 5aa806c..5e6510e 100644 --- a/electrumx/server/peers.py +++ b/electrumx/server/peers.py @@ -19,6 +19,7 @@ from aiorpcx import (Event, Notification, RPCError, RPCSession, SOCKSError, SOCKSProxy, TaskGroup, TaskTimeout, connect_rs, handler_invocation, ignore_after, sleep) +from aiorpcx.jsonrpc import CodeMessageError from electrumx.lib.peer import Peer from electrumx.lib.util import class_logger, json_deserialize @@ -286,7 +287,7 @@ async def _should_drop_peer(self, peer): self.logger.error(f'{peer_text} marking bad: ({e})') peer.mark_bad() break - except RPCError as e: + except CodeMessageError as e: self.logger.error(f'{peer_text} RPC error: {e.message} ' f'({e.code})') except (OSError, SOCKSError, ConnectionError, TaskTimeout) as e: From c91c6fbd46aa08d623e224a31d51e526caf47f6c Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 2 Jul 2020 15:15:04 +0200 Subject: [PATCH 025/386] docs: fix blockchain.scripthash.get_balance return value The docs and the actual behaviour are inconsistent. Which one do we fix? The Electrum client expects the current behaviour, so we fix the docs. --- docs/protocol-methods.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/protocol-methods.rst b/docs/protocol-methods.rst index 3ec1f50..1467d46 100644 --- a/docs/protocol-methods.rst +++ b/docs/protocol-methods.rst @@ -276,15 +276,15 @@ Return the confirmed and unconfirmed balances of a :ref:`script hash **Result** A dictionary with keys `confirmed` and `unconfirmed`. The value of - each is the appropriate balance in coin units as a string. + each is the appropriate balance in minimum coin units (satoshis). **Result Example** :: { - "confirmed": "1.03873966", - "unconfirmed": "0.236844" + "confirmed": 103873966, + "unconfirmed": 23684400 } blockchain.scripthash.get_history From 23633c2957bdc11fadf19c9e583530e093c68402 Mon Sep 17 00:00:00 2001 From: Franco Benner Date: Sun, 2 Aug 2020 15:21:51 -0400 Subject: [PATCH 026/386] Fix typo in block processor --- electrumx/server/block_processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electrumx/server/block_processor.py b/electrumx/server/block_processor.py index 5d731ff..38876e3 100644 --- a/electrumx/server/block_processor.py +++ b/electrumx/server/block_processor.py @@ -832,7 +832,7 @@ def backup_txs(self, txs, is_unspendable): continue # Get the hashX - hashX = script_hashX(txout.script) + hashX = script_hashX(txout.pk_script) cache_value = spend_utxo(tx_hash, idx) add_touched(cache_value[:-13]) From 33f63b6625245d32699ee14b24c97d751976daa1 Mon Sep 17 00:00:00 2001 From: SuBPaR42 Date: Sat, 5 Sep 2020 11:05:40 -0500 Subject: [PATCH 027/386] Update coins.py Removed a dead server and update TX Count and Block Count along with TX/per block --- electrumx/lib/coins.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index ee054bb..46cc851 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -578,13 +578,12 @@ class Bitcoin(BitcoinMixin, Coin): NAME = "Bitcoin" DESERIALIZER = lib_tx.DeserializerSegWit MEMPOOL_HISTOGRAM_REFRESH_SECS = 120 - TX_COUNT = 318337769 - TX_COUNT_HEIGHT = 524213 - TX_PER_BLOCK = 1400 + TX_COUNT = 565436782 + TX_COUNT_HEIGHT = 646855 + TX_PER_BLOCK = 2200 CRASH_CLIENT_VER = (3, 2, 3) BLACKLIST_URL = 'https://electrum.org/blacklist.json' PEERS = [ - 'E-X.not.fyi s t', 'electrum.vom-stausee.de s t', 'electrum.hsmiths.com s t', 'helicarrier.bauerj.eu s t', From 4120907c19025669efba08c2c60f25d370b6ac83 Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Sun, 11 Oct 2020 21:30:40 -0500 Subject: [PATCH 028/386] Fix TravisCI errors (#63) Move Python 3.7 and 3.8 CI to stable vers. Update C deps Veruscoin. --- .travis.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 604b389..1ceee2a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,12 +2,11 @@ sudo: required dist: bionic language: python before_install: - - sudo add-apt-repository -y ppa:streetcrypto7/rocksdb - sudo apt-get -qq update - - sudo apt-get install -yq libsnappy-dev zlib1g-dev libbz2-dev libgflags-dev liblz4-dev librocksdb-dev libleveldb-dev + - sudo apt-get install -yq libsnappy-dev zlib1g-dev libbz2-dev libgflags-dev liblz4-dev librocksdb-dev libleveldb-dev libboost-all-dev libsodium-dev python: - - "3.7-dev" - - "3.8-dev" + - "3.7" + - "3.8" - "3.9-dev" # command to install dependencies install: From d3aabfec6b6d82967ba85383ee189aa6a8eb3f9c Mon Sep 17 00:00:00 2001 From: 0x03 <4005813+BloodyNora@users.noreply.github.com> Date: Mon, 12 Oct 2020 03:18:46 +0000 Subject: [PATCH 029/386] Fix VRSC support (#50) Support new Veruscoin hash and add test blocks. --- electrumx/lib/coins.py | 4 +++- tests/blocks/verus_mainnet_1000000.json | 15 +++++++++++++++ tests/blocks/verus_mainnet_1120000.json | 15 +++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 tests/blocks/verus_mainnet_1000000.json create mode 100644 tests/blocks/verus_mainnet_1120000.json diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index ee054bb..8d6e7f7 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -1530,8 +1530,10 @@ def header_hash(cls, header): if header[0] == 4 and header[2] >= 1: if len(header) < 144 or header[143] < 3: return verushash.verushash_v2b(header) - else: + elif header[143] < 4: return verushash.verushash_v2b1(header) + else: + return verushash.verushash_v2b2(header) else: return verushash.verushash(header) diff --git a/tests/blocks/verus_mainnet_1000000.json b/tests/blocks/verus_mainnet_1000000.json new file mode 100644 index 0000000..39ea03c --- /dev/null +++ b/tests/blocks/verus_mainnet_1000000.json @@ -0,0 +1,15 @@ +{ + "hash": "473194b1e3301c0fac3b89ab100aedefb7c790aa9255f2e95be9da71bdb91050", + "size": 2066, + "height": 1000000, + "merkleroot": "e8349824644d9c4b13873a97addb0f4f2ac9946b0d047cdab27d3107068634b7", + "tx": [ + "baa658081ea730e0e591a29c914133d4370cb76365118ac7077c3a864b9235b7", + "7a9dee91d6faf67f18bea275db221b5d68f94e655710da1297dd8896d53176b3" + ], + "time": 1588821106, + "nonce": "25c927dc30975c0e490ea8140d27808a8dc7b0f13fd0866aa395bf901a0f7d81", + "bits": "1b6710fd", + "previousblockhash": "d90ec3df7f20cbc0b1bcfed2f56262888df35fd2686ff6ca95b1438de3b577ec", + "block": "04000100ec77b5e38d43b195caf66f68d25ff38d886262f5d2febcb1c0cb207fdfc30ed9b734860607317db2da7c040d6b94c92a4f0fdbad973a87134b9c4d64249834e816024948694a247d0ee86cfb3e6ee96ca9e28570d55597196efd289e50ac3320727cb35efd10671b817d0f1a90bf95a36a86d03ff1b0c78d8a80270d14a80e490e5c9730dc27c925fd4005030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff060340420f0101ffffffff0100180d8f00000000c32ea22c8020f17e0de592f8812d42cfe996da67a9ec4ee56ac667edfaba47e0fa0401b625c081031210008203000401cc4c90040101010221022ec0fa3cb4d36a646138e02e91570b242469f7ea325bb07205048d40183745a42103166b7813a4855a88e9ef7340a692ef3c2decedfdc2c7563ec79537e89667d9352009d06c734c420d2fa63cac0c379629b4573cd55533629a51d2fe519d15d0fbe220ec77b5e38d43b195caf66f68d25ff38d886262f5d2febcb1c0cb207fdfc30ed90440420f0075727cb35e0000000000000000000000000000000400008085202f8901898fa27eb5d2d495e9269c11c9e138c853054ab7a644c3d85ac849fd4d6cdfd7000000006a4730440220705bf400b43ae421b65667397baa96535f300cdc6a1a7d61351881c20826e17702206be03c46aa90098071c405e9cd7920867283769a3e9e2e0b226e576080227c690121022ec0fa3cb4d36a646138e02e91570b242469f7ea325bb07205048d40183745a4ffffffff0200b0eb0e0a0000001976a91445ce0a71d39a824181442a9979e44a38b900baeb88ac00000000000000004f6a4c4c5203b6ca0e0340420f20ec77b5e38d43b195caf66f68d25ff38d886262f5d2febcb1c0cb207fdfc30ed921022ec0fa3cb4d36a646138e02e91570b242469f7ea325bb07205048d40183745a400000000a4420f000000000000000000000000" +} diff --git a/tests/blocks/verus_mainnet_1120000.json b/tests/blocks/verus_mainnet_1120000.json new file mode 100644 index 0000000..12330ec --- /dev/null +++ b/tests/blocks/verus_mainnet_1120000.json @@ -0,0 +1,15 @@ +{ + "hash": "ceeed7ff1ea7f4c7ea5d05d283e184a552b653c2e082cf45351a2dd52637a2ba", + "size": 2072, + "height": 1120000, + "merkleroot": "c613bb889a37dbb0179f00b7ff90507c595cc0c0ab4f98b5a0946ecffddf38f2", + "tx": [ + "be7df819e96e67b749406568c5fc767136f3935936dcc4d2213d87a97e3d18ae", + "a8f393500c034e39710eb470ac738efd7a8b6d108c9250f62986f979a759123e" + ], + "time": 1596256893, + "nonce": "459e799954525dc16a96038635e0f3d93aa4c2a9c26c61a41220206e1a0f345d", + "bits": "1b264f78", + "previousblockhash": "9bfc7ee2dd2c07505be0014c85f50313a3d86196fa5d3efa38efe151f01ea90c", + "block": "040001000ca91ef051e1ef38fa3e5dfa9661d8a31303f5854c01e05b50072cdde27efc9bf238dffdcf6e94a0b5984fabc0c05c597c5090ffb7009f17b0db379a88bb13c66ea43156ba27ed409755035c4bb526b7a1c187759f1575eec0165bfd3301fb4f7df2245f784f261b5d340f1a6e202012a4616cc2a9c2a43ad9f3e0358603966ac15d525499799e45fd40050500000000000000660fb53b1f3876990b0179b556672b30b597b9c0caeeee56782d05b2c7c1cc81c2c3a7077fb810dd1e2a6e5beccb9e9762e4b26e40f80828dc5d0a885da854a8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020400008085202f89010000000000000000000000000000000000000000000000000000000000000000ffffffff050300171100ffffffff0100180d8f00000000c93d04030001021504a371d67642054b68c89853cb1d8f39e79dda12822103166b7813a4855a88e9ef7340a692ef3c2decedfdc2c7563ec79537e89667d935cc4c8704030101011504a371d67642054b68c89853cb1d8f39e79dda128243010000a18b5cc0c5c4400a8c1acd6f9963dd2312ca592fc09db02043d4ec557d376b170ca91ef051e1ef38fa3e5dfa9661d8a31303f5854c01e05b50072cdde27efc9b2704030101012103166b7813a4855a88e9ef7340a692ef3c2decedfdc2c7563ec79537e89667d935757df2245f0000000000000000000000000000000400008085202f8901cfd5badf1b48c58b21b4f8a40d1e0d68b122841a02b548abcf2ab5a5ee3d95b600000000694c670101010121031dfd266c4be9816a307844d5f3feba46655a69872555c81090cd116b5bea6df840f7ae89ea159cf596fbd7f82e99be91fefd2edcfee32382f5b10b61935e574d722fb40767fa17d3ac3d66160ab2102687546b75a62d2d2f653a8b58355416bf13ffffffff028f9a0ad5b601000024050403000000cc1b04030001011504a371d67642054b68c89853cb1d8f39e79dda1282750000000000000000466a445642020000007b0f1100001711000ca91ef051e1ef38fa3e5dfa9661d8a31303f5854c01e05b50072cdde27efc9b0414a371d67642054b68c89853cb1d8f39e79dda128200000000641711000000000000000000000000" +} From 892403219babc0ef2eaedf5435b17b69f551da0c Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Mon, 12 Oct 2020 09:56:46 -0500 Subject: [PATCH 030/386] Mark peer bad when RPC session is canceled during critical requests. (#35) If a peer fails to respond to server.version or server.add_peer calls, mark as bad. RPC sessions from aiorpcx that have closed have their send methods raise asyncio.CancelledError, which we catch in this case. --- electrumx/server/peers.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/electrumx/server/peers.py b/electrumx/server/peers.py index 5e6510e..f0ce3e8 100644 --- a/electrumx/server/peers.py +++ b/electrumx/server/peers.py @@ -362,7 +362,11 @@ async def _verify_peer(self, session, peer): # server.version goes first message = 'server.version' - result = await session.send_request(message, self.server_version_args) + try: + result = await session.send_request(message, self.server_version_args) + except asyncio.CancelledError: + raise BadPeerError('terminated before server.version response') + assert_good(message, result, list) # Protocol version 1.1 returns a pair with the version first @@ -386,7 +390,10 @@ async def _verify_peer(self, session, peer): if features: self.logger.info(f'registering ourself with {peer}') # We only care to wait for the response - await session.send_request('server.add_peer', [features]) + try: + await session.send_request('server.add_peer', [features]) + except asyncio.CancelledError: + raise BadPeerError('terminated before server.add_peer response') async def _send_headers_subscribe(self, session): message = 'blockchain.headers.subscribe' From 6ed1be16e352fce8ac79904a3f19befc58efa36a Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Mon, 12 Oct 2020 10:53:11 -0500 Subject: [PATCH 031/386] Increase width of Host column of electrumx_rpc peers. Fixes spesmilo/electrumx#7 (display of Tor addresses) --- electrumx/lib/text.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/electrumx/lib/text.py b/electrumx/lib/text.py index 665411e..04ef12a 100644 --- a/electrumx/lib/text.py +++ b/electrumx/lib/text.py @@ -62,7 +62,7 @@ def time_fmt(t): return util.formatted_time(now - t) now = time.time() - fmt = ('{:<30} {:<6} {:>5} {:>5} {:<17} {:>4} ' + fmt = ('{:<62} {:<6} {:>5} {:>5} {:<17} {:>4} ' '{:>4} {:>8} {:>11} {:>11} {:>5} {:>20} {:<15}') yield fmt.format('Host', 'Status', 'TCP', 'SSL', 'Server', 'Min', 'Max', 'Pruning', 'Last Good', 'Last Try', @@ -71,7 +71,7 @@ def time_fmt(t): features = item['features'] hostname = item['host'] host = features['hosts'][hostname] - yield fmt.format(hostname[:30], + yield fmt.format(hostname[:62], item['status'], host.get('tcp_port') or '', host.get('ssl_port') or '', From 48d53daa47e6f0dd45de016edce1d177de332cfd Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 20 Oct 2020 22:11:43 +0200 Subject: [PATCH 032/386] session: disable resource-limits for private IP sessions When hosting a server over .onion, all resource usage was accounted against the private IP of the Tor gateway (e.g. localhost). related: https://github.com/kyuupichan/electrumx/issues/817#issuecomment-489439898 --- electrumx/server/session.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/electrumx/server/session.py b/electrumx/server/session.py index ab13c22..eebaf34 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -17,6 +17,7 @@ from collections import defaultdict from functools import partial from ipaddress import IPv4Address, IPv6Address +from typing import Optional import attr import pylru @@ -765,18 +766,24 @@ async def _notify_sessions(self, height, touched): for session in self.sessions: await self._task_group.spawn(session.notify, touched, height_changed) - def _ip_addr_group_name(self, session): + def _ip_addr_group_name(self, session) -> Optional[str]: host = session.remote_address().host if isinstance(host, IPv4Address): - return '.'.join(str(host).split('.')[:3]) + if host.is_private: # exempt private addresses + return None + return '.'.join(str(host).split('.')[:3]) # /24 if isinstance(host, IPv6Address): - return ':'.join(host.exploded.split(':')[:3]) + if host.is_private: + return None + return ':'.join(host.exploded.split(':')[:3]) # /48 return 'unknown_addr' def _timeslice_name(self, session): return f't{int(session.start_time - self.start_time) // 300}' - def _session_group(self, name, weight): + def _session_group(self, name: Optional[str], weight: float) -> Optional[SessionGroup]: + if name is None: + return None group = self.session_groups.get(name) if not group: group = SessionGroup(name, weight, set(), 0) @@ -790,6 +797,7 @@ def add_session(self, session): self._session_group(self._timeslice_name(session), 0.03), self._session_group(self._ip_addr_group_name(session), 1.0), ) + groups = (group for group in groups if group is not None) self.sessions[session] = groups for group in groups: group.sessions.add(session) From 57101bd164ac1dbebe16e45f6054ffe23524e87f Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 20 Oct 2020 20:30:29 +0200 Subject: [PATCH 033/386] db: try to document db a bit, and factor out some magic numbers --- electrumx/lib/tx.py | 4 +- electrumx/server/block_processor.py | 53 +++++++++++--------- electrumx/server/db.py | 77 +++++++++++++++++++---------- electrumx/server/history.py | 33 ++++++++----- electrumx/server/mempool.py | 3 +- 5 files changed, 107 insertions(+), 63 deletions(-) diff --git a/electrumx/lib/tx.py b/electrumx/lib/tx.py index a579f30..091fa05 100644 --- a/electrumx/lib/tx.py +++ b/electrumx/lib/tx.py @@ -49,8 +49,8 @@ class Tx: '''Class representing a transaction.''' __slots__ = 'version', 'inputs', 'outputs', 'locktime' version: int - inputs: Sequence - outputs: Sequence + inputs: Sequence['TxInput'] + outputs: Sequence['TxOutput'] locktime: int def serialize(self): diff --git a/electrumx/server/block_processor.py b/electrumx/server/block_processor.py index 38876e3..4709478 100644 --- a/electrumx/server/block_processor.py +++ b/electrumx/server/block_processor.py @@ -11,6 +11,7 @@ import asyncio import time +from typing import Sequence, Tuple, List, Callable from aiorpcx import TaskGroup, run_in_thread, CancelledError @@ -21,7 +22,9 @@ from electrumx.lib.util import ( chunks, class_logger, pack_le_uint32, pack_le_uint64, unpack_le_uint64 ) -from electrumx.server.db import FlushData +from electrumx.lib.tx import Tx +from electrumx.server.db import FlushData, COMP_TXID_LEN +from electrumx.server.history import TXNUM_LEN class Prefetcher: @@ -177,7 +180,7 @@ def __init__(self, env, db, daemon, notifications): # Caches of unflushed items. self.headers = [] self.tx_hashes = [] - self.undo_infos = [] + self.undo_infos = [] # type: List[Tuple[Sequence[bytes], int]] # UTXO cache self.utxo_cache = {} @@ -406,7 +409,11 @@ def advance_blocks(self, blocks): self.headers += headers self.tip = self.coin.header_hash(headers[-1]) - def advance_txs(self, txs, is_unspendable): + def advance_txs( + self, + txs: Sequence[Tuple[Tx, bytes]], + is_unspendable: Callable[[bytes], bool], + ) -> Sequence[bytes]: self.tx_hashes.append(b''.join(tx_hash for tx, tx_hash in txs)) # Use local vars for speed in the loops @@ -425,7 +432,7 @@ def advance_txs(self, txs, is_unspendable): for tx, tx_hash in txs: hashXs = [] append_hashX = hashXs.append - tx_numb = to_le_uint64(tx_num)[:5] + tx_numb = to_le_uint64(tx_num)[:TXNUM_LEN] # Spend the inputs for txin in tx.inputs: @@ -433,7 +440,7 @@ def advance_txs(self, txs, is_unspendable): continue cache_value = spend_utxo(txin.prev_hash, txin.prev_idx) undo_info_append(cache_value) - append_hashX(cache_value[:-13]) + append_hashX(cache_value[:HASHX_LEN]) # Add the new UTXOs for idx, txout in enumerate(tx.outputs): @@ -499,9 +506,8 @@ def backup_txs(self, txs, is_unspendable): # Use local vars for speed in the loops put_utxo = self.utxo_cache.__setitem__ spend_utxo = self.spend_utxo - script_hashX = self.coin.hashX_from_script touched = self.touched - undo_entry_len = 13 + HASHX_LEN + undo_entry_len = HASHX_LEN + TXNUM_LEN + 8 for tx, tx_hash in reversed(txs): for idx, txout in enumerate(tx.outputs): @@ -511,9 +517,9 @@ def backup_txs(self, txs, is_unspendable): continue # Get the hashX - hashX = script_hashX(txout.pk_script) cache_value = spend_utxo(tx_hash, idx) - touched.add(cache_value[:-13]) + hashX = cache_value[:HASHX_LEN] + touched.add(hashX) # Restore the inputs for txin in reversed(tx.inputs): @@ -522,7 +528,8 @@ def backup_txs(self, txs, is_unspendable): n -= undo_entry_len undo_item = undo_info[n:n + undo_entry_len] put_utxo(txin.prev_hash + pack_le_uint32(txin.prev_idx), undo_item) - touched.add(undo_item[:-13]) + hashX = undo_item[:HASHX_LEN] + touched.add(hashX) assert n == 0 self.tx_count -= len(txs) @@ -581,8 +588,8 @@ def backup_txs(self, txs, is_unspendable): collision rate is low (<0.1%). ''' - def spend_utxo(self, tx_hash, tx_idx): - '''Spend a UTXO and return the 33-byte value. + def spend_utxo(self, tx_hash: bytes, tx_idx: int) -> bytes: + '''Spend a UTXO and return (hashX + tx_num + value_sats). If the UTXO is not in the cache it must be on disk. We store all UTXOs so not finding one indicates a logic error or DB @@ -595,18 +602,19 @@ def spend_utxo(self, tx_hash, tx_idx): return cache_value # Spend it from the DB. + txnum_padding = bytes(8-TXNUM_LEN) # Key: b'h' + compressed_tx_hash + tx_idx + tx_num # Value: hashX - prefix = b'h' + tx_hash[:4] + idx_packed + prefix = b'h' + tx_hash[:COMP_TXID_LEN] + idx_packed candidates = {db_key: hashX for db_key, hashX in self.db.utxo_db.iterator(prefix=prefix)} for hdb_key, hashX in candidates.items(): - tx_num_packed = hdb_key[-5:] + tx_num_packed = hdb_key[-TXNUM_LEN:] if len(candidates) > 1: - tx_num, = unpack_le_uint64(tx_num_packed + b'\0\0\0') + tx_num, = unpack_le_uint64(tx_num_packed + txnum_padding) hash, _height = self.db.fs_tx_hash(tx_num) if hash != tx_hash: assert hash is not None # Should always be found @@ -614,7 +622,7 @@ def spend_utxo(self, tx_hash, tx_idx): # Key: b'u' + address_hashX + tx_idx + tx_num # Value: the UTXO value as a 64-bit unsigned integer - udb_key = b'u' + hashX + hdb_key[-9:] + udb_key = b'u' + hashX + hdb_key[-4-TXNUM_LEN:] utxo_value_packed = self.db.utxo_db.get(udb_key) if utxo_value_packed: # Remove both entries for this UTXO @@ -759,7 +767,7 @@ def advance_txs(self, txs, is_unspendable): # Add the new UTXOs for (tx, tx_hash), hashXs in zip(txs, hashXs_by_tx): add_hashXs = hashXs.add - tx_numb = to_le_uint64(tx_num)[:5] + tx_numb = to_le_uint64(tx_num)[:TXNUM_LEN] for idx, txout in enumerate(tx.outputs): # Ignore unspendable outputs @@ -782,7 +790,7 @@ def advance_txs(self, txs, is_unspendable): continue cache_value = spend_utxo(txin.prev_hash, txin.prev_idx) undo_info_append(cache_value) - add_hashXs(cache_value[:-13]) + add_hashXs(cache_value[:HASHX_LEN]) # Update touched set for notifications for hashXs in hashXs_by_tx: @@ -805,9 +813,8 @@ def backup_txs(self, txs, is_unspendable): # Use local vars for speed in the loops put_utxo = self.utxo_cache.__setitem__ spend_utxo = self.spend_utxo - script_hashX = self.coin.hashX_from_script add_touched = self.touched.add - undo_entry_len = 13 + HASHX_LEN + undo_entry_len = HASHX_LEN + TXNUM_LEN + 8 # Restore coins that had been spent # (may include coins made then spent in this block) @@ -818,7 +825,7 @@ def backup_txs(self, txs, is_unspendable): continue undo_item = undo_info[n:n + undo_entry_len] put_utxo(txin.prev_hash + pack_le_uint32(txin.prev_idx), undo_item) - add_touched(undo_item[:-13]) + add_touched(undo_item[:HASHX_LEN]) n += undo_entry_len assert n == len(undo_info) @@ -832,8 +839,8 @@ def backup_txs(self, txs, is_unspendable): continue # Get the hashX - hashX = script_hashX(txout.pk_script) cache_value = spend_utxo(tx_hash, idx) - add_touched(cache_value[:-13]) + hashX = cache_value[:HASHX_LEN] + add_touched(hashX) self.tx_count -= len(txs) diff --git a/electrumx/server/db.py b/electrumx/server/db.py index 0e6d9f4..1ea3204 100644 --- a/electrumx/server/db.py +++ b/electrumx/server/db.py @@ -16,29 +16,30 @@ from bisect import bisect_right from dataclasses import dataclass from glob import glob +from typing import Dict, Sequence, Tuple import attr from aiorpcx import run_in_thread, sleep import electrumx.lib.util as util -from electrumx.lib.hash import hash_to_hex_str +from electrumx.lib.hash import hash_to_hex_str, HASHX_LEN from electrumx.lib.merkle import Merkle, MerkleCache from electrumx.lib.util import ( formatted_time, pack_be_uint16, pack_be_uint32, pack_le_uint64, pack_le_uint32, unpack_le_uint32, unpack_be_uint32, unpack_le_uint64 ) from electrumx.server.storage import db_class -from electrumx.server.history import History +from electrumx.server.history import History, TXNUM_LEN @dataclass(order=True) class UTXO: __slots__ = 'tx_num', 'tx_pos', 'tx_hash', 'height', 'height', 'value' - tx_num: int - tx_pos: int - tx_hash: bytes - height: int - value: int + tx_num: int # index of tx in chain order + tx_pos: int # tx output idx + tx_hash: bytes # txid + height: int # block height + value: int # in satoshis @attr.s(slots=True) @@ -48,12 +49,15 @@ class FlushData: headers = attr.ib() block_tx_hashes = attr.ib() # The following are flushed to the UTXO DB if undo_infos is not None - undo_infos = attr.ib() - adds = attr.ib() + undo_infos = attr.ib() # type: Sequence[Tuple[Sequence[bytes], int]] + adds = attr.ib() # type: Dict[bytes, bytes] # txid+out_idx -> hashX+tx_num+value_sats deletes = attr.ib() tip = attr.ib() +COMP_TXID_LEN = 4 + + class DB: '''Simple wrapper of the backend database for querying. @@ -84,7 +88,20 @@ def __init__(self, env): self.db_class = db_class(self.env.db_engine) self.history = History() + + # Key: b'u' + address_hashX + txout_idx + tx_num + # Value: the UTXO value as a 64-bit unsigned integer (in satoshis) + # "at address, at outpoint, there is a UTXO of value v" + # --- + # Key: b'h' + compressed_tx_hash + txout_idx + tx_num + # Value: hashX + # "some outpoint created a UTXO at address" + # --- + # Key: b'U' + block_height + # Value: byte-concat list of (hashX + tx_num + value_sats) + # "undo data: list of UTXOs spent at block height" self.utxo_db = None + self.utxo_flush_count = 0 self.fs_height = -1 self.fs_tx_count = 0 @@ -104,8 +121,11 @@ def __init__(self, env): self.merkle = Merkle() self.header_mc = MerkleCache(self.merkle, self.fs_block_hashes) + # on-disk: raw block headers in chain order self.headers_file = util.LogicalFile('meta/headers', 2, 16000000) + # on-disk: cumulative number of txs at the end of height N self.tx_counts_file = util.LogicalFile('meta/txcounts', 2, 2000000) + # on-disk: 32 byte txids in chain order, allows (tx_num -> txid) map self.hashes_file = util.LogicalFile('meta/hashes', 4, 16000000) if not self.coin.STATIC_BLOCK_HEADERS: self.headers_offsets_file = util.LogicalFile( @@ -125,7 +145,7 @@ async def _read_tx_counts(self): else: assert self.db_tx_count == 0 - async def _open_dbs(self, for_sync, compacting): + async def _open_dbs(self, for_sync: bool, compacting: bool): assert self.utxo_db is None # First UTXO DB @@ -287,7 +307,7 @@ def flush_fs(self, flush_data): def flush_history(self): self.history.flush() - def flush_utxo_db(self, batch, flush_data): + def flush_utxo_db(self, batch, flush_data: FlushData): '''Flush the cached DB writes and UTXO set to the batch.''' # Care is needed because the writes generated by flushing the # UTXO state may have keys in common with our write cache or @@ -305,11 +325,14 @@ def flush_utxo_db(self, batch, flush_data): # New UTXOs batch_put = batch.put for key, value in flush_data.adds.items(): - # suffix = tx_idx + tx_num - hashX = value[:-13] - suffix = key[-4:] + value[-13:-8] - batch_put(b'h' + key[:4] + suffix, hashX) - batch_put(b'u' + hashX + suffix, value[-8:]) + # key: txid+out_idx, value: hashX+tx_num+value_sats + hashX = value[:HASHX_LEN] + txout_idx = key[-4:] + tx_num = value[HASHX_LEN: HASHX_LEN+TXNUM_LEN] + value_sats = value[-8:] + suffix = txout_idx + tx_num + batch_put(b'h' + key[:COMP_TXID_LEN] + suffix, hashX) + batch_put(b'u' + hashX + suffix, value_sats) flush_data.adds.clear() # New undo information @@ -490,7 +513,7 @@ def min_undo_height(self, max_height): '''Returns a height from which we should store undo info.''' return max_height - self.env.reorg_limit + 1 - def undo_key(self, height): + def undo_key(self, height: int) -> bytes: '''DB key for undo information at the given height.''' return b'U' + pack_be_uint32(height) @@ -498,7 +521,9 @@ def read_undo_info(self, height): '''Read undo information from a file for the current height.''' return self.utxo_db.get(self.undo_key(height)) - def flush_undo_infos(self, batch_put, undo_infos): + def flush_undo_infos( + self, batch_put, undo_infos: Sequence[Tuple[Sequence[bytes], int]] + ): '''undo_infos is a list of (undo_info, height) pairs.''' for undo_info, height in undo_infos: batch_put(self.undo_key(height), b''.join(undo_info)) @@ -722,15 +747,16 @@ async def all_utxos(self, hashX): def read_utxos(): utxos = [] utxos_append = utxos.append - # Key: b'u' + address_hashX + tx_idx + tx_num + txnum_padding = bytes(8-TXNUM_LEN) + # Key: b'u' + address_hashX + txout_idx + tx_num # Value: the UTXO value as a 64-bit unsigned integer prefix = b'u' + hashX for db_key, db_value in self.utxo_db.iterator(prefix=prefix): - tx_pos, = unpack_le_uint32(db_key[-9:-5]) - tx_num, = unpack_le_uint64(db_key[-5:] + b'\0\0\0') + txout_idx, = unpack_le_uint32(db_key[-TXNUM_LEN-4:-TXNUM_LEN]) + tx_num, = unpack_le_uint64(db_key[-TXNUM_LEN:] + txnum_padding) value, = unpack_le_uint64(db_value) tx_hash, height = self.fs_tx_hash(tx_num) - utxos_append(UTXO(tx_num, tx_pos, tx_hash, height, value)) + utxos_append(UTXO(tx_num, txout_idx, tx_hash, height, value)) return utxos while True: @@ -753,15 +779,16 @@ def lookup_hashXs(): ''' def lookup_hashX(tx_hash, tx_idx): idx_packed = pack_le_uint32(tx_idx) + txnum_padding = bytes(8-TXNUM_LEN) # Key: b'h' + compressed_tx_hash + tx_idx + tx_num # Value: hashX - prefix = b'h' + tx_hash[:4] + idx_packed + prefix = b'h' + tx_hash[:COMP_TXID_LEN] + idx_packed # Find which entry, if any, the TX_HASH matches. for db_key, hashX in self.utxo_db.iterator(prefix=prefix): - tx_num_packed = db_key[-5:] - tx_num, = unpack_le_uint64(tx_num_packed + b'\0\0\0') + tx_num_packed = db_key[-TXNUM_LEN:] + tx_num, = unpack_le_uint64(tx_num_packed + txnum_padding) hash, _height = self.fs_tx_hash(tx_num) if hash == tx_hash: return hashX, idx_packed + tx_num_packed diff --git a/electrumx/server/history.py b/electrumx/server/history.py index 1974086..7a16209 100644 --- a/electrumx/server/history.py +++ b/electrumx/server/history.py @@ -20,6 +20,10 @@ unpack_be_uint16_from, unpack_le_uint64) +TXNUM_LEN = 5 +FLUSHID_LEN = 2 + + class History: DB_VERSIONS = (0, 1) @@ -35,6 +39,9 @@ def __init__(self): self.comp_cursor = -1 self.db_version = max(self.DB_VERSIONS) self.upgrade_cursor = -1 + + # Key: address_hashX + flush_id + # Value: sorted "list" of tx_nums in history of hashX self.db = None def open_db(self, db_class, for_sync, utxo_flush_count, compacting): @@ -91,7 +98,7 @@ def clear_excess(self, utxo_flush_count): keys = [] for key, _hist in self.db.iterator(prefix=b''): - flush_id, = unpack_be_uint16_from(key[-2:]) + flush_id, = unpack_be_uint16_from(key[-FLUSHID_LEN:]) if flush_id > utxo_flush_count: keys.append(key) @@ -122,7 +129,7 @@ def add_unflushed(self, hashXs_by_tx, first_tx_num): unflushed = self.unflushed count = 0 for tx_num, hashXs in enumerate(hashXs_by_tx, start=first_tx_num): - tx_numb = pack_le_uint64(tx_num)[:5] + tx_numb = pack_le_uint64(tx_num)[:TXNUM_LEN] hashXs = set(hashXs) for hashX in hashXs: unflushed[hashX] += tx_numb @@ -130,7 +137,7 @@ def add_unflushed(self, hashXs_by_tx, first_tx_num): self.unflushed_count += count def unflushed_memsize(self): - return len(self.unflushed) * 180 + self.unflushed_count * 5 + return len(self.unflushed) * 180 + self.unflushed_count * TXNUM_LEN def assert_flushed(self): assert not self.unflushed @@ -163,6 +170,7 @@ def backup(self, hashXs, tx_count): bisect_left = bisect.bisect_left chunks = util.chunks + txnum_padding = bytes(8-TXNUM_LEN) with self.db.write_batch() as batch: for hashX in sorted(hashXs): deletes = [] @@ -170,13 +178,13 @@ def backup(self, hashXs, tx_count): for key, hist in self.db.iterator(prefix=hashX, reverse=True): a = array( 'Q', - b''.join(item + b'\0\0\0' for item in chunks(hist, 5)) + b''.join(item + txnum_padding for item in chunks(hist, TXNUM_LEN)) ) # Remove all history entries >= tx_count idx = bisect_left(a, tx_count) nremoves += len(a) - idx if idx > 0: - puts[key] = hist[:5 * idx] + puts[key] = hist[:TXNUM_LEN * idx] break deletes.append(key) @@ -195,11 +203,12 @@ def get_txnums(self, hashX, limit=1000): limit to None to get them all. ''' limit = util.resolve_limit(limit) chunks = util.chunks + txnum_padding = bytes(8-TXNUM_LEN) for _key, hist in self.db.iterator(prefix=hashX): - for tx_numb in chunks(hist, 5): + for tx_numb in chunks(hist, TXNUM_LEN): if limit == 0: return - tx_num, = unpack_le_uint64(tx_numb + b'\0\0\0') + tx_num, = unpack_le_uint64(tx_numb + txnum_padding) yield tx_num limit -= 1 @@ -245,17 +254,17 @@ def _compact_hashX(self, hashX, hist_map, hist_list, write_items, keys_to_delete): '''Compres history for a hashX. hist_list is an ordered list of the histories to be compressed.''' - # History entries (tx numbers) are 4 bytes each. Distribute + # History entries (tx numbers) are TXNUM_LEN bytes each. Distribute # over rows of up to 50KB in size. A fixed row size means # future compactions will not need to update the first N - 1 # rows. - max_row_size = self.max_hist_row_entries * 5 + max_row_size = self.max_hist_row_entries * TXNUM_LEN full_hist = b''.join(hist_list) nrows = (len(full_hist) + max_row_size - 1) // max_row_size if nrows > 4: self.logger.info( f'hashX {hash_to_hex_str(hashX)} is large: ' - f'{len(full_hist) // 5:,d} entries across {nrows:,d} rows' + f'{len(full_hist) // TXNUM_LEN:,d} entries across {nrows:,d} rows' ) # Find what history needs to be written, and what keys need to @@ -284,13 +293,13 @@ def _compact_prefix(self, prefix, write_items, keys_to_delete): hist_map = {} hist_list = [] - key_len = HASHX_LEN + 2 + key_len = HASHX_LEN + FLUSHID_LEN write_size = 0 for key, hist in self.db.iterator(prefix=prefix): # Ignore non-history entries if len(key) != key_len: continue - hashX = key[:-2] + hashX = key[:-FLUSHID_LEN] if hashX != prior_hashX and prior_hashX: write_size += self._compact_hashX(prior_hashX, hist_map, hist_list, write_items, diff --git a/electrumx/server/mempool.py b/electrumx/server/mempool.py index 208b209..764aeac 100644 --- a/electrumx/server/mempool.py +++ b/electrumx/server/mempool.py @@ -12,6 +12,7 @@ from abc import ABC, abstractmethod from asyncio import Lock from collections import defaultdict +from typing import Sequence, Tuple import attr from aiorpcx import TaskGroup, run_in_thread, sleep @@ -23,7 +24,7 @@ @attr.s(slots=True) class MemPoolTx: - prevouts = attr.ib() + prevouts = attr.ib() # type: Sequence[Tuple[bytes, int]] # A pair is a (hashX, value) tuple in_pairs = attr.ib() out_pairs = attr.ib() From 029ffb3874aa9ba67774006cafad02f226daa5e1 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 21 Oct 2020 18:13:53 +0200 Subject: [PATCH 034/386] add some type hints --- electrumx/lib/coins.py | 12 ++++++++++-- electrumx/lib/server_base.py | 8 ++++++-- electrumx/server/block_processor.py | 27 ++++++++++++++++++--------- electrumx/server/daemon.py | 14 +++++++++++++- electrumx/server/db.py | 15 ++++++++++----- electrumx/server/env.py | 3 +++ electrumx/server/history.py | 14 +++++++++++++- electrumx/server/mempool.py | 7 +++++-- electrumx/server/peers.py | 8 +++++++- electrumx/server/session.py | 21 +++++++++++++++++++-- electrumx/server/storage.py | 3 ++- 11 files changed, 106 insertions(+), 26 deletions(-) diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 4bc862a..5427b26 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -35,7 +35,7 @@ from decimal import Decimal from functools import partial from hashlib import sha256 -from typing import Sequence +from typing import Sequence, Tuple import electrumx.lib.util as util from electrumx.lib.hash import Base58, double_sha256, hash_to_hex_str @@ -43,6 +43,7 @@ from electrumx.lib.script import (_match_ops, Script, ScriptError, ScriptPubKey, OpCodes) import electrumx.lib.tx as lib_tx +from electrumx.lib.tx import Tx import electrumx.lib.tx_dash as lib_tx_dash import electrumx.lib.tx_axe as lib_tx_axe import electrumx.server.block_processor as block_proc @@ -56,7 +57,7 @@ class Block: __slots__ = "raw", "header", "transactions" raw: bytes header: bytes - transactions: Sequence + transactions: Sequence[Tuple[Tx, bytes]] class CoinError(Exception): @@ -98,6 +99,13 @@ class Coin: CRASH_CLIENT_VER = None BLACKLIST_URL = None + TX_COUNT: int + TX_COUNT_HEIGHT: int + TX_PER_BLOCK: int + RPC_PORT: int + NAME: str + NET: str + @classmethod def lookup_coin_class(cls, name, net): '''Return a coin class given name and network. diff --git a/electrumx/lib/server_base.py b/electrumx/lib/server_base.py index 79cacf3..bd1051a 100644 --- a/electrumx/lib/server_base.py +++ b/electrumx/lib/server_base.py @@ -16,11 +16,15 @@ import time from contextlib import suppress from functools import partial +from typing import TYPE_CHECKING from aiorpcx import spawn from electrumx.lib.util import class_logger +if TYPE_CHECKING: + from electrumx.server.env import Env + class ServerBase: '''Base class server implementation. @@ -37,7 +41,7 @@ class ServerBase: SUPPRESS_TASK_REGEX = re.compile('accept_connection2') PYTHON_MIN_VERSION = (3, 6) - def __init__(self, env): + def __init__(self, env: 'Env'): '''Save the environment, perform basic sanity checks, and set the event loop policy. ''' @@ -65,7 +69,7 @@ def __init__(self, env): 'To continue as root anyway, restart with ' 'environment variable ALLOW_ROOT non-empty') - async def serve(self, shutdown_event): + async def serve(self, shutdown_event: asyncio.Event): '''Override to provide the main server functionality. Run as a task that will be cancelled to request shutdown. diff --git a/electrumx/server/block_processor.py b/electrumx/server/block_processor.py index 4709478..2700ccc 100644 --- a/electrumx/server/block_processor.py +++ b/electrumx/server/block_processor.py @@ -11,26 +11,31 @@ import asyncio import time -from typing import Sequence, Tuple, List, Callable +from typing import Sequence, Tuple, List, Callable, Optional, TYPE_CHECKING, Type from aiorpcx import TaskGroup, run_in_thread, CancelledError import electrumx -from electrumx.server.daemon import DaemonError +from electrumx.server.daemon import DaemonError, Daemon from electrumx.lib.hash import hash_to_hex_str, HASHX_LEN from electrumx.lib.script import is_unspendable_legacy, is_unspendable_genesis from electrumx.lib.util import ( chunks, class_logger, pack_le_uint32, pack_le_uint64, unpack_le_uint64 ) from electrumx.lib.tx import Tx -from electrumx.server.db import FlushData, COMP_TXID_LEN +from electrumx.server.db import FlushData, COMP_TXID_LEN, DB from electrumx.server.history import TXNUM_LEN +if TYPE_CHECKING: + from electrumx.lib.coins import Coin + from electrumx.server.env import Env + from electrumx.server.controller import Notifications + class Prefetcher: '''Prefetches blocks (in the forward direction only).''' - def __init__(self, daemon, coin, blocks_event): + def __init__(self, daemon: 'Daemon', coin: Type['Coin'], blocks_event: asyncio.Event): self.logger = class_logger(__name__, self.__class__.__name__) self.daemon = daemon self.coin = coin @@ -157,7 +162,7 @@ class BlockProcessor: Coordinate backing up in case of chain reorganisations. ''' - def __init__(self, env, db, daemon, notifications): + def __init__(self, env: 'Env', db: DB, daemon: Daemon, notifications: 'Notifications'): self.env = env self.db = db self.daemon = daemon @@ -173,7 +178,7 @@ def __init__(self, env, db, daemon, notifications): self.touched = set() self.reorg_count = 0 self.height = -1 - self.tip = None + self.tip = None # type: Optional[bytes] self.tx_count = 0 self._caught_up_event = None @@ -252,7 +257,7 @@ async def reorg_chain(self, count=None): self.logger.info(f'faking a reorg of {count:,d} blocks') await self.flush(True) - async def get_raw_blocks(last_height, hex_hashes): + async def get_raw_blocks(last_height, hex_hashes) -> Sequence[bytes]: heights = range(last_height, last_height - len(hex_hashes), -1) try: blocks = [self.db.read_raw_block(height) for height in heights] @@ -465,7 +470,7 @@ def advance_txs( return undo_info - def backup_blocks(self, raw_blocks): + def backup_blocks(self, raw_blocks: Sequence[bytes]): '''Backup the raw blocks and flush. The blocks should be in order of decreasing height, starting at. @@ -494,7 +499,11 @@ def backup_blocks(self, raw_blocks): self.logger.info(f'backed up to height {self.height:,d}') - def backup_txs(self, txs, is_unspendable): + def backup_txs( + self, + txs: Sequence[Tuple[Tx, bytes]], + is_unspendable: Callable[[bytes], bool], + ): # Prevout values, in order down the block (coinbase first if present) # undo_info is in reverse block order undo_info = self.db.read_undo_info(self.height) diff --git a/electrumx/server/daemon.py b/electrumx/server/daemon.py index 16b3d74..33f9b99 100644 --- a/electrumx/server/daemon.py +++ b/electrumx/server/daemon.py @@ -13,6 +13,7 @@ import time from calendar import timegm from struct import pack +from typing import TYPE_CHECKING, Type import aiohttp from aiorpcx import JSONRPC @@ -23,6 +24,9 @@ json_serialize, pack_varint, unpack_le_uint16_from) +if TYPE_CHECKING: + from electrumx.lib.coins import Coin + class DaemonError(Exception): '''Raised when the daemon returns an error in its results.''' @@ -43,7 +47,15 @@ class Daemon: WARMING_UP = -28 id_counter = itertools.count() - def __init__(self, coin, url, *, max_workqueue=10, init_retry=0.25, max_retry=4.0): + def __init__( + self, + coin: Type['Coin'], + url, + *, + max_workqueue=10, + init_retry=0.25, + max_retry=4.0, + ): self.coin = coin self.logger = class_logger(__name__, self.__class__.__name__) self.url_index = None diff --git a/electrumx/server/db.py b/electrumx/server/db.py index 1ea3204..cc18250 100644 --- a/electrumx/server/db.py +++ b/electrumx/server/db.py @@ -16,7 +16,7 @@ from bisect import bisect_right from dataclasses import dataclass from glob import glob -from typing import Dict, Sequence, Tuple +from typing import Dict, Sequence, Tuple, Optional, TYPE_CHECKING import attr from aiorpcx import run_in_thread, sleep @@ -28,9 +28,12 @@ formatted_time, pack_be_uint16, pack_be_uint32, pack_le_uint64, pack_le_uint32, unpack_le_uint32, unpack_be_uint32, unpack_le_uint64 ) -from electrumx.server.storage import db_class +from electrumx.server.storage import db_class, Storage from electrumx.server.history import History, TXNUM_LEN +if TYPE_CHECKING: + from electrumx.server.env import Env + @dataclass(order=True) class UTXO: @@ -51,7 +54,7 @@ class FlushData: # The following are flushed to the UTXO DB if undo_infos is not None undo_infos = attr.ib() # type: Sequence[Tuple[Sequence[bytes], int]] adds = attr.ib() # type: Dict[bytes, bytes] # txid+out_idx -> hashX+tx_num+value_sats - deletes = attr.ib() + deletes = attr.ib() # type: Sequence[bytes] # b'h' db keys, and b'u' db keys tip = attr.ib() @@ -67,10 +70,12 @@ class DB: DB_VERSIONS = (6, 7, 8) + utxo_db: Optional['Storage'] + class DBError(Exception): '''Raised on general DB errors generally indicating corruption.''' - def __init__(self, env): + def __init__(self, env: 'Env'): self.logger = util.class_logger(__name__, self.__class__.__name__) self.env = env self.coin = env.coin @@ -107,7 +112,7 @@ def __init__(self, env): self.fs_tx_count = 0 self.db_height = -1 self.db_tx_count = 0 - self.db_tip = None + self.db_tip = None # type: Optional[bytes] self.tx_counts = None self.last_flush = time.time() self.last_flush_tx_count = 0 diff --git a/electrumx/server/env.py b/electrumx/server/env.py index 04bb87f..b8fe8d7 100644 --- a/electrumx/server/env.py +++ b/electrumx/server/env.py @@ -10,6 +10,7 @@ import re from ipaddress import IPv4Address, IPv6Address +from typing import Type from aiorpcx import Service, ServicePart from electrumx.lib.coins import Coin @@ -31,6 +32,8 @@ class Env(EnvBase): SSL_PROTOCOLS = {'ssl', 'wss'} KNOWN_PROTOCOLS = {'ssl', 'tcp', 'ws', 'wss', 'rpc'} + coin: Type[Coin] + def __init__(self, coin=None): super().__init__() self.obsolete(["MAX_SUBSCRIPTIONS", "MAX_SUBS", "MAX_SESSION_SUBS", "BANDWIDTH_LIMIT", diff --git a/electrumx/server/history.py b/electrumx/server/history.py index 7a16209..73388a5 100644 --- a/electrumx/server/history.py +++ b/electrumx/server/history.py @@ -13,12 +13,16 @@ import time from array import array from collections import defaultdict +from typing import TYPE_CHECKING, Type, Optional import electrumx.lib.util as util from electrumx.lib.hash import HASHX_LEN, hash_to_hex_str from electrumx.lib.util import (pack_be_uint16, pack_le_uint64, unpack_be_uint16_from, unpack_le_uint64) +if TYPE_CHECKING: + from electrumx.server.storage import Storage + TXNUM_LEN = 5 FLUSHID_LEN = 2 @@ -28,6 +32,8 @@ class History: DB_VERSIONS = (0, 1) + db: Optional['Storage'] + def __init__(self): self.logger = util.class_logger(__name__, self.__class__.__name__) # For history compaction @@ -44,7 +50,13 @@ def __init__(self): # Value: sorted "list" of tx_nums in history of hashX self.db = None - def open_db(self, db_class, for_sync, utxo_flush_count, compacting): + def open_db( + self, + db_class: Type['Storage'], + for_sync: bool, + utxo_flush_count: int, + compacting: bool, + ): self.db = db_class('hist', for_sync) self.read_state() self.clear_excess(utxo_flush_count) diff --git a/electrumx/server/mempool.py b/electrumx/server/mempool.py index 764aeac..971131b 100644 --- a/electrumx/server/mempool.py +++ b/electrumx/server/mempool.py @@ -12,7 +12,7 @@ from abc import ABC, abstractmethod from asyncio import Lock from collections import defaultdict -from typing import Sequence, Tuple +from typing import Sequence, Tuple, TYPE_CHECKING, Type import attr from aiorpcx import TaskGroup, run_in_thread, sleep @@ -21,6 +21,9 @@ from electrumx.lib.util import class_logger, chunks from electrumx.server.db import UTXO +if TYPE_CHECKING: + from electrumx.lib.coins import Coin + @attr.s(slots=True) class MemPoolTx: @@ -102,7 +105,7 @@ class MemPool: hashXs: hashX -> set of all hashes of txs touching the hashX ''' - def __init__(self, coin, api, refresh_secs=5.0, log_status_secs=60.0): + def __init__(self, coin: Type['Coin'], api: MemPoolAPI, refresh_secs=5.0, log_status_secs=60.0): assert isinstance(api, MemPoolAPI) self.coin = coin self.api = api diff --git a/electrumx/server/peers.py b/electrumx/server/peers.py index f0ce3e8..c2bc69f 100644 --- a/electrumx/server/peers.py +++ b/electrumx/server/peers.py @@ -14,6 +14,7 @@ import time from collections import Counter, defaultdict from ipaddress import IPv4Address, IPv6Address +from typing import TYPE_CHECKING, Type import aiohttp from aiorpcx import (Event, Notification, RPCError, RPCSession, SOCKSError, @@ -24,6 +25,11 @@ from electrumx.lib.peer import Peer from electrumx.lib.util import class_logger, json_deserialize +if TYPE_CHECKING: + from electrumx.server.env import Env + from electrumx.server.db import DB + + PEER_GOOD, PEER_STALE, PEER_NEVER, PEER_BAD = range(4) STATUS_DESCS = ('good', 'stale', 'never', 'bad') STALE_SECS = 3 * 3600 @@ -59,7 +65,7 @@ class PeerManager: Attempts to maintain a connection with up to 8 peers. Issues a 'peers.subscribe' RPC to them and tells them our data. ''' - def __init__(self, env, db): + def __init__(self, env: 'Env', db: 'DB'): self.logger = class_logger(__name__, self.__class__.__name__) # Initialise the Peer class Peer.DEFAULT_PORTS = env.coin.PEER_DEFAULT_PORTS diff --git a/electrumx/server/session.py b/electrumx/server/session.py index eebaf34..5810397 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -17,7 +17,8 @@ from collections import defaultdict from functools import partial from ipaddress import IPv4Address, IPv6Address -from typing import Optional +from typing import Optional, TYPE_CHECKING +import asyncio import attr import pylru @@ -34,6 +35,14 @@ from electrumx.server.daemon import DaemonError from electrumx.server.peers import PeerManager +if TYPE_CHECKING: + from electrumx.server.db import DB + from electrumx.server.env import Env + from electrumx.server.block_processor import BlockProcessor + from electrumx.server.daemon import Daemon + from electrumx.server.mempool import MemPool + + BAD_REQUEST = 1 DAEMON_ERROR = 2 @@ -108,7 +117,15 @@ class SessionReferences: class SessionManager: '''Holds global state about all sessions.''' - def __init__(self, env, db, bp, daemon, mempool, shutdown_event): + def __init__( + self, + env: 'Env', + db: 'DB', + bp: 'BlockProcessor', + daemon: 'Daemon', + mempool: 'MemPool', + shutdown_event: asyncio.Event, + ): env.max_send = max(350000, env.max_send) self.env = env self.db = db diff --git a/electrumx/server/storage.py b/electrumx/server/storage.py index 4fab068..a45c270 100644 --- a/electrumx/server/storage.py +++ b/electrumx/server/storage.py @@ -9,11 +9,12 @@ import os from functools import partial +from typing import Type import electrumx.lib.util as util -def db_class(name): +def db_class(name) -> Type['Storage']: '''Returns a DB engine class.''' for db_class in util.subclasses(Storage): if db_class.__name__.lower() == name.lower(): From bb3a2c99dd9b092a8a8c1647f931e95698f5404b Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sat, 20 Jun 2020 05:19:56 +0200 Subject: [PATCH 035/386] sessions: cache estimatefee --- electrumx/lib/coins.py | 23 ++++++++++++ electrumx/server/block_processor.py | 4 +++ electrumx/server/session.py | 55 +++++++++++++++++++++++++---- 3 files changed, 76 insertions(+), 6 deletions(-) diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 5427b26..3a66797 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -98,6 +98,7 @@ class Coin: PEERS = [] CRASH_CLIENT_VER = None BLACKLIST_URL = None + ESTIMATEFEE_MODES = (None, 'CONSERVATIVE', 'ECONOMICAL') TX_COUNT: int TX_COUNT_HEIGHT: int @@ -268,6 +269,13 @@ def decimal_value(cls, value): def warn_old_client_on_tx_broadcast(cls, _client_ver): return False + @classmethod + def bucket_estimatefee_block_target(cls, n: int) -> int: + '''For caching purposes, it might be desirable to restrict the + set of values that can be queried as an estimatefee block target. + ''' + return n + class AuxPowMixin: STATIC_BLOCK_HEADERS = False @@ -619,6 +627,21 @@ def warn_old_client_on_tx_broadcast(cls, client_ver): '

') return False + @classmethod + def bucket_estimatefee_block_target(cls, n: int) -> int: + # values based on https://github.com/bitcoin/bitcoin/blob/af05bd9e1e362c3148e3b434b7fac96a9a5155a1/src/policy/fees.h#L131 # noqa + if n <= 1: + return 1 + if n <= 12: + return n + if n == 25: # so common that we make an exception for it + return n + if n <= 48: + return n // 2 * 2 + if n <= 1008: + return n // 24 * 24 + return 1008 + class BitcoinSegwit(Bitcoin): NAME = "BitcoinSegwit" # support legacy name diff --git a/electrumx/server/block_processor.py b/electrumx/server/block_processor.py index 2700ccc..04ac814 100644 --- a/electrumx/server/block_processor.py +++ b/electrumx/server/block_processor.py @@ -169,6 +169,7 @@ def __init__(self, env: 'Env', db: DB, daemon: Daemon, notifications: 'Notificat self.notifications = notifications self.coin = env.coin + # blocks_event: set when new blocks are put on the queue by the Prefetcher, to be processed self.blocks_event = asyncio.Event() self.prefetcher = Prefetcher(daemon, env.coin, self.blocks_event) self.logger = class_logger(__name__, self.__class__.__name__) @@ -179,6 +180,7 @@ def __init__(self, env: 'Env', db: DB, daemon: Daemon, notifications: 'Notificat self.reorg_count = 0 self.height = -1 self.tip = None # type: Optional[bytes] + self.tip_advanced_event = asyncio.Event() self.tx_count = 0 self._caught_up_event = None @@ -413,6 +415,8 @@ def advance_blocks(self, blocks): self.height = height self.headers += headers self.tip = self.coin.header_hash(headers[-1]) + self.tip_advanced_event.set() + self.tip_advanced_event.clear() def advance_txs( self, diff --git a/electrumx/server/session.py b/electrumx/server/session.py index 5810397..9ff1e02 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -7,6 +7,7 @@ '''Classes for local RPC server and remote client TCP/SSL servers.''' +import asyncio import codecs import datetime import itertools @@ -152,6 +153,7 @@ def __init__( self._merkle_cache = pylru.lrucache(1000) self._merkle_lookups = 0 self._merkle_hits = 0 + self.estimatefee_cache = pylru.lrucache(1000) self.notified_height = None self.hsub_results = None self._task_group = TaskGroup() @@ -269,7 +271,7 @@ async def _clear_stale_sessions(self): del stale_sessions async def _handle_chain_reorgs(self): - '''Clear caches on chain reorgs.''' + '''Clear certain caches on chain reorgs.''' while True: await self.bp.backed_up_event.wait() self.logger.info(f'reorg signalled; clearing tx_hashes and merkle caches') @@ -277,6 +279,12 @@ async def _handle_chain_reorgs(self): self._tx_hashes_cache.clear() self._merkle_cache.clear() + async def _handle_new_blocks(self): + '''Clear certain caches if the chain tip advanced.''' + while True: + await self.bp.tip_advanced_event.wait() + self.estimatefee_cache.clear() + async def _recalc_concurrency(self): '''Periodically recalculate session concurrency.''' session_class = self.env.coin.SESSIONCLS @@ -632,6 +640,7 @@ async def serve(self, notifications, event): await group.spawn(self.peer_mgr.discover_peers()) await group.spawn(self._clear_stale_sessions()) await group.spawn(self._handle_chain_reorgs()) + await group.spawn(self._handle_new_blocks()) await group.spawn(self._recalc_concurrency()) await group.spawn(self._log_sessions()) await group.spawn(self._manage_servers()) @@ -839,7 +848,15 @@ class SessionBase(RPCSession): session_counter = itertools.count() log_new = False - def __init__(self, session_mgr, db, mempool, peer_mgr, kind, transport): + def __init__( + self, + session_mgr: 'SessionManager', + db: 'DB', + mempool: 'MemPool', + peer_mgr: 'PeerManager', + kind: str, + transport, + ): connection = JSONRPCConnection(JSONRPCAutoDetect) super().__init__(transport, connection=connection) self.session_mgr = session_mgr @@ -1278,11 +1295,37 @@ async def estimatefee(self, number, mode=None): mode: CONSERVATIVE or ECONOMICAL estimation mode ''' number = non_negative_integer(number) - self.bump_cost(2.0) - if mode: - return await self.daemon_request('estimatefee', number, mode) + # use whitelist for mode, otherwise it would be easy to force a cache miss: + if mode not in self.coin.ESTIMATEFEE_MODES: + raise RPCError(BAD_REQUEST, f'unknown estimatefee mode: {mode}') + self.bump_cost(0.1) + + number = self.coin.bucket_estimatefee_block_target(number) + cache = self.session_mgr.estimatefee_cache + + cache_item = cache.get((number, mode)) + if cache_item is not None: + feerate, lock = cache_item + if feerate is not None: + return feerate else: - return await self.daemon_request('estimatefee', number) + # create lock now, store it, and only then await on it + lock = asyncio.Lock() + cache[(number, mode)] = (None, lock) + async with lock: + cache_item = cache.get((number, mode)) + if cache_item is not None: + feerate, lock = cache_item + if feerate is not None: + return feerate + self.bump_cost(2.0) # cache miss incurs extra cost + if mode: + feerate = await self.daemon_request('estimatefee', number, mode) + else: + feerate = await self.daemon_request('estimatefee', number) + cache[(number, mode)] = (feerate, lock) + assert feerate is not None + return feerate async def ping(self): '''Serves as a connection keep-alive mechanism and for the client to From f146e82601ebf9a568d258a039b0f1eef4cca321 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 23 Oct 2020 16:27:29 +0200 Subject: [PATCH 036/386] daemon: cache getnetworkinfo This is used by the 'server.banner' and 'blockchain.relayfee' protocol cmds. --- electrumx/server/daemon.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/electrumx/server/daemon.py b/electrumx/server/daemon.py index 33f9b99..54e3fcb 100644 --- a/electrumx/server/daemon.py +++ b/electrumx/server/daemon.py @@ -70,6 +70,9 @@ def __init__( self.available_rpcs = {} self.session = None + self._networkinfo_cache = (None, 0) + self._networkinfo_lock = asyncio.Lock() + async def __aenter__(self): self.session = aiohttp.ClientSession(connector=self.connector()) return self @@ -260,7 +263,13 @@ async def estimatefee(self, block_count, estimate_mode=None): async def getnetworkinfo(self): '''Return the result of the 'getnetworkinfo' RPC call.''' - return await self._send_single('getnetworkinfo') + async with self._networkinfo_lock: + cache_val, cache_time = self._networkinfo_cache + if time.time() - cache_time < 60: # seconds + return cache_val + val = await self._send_single('getnetworkinfo') + self._networkinfo_cache = (val, time.time()) + return val async def relayfee(self): '''The minimum fee a low-priority tx must pay in order to be accepted From 602c8e3da4faafaf4b6806c5ad7783007f81acd7 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 25 Oct 2020 20:40:06 +0100 Subject: [PATCH 037/386] setup.py: prepare for distributing on PyPI --- LICENCE | 1 + setup.py | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/LICENCE b/LICENCE index eac7aaf..08f48bc 100644 --- a/LICENCE +++ b/LICENCE @@ -1,3 +1,4 @@ +Copyright (c) 2020, The Electrum developers Copyright (c) 2016-2017, Neil Booth All rights reserved. diff --git a/setup.py b/setup.py index 0668e9e..fe794be 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ version = '1.15.0' setuptools.setup( - name='electrumX', + name='e-x', version=version, scripts=['electrumx_server', 'electrumx_rpc', 'electrumx_compact_history'], python_requires='>=3.7', @@ -26,8 +26,8 @@ }, packages=setuptools.find_packages(include=('electrumx*',)), description='ElectrumX Server', - author='Neil Booth', - author_email='kyuupichan@gmail.com', + author='Electrum developers', + author_email='electrumdev@gmail.com', license='MIT Licence', url='https://github.com/spesmilo/electrumx', long_description='Server implementation for the Electrum protocol', From ac60470ee9c2e403d36cba38acbbacc9f4552f62 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 26 Oct 2020 02:56:49 +0100 Subject: [PATCH 038/386] mempool: split out histogram compaction for easier testing, and add tests --- electrumx/server/mempool.py | 18 +++++++++++++--- tests/server/test_mempool.py | 41 ++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/electrumx/server/mempool.py b/electrumx/server/mempool.py index 971131b..e1ae981 100644 --- a/electrumx/server/mempool.py +++ b/electrumx/server/mempool.py @@ -12,7 +12,7 @@ from abc import ABC, abstractmethod from asyncio import Lock from collections import defaultdict -from typing import Sequence, Tuple, TYPE_CHECKING, Type +from typing import Sequence, Tuple, TYPE_CHECKING, Type, Dict import attr from aiorpcx import TaskGroup, run_in_thread, sleep @@ -147,6 +147,19 @@ def _update_histogram(self, bin_size): for tx in self.txs.values(): histogram[tx.fee // tx.size] += tx.size + compact = self._compress_histogram(histogram, bin_size=bin_size) + self.logger.info(f'compact fee histogram: {compact}') + self.cached_compact_histogram = compact + + @classmethod + def _compress_histogram( + cls, histogram: Dict[float, int], *, bin_size: int + ) -> Sequence[Tuple[float, int]]: + '''Calculate and return a compact fee histogram as needed for + "mempool.get_fee_histogram" protocol request. + + histogram: feerate (sat/byte) -> total size in bytes of txs that pay approx feerate + ''' # Now compact it. For efficiency, get_fees returns a # compact histogram with variable bin size. The compact # histogram is an array of (fee_rate, vsize) values. @@ -165,8 +178,7 @@ def _update_histogram(self, bin_size): r += cum_size - bin_size cum_size = 0 bin_size *= 1.1 - self.logger.info(f'compact fee histogram: {compact}') - self.cached_compact_histogram = compact + return compact def _accept_transactions(self, tx_map, utxo_map, touched): '''Accept transactions in tx_map to the mempool if all their inputs diff --git a/tests/server/test_mempool.py b/tests/server/test_mempool.py index 3dda425..d2ec75d 100644 --- a/tests/server/test_mempool.py +++ b/tests/server/test_mempool.py @@ -324,6 +324,47 @@ async def test_compact_fee_histogram(): assert all(rates[n] < rates[n - 1] for n in range(1, len(rates))) +def test_compress_histogram(): + histogram = { + 10: 100_000, + 11: 1_000, + 12: 10_000_000, + 13: 1_000, + 14: 1_000, + 15: 1_000, + 16: 1_000, + 17: 1_000, + 18: 900_000, + 19: 1_000, + 20: 1_000, + 21: 75_000, + 22: 1_000, + } + compact = MemPool._compress_histogram(histogram, bin_size=100_000) + assert compact == [ + (18, 978000), + (17, 1000), + (16, 1000), + (15, 1000), + (14, 1000), + (13, 1000), + (12, 10000000), + (11, 1000), + (10, 100000), + ] + + histogram = { + 1.0: 10_000_000, + 1.1: 30_000, + 1.2: 40_000, + 10: 500_000, + 10.1: 1_000, + 11: 50_000, + } + compact = MemPool._compress_histogram(histogram, bin_size=100_000) + assert compact == [(10, 551000), (1.2, 40000), (1.1, 30000), (1.0, 10000000)] + + @pytest.mark.asyncio async def test_potential_spends(): api = API() From 3effc8a87650c01e1568ca96515911c09f556a5a Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 26 Oct 2020 03:40:18 +0100 Subject: [PATCH 039/386] mempool: change fee histogram compaction logic try to put a single item directly left of large lumps, instead of many items to the right of them Anecdote: right now, on mainnet, there are lots of ~1 sat/byte txs in the mempool, but otherwise the mempool is practically empty, so e.g. 1.1 sat/byte tx could get mined fast. Server (without this diff) is sending a compact histogram of [[89, 127578], [10, 82676], [1, 14238757]]. So what should the client suggest? 10 sat/byte? :/ --- electrumx/server/mempool.py | 14 +++++++++++--- tests/server/test_mempool.py | 14 ++------------ 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/electrumx/server/mempool.py b/electrumx/server/mempool.py index e1ae981..25dd0cd 100644 --- a/electrumx/server/mempool.py +++ b/electrumx/server/mempool.py @@ -168,16 +168,24 @@ def _compress_histogram( # [rate_(n-1), rate_n)], and rate_(n-1) > rate_n. # Intervals are chosen to create tranches containing at # least 100kb of transactions + assert bin_size > 0 compact = [] cum_size = 0 - r = 0 # ? + prev_fee_rate = None for fee_rate, size in sorted(histogram.items(), reverse=True): + # if there is a big lump of txns at this specific size, + # consider adding the previous item now (if not added already) + if size > 2 * bin_size and prev_fee_rate is not None and cum_size > 0: + compact.append((prev_fee_rate, cum_size)) + cum_size = 0 + bin_size *= 1.1 + # now consider adding this item cum_size += size - if cum_size + r > bin_size: + if cum_size > bin_size: compact.append((fee_rate, cum_size)) - r += cum_size - bin_size cum_size = 0 bin_size *= 1.1 + prev_fee_rate = fee_rate return compact def _accept_transactions(self, tx_map, utxo_map, touched): diff --git a/tests/server/test_mempool.py b/tests/server/test_mempool.py index d2ec75d..b5a5095 100644 --- a/tests/server/test_mempool.py +++ b/tests/server/test_mempool.py @@ -341,17 +341,7 @@ def test_compress_histogram(): 22: 1_000, } compact = MemPool._compress_histogram(histogram, bin_size=100_000) - assert compact == [ - (18, 978000), - (17, 1000), - (16, 1000), - (15, 1000), - (14, 1000), - (13, 1000), - (12, 10000000), - (11, 1000), - (10, 100000), - ] + assert compact == [(19, 78000), (18, 900000), (13, 5000), (12, 10000000)] histogram = { 1.0: 10_000_000, @@ -362,7 +352,7 @@ def test_compress_histogram(): 11: 50_000, } compact = MemPool._compress_histogram(histogram, bin_size=100_000) - assert compact == [(10, 551000), (1.2, 40000), (1.1, 30000), (1.0, 10000000)] + assert compact == [(10.1, 51000), (10, 500000), (1.1, 70000), (1.0, 10000000)] @pytest.mark.asyncio From 21e3dd0d34e868bc77e98a4489386e1443546cca Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Mon, 26 Oct 2020 01:38:51 -0500 Subject: [PATCH 040/386] Fix redundant height slot declaration in db.UTXO class. --- electrumx/server/db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electrumx/server/db.py b/electrumx/server/db.py index cc18250..8a5f5d6 100644 --- a/electrumx/server/db.py +++ b/electrumx/server/db.py @@ -37,7 +37,7 @@ @dataclass(order=True) class UTXO: - __slots__ = 'tx_num', 'tx_pos', 'tx_hash', 'height', 'height', 'value' + __slots__ = 'tx_num', 'tx_pos', 'tx_hash', 'height', 'value' tx_num: int # index of tx in chain order tx_pos: int # tx output idx tx_hash: bytes # txid From 3068efc605c4c117850002a1a5f40ab026fac679 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 26 Oct 2020 14:38:05 +0100 Subject: [PATCH 041/386] require Python 3.7: follow-up left out from d7f8f1f321406f37047b867986e8c8dd92646cf2 --- electrumx/lib/server_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electrumx/lib/server_base.py b/electrumx/lib/server_base.py index bd1051a..ae5b234 100644 --- a/electrumx/lib/server_base.py +++ b/electrumx/lib/server_base.py @@ -39,7 +39,7 @@ class ServerBase: 'SSL error in data received|' 'socket.send() raised exception') SUPPRESS_TASK_REGEX = re.compile('accept_connection2') - PYTHON_MIN_VERSION = (3, 6) + PYTHON_MIN_VERSION = (3, 7) def __init__(self, env: 'Env'): '''Save the environment, perform basic sanity checks, and set the From ff1a440d5ece038d0377c8ca137b1667e58c096f Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Mon, 26 Oct 2020 08:45:25 -0500 Subject: [PATCH 042/386] FlushData.undo_infos and FlushData.deletes are lists (#69) We call the .clear method on them which is not supported in the more abstract Sequence class. --- electrumx/server/db.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/electrumx/server/db.py b/electrumx/server/db.py index 8a5f5d6..f7ad3e3 100644 --- a/electrumx/server/db.py +++ b/electrumx/server/db.py @@ -16,7 +16,7 @@ from bisect import bisect_right from dataclasses import dataclass from glob import glob -from typing import Dict, Sequence, Tuple, Optional, TYPE_CHECKING +from typing import Dict, List, Sequence, Tuple, Optional, TYPE_CHECKING import attr from aiorpcx import run_in_thread, sleep @@ -52,9 +52,9 @@ class FlushData: headers = attr.ib() block_tx_hashes = attr.ib() # The following are flushed to the UTXO DB if undo_infos is not None - undo_infos = attr.ib() # type: Sequence[Tuple[Sequence[bytes], int]] + undo_infos = attr.ib() # type: List[Tuple[Sequence[bytes], int]] adds = attr.ib() # type: Dict[bytes, bytes] # txid+out_idx -> hashX+tx_num+value_sats - deletes = attr.ib() # type: Sequence[bytes] # b'h' db keys, and b'u' db keys + deletes = attr.ib() # type: List[bytes] # b'h' db keys, and b'u' db keys tip = attr.ib() From 42fcd3b04b0ae3e0167479a81a2080c77e81f630 Mon Sep 17 00:00:00 2001 From: Stephan Oeste Date: Mon, 26 Oct 2020 20:13:22 +0100 Subject: [PATCH 043/386] servers default list: (bitcoin) add electrum.emzy.de (#71) --- electrumx/lib/coins.py | 1 + 1 file changed, 1 insertion(+) diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 3a66797..397e891 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -614,6 +614,7 @@ class Bitcoin(BitcoinMixin, Coin): 'currentlane.lovebitco.in s t', 'electrum.jochen-hoenicke.de s50005 t50003', 'vps5.hsmiths.com s', + 'electrum.emzy.de s', ] @classmethod From 8204829e7c15e288bf620c2295cb6495314f162b Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 27 Oct 2020 05:01:18 +0100 Subject: [PATCH 044/386] fix memory leak fixes #66 That line turned the tuple to a **generator**. groups is iterated over twice: once when adding sessions, once when removing them. They will successfully be added, but never removed, as the generator will be exhausted after adding. arghhh :/ --- electrumx/server/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electrumx/server/session.py b/electrumx/server/session.py index 9ff1e02..ab3fea3 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -823,7 +823,7 @@ def add_session(self, session): self._session_group(self._timeslice_name(session), 0.03), self._session_group(self._ip_addr_group_name(session), 1.0), ) - groups = (group for group in groups if group is not None) + groups = tuple(group for group in groups if group is not None) self.sessions[session] = groups for group in groups: group.sessions.add(session) From 45937c5836f71cc8edfc2398b496ba99d17a6ce9 Mon Sep 17 00:00:00 2001 From: Michal Taborsky Date: Tue, 27 Oct 2020 14:47:18 +0100 Subject: [PATCH 045/386] Remove time-based groups (#70) * Remove time-based groups --- docs/rpc-interface.rst | 8 ++++---- electrumx/server/session.py | 4 ---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/docs/rpc-interface.rst b/docs/rpc-interface.rst index 9b90f8a..1ae69a7 100644 --- a/docs/rpc-interface.rst +++ b/docs/rpc-interface.rst @@ -136,10 +136,10 @@ Apart from very short intervals, typically after a new block or when a client ha connected, the number of unprocessed requests should be low, say 250 or fewer. If it is over 1,000 the server is overloaded. -Sessions are put into groups, primarily as an anti-DoS measure. Currently each session -goes into two groups: one for an IP subnet, and one based on the timeslice it connected -in. Each member of a group incurs a fraction of the costs of the other group members. -This appears in the `sessions_` list under the column XCost. +Sessions are put into groups, primarily as an anti-DoS measure. Each session goes into +a group based on its IP subnet (e.g. /24 for IPv4). Each member of a group incurs a fraction +of the costs of the other group members. This appears in the `sessions_` list under the +column XCost. groups ------ diff --git a/electrumx/server/session.py b/electrumx/server/session.py index ab3fea3..123096f 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -804,9 +804,6 @@ def _ip_addr_group_name(self, session) -> Optional[str]: return ':'.join(host.exploded.split(':')[:3]) # /48 return 'unknown_addr' - def _timeslice_name(self, session): - return f't{int(session.start_time - self.start_time) // 300}' - def _session_group(self, name: Optional[str], weight: float) -> Optional[SessionGroup]: if name is None: return None @@ -820,7 +817,6 @@ def add_session(self, session): self.session_event.set() # Return the session groups groups = ( - self._session_group(self._timeslice_name(session), 0.03), self._session_group(self._ip_addr_group_name(session), 1.0), ) groups = tuple(group for group in groups if group is not None) From 68fa091263849626df312256ee420353701bbe64 Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Tue, 27 Oct 2020 11:36:14 -0500 Subject: [PATCH 046/386] Use monotonic time for duration calculations where possible. (#68) --- electrumx/server/block_processor.py | 8 ++++---- electrumx/server/daemon.py | 2 +- electrumx/server/db.py | 20 ++++++++++---------- electrumx/server/history.py | 8 ++++---- electrumx/server/mempool.py | 4 ++-- electrumx/server/peers.py | 2 ++ electrumx/server/session.py | 1 + tests/lib/test_merkle.py | 4 ++-- 8 files changed, 26 insertions(+), 23 deletions(-) diff --git a/electrumx/server/block_processor.py b/electrumx/server/block_processor.py index 04ac814..dd4b9a5 100644 --- a/electrumx/server/block_processor.py +++ b/electrumx/server/block_processor.py @@ -225,14 +225,14 @@ async def check_and_advance_blocks(self, raw_blocks): chain = [self.tip] + [self.coin.header_hash(h) for h in headers[:-1]] if hprevs == chain: - start = time.time() + start = time.monotonic() await self.run_in_thread_with_lock(self.advance_blocks, blocks) await self._maybe_flush() if not self.db.first_sync: s = '' if len(blocks) == 1 else 's' blocks_size = sum(len(block) for block in raw_blocks) / 1_000_000 self.logger.info(f'processed {len(blocks):,d} block{s} size {blocks_size:.2f} MB ' - f'in {time.time() - start:.1f}s') + f'in {time.monotonic() - start:.1f}s') if self._caught_up_event.is_set(): await self.notifications.on_block(self.touched, self.height) self.touched = set() @@ -362,11 +362,11 @@ async def _maybe_flush(self): # performed on the DB. if self._caught_up_event.is_set(): await self.flush(True) - elif time.time() > self.next_cache_check: + elif time.monotonic() > self.next_cache_check: flush_arg = self.check_cache_size() if flush_arg is not None: await self.flush(flush_arg) - self.next_cache_check = time.time() + 30 + self.next_cache_check = time.monotonic() + 30 def check_cache_size(self): '''Flush a cache if it gets too big.''' diff --git a/electrumx/server/daemon.py b/electrumx/server/daemon.py index 54e3fcb..c58e188 100644 --- a/electrumx/server/daemon.py +++ b/electrumx/server/daemon.py @@ -133,7 +133,7 @@ async def _send(self, payload, processor): ''' def log_error(error): nonlocal last_error_log, retry - now = time.time() + now = time.monotonic() if now - last_error_log > 60: last_error_log = now self.logger.error(f'{error}. Retrying occasionally...') diff --git a/electrumx/server/db.py b/electrumx/server/db.py index f7ad3e3..fae1885 100644 --- a/electrumx/server/db.py +++ b/electrumx/server/db.py @@ -205,9 +205,9 @@ async def open_for_serving(self): async def populate_header_merkle_cache(self): self.logger.info('populating header merkle cache...') length = max(1, self.db_height - self.env.reorg_limit) - start = time.time() + start = time.monotonic() await self.header_mc.initialize(length) - elapsed = time.time() - start + elapsed = time.monotonic() - start self.logger.info(f'header merkle cache populated in {elapsed:.1f}s') async def header_branch_and_root(self, length, height): @@ -289,7 +289,7 @@ def flush_fs(self, flush_data): assert len(hashes) // 32 == flush_data.tx_count - prior_tx_count # Write the headers, tx counts, and tx hashes - start_time = time.time() + start_time = time.monotonic() height_start = self.fs_height + 1 offset = self.header_offset(height_start) self.headers_file.write(offset, b''.join(flush_data.headers)) @@ -306,7 +306,7 @@ def flush_fs(self, flush_data): self.fs_tx_count = flush_data.tx_count if self.utxo_db.for_sync: - elapsed = time.time() - start_time + elapsed = time.monotonic() - start_time self.logger.info(f'flushed filesystem data in {elapsed:.2f}s') def flush_history(self): @@ -317,7 +317,7 @@ def flush_utxo_db(self, batch, flush_data: FlushData): # Care is needed because the writes generated by flushing the # UTXO state may have keys in common with our write cache or # may be in the DB already. - start_time = time.time() + start_time = time.monotonic() add_count = len(flush_data.adds) spend_count = len(flush_data.deletes) // 2 @@ -347,7 +347,7 @@ def flush_utxo_db(self, batch, flush_data: FlushData): if self.utxo_db.for_sync: block_count = flush_data.height - self.db_height tx_count = flush_data.tx_count - self.db_tx_count - elapsed = time.time() - start_time + elapsed = time.monotonic() - start_time self.logger.info(f'flushed {block_count:,d} blocks with ' f'{tx_count:,d} txs, {add_count:,d} UTXO adds, ' f'{spend_count:,d} spends in ' @@ -670,12 +670,12 @@ def upgrade_u_prefix(prefix): batch_put(db_key + b'\0', db_value) return count - last = time.time() + last = time.monotonic() count = 0 for cursor in range(65536): prefix = b'u' + pack_be_uint16(cursor) count += upgrade_u_prefix(prefix) - now = time.time() + now = time.monotonic() if now > last + 10: last = now self.logger.info(f'DB 1 of 3: {count:,d} entries updated, ' @@ -704,12 +704,12 @@ def upgrade_h_prefix(prefix): batch_put(db_key + b'\0', db_value) return count - last = time.time() + last = time.monotonic() count = 0 for cursor in range(65536): prefix = b'h' + pack_be_uint16(cursor) count += upgrade_h_prefix(prefix) - now = time.time() + now = time.monotonic() if now > last + 10: last = now self.logger.info(f'DB 2 of 3: {count:,d} entries updated, ' diff --git a/electrumx/server/history.py b/electrumx/server/history.py index 73388a5..dc7dd8d 100644 --- a/electrumx/server/history.py +++ b/electrumx/server/history.py @@ -155,7 +155,7 @@ def assert_flushed(self): assert not self.unflushed def flush(self): - start_time = time.time() + start_time = time.monotonic() self.flush_count += 1 flush_id = pack_be_uint16(self.flush_count) unflushed = self.unflushed @@ -171,7 +171,7 @@ def flush(self): self.unflushed_count = 0 if self.db.for_sync: - elapsed = time.time() - start_time + elapsed = time.monotonic() - start_time self.logger.info(f'flushed history in {elapsed:.1f}s ' f'for {count:,d} addrs') @@ -386,12 +386,12 @@ def upgrade_cursor(cursor): self.write_state(batch) return count - last = time.time() + last = time.monotonic() count = 0 for cursor in range(self.upgrade_cursor + 1, 65536): count += upgrade_cursor(cursor) - now = time.time() + now = time.monotonic() if now > last + 10: last = now self.logger.info(f'DB 3 of 3: {count:,d} entries updated, ' diff --git a/electrumx/server/mempool.py b/electrumx/server/mempool.py index 971131b..d31b90b 100644 --- a/electrumx/server/mempool.py +++ b/electrumx/server/mempool.py @@ -122,9 +122,9 @@ async def _logging(self, synchronized_event): '''Print regular logs of mempool stats.''' self.logger.info('beginning processing of daemon mempool. ' 'This can take some time...') - start = time.time() + start = time.monotonic() await synchronized_event.wait() - elapsed = time.time() - start + elapsed = time.monotonic() - start self.logger.info(f'synced in {elapsed:.2f}s') while True: mempool_size = sum(tx.size for tx in self.txs.values()) / 1_000_000 diff --git a/electrumx/server/peers.py b/electrumx/server/peers.py index c2bc69f..dcb9ab7 100644 --- a/electrumx/server/peers.py +++ b/electrumx/server/peers.py @@ -300,6 +300,8 @@ async def _should_drop_peer(self, peer): self.logger.info(f'{peer_text} {e}') if is_good: + # Monotonic time would be better, but last_good and last_try are + # exported to admin RPC client. now = time.time() elapsed = now - peer.last_try self.logger.info(f'{peer_text} verified in {elapsed:.1f}s') diff --git a/electrumx/server/session.py b/electrumx/server/session.py index 123096f..0b26eb5 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -140,6 +140,7 @@ def __init__( self.sessions = {} # session->iterable of its SessionGroups self.session_groups = {} # group name->SessionGroup instance self.txs_sent = 0 + # Would use monotonic time, but aiorpcx sessions use Unix time: self.start_time = time.time() self._method_counts = defaultdict(int) self._reorg_count = 0 diff --git a/tests/lib/test_merkle.py b/tests/lib/test_merkle.py index 4675ffa..de4d8d9 100644 --- a/tests/lib/test_merkle.py +++ b/tests/lib/test_merkle.py @@ -272,9 +272,9 @@ async def time_it(): await cache.initialize(cp_length) cp_hashes = await source(0, cp_length) brs2 = [] - t1 = time.time() + t1 = time.monotonic() for index in range(5, 400000, 500): brs2.append(await cache.branch_and_root(cp_length, index)) - t2 = time.time() + t2 = time.monotonic() print(t2 - t1) assert False From ff4ca0a67fe336857e05a1ce45db097b19dfe024 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 26 Oct 2020 03:53:59 +0100 Subject: [PATCH 047/386] mempool: fee histogram now uses 0.1 sat/byte resolution instead of ints --- electrumx/server/mempool.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/electrumx/server/mempool.py b/electrumx/server/mempool.py index 25dd0cd..36686c4 100644 --- a/electrumx/server/mempool.py +++ b/electrumx/server/mempool.py @@ -13,6 +13,7 @@ from asyncio import Lock from collections import defaultdict from typing import Sequence, Tuple, TYPE_CHECKING, Type, Dict +import math import attr from aiorpcx import TaskGroup, run_in_thread, sleep @@ -145,7 +146,13 @@ def _update_histogram(self, bin_size): # Build a histogram by fee rate histogram = defaultdict(int) for tx in self.txs.values(): - histogram[tx.fee // tx.size] += tx.size + fee_rate = tx.fee / tx.size + # use 0.1 sat/byte resolution + # note: rounding *down* is intentional. This ensures txs + # with a given fee rate will end up counted in the expected + # bucket/interval of the compact histogram. + fee_rate = math.floor(10 * fee_rate) / 10 + histogram[fee_rate] += tx.size compact = self._compress_histogram(histogram, bin_size=bin_size) self.logger.info(f'compact fee histogram: {compact}') From 46bb64145b94bd86a9afbeb5cca283e18fc692da Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 27 Oct 2020 17:54:06 +0100 Subject: [PATCH 048/386] session: revert to old impl of estimatefee cache related: #24 It's easier to reason about behaviour if cache is also storing the blockhash. Old code might cache stale value if cache is being cleared while awaiting daemon, which would only be cleared after waiting for another block. --- electrumx/server/session.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/electrumx/server/session.py b/electrumx/server/session.py index 0b26eb5..3c3e83a 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -280,12 +280,6 @@ async def _handle_chain_reorgs(self): self._tx_hashes_cache.clear() self._merkle_cache.clear() - async def _handle_new_blocks(self): - '''Clear certain caches if the chain tip advanced.''' - while True: - await self.bp.tip_advanced_event.wait() - self.estimatefee_cache.clear() - async def _recalc_concurrency(self): '''Periodically recalculate session concurrency.''' session_class = self.env.coin.SESSIONCLS @@ -641,7 +635,6 @@ async def serve(self, notifications, event): await group.spawn(self.peer_mgr.discover_peers()) await group.spawn(self._clear_stale_sessions()) await group.spawn(self._handle_chain_reorgs()) - await group.spawn(self._handle_new_blocks()) await group.spawn(self._recalc_concurrency()) await group.spawn(self._log_sessions()) await group.spawn(self._manage_servers()) @@ -1302,26 +1295,28 @@ async def estimatefee(self, number, mode=None): cache_item = cache.get((number, mode)) if cache_item is not None: - feerate, lock = cache_item - if feerate is not None: + blockhash, feerate, lock = cache_item + if blockhash and blockhash == self.session_mgr.bp.tip: return feerate else: # create lock now, store it, and only then await on it lock = asyncio.Lock() - cache[(number, mode)] = (None, lock) + cache[(number, mode)] = (None, None, lock) async with lock: cache_item = cache.get((number, mode)) if cache_item is not None: - feerate, lock = cache_item - if feerate is not None: + blockhash, feerate, lock = cache_item + if blockhash == self.session_mgr.bp.tip: return feerate self.bump_cost(2.0) # cache miss incurs extra cost + blockhash = self.session_mgr.bp.tip if mode: feerate = await self.daemon_request('estimatefee', number, mode) else: feerate = await self.daemon_request('estimatefee', number) - cache[(number, mode)] = (feerate, lock) assert feerate is not None + assert blockhash is not None + cache[(number, mode)] = (blockhash, feerate, lock) return feerate async def ping(self): From 95a79754bd9407e02ab0bbbf6a9cd0df301df699 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 1 Nov 2020 00:06:11 +0100 Subject: [PATCH 049/386] env: add sanity checks for user-configured COST_*_LIMIT values Ensure that COST_HARD_LIMIT is strictly higher than COST_SOFT_LIMIT as otherwise no resource-usage checks are done. --- electrumx/server/env.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/electrumx/server/env.py b/electrumx/server/env.py index b8fe8d7..0ebbb88 100644 --- a/electrumx/server/env.py +++ b/electrumx/server/env.py @@ -88,6 +88,7 @@ def __init__(self, coin=None): self.request_sleep = self.integer('REQUEST_SLEEP', 2500) self.request_timeout = self.integer('REQUEST_TIMEOUT', 30) self.session_timeout = self.integer('SESSION_TIMEOUT', 600) + self._check_and_fix_cost_limits() # Services last - uses some env vars above @@ -118,6 +119,17 @@ def sane_max_sessions(self): value = 512 # that is what returned by stdio's _getmaxstdio() return value + def _check_and_fix_cost_limits(self): + if self.cost_hard_limit < self.cost_soft_limit: + raise self.Error(f"COST_HARD_LIMIT must be >= COST_SOFT_LIMIT. " + f"got (COST_HARD_LIMIT={self.cost_hard_limit} " + f"and COST_SOFT_LIMIT={self.cost_soft_limit})") + # hard limit should be strictly higher than soft limit (unless both are 0) + if self.cost_hard_limit == self.cost_soft_limit and self.cost_soft_limit > 0: + self.logger.info("found COST_HARD_LIMIT == COST_SOFT_LIMIT. " + "bumping COST_HARD_LIMIT by 1.") + self.cost_hard_limit = self.cost_soft_limit + 1 + def _parse_services(self, services_str, default_func): result = [] for service_str in services_str.split(','): From a61136c596d6a0290a6be9d21fb7c095c3cea21e Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 1 Nov 2020 00:21:11 +0100 Subject: [PATCH 050/386] session: allow configuring session_group IP subnet sizes --- docs/environment.rst | 6 ++++++ electrumx/server/env.py | 2 ++ electrumx/server/session.py | 17 ++++++++++------- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/docs/environment.rst b/docs/environment.rst index 3a9dd34..ceae2fc 100644 --- a/docs/environment.rst +++ b/docs/environment.rst @@ -332,6 +332,8 @@ raise them. .. envvar:: COST_HARD_LIMIT .. envvar:: REQUEST_SLEEP .. envvar:: INITIAL_CONCURRENT +.. envvar:: SESSION_GROUP_BY_SUBNET_IPV4 +.. envvar:: SESSION_GROUP_BY_SUBNET_IPV6 All values are integers. :envvar:`COST_SOFT_LIMIT` defaults to :const:`1,000`, :envvar:`COST_HARD_LIMIT` to :const:`10,000`, :envvar:`REQUEST_SLEEP` to :const:`2,500` @@ -362,6 +364,10 @@ raise them. If a session disconnects, ElectrumX continues to associate its cost with its IP address, so if it immediately reconnects it will re-acquire its previous cost allocation. + Moreover, sessions are also grouped together based on their IP address subnets, and cost + is accrued for the whole group. What subnet sizes to use can be configured via + :envvar:`SESSION_GROUP_BY_SUBNET_IPV4` (by default /24) and + :envvar:`SESSION_GROUP_BY_SUBNET_IPV6` (by default /48). A server operator should experiment with different values according to server loads. It is not necessarily true that e.g. having a low soft limit, decreasing concurrency and diff --git a/electrumx/server/env.py b/electrumx/server/env.py index 0ebbb88..1a4cd9c 100644 --- a/electrumx/server/env.py +++ b/electrumx/server/env.py @@ -88,6 +88,8 @@ def __init__(self, coin=None): self.request_sleep = self.integer('REQUEST_SLEEP', 2500) self.request_timeout = self.integer('REQUEST_TIMEOUT', 30) self.session_timeout = self.integer('SESSION_TIMEOUT', 600) + self.session_group_by_subnet_ipv4 = self.integer('SESSION_GROUP_BY_SUBNET_IPV4', 24) + self.session_group_by_subnet_ipv6 = self.integer('SESSION_GROUP_BY_SUBNET_IPV6', 48) self._check_and_fix_cost_limits() # Services last - uses some env vars above diff --git a/electrumx/server/session.py b/electrumx/server/session.py index 3c3e83a..4d8c5d4 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -17,7 +17,7 @@ import time from collections import defaultdict from functools import partial -from ipaddress import IPv4Address, IPv6Address +from ipaddress import IPv4Address, IPv6Address, IPv4Network, IPv6Network from typing import Optional, TYPE_CHECKING import asyncio @@ -788,14 +788,17 @@ async def _notify_sessions(self, height, touched): def _ip_addr_group_name(self, session) -> Optional[str]: host = session.remote_address().host - if isinstance(host, IPv4Address): + if isinstance(host, (IPv4Address, IPv6Address)): if host.is_private: # exempt private addresses return None - return '.'.join(str(host).split('.')[:3]) # /24 - if isinstance(host, IPv6Address): - if host.is_private: - return None - return ':'.join(host.exploded.split(':')[:3]) # /48 + if isinstance(host, IPv4Address): + subnet_size = self.env.session_group_by_subnet_ipv4 + subnet = IPv4Network(host).supernet(prefixlen_diff=32 - subnet_size) + return str(subnet) + elif isinstance(host, IPv6Address): + subnet_size = self.env.session_group_by_subnet_ipv6 + subnet = IPv6Network(host).supernet(prefixlen_diff=128 - subnet_size) + return str(subnet) return 'unknown_addr' def _session_group(self, name: Optional[str], weight: float) -> Optional[SessionGroup]: From 4b3f6510e94670a013c1abe6247cdd2b0e7e6f8c Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 1 Nov 2020 00:27:20 +0100 Subject: [PATCH 051/386] session: disconnect excessive res-usage sessions faster session.extra_cost() only 'works' after session_mgr.add_session(session) was called. session.recalc_concurrency() uses extra_cost. So previously, sessions that belong to a session_group that accrued a lot of cost would only get disconnected if either (1) they themselves also accrued at least 100 cost (trigger for recalc_concurrency inside bump_cost), or (2) during the periodic recalc (every 5 mins)... Now, the session will get disconnect at the latest when they send their first request. --- electrumx/server/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electrumx/server/session.py b/electrumx/server/session.py index 4d8c5d4..9acde44 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -871,8 +871,8 @@ def __init__( self.logger = util.ConnectionLogger(logger, context) self.logger.info(f'{self.kind} {self.remote_address_string()}, ' f'{self.session_mgr.session_count():,d} total') - self.recalc_concurrency() self.session_mgr.add_session(self) + self.recalc_concurrency() # must be called after session_mgr.add_session async def notify(self, touched, height_changed): pass From 63cc14e4f1b15be7f66bddb45098912cdfa9e552 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 1 Nov 2020 00:49:53 +0100 Subject: [PATCH 052/386] fix tests follow-up 95a79754bd9407e02ab0bbbf6a9cd0df301df699 --- tests/server/test_env.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/server/test_env.py b/tests/server/test_env.py index 5e60010..84595bb 100644 --- a/tests/server/test_env.py +++ b/tests/server/test_env.py @@ -42,12 +42,13 @@ def assert_default(env_var, attr, default): assert getattr(e, attr) == 'foo' -def assert_integer(env_var, attr, default=''): +def assert_integer( + env_var, attr, default='', *, min_value=5, max_value=2000): setup_base_env() if default != '': e = Env() assert getattr(e, attr) == default - value = random.randrange(5, 2000) + value = random.randrange(min_value, max_value) os.environ[env_var] = str(value) + '.1' with pytest.raises(Env.Error): Env() @@ -276,7 +277,13 @@ def test_REORG_LIMIT(): def test_COST_HARD_LIMIT(): - assert_integer('COST_HARD_LIMIT', 'cost_hard_limit', 10000) + assert_integer( + 'COST_HARD_LIMIT', + 'cost_hard_limit', + 10000, + min_value=5000, + max_value=20000, + ) def test_COST_SOFT_LIMIT(): From 62a6684492b426a0506e693f03cd532e84204451 Mon Sep 17 00:00:00 2001 From: michael hannes Date: Mon, 9 Nov 2020 18:38:23 +0000 Subject: [PATCH 053/386] altcoin: Add Auroracoin (#78) * Add Auroracoin Initial submit --- electrumx/lib/coins.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 397e891..4dbf997 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -3583,6 +3583,22 @@ class Defcoin(Coin): REORG_LIMIT = 5000 +class Auroracoin(Coin): + NAME = "Auroracoin" + SHORTNAME = "AUR" + NET = "mainnet" + P2PKH_VERBYTE = bytes.fromhex("17") + P2SH_VERBYTES = bytes.fromhex("05") + WIF_BYTE = bytes.fromhex("b0") + GENESIS_HASH = ('2a8e100939494904af825b488596ddd5' + '36b3a96226ad02e0f7ab7ae472b27a8e') + TX_COUNT = 2800000 + TX_COUNT_HEIGHT = 2778987 + TX_PER_BLOCK = 1 + RPC_PORT = 12341 + REORG_LIMIT = 5000 + + class Smileycoin(Coin): NAME = "Smileycoin" SHORTNAME = "SMLY" From bf430353d635eeaf1d4fc0f107d6b947846e1d7f Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 10 Nov 2020 19:31:34 +0100 Subject: [PATCH 054/386] tests: make test pass more reliably (can fail due to async timing) --- tests/server/test_mempool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/server/test_mempool.py b/tests/server/test_mempool.py index b5a5095..2ee33b9 100644 --- a/tests/server/test_mempool.py +++ b/tests/server/test_mempool.py @@ -529,7 +529,7 @@ async def test_notifications(caplog): api.txs = {hash: txs[hash] for hash in second_hashes} # Delay the DB update assert not in_caplog(caplog, 'waiting for DB to sync') - async with ignore_after(mempool.refresh_secs * 2): + async with ignore_after(max(mempool.refresh_secs * 2, 0.5)): await event.wait() assert in_caplog(caplog, 'waiting for DB to sync') assert len(api.on_mempool_calls) == 2 From f7e6426628aa56f16bbe3f75775941b84e07f320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=8E=A0=E1=8E=A1=2E=20=D0=85=CF=B5rg=CF=B5=20=D1=B4ictor?= Date: Fri, 11 Dec 2020 00:27:30 +0700 Subject: [PATCH 055/386] Added IPv6-only litecoin testnet electrum instance (#84) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We need at least one IPv6 host running. It is mine 👍 --- electrumx/lib/coins.py | 1 + 1 file changed, 1 insertion(+) diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 4dbf997..8978703 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -989,6 +989,7 @@ class LitecoinTestnet(Litecoin): PEERS = [ 'electrum-ltc.bysh.me s t', 'electrum.ltc.xurious.com s t', + 'ipv6-only.electrum.random.re s t', ] From 8dc399d4474293be24e27a343241902528d39841 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 10 Dec 2020 19:16:21 +0100 Subject: [PATCH 056/386] docs: update more references to author/repo --- LICENCE | 2 +- docs/authors.rst | 6 +++++- docs/conf.py | 6 +++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/LICENCE b/LICENCE index 08f48bc..ee2d4de 100644 --- a/LICENCE +++ b/LICENCE @@ -1,5 +1,5 @@ Copyright (c) 2020, The Electrum developers -Copyright (c) 2016-2017, Neil Booth +Copyright (c) 2016-2020, Neil Booth All rights reserved. diff --git a/docs/authors.rst b/docs/authors.rst index ee20e39..13d2964 100644 --- a/docs/authors.rst +++ b/docs/authors.rst @@ -5,7 +5,11 @@ Authors * Neil Booth - Creator and maintainer. + Original creator, past maintainer. + +* Electrum developers + + Current maintainers. * Johann Bauer diff --git a/docs/conf.py b/docs/conf.py index 94d2387..7719437 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,8 +20,8 @@ # -- Project information ----------------------------------------------------- project = 'ElectrumX' -copyright = '2016-2020, Neil Booth' -author = 'Neil Booth' +copyright = '2016-2020, various' +author = 'Electrum developers' # The full version including branding release = VERSION @@ -82,7 +82,7 @@ # html_theme_options = { 'description': 'Lightweight Electrum Server in Python', - 'github_user': 'kyuupichan', + 'github_user': 'spesmilo', 'github_repo': 'electrumx', 'github_button': True, 'github_type': 'star', From 914938264e5621ea8980be6d3e69964e7f219d16 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 10 Dec 2020 19:30:11 +0100 Subject: [PATCH 057/386] Prepare 1.16 --- docs/changelog.rst | 49 +++++++++++++++++++++++++++++++++++++++++++ docs/conf.py | 2 +- electrumx/__init__.py | 2 +- setup.py | 2 +- 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 4e0a652..58302db 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -3,6 +3,35 @@ =========== +Version 1.16.0 (10 Dec 2020) +============================ + +Note: this is the first release since forking from kyuupichan/electrumx. +kyuupichan has the :code:`electrumx` name on PyPI, so we needed a new name there. +We are using the `e-x `_ name on PyPI, so you can +install this package via e.g. :code:`pip install e-x`. + +* security: a vulnerability has been fixed that allowed a remote attacker to + crash electrumx if peer discovery was enabled (`#22`_) +* fixed some peer-discovery-related bugs (e.g. `#35`_) +* ENV: when using Bitcoin, the COIN ENV var can now be set to :code:`Bitcoin`. + For compatibility, using :code:`BitcoinSegwit` will also keep working. + (`#5`_) +* session resource limits: made more useful in general. connection-time-based + grouping has been removed (`#70`_). Disconnects of over-limit sessions happen + sooner (`4b3f6510`_). Subnet sizes for IP-based grouping can now be + configured (`a61136c5`_). +* protocol: :code:`mempool.get_fee_histogram` now returns fee rates with + 0.1 sat/byte resolution instead of 1 sat/byte, and the compact histogram + is calculated differently. (`#67`_) +* performance: bitcoind is now queried less frequently as estimatefee, + relayfee, and server.banner requests are now cached (`#24`_) +* performance: json ser/deser is now abstracted away and the :code:`ujson` and + :code:`rapidjson` exras can be used for somewhat faster block processing. + (`#11`_) +* multiple other small performance improvements + + Version 1.15.0 (27 May 2020) ============================ @@ -146,8 +175,15 @@ Version 1.9.0 (10 Jan 2019) * other minor tweaks (Michael Schmoock, Michael Taborsky) +Original author of ElectrumX: + **Neil Booth** kyuupichan@gmail.com https://github.com/kyuupichan +This fork maintained by: + +**Electrum developers** electrumdev@gmail.com https://github.com/spesmilo + + .. _#554: https://github.com/kyuupichan/electrumx/issues/554 .. _#566: https://github.com/kyuupichan/electrumx/issues/566 .. _#653: https://github.com/kyuupichan/electrumx/issues/653 @@ -159,3 +195,16 @@ Version 1.9.0 (10 Jan 2019) .. _#731: https://github.com/kyuupichan/electrumx/issues/731 .. _#795: https://github.com/kyuupichan/electrumx/issues/795 .. _#909: https://github.com/kyuupichan/electrumx/issues/909 + + +.. _#5: https://github.com/spesmilo/electrumx/pull/5 +.. _#11: https://github.com/spesmilo/electrumx/pull/11 +.. _#22: https://github.com/spesmilo/electrumx/issue/22 +.. _#24: https://github.com/spesmilo/electrumx/pull/24 +.. _#35: https://github.com/spesmilo/electrumx/pull/35 +.. _#67: https://github.com/spesmilo/electrumx/pull/67 +.. _#70: https://github.com/spesmilo/electrumx/pull/70 + + +.. _4b3f6510: https://github.com/spesmilo/electrumx/commit/4b3f6510e94670a013c1abe6247cdd2b0e7e6f8c +.. _a61136c5: https://github.com/spesmilo/electrumx/commit/a61136c596d6a0290a6be9d21fb7c095c3cea21e diff --git a/docs/conf.py b/docs/conf.py index 7719437..ba4c166 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -15,7 +15,7 @@ import os import sys sys.path.insert(0, os.path.abspath('..')) -VERSION="ElectrumX 1.15.0" +VERSION="ElectrumX 1.16.0" # -- Project information ----------------------------------------------------- diff --git a/electrumx/__init__.py b/electrumx/__init__.py index 24be271..5a4a6cb 100644 --- a/electrumx/__init__.py +++ b/electrumx/__init__.py @@ -1,4 +1,4 @@ -version = 'ElectrumX 1.15.0' +version = 'ElectrumX 1.16.0' version_short = version.split()[-1] from electrumx.server.controller import Controller diff --git a/setup.py b/setup.py index fe794be..eed0d2f 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ import setuptools -version = '1.15.0' +version = '1.16.0' setuptools.setup( name='e-x', From f9fec2233d999cdceda814d67b129094718bb513 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 10 Dec 2020 19:55:38 +0100 Subject: [PATCH 058/386] document release process --- .gitignore | 1 + docs/release_process.txt | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 docs/release_process.txt diff --git a/.gitignore b/.gitignore index c0894bf..cceb134 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ docs/_build /build /dist /electrumx.egg-info +/e_x.egg-info .vscode/ .mypy_cache/ .idea/ diff --git a/docs/release_process.txt b/docs/release_process.txt new file mode 100644 index 0000000..792d830 --- /dev/null +++ b/docs/release_process.txt @@ -0,0 +1,19 @@ +// 1. Add changelog + +// 2. Update version number, in: +// - docs/conf.py +// - electrumx/__init__.py +// - setup.py + +// 3. Tag +$ git tag -s VERSION -m "VERSION" + +// 4. Build dists +$ python3 setup.py sdist bdist_wheel + +// 5. Upload to PyPI +// python3 -m twine upload --repository testpypi dist/* +$ python3 -m twine upload dist/* + +// You will be prompted for a username and password. For the username, use __token__. +// For the password, use API token from https://pypi.org/manage/account/ , including the pypi- prefix. From fe6bf7e4ce651d4c805cc9935f1a9dac26fe90be Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 10 Dec 2020 23:49:06 +0100 Subject: [PATCH 059/386] changelog: fix typo --- docs/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 58302db..64a800b 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -199,7 +199,7 @@ This fork maintained by: .. _#5: https://github.com/spesmilo/electrumx/pull/5 .. _#11: https://github.com/spesmilo/electrumx/pull/11 -.. _#22: https://github.com/spesmilo/electrumx/issue/22 +.. _#22: https://github.com/spesmilo/electrumx/issues/22 .. _#24: https://github.com/spesmilo/electrumx/pull/24 .. _#35: https://github.com/spesmilo/electrumx/pull/35 .. _#67: https://github.com/spesmilo/electrumx/pull/67 From 6d7763a21ecbda1d880d63230130850e7e475509 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 15 Dec 2020 14:41:48 +0100 Subject: [PATCH 060/386] docs: rm outdated roadmap Most of this is outdated; some already done. --- docs/features.rst | 31 +------------------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/docs/features.rst b/docs/features.rst index 2018148..8e5f869 100644 --- a/docs/features.rst +++ b/docs/features.rst @@ -39,9 +39,7 @@ blockchain indexer: and history. Electrum Server stores full transaction hash and height for each UTXO, and does the same in its pruned history. In contrast ElectrumX just stores the transaction number in the linear - history of transactions. For at least another 5 years this - transaction number will fit in a 4-byte integer, and when necessary - expanding to 5 or 6 bytes is trivial. ElectrumX can determine block + history of transactions. ElectrumX can determine block height from a simple binary search of tx counts stored on disk. ElectrumX stores historical transaction hashes in a linear array on disk. @@ -59,30 +57,3 @@ blockchain indexer: Python's :mod:`asyncio` means ElectrumX has no (direct) use for threads and associated complications. - -Roadmap -======= - -* break ElectrumX up into simple services that initially can be run in - separate processes on a single host. Then support running them on - different hosts, and finally support sharding. With this we can - take advantage of multiple cores and hosts, and scale to much larger - block sizes. This should solve several issues with the current - history storage mechanism. -* fully asynchronous operation. At present too much is synchronous, such - as file system access. -* protocol improvements targeting better client and server scalability - to large wallets (100k addresses) and address histories. Some - aspects of the current protocol are very inefficient. -* investigate speaking the Bitcoin protocol and connecting to the - Bitcoin network directly for some queries. This could lead to - ElectrumX being runnable with a node without a tx index, or a - pruning node, or not needing to run a node at all. ElectrumX would - store all blocks itself and index the transactions therein. -* lifting internal limits such as maximum 4 billion transactions -* supporting better user privacy. I believe significantly improved - user address privacy might be possible with a simple addition to the - protocol, and assuming a server network of which a reasonable - fraction (40%?) are cooperative and non-colluding -* new features such as possibly adding label or wallet server - functionality From aa0d2ce83ff90055fec3e51d906607790f3cd44b Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 23 Dec 2020 18:46:35 +0100 Subject: [PATCH 061/386] requirements: bump min aiorpcx to 0.18.5 --- electrumx/server/controller.py | 4 ++-- setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/electrumx/server/controller.py b/electrumx/server/controller.py index 79cf126..032159f 100644 --- a/electrumx/server/controller.py +++ b/electrumx/server/controller.py @@ -82,8 +82,8 @@ async def serve(self, shutdown_event): '''Start the RPC server and wait for the mempool to synchronize. Then start serving external clients. ''' - if not (0, 18, 1) <= aiorpcx_version < (0, 19): - raise RuntimeError('aiorpcX version 0.18.x is required') + if not (0, 18, 5) <= aiorpcx_version < (0, 19): + raise RuntimeError('aiorpcX version 0.18.5+ is required') env = self.env min_str, max_str = env.coin.SESSIONCLS.protocol_min_max_strings() diff --git a/setup.py b/setup.py index eed0d2f..953bf59 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ version=version, scripts=['electrumx_server', 'electrumx_rpc', 'electrumx_compact_history'], python_requires='>=3.7', - install_requires=['aiorpcX[ws]>=0.18.3,<0.19', 'attrs', + install_requires=['aiorpcX[ws]>=0.18.5,<0.19', 'attrs', 'plyvel', 'pylru', 'aiohttp>=3.3'], extras_require={ 'rapidjson': ['python-rapidjson>=0.4.1,<1.0.0'], From cc28e6d658edc058e859313d0fb8e8c299492a4c Mon Sep 17 00:00:00 2001 From: SomberNight Date: Sun, 1 Nov 2020 00:28:43 +0100 Subject: [PATCH 062/386] session: log IP address of excessive resusage sessions --- electrumx/server/session.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/electrumx/server/session.py b/electrumx/server/session.py index 9acde44..fa2cbd6 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -990,6 +990,12 @@ def protocol_version_string(self): def extra_cost(self): return self.session_mgr.extra_cost(self) + def on_disconnect_due_to_excessive_session_cost(self): + ip_addr = self.remote_address().host + groups = self.session_mgr.sessions[self] + group_names = [group.name for group in groups] + self.logger.info(f"closing session over res usage. ip: {ip_addr}. groups: {group_names}") + def sub_count(self): return len(self.hashX_subs) From 77791c6122a4dee64767d5dc57d02e69f3737de0 Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Fri, 8 Jan 2021 00:19:15 -0600 Subject: [PATCH 063/386] Lock aiohttp dependency to 3.x until 4.x has been tested. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 953bf59..78472eb 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ scripts=['electrumx_server', 'electrumx_rpc', 'electrumx_compact_history'], python_requires='>=3.7', install_requires=['aiorpcX[ws]>=0.18.5,<0.19', 'attrs', - 'plyvel', 'pylru', 'aiohttp>=3.3'], + 'plyvel', 'pylru', 'aiohttp>=3.3,<4'], extras_require={ 'rapidjson': ['python-rapidjson>=0.4.1,<1.0.0'], 'rocksdb': ['python-rocksdb>=0.6.9'], From e91db494dcdfaacb1cb24fcfe5a2fe1a5a792087 Mon Sep 17 00:00:00 2001 From: Kristian Kramer Date: Mon, 1 Feb 2021 01:51:37 +0000 Subject: [PATCH 064/386] Add Beyondcoin (#93) * Add Beyondcoin * Add test for Beyondcoin * Fix whitespace issue --- electrumx/lib/coins.py | 19 +++++++++++++++++++ tests/blocks/beyondcoin_mainnet_143500.json | 15 +++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 tests/blocks/beyondcoin_mainnet_143500.json diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 8978703..a1a7918 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -3856,3 +3856,22 @@ class Quebecoin(AuxPowMixin, Coin): TX_PER_BLOCK = 20 REORG_LIMIT = 2000 RPC_PORT = 10890 + + +class Beyondcoin(Coin): + NAME = "Beyondcoin" + SHORTNAME = "BYND" + NET = "mainnet" + XPUB_VERBYTES = bytes.fromhex("ff88b21e") + XPRV_VERBYTES = bytes.fromhex("ff88ade4") + P2PKH_VERBYTE = bytes.fromhex("19") + P2SH_VERBYTES = [bytes.fromhex("1a"), bytes.fromhex("05")] + WIF_BYTE = bytes.fromhex("b0") + GENESIS_HASH = ('0a9e3b5fce3aee6e04f06dfd6ad380a6' + 'c0f9d8420f53a4ca97845756ee5d56e7') + DESERIALIZER = lib_tx.DeserializerSegWit + TX_COUNT = 287000 + TX_COUNT_HEIGHT = 133700 + TX_PER_BLOCK = 2 + RPC_PORT = 10332 + REORG_LIMIT = 5000 diff --git a/tests/blocks/beyondcoin_mainnet_143500.json b/tests/blocks/beyondcoin_mainnet_143500.json new file mode 100644 index 0000000..c75fe8e --- /dev/null +++ b/tests/blocks/beyondcoin_mainnet_143500.json @@ -0,0 +1,15 @@ +{ + "hash": "5276d5e76ba065c2f3ac1a02cf31ae9d05d75b3151c64a51194b41b9aa5c6159", + "size": 451, + "height": 143500, + "merkleroot": "8a04f00045f903b5ae1a378e02bada5d6bdaf0fd385da1719427c5fa0cc63b65", + "tx": [ + "05df92547ad12bf278fe453b591322bd58695795a5f917cba46613fe10dfcf7b", + "1594dad49084d64c674f3f1e7d6f13615a3bc044b0ff576beeb6d68479f56e4e" + ], + "time": 1611371188, + "nonce": 8744379, + "bits": "1c00a30f", + "previousblockhash": "31b55b0ecdc57b8163bc4d5daf8d4a1d5c5321c3c254d4e4563d34858177f554", + "block": "0000002054f5778185343d56e4d454c2c321535c1d4a8daf5d4dbc63817bc5cd0e5bb531653bc60cfac5279471a15d38fdf0da6b5ddaba028e371aaeb503f94500f0048ab4920b600fa3001cbb6d85000201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff20038c300204b4920b60088106ad328a0200004265796f6e64636f696e506f6f6c00000000013855062a010000001976a9146ec63057741c13c298c1639fb2ac8922d57ea27288ac000000000200000001cffdc62fa8c75f01269ca466f08750b98d4b1cfeda9fce8ab3d612572eb5f845000000008a473044022027e1a06e9afcb5ba4fcb09f9ed24c6fd81f490583ed73099697cae828cbb556502206c0c3a45c13d2e5a897a63261a1f6f5418b38ee12d77055a77d78f19cdd24c8a0141045a39ddce002c9b80c7fc3d6136ab18142005a5c064c4178f07d8dd9e47c7a156c36cfd9ce8ebd4eec41ebd2a8000fd9bc8608b670861baacf86bfb31538ae295feffffff026c35091f0100000017a914127c418ff54ef530f577e9d8b58c6a7cc75553ba875c59fc0a0000000017a9145a82f4ece6b7bc85e581222e09efd483c055a667878b300200" +} \ No newline at end of file From 01736cb7248e1158c27d9cb1fa31da6ab4e56c2a Mon Sep 17 00:00:00 2001 From: aardvark3 <44991066+aardvark3@users.noreply.github.com> Date: Mon, 1 Feb 2021 02:12:38 +0000 Subject: [PATCH 065/386] Adjust Primecoin ESTIMATE_FEE (#89) Co-authored-by: ankang --- electrumx/lib/coins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index a1a7918..54b6fa0 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -3492,7 +3492,7 @@ class Primecoin(PrimeChainPowMixin, Coin): GENESIS_HASH = ('963d17ba4dc753138078a2f56afb3af9' '674e2546822badff26837db9a0152106') DAEMON = daemon.FakeEstimateFeeDaemon - ESTIMATE_FEE = 1.024 + ESTIMATE_FEE = 1. TX_COUNT = 7138730 TX_COUNT_HEIGHT = 3639500 TX_PER_BLOCK = 2 From 1a70cdc93f377572fcd64967c20c960f285acd5e Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 1 Feb 2021 03:22:11 +0100 Subject: [PATCH 066/386] github: add PR template re altcoins; this in itself is an attempt to minimise maintenance burden --- .github/pull_request_template.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..c91881c --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,4 @@ + \ No newline at end of file From a6cb22aa3717d515e6853c5603b21d828d619d4e Mon Sep 17 00:00:00 2001 From: Matias Furszyfer Date: Mon, 1 Feb 2021 09:13:08 -0300 Subject: [PATCH 067/386] PIVX v5 network upgrade (#95) * PIVX v5 upgrade header change, adding sapling merkle root hash * add transaction deserializer for PIVX Sapling upgrade * PIVX: adding coverage for v8 blocks + changed testnet4 for new testnet5 blocks --- electrumx/lib/coins.py | 25 +++++++++-- electrumx/lib/tx.py | 59 +++++++++++++++++++++++++- tests/blocks/pivx_mainnet_2703076.json | 17 ++++++++ tests/blocks/pivx_testnet_1000.json | 19 +++++---- tests/blocks/pivx_testnet_10000.json | 17 ++++---- 5 files changed, 115 insertions(+), 22 deletions(-) create mode 100644 tests/blocks/pivx_mainnet_2703076.json diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 54b6fa0..0b6563a 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -2733,20 +2733,25 @@ class Pivx(Coin): P2PKH_VERBYTE = bytes.fromhex("1e") P2SH_VERBYTE = bytes.fromhex("0d") WIF_BYTE = bytes.fromhex("d4") + DESERIALIZER = lib_tx.DeserializerPIVX TX_COUNT_HEIGHT = 569399 TX_COUNT = 2157510 TX_PER_BLOCK = 1 STATIC_BLOCK_HEADERS = False RPC_PORT = 51470 - ZEROCOIN_HEADER = 112 + REORG_LIMIT = 100 + EXPANDED_HEADER = 112 ZEROCOIN_START_HEIGHT = 863787 + ZEROCOIN_END_HEIGHT = 2153200 ZEROCOIN_BLOCK_VERSION = 4 + SAPLING_START_HEIGHT = 2700500 @classmethod def static_header_len(cls, height): '''Given a header height return its length.''' - if height >= cls.ZEROCOIN_START_HEIGHT: - return cls.ZEROCOIN_HEADER + if (height >= cls.ZEROCOIN_START_HEIGHT and height < cls.ZEROCOIN_END_HEIGHT) \ + or (height >= cls.SAPLING_START_HEIGHT): + return cls.EXPANDED_HEADER else: return cls.BASIC_HEADER_SIZE @@ -2771,7 +2776,19 @@ class PivxTestnet(Pivx): WIF_BYTE = bytes.fromhex("EF") TX_PER_BLOCK = 4 RPC_PORT = 51472 - ZEROCOIN_START_HEIGHT = 201564 + ZEROCOIN_START_HEIGHT = 201 + ZEROCOIN_END_HEIGHT = 201 + ZEROCOIN_BLOCK_VERSION = 4 + SAPLING_START_HEIGHT = 201 + + @classmethod + def static_header_len(cls, height): + '''Given a header height return its length.''' + if (height >= cls.ZEROCOIN_START_HEIGHT and height < cls.ZEROCOIN_END_HEIGHT) or ( + height >= cls.SAPLING_START_HEIGHT): + return cls.EXPANDED_HEADER + else: + return cls.BASIC_HEADER_SIZE class Bitg(Coin): diff --git a/electrumx/lib/tx.py b/electrumx/lib/tx.py index 091fa05..262d557 100644 --- a/electrumx/lib/tx.py +++ b/electrumx/lib/tx.py @@ -37,7 +37,7 @@ unpack_le_int32_from, unpack_le_int64_from, unpack_le_uint16_from, unpack_be_uint16_from, unpack_le_uint32_from, unpack_le_uint64_from, pack_le_int32, pack_varint, - pack_le_uint32, pack_le_int64, pack_varbytes, + pack_le_uint16, pack_le_uint32, pack_le_int64, pack_varbytes, ) ZERO = bytes(32) @@ -417,6 +417,63 @@ def read_tx(self): return base_tx +@dataclass +class TxPIVX: + '''Class representing a PIVX transaction.''' + __slots__ = 'version', "txtype", 'inputs', 'outputs', 'locktime' + version: int + txtype: int + inputs: Sequence['TxInput'] + outputs: Sequence['TxOutput'] + locktime: int + + def serialize(self): + return b''.join(( + pack_le_uint16(self.version), + pack_le_uint16(self.txtype), + pack_varint(len(self.inputs)), + b''.join(tx_in.serialize() for tx_in in self.inputs), + pack_varint(len(self.outputs)), + b''.join(tx_out.serialize() for tx_out in self.outputs), + pack_le_uint32(self.locktime) + )) + + +class DeserializerPIVX(Deserializer): + def read_tx(self): + header = self._read_le_uint32() + tx_type = header >> 16 # DIP2 tx type + if tx_type: + version = header & 0x0000ffff + else: + version = header + + if tx_type and version < 3: + version = header + tx_type = 0 + + base_tx = TxPIVX( + version, + tx_type, + self._read_inputs(), # inputs + self._read_outputs(), # outputs + self._read_le_uint32() # locktime + ) + + if version >= 3: # >= sapling + self._read_varint() + self.cursor += 8 # valueBalance + shielded_spend_size = self._read_varint() + self.cursor += shielded_spend_size * 384 # vShieldedSpend + shielded_output_size = self._read_varint() + self.cursor += shielded_output_size * 948 # vShieldedOutput + self.cursor += 64 # bindingSig + if (tx_type > 0): + self.cursor += 2 # extraPayload + + return base_tx + + @dataclass class TxTime: '''Class representing transaction that has a time field.''' diff --git a/tests/blocks/pivx_mainnet_2703076.json b/tests/blocks/pivx_mainnet_2703076.json new file mode 100644 index 0000000..79e6a8a --- /dev/null +++ b/tests/blocks/pivx_mainnet_2703076.json @@ -0,0 +1,17 @@ +{ + "hash": "a3a5538053fad3923ff372ae0c84b08d956c205bd3b707115b2668ba9e6ca25c", + "size": 2135, + "height": 2703076, + "version": 8, + "merkleroot": "264291bc3574a9e150ed4f7d7c1462b097c5b16c6b140053232662ed17d39428", + "tx": [ + "81b265c025c66ab18950e7ff8e5bd8e4d950dd8dab82cf43c6b32a938693a082", + "ce12caf59972a96698e39f4aa12f87343e9a44692e2bee6e2c1b8e967471b45d", + "a8cd4b78d8b7a92f258317953666dd0c63b1d2095be3d5198f371fd6f5089110" + ], + "time": 1612153185, + "nonce": 0, + "bits": "1b04fa91", + "previousblockhash": "2efb1be9f9d49c08a5403291bf15e22f36e5c62fd5a73b8f3fbdfddf6d398585", + "block": "080000008585396ddffdbd3f8f3ba7d52fc6e5362fe215bf913240a5089cd4f9e91bfb2e2894d317ed6226235300146b6cb1c597b062147c7d4fed50e1a97435bc9142266181176091fa041b00000000362846ebf72b50f017794305810fbc6cd68f830e16330efbd0d836e7cd798e500301000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0503e43e2900ffffffff010000000000000000000000000001000000014e87bbd48c7de9ec33dc93761840b38c60891bd6e904162d8957dd915d65d8d8010000006c4730440220318a9cc9d4b58f7663ea791ea4f6e8a35e6082c45fe9f12e725317be8af72dca02206f04604c429c6ff56ad8441b074d10e635ae54636864d114b71450bb15fde15a01015121025559a792b8805da7f5d61237a45d7e70c9b3133a176e755e7e90514e37673720ffffffff03000000000000000000705449fa0b0000003376a97b63d114490254a35e71df73197db35a8d4c5f50068cd1f26714ee88652a3aad34d56936901e51fb7eada866dcf76888ac00a3e111000000001976a9147071c81f9885483a0097f42fb4dc1fec43ac09c788ac00000000030000000482a17f03520de5486f191fbe71c76a91a83fd7403dc2fba4ed4ec0120f65c60b000000006a47304402202e4f2409be802607f8f8a99f498bcdf721b2860624dd1d43004ebb7c7a07260202203fc2f750c0c1dd7d07944fd920591d6fda057ff18be072e4b6d50da9866e35cc0121029e41f5e8bc6330caf3175677089350c01855598b491b483de4b8f506af327b5bffffffff94f8dc7d8564cc7d62674627184b6a40cdc9ca88614d9f70884562f30a261e83010000006b483045022100947e79ddefae2cfa23c2b3305d68c97e4c8d6fceee6fcb112676a95ba576521702203e4a47809d5c6e063aadc8ffadcd48abb24a7ee2e91adc1564a0c91acc568e33012102601c05e9644919f9a244541a002dd76eaa58115fa174cda686c4958a167242c6ffffffff03c741521547fc667fdaa098a036cfa33fe98f618daa0d8be77e7984a61bea96000000006b483045022100e8829961a55d42d8b9a84d9bb51b2a69188bddf25c183919566e48932f5e32d8022019fd005c8951cde8bb1a7bd32518bc7bfd284ec7aa5b1a51430588c6140a8144012103b4d1a27c58a51606de53afca9466e3ec6964eacd7ff3aa385a1a983cbb9c54d0fffffffff170122e910f335ea646fb0275bf195cd68178c6474e6fdd92e1b943fb6f478c010000006b4830450221008d6399deb3c4cbbe4a980945f87b3c017d80c1fabfadcce037b7e5b38cf4871b0220523f207d04971f84cbb35a4a4a7996da065a1e3418b64f24f7c5ec96ed3ad7bd0121021928a686263c49790b407b12ff839337c65e926e73b6a10be5d31eaaa690880dffffffff000000000001c07e45feffffffff00015a72176c9bf42f76a851f87991cf02f8091c6a4e99c135a9ff444b7de05c7f35c8369f9c0369bcc91a48c9e9546c127a06f4687fd96e11b80e2eeeebd391276a96ec38ba77aa16c738cb7f8d3561e7398eb8ce5ef3969c0bb95980149e67e6c81c41040b0c5d1fb1bd875c2623c847028d73ee553e3a9e83c66c079fdafdd6f861303b3dfcf0168b08cceea1021753d24cdff91afb60764821b68a61f0596fffb0fd59045c9e44638aff8e72dd59a42c7a5999dbb3cba7ce55a94b73302087ebbb7ff57cc6578580d3242f9a87d1926e477f91f1f9c3b99dfd497639791c93ed67a2a145a42fe33599970558fbea80aa996d8562f90c8d2153e01a7e212ce039c2b0b0efb785b5ec1b8be2a8e6d7835eb84beea60f5091e1228a4df21ddc70f1bd70e9009d5374cd35c032d0fb2a3664ae5c838291c20900d798cfa4154f7428e41dc5a2dbbfa6c61a0f9870e2579ea049b5a02e66bcf967535af58ebad9c1992c376938ce4f85b2b55e969af71f7043d311fad7505a1f0b90f8f1b102af8d7e02995012ed1fbe4c39a6ba16894b5a386ceb150673b840ca9fe6d48f6861d377531179655e3b500b00739a76ec20345a7cc5258927ce4fb7b9f40185dffed870f633b94417f3ef73b7ddac38bb30e2b104b5ddb0e565fe5064556cd6f8faa4089163f5bd71f3e67bfb0efdee8412dbc00dc8caf0f0e63cd0108c4919b2eabc78b49175f9707584d45a683109e5781bd2a7c3c48e46c2e177d68a4c8db46ccb43b5b9a83adc58dafd1d10dbe480f0569291bb2ee83092b6a3b12806bc0426eed9c54390eb298d0878af00f78b024b8a060210183e2537cc5200cad1b454196dd8b6fa7fb2201ef82e7d515e85340a3d381abb8afae5888952a924cfd4f09504eb5853cf3c8a3a73c28d4c2a4c04f8c9ced0ddd0b7b713f02e34aa46fbb4c4af6e6d99aa05cb653f4aeecf4fdfad80c731e5d87a7ae6030d5aeb9c64936b809a051c7b726bdd679f059c6a823d8432731d8e6abc0a3eaca2796713aa68b81f2981a3e2b2cad351d96742980408391a0f7a56a1ac51b2e7f0faf076751edda0c5c72357e7cbfa9edfbed41adcc9e390f396badfe0deb3eec4e7631eda2288279823d36a339a982c8e092993cc5c12d924b5c9ed37a9976d76392ec8f0762398eef15f54d6e323c581c761113f051a20e4b01790486812253875e684e30c57d46617dc1027535b368ac92248da74c9393329bd882e08d6330238e53aeea93e63bda935d8b54c975f9249f5666dc2116a50c5ba35ddae87567b619ba4b1a9ff38040d85d385c40fa650ee2bb584e36bb41a658ad324f50bf0c76c17a380331cc1240562f818f215d1ad95943c2df072a879e29e41673ce482abca314096ec3a24e842b967eab7eed71c0261fd9abc5206c2108ea96b0446304402205f4f686273a6a6706cc3c1c5b68f0958160f69077a40aa916309e9279a18ae4402206272e4f7cf00a0fb9550c9801c0b8a27be6bdaf16784590a209299ce48c2271d" +} \ No newline at end of file diff --git a/tests/blocks/pivx_testnet_1000.json b/tests/blocks/pivx_testnet_1000.json index 7c4d3e8..e9cca06 100644 --- a/tests/blocks/pivx_testnet_1000.json +++ b/tests/blocks/pivx_testnet_1000.json @@ -1,12 +1,13 @@ { - "hash": "0923838e270b6c62e8ee957541793511c8aca8db2f6edea5209d3c6fd03b3bbb", - "size": 461, - "height": 10000, - "merkleroot": "af2180392bc2a7d273f8da7c307a12e97ebc19271b7612d2b885079564fc86cc", - "tx": ["df275d713fcf5e78e7e8369d640201d46736c0d2255e31ce45bd5aa0206f861f", "b68d2b93908869b5cf6ebe069aa21dd7ca3f6d5637cc098f3ec3f6f0406f6093"], - "time": 1489054870, + "hash": "f593fd5acdcfd74427315d10ecbf64e4cd61b8212524d4efaf1996df27f04b69", + "size": 482, + "height": 1000, + "version": 8, + "merkleroot": "f08a6baade71d02dd56e245956abaf13ea1cbde28a35264dda9aa472b0aaed9d", + "tx": ["ed36caa75b195b7ecbef51fc3577ea55bf9e3483416c7e4b66d851b5778e9902", "b718c63b33bdf9fab88fe2006f3d3e6d0733717656a9f38784ab9278c8fbe1d4"], + "time": 1608265980, "nonce": 0, - "bits": "1b0101e7", - "previousblockhash": "17314f4629950d94c597f50434a7587af405e84aa5d825f35e09bbdc5cc969e0", - "block": "03000000e069c95cdcbb095ef325d8a54ae805f47a58a73404f597c5940d9529464f3117cc86fc64950785b8d212761b2719bc7ee9127a307cdaf873d2a7c22b398021af962cc158e701011b000000000201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0502e8030101ffffffff010000000000000000000000000001000000016d2ef9508a20c024b554567a1847b565f968b0926faa17a60c915c62f124343c1a0100006a473044022026966a060563b855e2dd95644d79af3713b81f65820a12e854dc40662034e9f702200ae2bee76842c5e21fbc9f17ea7303179e7c00447380ef3bd53a171541101451012102624e3321ae66cfa542f1febe954b052cb560a8392c5ffd9a3d821cd17f47b682ffffffff03000000000000000000f082fdc361000000232102624e3321ae66cfa542f1febe954b052cb560a8392c5ffd9a3d821cd17f47b682ac00f2052a010000001976a9144e22534048a03912670ffbc0714bb06678024d7588ac00000000463044022050067ba9a6b187114651909b16d67b367e1ced79eb2f8b8750c99da55ece848d0220237d89ec7a9cbfdf823ccd24842041cccc350ca518408bb284ed7a5b3e60de47" + "bits": "1b039118", + "previousblockhash": "5a6a37c9aa62972936fc06f4cedde941122238a778c93db70c7aa5ca00455021", + "block": "0800000021504500caa57a0cb73dc978a738221241e9ddcef406fc36299762aac9376a5a9dedaab072a49ada4d26358ae2bd1cea13afab5659246ed52dd071deaa6b8af0fc30dc5f1891031b00000000f661ea02e86510d1d1b64129a8c3fe99ebbfd4dc9b5010cd33757d5fddee292f0201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0402e80300ffffffff01000000000000000000000000000100000001a08fec0c563f179ebff335f73a67dd635a4d16fb27f92b16ea89cbae5b5a5c3a010000006a47304402205b730c7538d77dd5c34ea34eb779c239ceee82772e8a6e9d63651f9607a53d2c022078b03800819b1021d39fb5452972db2eca19fb36b7e72952638cc5a25bdc18d901210302ee27887bc4e61248d0d4bbc8ec40b44de9b059dd56903fbc77edccd840031affffffff03000000000000000000aad1b205ea0100001976a914bc9ee38b953094dca0fdd84cb60c099387abffc188ac00a3e111000000001976a914c379047f20e9e09020094032b65653c9db4e17fe88ac00000000463044022001c5ae6999a8e489f349a9546bd0aba7f3c9e30a19bb5d7e696523fdb95ef72002201796c8cf6a2e139d137e713cf9ad650d5343102807e6e2424331aea992f73e10" } \ No newline at end of file diff --git a/tests/blocks/pivx_testnet_10000.json b/tests/blocks/pivx_testnet_10000.json index 9c66cda..1473ac9 100644 --- a/tests/blocks/pivx_testnet_10000.json +++ b/tests/blocks/pivx_testnet_10000.json @@ -1,14 +1,15 @@ { - "hash": "e5131e1610f037fe5b2ccce9e0c521d7811a0c46392238f0fadb06e5c25187e1", - "size": 427, + "hash": "7f60e86164bd23295182f92b1120b67bb202599a7cbcc72aa677329a558fa8fc", + "size": 511, "height": 10000, - "merkleroot": "1094dedef85d4be585ee2f07af0f3f8d42634413493f67a5beee5f98dbdfbe94", + "version": 8, + "merkleroot": "5bcd16ffb5e3640e22a17ef3eb4baf462aa826c6bd3a4ec9c55cfa67e4898d5f", "tx": [ - "705e9918f8cb302b9c498396b89f61ce111830512a2809ef2febc315797f777f", "2b255a6f234cfc1f82573bcff4158f9a08915c6cc78243a5c41c41f2e45b3561" + "32dbdea0cadcc47d3c4673d8f945696e91cf848e53416b3aa2b41aab77a6bffe", "9ab58bfc190e8c2bbc38da3effd79322405923b82dc1ca7aed1035c5a9f61b1b" ], - "time": 1489600717, + "time": 1608824235, "nonce": 0, - "bits": "1b00aafa", - "previousblockhash": "1002a4f8555112786b9367fb77152bcf4c058ab756d2dde312f04fbbfd6de02a", - "block": "030000002ae06dfdbb4ff012e3ddd256b78a054ccf2b1577fb67936b78125155f8a4021094bedfdb985feebea5673f49134463428d3f0faf072fee85e54b5df8dede9410cd80c958faaa001b000000000201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff050210270101ffffffff01000000000000000000000000000100000001db86fae450b5cf86e783430b20d1ec5831971cc357ff0cb77458623495235a0b0100000048473044022048ac464dd1a72054a1df51982782a9d2bb8ab1922b8d24b9978ddce6cdbff6e30220497bc6951638b7d5dcae9645e8e2a687c6d736ff6a096debd70804c5139ef14101ffffffff0300000000000000000020c7f1052a010000232102624e3321ae66cfa542f1febe954b052cb560a8392c5ffd9a3d821cd17f47b682ac00f2052a010000001976a914fffc52dc6b3745407753734e8d4dbafa962b869b88ac0000000046304402206506b34c75aa787836034fcf5f01a0e051cd4eedbdd5d32fda015316c2e1951d02206db8085ec577ec6e2942d70c8c2879dfa69b6b53653c0b2c42601a1a47eea70c" + "bits": "1b0a372c", + "previousblockhash": "c2c6d9ceca0d3778f1a8c54f3221e6e30172c898e369170bb9a26270bf08f85a", + "block": "080000005af808bf7062a2b90b1769e398c87201e3e621324fc5a8f178370dcaced9c6c25f8d89e467fa5cc5c94e3abdc626a82a46af4bebf37ea1220e64e3b5ff16cd5babb5e45f2c370a1b0000000062ee99539aea3aebe1e4d5298c02f60f92ef0a48e37cf149e4e9cb286655a3210201000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0402102700ffffffff010000000000000000000000000001000000017f0dc8fe2ebf5791b16aa579b950c483f643f043fb35b5b26138c35562ddb2a5010000006c47304402201771cb3eee694776d406e6bf4ddbece93f0cf720d8c6e51f538afa6d07d62a1d022016ada904ce64c55b8850d0cc1b045532caf490ede42dc4072eed71964b3060f701015121039713fce1e1d3249ecd647e560baf6e053a039cd85c0617bffa2f305c264ac288ffffffff030000000000000000005adadf69e50100003376a97b63d114e76a4d75d3083f8b76eda8757f266449f66777916714aa2dc09e983d351dd19f90f872dcc35289c563536888ac00a3e111000000001976a914f1fe033465bea0949ee1ed04138948c47229989588ac000000004730450221008af30085a605acffa0dfc5d39667c2b03a44e1b0e6a71a37d9538e639a13ff1302201271df57aac349b3078849864555f63499dda41740afefd47a33a74257c59a0d" } \ No newline at end of file From 6b077db6790d599817b6467d047220e8b497a125 Mon Sep 17 00:00:00 2001 From: cipig <32116761+cipig@users.noreply.github.com> Date: Wed, 17 Feb 2021 23:18:48 +0000 Subject: [PATCH 068/386] fix DOGE sync (#100) --- electrumx/lib/coins.py | 1 + 1 file changed, 1 insertion(+) diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 0b6563a..8e3ee44 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -1198,6 +1198,7 @@ class Dogecoin(AuxPowMixin, Coin): TX_COUNT_HEIGHT = 1604979 TX_PER_BLOCK = 20 REORG_LIMIT = 2000 + DESERIALIZER = lib_tx.DeserializerAuxPowSegWit class DogecoinTestnet(Dogecoin): From 4ff6a9c4f022bcddad9c7f99d6f2f47360fb222d Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 18 Feb 2021 20:28:32 +0100 Subject: [PATCH 069/386] docs: "blockchain.block.headers": mention recommended 'max' value The Electrum client atm *requires* 'max' to be at least 2016. --- docs/protocol-methods.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/protocol-methods.rst b/docs/protocol-methods.rst index 1467d46..dcf3ae3 100644 --- a/docs/protocol-methods.rst +++ b/docs/protocol-methods.rst @@ -121,7 +121,8 @@ Return a concatenated chunk of block headers from the main chain. * *max* The maximum number of headers the server will return in a single - request. + request. (Recommended to be at least one difficulty retarget period, + i.e. 2016) The dictionary additionally has the following keys if *count* and *cp_height* are not zero. This provides a proof that all the given From 8f459984dfad114be0a460167e8357caec4a88c1 Mon Sep 17 00:00:00 2001 From: sunerok <8443713+justinvforvendetta@users.noreply.github.com> Date: Thu, 25 Feb 2021 02:40:48 -0500 Subject: [PATCH 070/386] add verge support (#102) * add verge support add verge support * fix name for block test * add line --- electrumx/lib/coins.py | 24 ++++++++++++++++++++++++ electrumx/lib/tx.py | 11 +++++++++++ tests/blocks/verge_mainnet_30000.json | 23 +++++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 tests/blocks/verge_mainnet_30000.json diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 8e3ee44..82a0e34 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -534,6 +534,30 @@ def block_header(cls, block, height): return deserializer.read_header(cls.BASIC_HEADER_SIZE) +class Verge(Coin): + NAME = "Verge" + SHORTNAME = "XVG" + NET = "mainnet" + XPUB_VERBYTES = bytes.fromhex("022d2533") + XPRV_VERBYTES = bytes.fromhex("0221312b") + P2PKH_VERBYTE = bytes.fromhex("30") + P2SH_VERBYTES = [bytes.fromhex("33")] + WIF_BYTE = bytes.fromhex("9E") + GENESIS_HASH = ('00000fc63692467faeb20cdb3b53200d' + 'c601d75bdfa1001463304cc790d77278') + RPC_PORT = 20102 + TX_COUNT = 500000 + TX_COUNT_HEIGHT = 3082138 + TX_PER_BLOCK = 1 + DESERIALIZER = lib_tx.DeserializerVerge + + @classmethod + def header_hash(cls, header): + '''Given a header return the hash.''' + import scrypt + return scrypt.hash(header, header, 1024, 1, 1, 32) + + class HOdlcoin(Coin): NAME = "HOdlcoin" SHORTNAME = "HODLC" diff --git a/electrumx/lib/tx.py b/electrumx/lib/tx.py index 262d557..faf210b 100644 --- a/electrumx/lib/tx.py +++ b/electrumx/lib/tx.py @@ -715,6 +715,17 @@ def read_tx(self): return TxTime(version, time, inputs, outputs, locktime) +class DeserializerVerge(Deserializer): + def read_tx(self): + version = self._read_le_int32() + time = self._read_le_uint32() + inputs = self._read_inputs() + outputs = self._read_outputs() + locktime = self._read_le_uint32() + + return TxTime(version, time, inputs, outputs, locktime) + + class DeserializerEmercoin(DeserializerTxTimeSegWit): VERSION_AUXPOW = (1 << 8) diff --git a/tests/blocks/verge_mainnet_30000.json b/tests/blocks/verge_mainnet_30000.json new file mode 100644 index 0000000..b7cb38a --- /dev/null +++ b/tests/blocks/verge_mainnet_30000.json @@ -0,0 +1,23 @@ +{ + "hash": "0000000000016d4250cba4335ffa6bad92d06d677f3dc52ef35d1531e2e58a4c", + "size": 2946, + "height": 30000, + "merkleroot": "c919c7df0d6aed0ef1e7c01056669825d687c8827de112151bbddc476ba1e783", + "tx": [ + "6c1ab1c76c329c433538df2d27775e363684ca307cc99fb4250eeb9f5f19b050", + "8ca961070fcdf2a5044b5b0074391db3e43c942994f1c9f269cecd2218e364b0", + "e8e6f937491196b692f65df37e82cc66ed082a7619d4aebfb657769a3d482393", + "8cb8eba0d3fb4bae473943c1fdf04de433aebf2effd9219fd9c5ba237fda2ac7", + "44e977088f4dcac2936e59238f1b3b847ff2898b2d94b64b9c7f8ab530f0540f", + "ad4fa8d55e6e9d389ef953bedc6cc4c41fff1402dfe26a24657536c227356023", + "d4aff2dca944104b76078a5592c433200b1895cd19e02c2047d664b324a4dcaa", + "274e1e48df441570adeaae69bcfd8db2b73241c0030c67d33ae92d97f8aa24d8", + "f38ae6de2add1eb686244f8c4bc37419ee0a7f82e0cf4a24cdf0bb8572ccefbf", + "9a9cb74af8bde504f939e0af19244b04dbfe235a34d0cfcb2fecc7200907a6c3" + ], + "time": 1415200907, + "nonce": 3550806389, + "bits": "1c04878d", + "previousblockhash": "0000000009022174ecaf5b6c3154c48c9e22d6c919040bb03b030212bc32dd9c", + "block": "040000009cdd32bc1202033bb00b0419c9d6229e8cc454316c5bafec742102090000000083e7a16b47dcbd1b1512e17d82c887d62598665610c0e7f10eed6a0ddfc719c98b405a548d87041c7501a5d30a010000008b405a54010000000000000000000000000000000000000000000000000000000000000000ffffffff26023075062f503253482f04b4405a540810000139180000000d2f6e6f64655374726174756d2f0000000001a02f49a40b000000232102ebcae5280124fdef8971f19a503b9aaed9a635399ea032bf2f8efc141d6eb055ac00000000010000005e3f5a54011a735216f0be2f4dfcfa74f64c295cca08145b3e6428da3c0d48e51003c7b78b000000004a493046022100e03ab48e4434116bdab784dd70b713a71644369dc8d8994b5f35b11a8e892808022100b4bdc4bf41c395300314fbe5ae458a963a5acefe1519a210ed2b2a7c5e532df901ffffffff02f77ec95a0a0000001976a914f7c8c0484795bb52c2cada85d0d799ee14d0016188ac696e7049010000001976a9145f278b723feaa4bc06759c3ea356420fbf86fb5d88ac00000000010000005d3f5a5402e1b02ed28bdd985cb0818322a12c629d0df89a334558cba2ff652a0909864f2200000000494830450221009c1abb2da0e89cb53e3fd82139f999d9b627ab71146e6d25d7a1f93fb03ac2ba02204bf2d976ef1902ba86197c390687e0b184ef3f7dd1fa9f2fefcdb84f49889a5e01ffffffff4cd3e2f0b63e3b9ef3cd7daaa10974d4845b7619755bf5465cea5993784e3975000000006c493046022100e2af299c2eced2e33633b264972690fc18fd6ac054cea37ed6a4dadc3bfec86c022100bab77188e6533bc483dad2cbfd8a20689ae405128c5ace56bd398ad1327189c60121039a0857e144b67eeef5545a7f81cd0e873a82436495aa89b5d1c6a0941abdb74bffffffff026a9e1238000000001976a9142258841ba8d838a917a5af61fd01b962fd9b1d5b88acf09639250d0000001976a91442bb00bf1065f629f1bd857f22078087f714fa2088ac0000000001000000b83f5a5406d25f668ae0b00a515a4db1478ab60a1e92b917b3189c3526167f173ba47a44e00000000049483045022100af46285927509905295a1e9f3f182f3731d03b79577b900191cf3ec4ce7bd9b702204339c5e4a478712081059f2f94a7884c1c646b90e1ce990aa491a0a013d1dd3001ffffffff0a4155a0e9cbcd5f32ca4d104d7e95c48b81cec2e75f7b842fbcae2e6194afc7000000004948304502205d558d4aac070aeea5a53f3ef4ad6044ec75c07d396d28e7fc0c0cf04f11e93a022100ccaf9e87441cad6bc83c80a5753e6d6898b8d93ee500bf2971e6198349a6cbce01ffffffff3471210e29f9c6fceed18f4b21319bfea1e565bf966c66ba39060ccf5959a306000000004948304502206a8f8dfc29acd34fa2b4a04b63d30c0a391ab29f5860b1c8bc7909cc40b30438022100d42197c6eabbd04e88b64de56647af0ec727b0306050f835eb78298fe32b683601ffffffffdd9192f1c821acdcd827cf68f9683bc60acd08199f81c2e8fee0ef32669d51f200000000494830450221009b450cd741892e71844442c3fe0759c8f5e6f11ebf5c153e7dde7f37d92e64650220412e70e317babf9d86f0710f74db3391ae319b3e4903bdf8a20d012b3c80291801ffffffff5003d32ffbbe6f1a0252c0e7cbf3e82affebb9b14fb0c150a5be0cfe6c5fe1d0000000004a4930460221008262ea444222b16e0690c05253f8e8294bdb2953bb83a665d41de5ff6858e12c022100c9bbe4af76cd271e287a9d608e8f9d8ef01e818023210ea4a6719c4644a1251c01ffffffff5169dcf239506a40a2edcab02cb6832cf09dbbc68e06d307dbf3618ee23d6e8a000000004a493046022100e206ec109a9b263a9297ef040a69c1d4dd979caf0f73d75cdd0fab6664b6f9d7022100ae8e334e0ff0956caeb7dbbd4e99beca8b05e4a8976a6b78aa47fcb258be661401ffffffff0180d26ad9450000001976a914f3d1564691e4a07e291af35be7d1cfd3a18be81588ac00000000010000005d3f5a540121c2a364aae4959f326fb5a71d0b33fa9798f2b92f9756e16462058773cf6be8000000006b483045022100c43e500448ad8d9f4b0a631661d96f1793c31546e25500568da5df9355c34d1902206eb8f4c207f444d9c22e9bddc85dd5c1c9d9c4141908bbe588144cb9c0dc00a4012103be97dd65dac7738644d310be4f3c40872fa167d5f4e4da83e3b6d54877977092ffffffff02440e2deb0a0000001976a914292db18f666089a9b113e57efd626e74e97591e588acb05bbf8e000000001976a914d1421319b692ea7e9ff86f79b68a09d25dc713b288ac00000000010000005d3f5a54014736123327ff77f2fa9801e5c33945a05af4335a864c699a3d5d48af06cc3c7a000000006c493046022100e698abb8dae50f8bbb88c97a71c2e0156586f6c602dc62787a1b70399ea8c592022100a383913269e3596855ecfb53f4f18f28cc697791df9d0010c56ceb844b080ca0012103c6f188d112687adfb6ac30bd50cbc15a247e1241fce504660f78c43d201750c0ffffffff02f01ee6bb090000001976a914fbb1277ee83f5317f3809d4085a02cca830b600888ac9a8d3496000000001976a914e6ea703005e4046e6f2b1201e072f41c6133a11b88ac00000000010000005d3f5a5401aeac5c70b6717843dc353ac13f4eb868b67e37df9504f753d5c1e1050244a820000000006b483045022100a26d7b6bccba0b0baba842068248d3f526ea5318dfd9ba42b686a1a955df915302206831b6b98a0da6db8e87c0a9ece1b1cae415a760a84588b3f8932c989a0eecd00121036a6b9416c724415559ca9027c181af753b8bdff78fb318bef315d4eaf86ce7ccffffffff0293d8d67e060000001976a9149f819a91865a697c9164dca13e657a3abf70a06088ace5b4a050010000001976a9146d794f23b50fd6ea052b72d0a241aa8649443e7988ac00000000010000005d3f5a5401db69409ad25eca6399ad6c858ad725a72f88e66bfb7b92ff13e2b87995bb12a5000000006a473044022064aff7371d43c639060515c9127e3354bc0b996cb0fe8146e52dfc8d2aa78ae402205bf6187c517473ad65101121fa68a54700045572d661ea8f1a1b2389bbb468e6012103db1a048f51b5193f05743b617c091a3fefd54176d45dc787c2e61859b2eb32a5ffffffff021acf7be9020000001976a9141cbfeb798ebff07ba3e5608c526b01313d205d7488acd5e47b68020000001976a914033d8fa90226407c7647b4efa0cf9bf8afaf993988ac00000000010000005c3f5a54010777ed43648077c33a80149ebd65448c54ebbf84c34668e1b1edac464065fdd3000000006b483045022060ffbac0c7826560ceda2fb72fbc647ddfc8eaff2a5ab10972101e0435efa8af022100f912d2263ff147eaf0afd892b09d2f8dde2c16e2db20ec665671fa1494a09707012103bbd097bd65280803eb1d786861b7b3781d0e20d6c70c5b473440cd2efa2ed612ffffffff02d98dd6ef000000001976a9144cfc1992a03805e0c4c5abfb5229f31b1948ccdf88ace8a7c0b9000000001976a914cdcaed5441426254dfae4d9006dd413a2539588e88ac00000000010000005d3f5a540189c55c16d7ba925fcc3da3adb10af7425c32ec106c5a04a8a7cfd1515af53239000000006b48304502204cdb18bb0fab940d52c0812ca4a5c47c1aca17c0c00b2159d061cfd41633543a022100b97eb49b6fc7574288d60810837cf61f565d666fbcefaea2f0bc2ec349c1956c012103f9ef6e77ab82ab0bb2877b960a75d1d4c48dc1357e5c7eeae60776c3acda0fb6ffffffff020689ea05000000001976a914f38e5277a497bd975eaea28ae524aad365ca731d88ac7d96dc0d000000001976a91474a0a02dd0656aa2519f1897663746f8383f9b0888ac00000000473045022100df107ba5ad0dd3813d969ac7479860bb0b928e96908e5c3dee34b2d1413a9c23022078e349112a317ea393080197a4b4e3cf28b391c21d238652c67e86af635229f3" + } \ No newline at end of file From 42ad745b2b241c90d1f2a80e3ad68e89d24067ab Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 5 Mar 2021 21:44:32 +0100 Subject: [PATCH 071/386] coins: add comments to clarify some fields related spesmilo/electrumx#105 --- electrumx/lib/coins.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 82a0e34..cbec117 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -100,13 +100,15 @@ class Coin: BLACKLIST_URL = None ESTIMATEFEE_MODES = (None, 'CONSERVATIVE', 'ECONOMICAL') - TX_COUNT: int - TX_COUNT_HEIGHT: int - TX_PER_BLOCK: int RPC_PORT: int NAME: str NET: str + # only used for initial db sync ETAs: + TX_COUNT_HEIGHT: int # at a given snapshot of the chain, + TX_COUNT: int # there have been this many txs so far, + TX_PER_BLOCK: int # and from that height onwards, we guess this many txs per block + @classmethod def lookup_coin_class(cls, name, net): '''Return a coin class given name and network. From 506b296b1a1a8459d61efdbd4483a70c5e8c2a21 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Wed, 10 Mar 2021 18:17:29 +0200 Subject: [PATCH 072/386] Fix a small typo --- docs/protocol-changes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/protocol-changes.rst b/docs/protocol-changes.rst index d3faa6e..151792c 100644 --- a/docs/protocol-changes.rst +++ b/docs/protocol-changes.rst @@ -165,4 +165,4 @@ Version 1.4.1 New methods ----------- - * :func:`blockchain.scipthash.unsubscribe` to unsubscribe from a script hash. + * :func:`blockchain.scripthash.unsubscribe` to unsubscribe from a script hash. From b2e7482cb9d3b4b07905a503913a54afe2ae595a Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Wed, 10 Mar 2021 18:45:36 +0200 Subject: [PATCH 073/386] Correct latest version in docs/protocol-changes.rst --- docs/protocol-changes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/protocol-changes.rst b/docs/protocol-changes.rst index 151792c..a13ffbd 100644 --- a/docs/protocol-changes.rst +++ b/docs/protocol-changes.rst @@ -159,7 +159,7 @@ Changes Non-AuxPoW chains are unaffected. -Version 1.4.1 +Version 1.4.2 ============= New methods From 184c87487717e02f7440e8c6a855403bf7af2311 Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Sun, 11 Apr 2021 14:38:49 -0500 Subject: [PATCH 074/386] electrumx is compatible with rapidjson 1.0. It only breaks Python 3.5 support, already broken in electrumx. Rapidjson has also switched to two-segments versioning. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 78472eb..d8f9427 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ install_requires=['aiorpcX[ws]>=0.18.5,<0.19', 'attrs', 'plyvel', 'pylru', 'aiohttp>=3.3,<4'], extras_require={ - 'rapidjson': ['python-rapidjson>=0.4.1,<1.0.0'], + 'rapidjson': ['python-rapidjson>=0.4.1,<2.0'], 'rocksdb': ['python-rocksdb>=0.6.9'], 'ujson': ['ujson>=2.0.0,<4.0.0'], 'uvloop': ['uvloop>=0.14'], From 7f5c38bb971e440da95dc40152320dc7e146cea7 Mon Sep 17 00:00:00 2001 From: Justin Turner Arthur Date: Mon, 19 Apr 2021 02:21:59 -0500 Subject: [PATCH 075/386] Slimmer docker image, and uses PyPI instead of GitHub artifacts. (#118) Based on the official Python-on-debian images instead of Ubuntu base. --- contrib/Dockerfile | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/contrib/Dockerfile b/contrib/Dockerfile index f5803c0..11cbe37 100644 --- a/contrib/Dockerfile +++ b/contrib/Dockerfile @@ -1,20 +1,31 @@ -# example of Dockerfile that builds release of electrumx-1.13.0 -# ENV variables can be overrided on the `docker run` command +# example of Dockerfile that installs spesmilo electrumx 1.16.0 +# ENV variables can be overriden on the `docker run` command -FROM ubuntu:20.04 +FROM python:3.9.4-buster AS builder -WORKDIR / -ADD https://github.com/spesmilo/electrumx/archive/1.15.0.tar.gz / -RUN tar zxvf *.tar.gz +WORKDIR /usr/src/app -RUN apt-get update && \ - apt-get -y install python3.8 python3-pip librocksdb-dev libsnappy-dev libbz2-dev libz-dev liblz4-dev +# Install the libs needed by rocksdb (including development headers) +RUN apt-get update \ + && apt-get -y --no-install-recommends install \ + librocksdb-dev libsnappy-dev libbz2-dev libz-dev liblz4-dev \ + && rm -rf /var/lib/apt/lists/* -RUN cd /electrumx* && pip3 install .[rocksdb] +RUN python -m venv venv \ + && venv/bin/pip install --no-cache-dir e-x[rapidjson,rocksdb]==1.16.0 + + +FROM python:3.9.4-slim-buster + +# Install the libs needed by rocksdb (no development headers or statics) +RUN apt-get update \ + && apt-get -y --no-install-recommends install \ + librocksdb5.17 libsnappy1v5 libbz2-1.0 zlib1g liblz4-1 \ + && rm -rf /var/lib/apt/lists/* ENV SERVICES="tcp://:50001" ENV COIN=Bitcoin -ENV DB_DIRECTORY=/db +ENV DB_DIRECTORY=/var/lib/electrumx ENV DAEMON_URL="http://username:password@hostname:port/" ENV ALLOW_ROOT=true ENV DB_ENGINE=rocksdb @@ -22,13 +33,16 @@ ENV MAX_SEND=10000000 ENV BANDWIDTH_UNIT_COST=50000 ENV CACHE_MB=2000 -VOLUME /db +WORKDIR /usr/src/app +COPY --from=builder /usr/src/app . + +VOLUME /var/lib/electrumx RUN mkdir -p "$DB_DIRECTORY" && ulimit -n 1048576 -CMD ["/usr/bin/python3", "/usr/local/bin/electrumx_server"] +CMD ["/usr/src/app/venv/bin/python", "/usr/src/app/venv/bin/electrumx_server"] # build it with eg.: `docker build -t electrumx .` # run it with eg.: -# `docker run -d --net=host -v /home/electrumx/db/:/db -e DAEMON_URL="http://youruser:yourpass@localhost:8332" -e REPORT_SERVICES=tcp://example.com:50001 electrumx` -# for a proper clean shutdown, send TERM signal to the running container eg.: `docker kill --signal="TERM" CONTAINER_ID` +# `docker run -d --net=host -v /home/electrumx/db/:/var/lib/electrumx -e DAEMON_URL="http://youruser:yourpass@localhost:8332" -e REPORT_SERVICES=tcp://example.com:50001 electrumx` +# for a clean shutdown, send TERM signal to the running container eg.: `docker kill --signal="TERM" CONTAINER_ID` From fe581bd1b0579a01284ca439cea150de2a12e91c Mon Sep 17 00:00:00 2001 From: imaginaryusername <34949601+imaginaryusername@users.noreply.github.com> Date: Fri, 30 Apr 2021 22:51:14 -0700 Subject: [PATCH 076/386] Fix BCH entry in coins.py (#127) It is no longer appropriate to call BCH "Bitcoin Cash ABC", even for convenience; this PR reflects that. Also updates default server list. --- electrumx/lib/coins.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index cbec117..0e8eeca 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -592,7 +592,7 @@ class BitcoinSV(BitcoinMixin, Coin): class BitcoinCash(BitcoinMixin, Coin): - NAME = "BitcoinCashABC" # Some releases later remove the ABC suffix + NAME = "BitcoinCash" SHORTNAME = "BCH" TX_COUNT = 265479628 TX_COUNT_HEIGHT = 556592 @@ -600,7 +600,10 @@ class BitcoinCash(BitcoinMixin, Coin): PEERS = [ 'bch.imaginary.cash s t', 'electroncash.dk s t', - 'wallet.satoshiscoffeehouse.com s t', + 'electrum.imaginary.cash s t', + 'bch.loping.net s t', + 'electroncash.de s t', + 'blackie.c3-soft.com s t', ] BLOCK_PROCESSOR = block_proc.LTORBlockProcessor @@ -858,7 +861,7 @@ def max_fetch_blocks(cls, height): class BitcoinCashTestnet(BitcoinTestnetMixin, Coin): '''Bitcoin Testnet for Bitcoin Cash daemons.''' - NAME = "BitcoinCashABC" + NAME = "BitcoinCash" PEERS = [ 'bch0.kister.net t s', 'testnet.imaginary.cash t50001 s50002', @@ -1029,7 +1032,7 @@ class LitecoinRegtest(LitecoinTestnet): class BitcoinCashRegtest(BitcoinTestnetMixin, Coin): - NAME = "BitcoinCashABC" # Some releases later remove the ABC suffix + NAME = "BitcoinCash" NET = "regtest" PEERS = [] GENESIS_HASH = ('0f9188f13cb7b2c71f2a335e3a4fc328' From fab31f419a549415515c2532994ad56bbcb6c5fe Mon Sep 17 00:00:00 2001 From: cipig <32116761+cipig@users.noreply.github.com> Date: Sat, 1 May 2021 15:38:54 +0000 Subject: [PATCH 077/386] add Syscoin (SYS) (#112) * add Syscoin (SYS) * fix testblock --- electrumx/lib/coins.py | 18 ++++++++++++++++++ tests/blocks/syscoin_mainnet_430000.json | 14 ++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 tests/blocks/syscoin_mainnet_430000.json diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 0e8eeca..a344856 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -3922,3 +3922,21 @@ class Beyondcoin(Coin): TX_PER_BLOCK = 2 RPC_PORT = 10332 REORG_LIMIT = 5000 + + +class Syscoin(AuxPowMixin, Coin): + NAME = "Syscoin" + SHORTNAME = "SYS" + NET = "mainnet" + P2PKH_VERBYTE = bytes.fromhex("3f") + P2SH_VERBYTES = (bytes.fromhex("05"),) + WIF_BYTE = bytes.fromhex("80") + GENESIS_HASH = ('0000022642db0346b6e01c2a397471f4' + 'f12e65d4f4251ec96c1f85367a61a7ab') + DESERIALIZER = lib_tx.DeserializerAuxPowSegWit + TX_COUNT = 911232 + TX_COUNT_HEIGHT = 954572 + TX_PER_BLOCK = 1 + RPC_PORT = 8370 + REORG_LIMIT = 2000 + CHUNK_SIZE = 360 diff --git a/tests/blocks/syscoin_mainnet_430000.json b/tests/blocks/syscoin_mainnet_430000.json new file mode 100644 index 0000000..008cf6c --- /dev/null +++ b/tests/blocks/syscoin_mainnet_430000.json @@ -0,0 +1,14 @@ +{ + "hash": "d9b0f166c2dbed6e76aeaa118da601c46e79a631b44d542da54f8ef08658dc64", + "size": 1323, + "height": 430000, + "merkleroot": "cd413666cf617e193e5e6aaf1cc90a78028299b00ede99efc6122e0417b82010", + "tx": [ + "cd413666cf617e193e5e6aaf1cc90a78028299b00ede99efc6122e0417b82010" + ], + "time": 1585179744, + "nonce": 0, + "bits": "1804bd23", + "previousblockhash": "70735969ca5ff88db21dec34ed5d9b72fc894c8f22b4e56e4607cb261bae13d1", + "block": "04010010d113ae1b26cb07466ee5b4228f4c89fc729b5ded34ec1db28df85fca695973701020b817042e12c6ef99de0eb0998202780ac91caf6a5e3e197e61cf663641cd60ec7b5e23bd04180000000001000000010000000000000000000000000000000000000000000000000000000000000000ffffffff64034f81092cfabe6d6df55b0a430af81f0e006f0a0938965706cf5f299537635e1d4668fb8792bcb19210000000f09f909f000e4d696e6564206279206d75797537000000000000000000000000000000000000000000000000000000000005002771030000000000045f4c2c4d000000001976a914c825a1ecf2a6830c4401620c3a16f1995057c2ab88ac00000000000000002f6a24aa21a9ed72f224837e350b7bf33998dbbbb87744e261943310ae0c31cdb93c61728bea7808000000000000000000000000000000002c6a4c2952534b424c4f434b3a3be0a1bb84a0c2c5967888df3d763592290001d95a07ac00e8e85c350021deba0000000000000000266a24b9e11b6d2cd45dc05f4146da87f6404ad2fe62dafcd4deae302fea9bc49054f11d7c97b89d2b1c3d00000000000000000000000000000000000000000000000000000000000000000c5c762275442c2b26114f71d68a065d5248d8bf65c1ed1f3680d84f6aa628c3d788de690530919281e666d32b4bb6cba64d48cf5971e7e68c32d512bd8bad4bce83c1488fd88edff35ea0401e1b626474d164b0d2a13e5f8cd4a08e7be76bb856fd956e0dcb37ccb58b1d0f2dba8f0692cdd3f0a02ebd166d2be109cbff63032f087ff84a4375141ace40bdd4e611f06bee05766c83af47b4a2b464de1597c6b3f1a105b4ab375e664ee0e166bf055331a168e339cfde93381f244a4744ca157b8025b9c8653a2acacd3557201745493c06291671b5b3ac9dfbde442be97c2d3ce3780afcc50e5e2e732610eb121eeddc486a2e791a30274d2fba832a6a0d3953fd7a5d5a64831e87f57fdb3935d354ee4f0aafdef30ae11b6bfee54f0e9b9f213a6ad745c6af097b7e9915ea63c6ce8842d3e52c5128ec42470297154874631db66bb4631250de6bfa1fd46a77538da3ed4d99d8a99e323e42923e7018199134cd42a62e86d005ce77c57dbc4285da06d8ec2660c204b52f5c95c8060290deda000000000490457845635ea8daa96e1d7efde99cc4f435a40edcb363a61a9d915542ec61a33feee5244c18afb612c4846a8bf1b07759fedbdec9d139ade9f4c82736e17fe8f5b70f04e4fa1c7f9cd9bd0d020107f3845137d5cc55f4f31c8ca586b66211fbf1c66a24cb32da29fa240423eccca0ebba048c44e05941f4f64f0637c9dfb88a0e0000000000c0207774dfabd98c6e328ed08862170190ae59e059ff62ca0a000000000000000000fd98ff179e63b3a62079efaab5c1d16cb39589ebdc0635fe1002d95e98570d1f87ec7b5e19011117421c056c01020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff3703b08f0601010fe4b883e5bda9e7a59ee4bb99e9b1bc205b323032302d30332d32355432333a34323a32342e3338343337373239305a5dffffffff0310eda1330000000023210214a5f15a73686b64cf27405e018e2f06e0501b52f4ff98282badd9d6948fb57dac30c7e59a000000001976a9145ed710a7a82410e32ff0d29fe346b6b5ae0b8e6088ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000" +} From 8193bc6ebcada388075875612b1f4d88a0de9c74 Mon Sep 17 00:00:00 2001 From: Jin Eguchi Date: Sun, 2 May 2021 00:40:48 +0900 Subject: [PATCH 078/386] Add support bitcoin signet (#122) * Add signet * Add bitcoin signet test --- electrumx/lib/coins.py | 14 ++++++++++++++ tests/blocks/bitcoin_signet_31500.json | 16 ++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 tests/blocks/bitcoin_signet_31500.json diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index a344856..d785d05 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -935,6 +935,20 @@ class BitcoinSegwitRegtest(BitcoinRegtest): NAME = "BitcoinSegwit" # support legacy name +class BitcoinSignet(BitcoinTestnet): + NAME = "Bitcoin" + NET = "signet" + GENESIS_HASH = ('00000008819873e925422c1ff0f99f7c' + 'c9bbb232af63a077a480a3633bee1ef6') + PEERS = [] + TX_COUNT = 1 + TX_COUNT_HEIGHT = 1 + + +class BitcoinSegwitSignet(BitcoinSignet): + NAME = "BitcoinSegwit" # support legacy name + + class BitcoinNolnet(BitcoinCash): '''Bitcoin Unlimited nolimit testnet.''' NET = "nolnet" diff --git a/tests/blocks/bitcoin_signet_31500.json b/tests/blocks/bitcoin_signet_31500.json new file mode 100644 index 0000000..1bc0651 --- /dev/null +++ b/tests/blocks/bitcoin_signet_31500.json @@ -0,0 +1,16 @@ +{ + "hash": "000001279d712f44f617a4aba70521e9612115be3663cba9a3cedcdade8bdc43", + "size": 528, + "height": 31500, + "merkleroot": "935fb3ab834229151ed9999b5ce12b342a33d0121e667a9c40235a385242c623", + "tx": [ + "4b314b9112077eb2c948b6164afbf246c773dac4ef63fd5e88cf35f954fb4838", + "eade0fce56112066bfffc031aa54dd186d12758c8d462792b7e1bbe17e5fc76a", + "ffa88d535d70527c5cf97ef074ba388fc5c0c098288c193fc73e0756539144b1" + ], + "time": 1617216417, + "nonce": 7764888, + "bits": "1e015ded", + "previousblockhash": "00000040f59599972481c6852e71319d079393f1fafad4acb1e4e5b4755c99c3", + "block": "00000020c3995c75b4e5e4b1acd4fafaf19393079d31712e85c68124979995f54000000023c64252385a23409c7a661e12d0332a342be15c9b99d91e15294283abb35f93a1c36460ed5d011e987b760003010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03020c7bfeffffff02faf2052a0100000016001481113cad52683679a83e76f76f84a4cfe36f75010000000000000000776a24aa21a9ed3b31ecce6ca20fb18ddde6c3408b8b1bb44c92c2a906f106bda9634cac4b578b4c4fecc7daa2490047304402203424753b5d6e192026a09176012fdec097bc7c085bcd933690f792c9578b3f2402204ae2401b38d58d62c16600a645496b7a175549e6fd4c2c7163225094640eae66010001200000000000000000000000000000000000000000000000000000000000000000000000000200000000010173219026eba80ff9c7ca506376aa8b7db9dc4b82924838989c21b9ada48bd7cd0000000000ffffffff0106aac41d000000002200204ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc33260010151000000000200000000010197250c31a4a0341e5423f281391292495901ed5c575a5ad22b08ba1515cc179c0000000000ffffffff012b25ed05000000002200204ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc3326001015100000000" +} From 339999d629ab9d0976e806c7bd04d1ba1faae2e7 Mon Sep 17 00:00:00 2001 From: Willy Date: Sat, 1 May 2021 17:44:12 +0200 Subject: [PATCH 079/386] add peercoin testnet electrum to peers list (#116) --- electrumx/lib/coins.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index d785d05..7067055 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -1730,6 +1730,9 @@ class PeercoinTestnet(Peercoin): GENESIS_HASH = ('00000001f757bb737f6596503e17cd17' 'b0658ce630cc727c0cca81aec47c9f06') ESTIMATE_FEE = 0.001 + PEERS = [ + "testnet-electrum.peercoinexplorer.net s" + ] class Trezarcoin(Coin): From 1c680d362455687db42c2498f9ff6cf1bf8eda91 Mon Sep 17 00:00:00 2001 From: John Studnicka Date: Sat, 1 May 2021 08:46:43 -0700 Subject: [PATCH 080/386] Add Electra Protocol (XEP) support (#124) --- electrumx/lib/coins.py | 32 +++++++++++++++++++ .../blocks/electraprotocol_mainnet_1000.json | 18 +++++++++++ .../electraprotocol_mainnet_130000.json | 19 +++++++++++ 3 files changed, 69 insertions(+) create mode 100644 tests/blocks/electraprotocol_mainnet_1000.json create mode 100644 tests/blocks/electraprotocol_mainnet_130000.json diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 7067055..9a693ae 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -3489,6 +3489,38 @@ def header_hash(cls, header): return double_sha256(header) +class ElectraProtocol(Coin): + NAME = 'ElectraProtocol' + SHORTNAME = 'XEP' + NET = 'mainnet' + XPUB_VERBYTES = bytes.fromhex('0488b21e') + XPRV_VERBYTES = bytes.fromhex('0488ade4') + P2PKH_VERBYTE = bytes.fromhex('37') + P2SH_VERBYTE = bytes.fromhex('89') + WIF_BYTE = bytes.fromhex('a2') + GENESIS_HASH = '000000954c02f260a6db02c712557adcb5a7a8a0a9acfd3d3c2b3a427376c56f' + RPC_PORT = 16816 + TX_COUNT = 264299 + TX_COUNT_HEIGHT = 130000 + TX_PER_BLOCK = 5 + REORG_LIMIT = 1080 + DESERIALIZER = lib_tx.DeserializerSegWit + PEERS = [ + 'electrumx1.electraprotocol.eu s t', + 'electrumx2.electraprotocol.eu s t', + 'electrumx3.electraprotocol.eu s t', + 'electrumx4.electraprotocol.eu s t', + 'electrumx5.electraprotocol.eu s t', + ] + + @classmethod + def genesis_block(cls, block): + super().genesis_block(block) + + # XEP has spendable genesis outputs, so we return the full block here. + return block + + class Myce(Coin): NAME = "Myce" SHORTNAME = "YCE" diff --git a/tests/blocks/electraprotocol_mainnet_1000.json b/tests/blocks/electraprotocol_mainnet_1000.json new file mode 100644 index 0000000..0d9d8ac --- /dev/null +++ b/tests/blocks/electraprotocol_mainnet_1000.json @@ -0,0 +1,18 @@ +{ + "hash": "00000000000026aa702f9a93f51398b29267be70ea036fe27bc1af2395d6d51a", + "size": 197, + "height": 1000, + "version": 1073741824, + "merkleroot": "856e4a1024c3ec6d788fc3129f2ac7aeebbd03106cb5d6916658ecfd98aef412", + "tx": [ + "856e4a1024c3ec6d788fc3129f2ac7aeebbd03106cb5d6916658ecfd98aef412" + ], + "time": 1609299575, + "nonce": 2554065598, + "bits": "1a2de135", + "difficulty": 365672.7164172354, + "chainwork": "0000000000000000000000000000000000000000000000000033122accfe91ae", + "previousblockhash": "bb7576f62ae30a8dd0ae4e382fe7319b4c677a28b94c062d33ebfc0e3109e5cc", + "nextblockhash": "dfca21fb5f64e3bf04ec1df663602a1f2c2b12b4647c38a04082c38ecfc1d648", + "block": "00000040cce509310efceb332d064cb9287a674c9b31e72f384eaed08d0ae32af67675bb12f4ae98fdec586691d6b56c1003bdebaec72a9f12c38f786decc324104a6e8577f6eb5f35e12d1abef23b980102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1f02e8030477f6eb5f088100465aa40e0000746865706f6f6c2e6c6966655c30000000000100e1f505000000001976a91457aca8e848f90187f48540afc4477ceb1bc8c08388ac00000000" +} \ No newline at end of file diff --git a/tests/blocks/electraprotocol_mainnet_130000.json b/tests/blocks/electraprotocol_mainnet_130000.json new file mode 100644 index 0000000..e4ab2f3 --- /dev/null +++ b/tests/blocks/electraprotocol_mainnet_130000.json @@ -0,0 +1,19 @@ +{ + "hash": "2a4bf3cc319b5dc03f5688c23d7613607162c2ba2cecaa768452b2e3f70b908c", + "size": 502, + "height": 130000, + "version": 536870912, + "merkleroot": "40727b97abcd14148160b3f15133263ab467d7cf3330453bcc609366a64e9eac", + "tx": [ + "5574d7fcfbd6dc4cb61bfd1ea7d0ff154d0f7a57af5fc2e3338e1a828549aecd", + "18550128a77bbe8901155867fe3b797bb03e436248ec72b852e2b29ec1137be5" + ], + "time": 1619404864, + "nonce": 0, + "bits": "19726f80", + "difficulty": 37531185.69609667, + "chainwork": "00000000000000000000000000000000000000000000032f4fdc40d8ac24428a", + "previousblockhash": "bfa2271500fb015998ef2251091cf2c1e98694188259a65c61cea2ea10485b40", + "nextblockhash": "9c3e7d5afaeda58c102db5dda8a51c800d0cedca03db3e18ddb216f4c25fea03", + "block": "00000020405b4810eaa2ce615ca65982189486e9c1f21c095122ef985901fb001527a2bfac9e4ea6669360cc3b453033cfd767b43a263351f1b360811414cdab977b724040288660806f72190000000002020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0603d0fb010114ffffffff020000000000000000000000000000000000266a24aa21a9edcefd1080247b1f4381846620c0aa184c8f10bb17ba264f375d536aebaab1ebe20120000000000000000000000000000000000000000000000000000000000000000000000000020000000001010af63b6d57699f810174c3b44d6ff12aa594ad2d57772e78c322a21e5ab16f530100000000ffffffff020000000000000000000fb9a50df8b603001600142d43b595b77816eab55f3ba7a58467905202e3b30247304402206ae5aec30ed68977edff570b61f4577f89262388661ee5faf8d56952dd51f9630220197bb06e60c06bfab47acb1ed1a095f92249bd0d6f769141c316efdddbd53b240121023482539255d9405aed1eba53900225683e2f2f7a4ef27cc262e9addf4b8c1a0900000000473045022100a5b9e5d8d6484195ae6d384f57093f3b589798afe4e0d70191065f3f3c912cfd022024a8861a09b9d5c233e26ba3019e802347df4acb0a593783145cdd431a50affc" +} \ No newline at end of file From 442057e68808cdb9472b3c48d730466fccb8e073 Mon Sep 17 00:00:00 2001 From: coinomiAngelos <75318150+coinomiAngelos@users.noreply.github.com> Date: Thu, 6 May 2021 18:58:26 +0300 Subject: [PATCH 081/386] Add fix for Defcoin (#128) * Add fix for Defcoin * Update coins.py --- electrumx/lib/coins.py | 1 + 1 file changed, 1 insertion(+) diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 9a693ae..e48bb09 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -3678,6 +3678,7 @@ class Defcoin(Coin): TX_PER_BLOCK = 1 RPC_PORT = 9386 REORG_LIMIT = 5000 + DESERIALIZER = lib_tx.DeserializerAuxPowSegWit class Auroracoin(Coin): From a0d3053f60074264b54b3ef7583f2600f4b73ead Mon Sep 17 00:00:00 2001 From: Neil Booth Date: Tue, 18 May 2021 15:56:16 +0100 Subject: [PATCH 082/386] Add MAX_RECV environment variable. Default to 1 million bytes. (so same as before, but the setting is now exposed) see https://github.com/kyuupichan/electrumx/issues/1053 --- docs/environment.rst | 8 ++++++++ electrumx/server/env.py | 1 + electrumx/server/session.py | 6 +++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/environment.rst b/docs/environment.rst index ceae2fc..f321de3 100644 --- a/docs/environment.rst +++ b/docs/environment.rst @@ -307,6 +307,14 @@ raise them. SSL listening sockets are closed until the session count drops naturally to 95% of the limit. Defaults to 1,000. +.. envvar:: MAX_RECV + + The maximum size of an incoming message in bytes, the default is 1,000,000 bytes. + Note that the smallest sane/safe value for Bitcoin is ~800,100 bytes, + as the largest standard tx can have a weight of 400K but the protocol hex-encodes that, + plus there is a few bytes of protocol overhead. Setting this to lower than that + would preclude clients from broadcasting txs that could propagate over the network. + .. envvar:: MAX_SEND The maximum size of a response message to send over the wire, in diff --git a/electrumx/server/env.py b/electrumx/server/env.py index 1a4cd9c..12735a1 100644 --- a/electrumx/server/env.py +++ b/electrumx/server/env.py @@ -80,6 +80,7 @@ def __init__(self, coin=None): # Server limits to help prevent DoS self.max_send = self.integer('MAX_SEND', self.coin.DEFAULT_MAX_SEND) + self.max_recv = self.integer('MAX_RECV', 1_000_000) self.max_sessions = self.sane_max_sessions() self.cost_soft_limit = self.integer('COST_SOFT_LIMIT', 1000) self.cost_hard_limit = self.integer('COST_HARD_LIMIT', 10000) diff --git a/electrumx/server/session.py b/electrumx/server/session.py index fa2cbd6..e5cb170 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -25,7 +25,8 @@ import pylru from aiorpcx import (Event, JSONRPCAutoDetect, JSONRPCConnection, ReplyAndDisconnect, Request, RPCError, RPCSession, - TaskGroup, handler_invocation, serve_rs, serve_ws, sleep) + TaskGroup, handler_invocation, serve_rs, serve_ws, sleep, + NewlineFramer) import electrumx import electrumx.lib.util as util @@ -877,6 +878,9 @@ def __init__( async def notify(self, touched, height_changed): pass + def default_framer(self): + return NewlineFramer(max_size=self.env.max_recv) + def remote_address_string(self, *, for_log=True): '''Returns the peer's IP address and port as a human-readable string, respecting anon logs if the output is for a log.''' From be18d1265ab7ab8530d79bf86cd74592ed0ee310 Mon Sep 17 00:00:00 2001 From: Jeremy Rand Date: Mon, 19 Jul 2021 16:50:38 +0000 Subject: [PATCH 083/386] Namecoin: add more peers --- electrumx/lib/coins.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index e48bb09..834158c 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -1179,10 +1179,14 @@ class Namecoin(NameIndexMixin, AuxPowMixin, Coin): TX_PER_BLOCK = 10 RPC_PORT = 8336 PEERS = [ + '188.167.144.126 s50002', + '46.229.238.187 s57002', + '82.119.233.36 s50002', 'electrum-nmc.le-space.de s50002', 'ex.lug.gs s446', 'luggscoqbymhvnkp.onion t82', 'nmc.bitcoins.sk s50002', + 'nmc2.bitcoins.sk s57002', 'ulrichard.ch s50006 t50005', ] BLOCK_PROCESSOR = block_proc.NameIndexBlockProcessor From 80fe5aab4e8e4ce4d6d3b04630fec976d59d3bbc Mon Sep 17 00:00:00 2001 From: "evilmaid.crypto" <91712245+evil-maid@users.noreply.github.com> Date: Fri, 1 Oct 2021 06:34:38 -0700 Subject: [PATCH 084/386] Update README.rst - one grammar fix --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index d9cf58c..597afd4 100644 --- a/README.rst +++ b/README.rst @@ -15,7 +15,7 @@ This project is a fork of `kyuupichan/electrumx Date: Fri, 1 Oct 2021 06:37:21 -0700 Subject: [PATCH 085/386] Update electrumx_compact_history - grammar fix --- electrumx_compact_history | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/electrumx_compact_history b/electrumx_compact_history index 35f25e7..b2cd5fc 100755 --- a/electrumx_compact_history +++ b/electrumx_compact_history @@ -15,14 +15,14 @@ This needs to lock the database so ElectrumX must not be running - shut it down cleanly first. It is recommended you run this script with the same environment as -ElectrumX. However it is intended to be runnable with just +ElectrumX. However, it is intended to be runnable with just DB_DIRECTORY and COIN set (COIN defaults as for ElectrumX). If you use daemon tools, you might run this script like so: envdir /path/to/the/environment/directory ./compact_history.py -Depending on your hardware this script may take up to 6 hours to +Depending on your hardware, this script may take up to 6 hours to complete; it logs progress regularly. Compaction can be interrupted and restarted harmlessly and will pick From 3284aacbf6f98e1341738492b4dbd2045eafc354 Mon Sep 17 00:00:00 2001 From: "evilmaid.crypto" <91712245+evil-maid@users.noreply.github.com> Date: Fri, 1 Oct 2021 06:47:44 -0700 Subject: [PATCH 086/386] Update HOWTO.rst - grammar fixes --- docs/HOWTO.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/HOWTO.rst b/docs/HOWTO.rst index b9a22eb..b1e8a8b 100644 --- a/docs/HOWTO.rst +++ b/docs/HOWTO.rst @@ -29,7 +29,7 @@ functions. For example, `x11_hash`_ is required for DASH. Scrypt coins require a Python interpreter compiled and/or linked with OpenSSL 1.1.0 or higher. -You **must** to be running a non-pruning bitcoin daemon with:: +You **must** be running a non-pruning bitcoin daemon with:: txindex=1 @@ -64,7 +64,7 @@ Database Engine You can choose from LevelDB and RocksDB to store transaction information on disk. The time taken and DB size is not significantly -different. We tried to support LMDB but its history write performance +different. We tried to support LMDB, but its history write performance was much worse. You will need to install one of: From 566146a42787b935edfe81f897eb44630c107f98 Mon Sep 17 00:00:00 2001 From: "evilmaid.crypto" <91712245+evil-maid@users.noreply.github.com> Date: Fri, 1 Oct 2021 06:57:51 -0700 Subject: [PATCH 087/386] Update architecture.rst - spelling fix --- docs/architecture.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/architecture.rst b/docs/architecture.rst index 6ac192d..edaea58 100644 --- a/docs/architecture.rst +++ b/docs/architecture.rst @@ -7,7 +7,7 @@ Architecture Env --- -Holds configuration taken from the environment, with apprioriate +Holds configuration taken from the environment, with appropriate defaulting appropriately. Generally passed to the constructor of other components which take their settings from it. From 29b4f81fcb96e321de6a64a0f0735bb8265a0b2e Mon Sep 17 00:00:00 2001 From: "evilmaid.crypto" <91712245+evil-maid@users.noreply.github.com> Date: Fri, 1 Oct 2021 07:02:54 -0700 Subject: [PATCH 088/386] Update index.rst - small grammar fix --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 79d8fef..216eeca 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -46,7 +46,7 @@ See :ref:`HOWTO`. There is also an `installer`_ available that simplifies the installation on various Linux-based distributions, and a `Dockerfile`_ -available . +available. .. _installer: https://github.com/bauerj/electrumx-installer .. _Dockerfile: https://github.com/lukechilds/docker-electrumx From b4b963d9dbb6f3e5559dc934d7f5da5e166b20fc Mon Sep 17 00:00:00 2001 From: "evilmaid.crypto" <91712245+evil-maid@users.noreply.github.com> Date: Fri, 1 Oct 2021 07:13:47 -0700 Subject: [PATCH 089/386] Update environment.rst - grammar fix --- docs/environment.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/environment.rst b/docs/environment.rst index f321de3..4139e50 100644 --- a/docs/environment.rst +++ b/docs/environment.rst @@ -327,9 +327,9 @@ raise them. served all at once or not at all, an obvious avenue for abuse. :envvar:`MAX_SEND` is a stop-gap until the protocol is improved to admit incremental history requests. Each history entry is - approximately 100 bytes so the default is equivalent to a history + approximately 100 bytes, so the default is equivalent to a history limit of around 10,000 entries, which should be ample for most - legitimate users. If you use a higher default bear in mind one + legitimate users. If you use a higher default, bear in mind one client can request history for multiple addresses. Also note that the largest raw transaction you will be able to serve to a client is just under half of :envvar:`MAX_SEND`, as each raw byte becomes 2 @@ -418,7 +418,7 @@ If you are not running a Tor proxy ElectrumX will be unable to connect to onion server peers, in which case rather than returning no onion peers it will fall back to a hard-coded list. -To give incoming clients a full range of onion servers you will need +To give incoming clients a full range of onion servers, you will need to be running a Tor proxy for ElectrumX to use. ElectrumX will perform peer-discovery by default and announce itself From 841802c88986c5c9d194e7cdcd4b7701d4a860e9 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Fri, 15 Oct 2021 20:56:18 +0300 Subject: [PATCH 090/386] Remove unused RPCError import --- electrumx/server/peers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electrumx/server/peers.py b/electrumx/server/peers.py index dcb9ab7..e85b5e6 100644 --- a/electrumx/server/peers.py +++ b/electrumx/server/peers.py @@ -17,7 +17,7 @@ from typing import TYPE_CHECKING, Type import aiohttp -from aiorpcx import (Event, Notification, RPCError, RPCSession, SOCKSError, +from aiorpcx import (Event, Notification, RPCSession, SOCKSError, SOCKSProxy, TaskGroup, TaskTimeout, connect_rs, handler_invocation, ignore_after, sleep) from aiorpcx.jsonrpc import CodeMessageError From f237ccd9265d3ac5dd87e40a3d70421afb04df6d Mon Sep 17 00:00:00 2001 From: gruve-p Date: Sun, 19 Dec 2021 14:26:18 +0100 Subject: [PATCH 091/386] Update tor peers of GRS Also adds regtest and signet mode for GRS --- electrumx/lib/coins.py | 25 ++++++++++++++++++++---- tests/blocks/groestlcoin_signet_281.json | 17 ++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 tests/blocks/groestlcoin_signet_281.json diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 834158c..9f1b893 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -2738,8 +2738,8 @@ class Groestlcoin(Coin): PEERS = [ 'electrum1.groestlcoin.org s t', 'electrum2.groestlcoin.org s t', - '6brsrbiinpc32tfc.onion t', - 'xkj42efxrcy6vbfw.onion t', + 'glzyzqiulwclsowniyjeg5tspdojfgiiizbpnepcxoswqkmsjzlkucqd.onion t', + 'jcv7kwu3gopzxp3r2ve43m6nahrxzc426hif4o3a2vt7wl4xq6tg3xqd.onion t', ] def grshash(data): @@ -2769,10 +2769,27 @@ class GroestlcoinTestnet(Groestlcoin): PEERS = [ 'electrum-test1.groestlcoin.org s t', 'electrum-test2.groestlcoin.org s t', - '7frvhgofuf522b5i.onion t', - 'aocojvqcybdoxekv.onion t', + 'v2wuvscywpli35kgolqrt2kw67rqfbfwfn4bv3pc6gtugkexqv675uqd.onion t', + '75dycxl6lqxujplls3qkhkzffptzdfohv3y5um7s5nhyu6idayqmk7id.onion t', ] +class GroestlcoinRegtest(GroestlcoinTestnet): + SHORTNAME = "GRSRT" + NET = "regtest" + RPC_PORT = 18443 + PEERS = [] + TX_COUNT = 1 + TX_COUNT_HEIGHT = 1 + +class GroestlcoinSignet(GroestlcoinTestnet): + SHORTNAME = "SGRS" + NET = "signet" + GENESIS_HASH = ('0000007fcaa2a27993c6cde9e7818c25' + '4357af517b876ceba2f23592bb14ab31') + RPC_PORT = 31441 + PEERS = [] + TX_COUNT = 1 + TX_COUNT_HEIGHT = 1 class Pivx(Coin): NAME = "PIVX" diff --git a/tests/blocks/groestlcoin_signet_281.json b/tests/blocks/groestlcoin_signet_281.json new file mode 100644 index 0000000..766bdc6 --- /dev/null +++ b/tests/blocks/groestlcoin_signet_281.json @@ -0,0 +1,17 @@ +{ + "hash": "000002bb5fe08f079796235dc1f762d76db4f9173fdb0fdf4499af14e1e9f85b", + "size": 996, + "height": 281, + "merkleroot": "f3329bfad6b8cb36cac2e872271ce2be0fb920b70d78ffc73520fd35a9a2d8e5", + "tx": [ + "7376328af10b601c331e6f2903fc92a24eb3b840ea88fddea35f08fda3f8fca5", + "fc0cdf6a0a1da19084a09bbb6f9d160a4265e3c64cd1b73e88be3902f81dc033", + "aabcb0ad7827db3fcefa619a6e9b7692744e2e531e8e93630a72c6911a058c81", + "b3944450bc9d2138abbea429aa96f751fb44babd286bcc2925bb0b9220a210a4" + ], + "time": 1608741268, + "nonce": 395699, + "bits": "1e0377ae", + "previousblockhash": "0000002356c493963c7a18c0f6649f79daf88eb1c64cf344fa978becc20c87ac", + "block": "00000020ac870cc2ec8b97fa44f34cc6b18ef8da799f64f6c0187a3c9693c45623000000e5d8a2a935fd2035c7ff780db720b90fbee21c2772e8c2ca36cbb8d6fa9b32f39471e35fae77031eb309060004010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03021901feffffff020c21c2eb0b000000160014985f5d8b4bfd29a1a643634a8923682c428300e60000000000000000776a24aa21a9ed5fcd569bcc6915cb6e96a0486ae9b0546cabb6b950fcaa5345ccbc716f5ed4fd4c4fecc7daa2490047304402207be0b0fcb25e96b09d05e3d9818c9a7091f8a010cd410630567c97d53a53a60702202e6aa6f37465aa932660786f928e29d44eb402fc4a4bf35c78758b3688f982f20100012000000000000000000000000000000000000000000000000000000000000000000000000002000000000101dd6fd82eaee740bc0fb29e324a27728d9ff5f4430d9404a592c1dac4013a97850000000000fdffffff02fc80864700000000160014dcc228262c9bbd0dbf34cbf929ccaf51e01bae0600743ba40b0000001600143a7ac62c13adfaca9964c35d0f7fec982ff5229c02473044022024ac010a3b8e2f55efe40f8c923f919ea10ed4ba3e8bcda3185cd2e9adf37e06022019f0d49651861df6f18e1962a3aff0286aed6c9466941ac6862daa9432aa968601210202000a6b1e5eafd72ba6d5e2b8762a8505e76626ae21de8c4575d96e8358e78917010000020000000001015f49470de5c3bea7a7600c9307ce6e4cde882116ff07f67348e56b026d8a81650000000000fdffffff0200743ba40b0000001600140c75f735f2a0d39727dd3c5bc79aa72f14abb107fc80864700000000160014bd99c1cabc2be8abb5ad712520a98a8a50c2ee9c0247304402207e80d280639ce4c3639b4b3afcb776888a96b81ce9b87a6e94a5f83e4a787fe502204f4c4410c980cd286a52e9f64c0ca0e162c5c6845caa8c8d2cbf7c3ea15b5d4601210202000a6b1e5eafd72ba6d5e2b8762a8505e76626ae21de8c4575d96e8358e7891601000002000000000101a4336d24594e2614c33a6b939c03b5f9746fc273a20febfc2aff5c48c1cb0c920000000000fdffffff0200743ba40b0000001600144e22e56bcab333eeb084a8f892529fdf702208b9fc80864700000000160014c85e2477ead22fc8f7ed5488d3b2c1e962caa3c902473044022070c9fd8afbb15de45f7ec5f0262783b4788a0bfc91ba15be8371e74cfefa6e0d02203498008cc3a3e5fa0a8586df47e7764ba36853e16198361a9e129d34778fcec401210202000a6b1e5eafd72ba6d5e2b8762a8505e76626ae21de8c4575d96e8358e78917010000" +} From 3f12f288a442d4c3429de0a813c6355a0132633f Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 28 Jan 2022 11:17:19 +0100 Subject: [PATCH 092/386] bump min required Python version to 3.8 --- .travis.yml | 3 +-- README.rst | 2 +- docs/HOWTO.rst | 2 +- docs/index.rst | 2 +- electrumx_compact_history | 4 ++-- electrumx_server | 4 ++-- setup.py | 4 ++-- 7 files changed, 10 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1ceee2a..96dea4a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,6 @@ before_install: - sudo apt-get -qq update - sudo apt-get install -yq libsnappy-dev zlib1g-dev libbz2-dev libgflags-dev liblz4-dev librocksdb-dev libleveldb-dev libboost-all-dev libsodium-dev python: - - "3.7" - "3.8" - "3.9-dev" # command to install dependencies @@ -44,7 +43,7 @@ script: - sh -c "cd docs && make html" # Dont report coverage from nightly after_success: - - if [[ $(python3 -V 2>&1) == *"Python 3.7"* ]]; then + - if [[ $(python3 -V 2>&1) == *"Python 3.8"* ]]; then pip install coveralls; coveralls; fi diff --git a/README.rst b/README.rst index 597afd4..d738420 100644 --- a/README.rst +++ b/README.rst @@ -8,7 +8,7 @@ ElectrumX - Reimplementation of electrum-server =============================================== :Licence: MIT - :Language: Python (>= 3.7) + :Language: Python (>= 3.8) :Original Author: Neil Booth This project is a fork of `kyuupichan/electrumx `_. diff --git a/docs/HOWTO.rst b/docs/HOWTO.rst index b1e8a8b..4ec30fb 100644 --- a/docs/HOWTO.rst +++ b/docs/HOWTO.rst @@ -15,7 +15,7 @@ small - pull requests are welcome. ================ ======================== Package Notes ================ ======================== -Python3 ElectrumX uses asyncio. Python version >= 3.7 is +Python3 ElectrumX uses asyncio. Python version >= 3.8 is **required**. `aiohttp`_ Python library for asynchronous HTTP. Version >= 2.0 required. diff --git a/docs/index.rst b/docs/index.rst index 216eeca..0adfd27 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -34,7 +34,7 @@ Authors and License =================== Neil Booth wrote the vast majority of the code; see :ref:`Authors`. -Python version at least 3.7 is required. +Python version at least 3.8 is required. The code is released under the `MIT Licence `_. diff --git a/electrumx_compact_history b/electrumx_compact_history index b2cd5fc..423aa56 100755 --- a/electrumx_compact_history +++ b/electrumx_compact_history @@ -42,8 +42,8 @@ from electrumx.server.db import DB async def compact_history(): - if sys.version_info < (3, 7): - raise RuntimeError('Python >= 3.7 is required to run ElectrumX') + if sys.version_info < (3, 8): + raise RuntimeError('Python >= 3.8 is required to run ElectrumX') environ['DAEMON_URL'] = '' # Avoid Env erroring out env = Env() diff --git a/electrumx_server b/electrumx_server index 3b126a4..efbe5d4 100755 --- a/electrumx_server +++ b/electrumx_server @@ -26,8 +26,8 @@ def main(): logger.info('ElectrumX server starting') try: - if sys.version_info < (3, 7): - raise RuntimeError('ElectrumX requires Python 3.7 or greater') + if sys.version_info < (3, 8): + raise RuntimeError('ElectrumX requires Python 3.8 or greater') env = Env() logger.info(f'logging level: {env.log_level}') logger.setLevel(env.log_level) diff --git a/setup.py b/setup.py index d8f9427..b054251 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ name='e-x', version=version, scripts=['electrumx_server', 'electrumx_rpc', 'electrumx_compact_history'], - python_requires='>=3.7', + python_requires='>=3.8', install_requires=['aiorpcX[ws]>=0.18.5,<0.19', 'attrs', 'plyvel', 'pylru', 'aiohttp>=3.3,<4'], extras_require={ @@ -38,7 +38,7 @@ 'Framework :: AsyncIO', 'License :: OSI Approved :: MIT License', 'Operating System :: Unix', - "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "Topic :: Database", 'Topic :: Internet', ], From 0256e97715f16730be7293d5f8d893bb476315dc Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 28 Jan 2022 11:58:21 +0100 Subject: [PATCH 093/386] requirements: bump min aiorpcx to 0.22.0 --- electrumx/lib/server_base.py | 2 +- electrumx/lib/util.py | 24 ++++++++++++++++++++++++ electrumx/server/block_processor.py | 6 +++--- electrumx/server/controller.py | 10 +++++----- electrumx/server/mempool.py | 8 ++++---- electrumx/server/peers.py | 8 ++++---- electrumx/server/session.py | 7 ++++--- setup.py | 2 +- tests/server/test_mempool.py | 23 ++++++++++++----------- 9 files changed, 58 insertions(+), 32 deletions(-) diff --git a/electrumx/lib/server_base.py b/electrumx/lib/server_base.py index ae5b234..acfe171 100644 --- a/electrumx/lib/server_base.py +++ b/electrumx/lib/server_base.py @@ -115,7 +115,7 @@ async def serve(): loop.set_exception_handler(self.on_exception) # Start serving and wait for shutdown, log receipt of the event - server_task = await spawn(serve, report_crash=False) + server_task = await spawn(serve, daemon=True) try: await shutdown_event.wait() except KeyboardInterrupt: diff --git a/electrumx/lib/util.py b/electrumx/lib/util.py index 82a7daf..fd6c90d 100644 --- a/electrumx/lib/util.py +++ b/electrumx/lib/util.py @@ -28,6 +28,7 @@ from array import array +import asyncio import inspect from ipaddress import ip_address import logging @@ -35,6 +36,8 @@ from collections.abc import Container, Mapping from struct import Struct +from aiorpcx import TaskGroup + # Use system-compiled JSON lib if available, fallback to stdlib try: @@ -354,3 +357,24 @@ def pack_varint(n): def pack_varbytes(data): return pack_varint(len(data)) + data + + +class OldTaskGroup(TaskGroup): + """Automatically raises exceptions on join; as in aiorpcx prior to version 0.20""" + async def join(self): + await super().join() + # FIXME: we should raise the "first" (original) exception found by the join, + # the one that is responsible for join returning. + # However the current aiorpcx API does not expose that, so for now + # we raise one of the exceptions found (not necessarily the first). + # Note that this exception might be a consequence of the join + # finding the original one and cancelling the other tasks. + # see https://github.com/kyuupichan/aiorpcX/issues/43 + for exc in self.exceptions: # in arbitrary order + if exc is None: + continue + try: + raise exc + except asyncio.CancelledError: + pass + diff --git a/electrumx/server/block_processor.py b/electrumx/server/block_processor.py index dd4b9a5..376811f 100644 --- a/electrumx/server/block_processor.py +++ b/electrumx/server/block_processor.py @@ -13,14 +13,14 @@ import time from typing import Sequence, Tuple, List, Callable, Optional, TYPE_CHECKING, Type -from aiorpcx import TaskGroup, run_in_thread, CancelledError +from aiorpcx import run_in_thread, CancelledError import electrumx from electrumx.server.daemon import DaemonError, Daemon from electrumx.lib.hash import hash_to_hex_str, HASHX_LEN from electrumx.lib.script import is_unspendable_legacy, is_unspendable_genesis from electrumx.lib.util import ( - chunks, class_logger, pack_le_uint32, pack_le_uint64, unpack_le_uint64 + chunks, class_logger, pack_le_uint32, pack_le_uint64, unpack_le_uint64, OldTaskGroup ) from electrumx.lib.tx import Tx from electrumx.server.db import FlushData, COMP_TXID_LEN, DB @@ -697,7 +697,7 @@ async def fetch_and_process_blocks(self, caught_up_event): self._caught_up_event = caught_up_event await self._first_open_dbs() try: - async with TaskGroup() as group: + async with OldTaskGroup() as group: await group.spawn(self.prefetcher.main_loop(self.height)) await group.spawn(self._process_prefetched_blocks()) # Don't flush for arbitrary exceptions as they might be a cause or consequence of diff --git a/electrumx/server/controller.py b/electrumx/server/controller.py index 032159f..bbe3392 100644 --- a/electrumx/server/controller.py +++ b/electrumx/server/controller.py @@ -7,11 +7,11 @@ from asyncio import Event -from aiorpcx import _version as aiorpcx_version, TaskGroup +from aiorpcx import _version as aiorpcx_version import electrumx from electrumx.lib.server_base import ServerBase -from electrumx.lib.util import version_string +from electrumx.lib.util import version_string, OldTaskGroup from electrumx.server.db import DB from electrumx.server.mempool import MemPool, MemPoolAPI from electrumx.server.session import SessionManager @@ -82,8 +82,8 @@ async def serve(self, shutdown_event): '''Start the RPC server and wait for the mempool to synchronize. Then start serving external clients. ''' - if not (0, 18, 5) <= aiorpcx_version < (0, 19): - raise RuntimeError('aiorpcX version 0.18.5+ is required') + if not (0, 22, 0) <= aiorpcx_version < (0, 23): + raise RuntimeError('aiorpcX version 0.22.x is required') env = self.env min_str, max_str = env.coin.SESSIONCLS.protocol_min_max_strings() @@ -128,7 +128,7 @@ async def wait_for_catchup(): await group.spawn(db.populate_header_merkle_cache()) await group.spawn(mempool.keep_synchronized(mempool_event)) - async with TaskGroup() as group: + async with OldTaskGroup() as group: await group.spawn(session_mgr.serve(notifications, mempool_event)) await group.spawn(bp.fetch_and_process_blocks(caught_up_event)) await group.spawn(wait_for_catchup()) diff --git a/electrumx/server/mempool.py b/electrumx/server/mempool.py index 2a7f589..5f83105 100644 --- a/electrumx/server/mempool.py +++ b/electrumx/server/mempool.py @@ -16,10 +16,10 @@ import math import attr -from aiorpcx import TaskGroup, run_in_thread, sleep +from aiorpcx import run_in_thread, sleep from electrumx.lib.hash import hash_to_hex_str, hex_str_to_hash -from electrumx.lib.util import class_logger, chunks +from electrumx.lib.util import class_logger, chunks, OldTaskGroup from electrumx.server.db import UTXO if TYPE_CHECKING: @@ -286,7 +286,7 @@ async def _process_mempool(self, all_hashes, touched, mempool_height): # Process new transactions new_hashes = list(all_hashes.difference(txs)) if new_hashes: - group = TaskGroup() + group = OldTaskGroup() for hashes in chunks(new_hashes, 200): coro = self._fetch_and_accept(hashes, all_hashes, touched) await group.spawn(coro) @@ -360,7 +360,7 @@ def deserialize_txs(): # This function is pure async def keep_synchronized(self, synchronized_event): '''Keep the mempool synchronized with the daemon.''' - async with TaskGroup() as group: + async with OldTaskGroup() as group: await group.spawn(self._refresh_hashes(synchronized_event)) await group.spawn(self._refresh_histogram(synchronized_event)) await group.spawn(self._logging(synchronized_event)) diff --git a/electrumx/server/peers.py b/electrumx/server/peers.py index e85b5e6..dbd53dc 100644 --- a/electrumx/server/peers.py +++ b/electrumx/server/peers.py @@ -18,12 +18,12 @@ import aiohttp from aiorpcx import (Event, Notification, RPCSession, SOCKSError, - SOCKSProxy, TaskGroup, TaskTimeout, connect_rs, + SOCKSProxy, TaskTimeout, connect_rs, handler_invocation, ignore_after, sleep) from aiorpcx.jsonrpc import CodeMessageError from electrumx.lib.peer import Peer -from electrumx.lib.util import class_logger, json_deserialize +from electrumx.lib.util import class_logger, json_deserialize, OldTaskGroup if TYPE_CHECKING: from electrumx.server.env import Env @@ -84,7 +84,7 @@ def __init__(self, env: 'Env', db: 'DB'): self.peers = set() self.permit_onion_peer_time = time.time() self.proxy = None - self.group = TaskGroup() + self.group = OldTaskGroup() self.recent_peer_adds = {} # refreshed self.blacklist = set() @@ -384,7 +384,7 @@ async def _verify_peer(self, session, peer): peer.server_version = server_version peer.features['server_version'] = server_version - async with TaskGroup() as g: + async with OldTaskGroup() as g: await g.spawn(self._send_headers_subscribe(session)) await g.spawn(self._send_server_features(session, peer)) peers_task = await g.spawn(self._send_peers_subscribe diff --git a/electrumx/server/session.py b/electrumx/server/session.py index e5cb170..69a0ac1 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -25,11 +25,12 @@ import pylru from aiorpcx import (Event, JSONRPCAutoDetect, JSONRPCConnection, ReplyAndDisconnect, Request, RPCError, RPCSession, - TaskGroup, handler_invocation, serve_rs, serve_ws, sleep, + handler_invocation, serve_rs, serve_ws, sleep, NewlineFramer) import electrumx import electrumx.lib.util as util +from electrumx.lib.util import OldTaskGroup from electrumx.lib.hash import (HASHX_LEN, Base58Error, hash_to_hex_str, hex_str_to_hash, sha256) from electrumx.lib.merkle import MerkleCache @@ -158,7 +159,7 @@ def __init__( self.estimatefee_cache = pylru.lrucache(1000) self.notified_height = None self.hsub_results = None - self._task_group = TaskGroup() + self._task_group = OldTaskGroup() self._sslc = None # Event triggered when electrumx is listening for incoming requests. self.server_listening = Event() @@ -642,7 +643,7 @@ async def serve(self, notifications, event): finally: # Close servers then sessions await self._stop_servers(self.servers.keys()) - async with TaskGroup() as group: + async with OldTaskGroup() as group: for session in list(self.sessions): await group.spawn(session.close(force_after=1)) diff --git a/setup.py b/setup.py index b054251..258c0a7 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ version=version, scripts=['electrumx_server', 'electrumx_rpc', 'electrumx_compact_history'], python_requires='>=3.8', - install_requires=['aiorpcX[ws]>=0.18.5,<0.19', 'attrs', + install_requires=['aiorpcX[ws]>=0.22.0,<0.23', 'attrs', 'plyvel', 'pylru', 'aiohttp>=3.3,<4'], extras_require={ 'rapidjson': ['python-rapidjson>=0.4.1,<2.0'], diff --git a/tests/server/test_mempool.py b/tests/server/test_mempool.py index 2ee33b9..a10f0ef 100644 --- a/tests/server/test_mempool.py +++ b/tests/server/test_mempool.py @@ -7,12 +7,13 @@ from random import randrange, choice, seed import pytest -from aiorpcx import Event, TaskGroup, sleep, ignore_after +from aiorpcx import Event, sleep, ignore_after from electrumx.server.mempool import MemPool, MemPoolAPI from electrumx.lib.coins import BitcoinCash from electrumx.lib.hash import HASHX_LEN, hex_str_to_hash, hash_to_hex_str from electrumx.lib.tx import Tx, TxInput, TxOutput +from electrumx.lib.util import OldTaskGroup coin = BitcoinCash @@ -267,7 +268,7 @@ async def test_keep_synchronized(caplog): mempool = MemPool(coin, api) event = Event() with caplog.at_level(logging.INFO): - async with TaskGroup() as group: + async with OldTaskGroup() as group: await group.spawn(mempool.keep_synchronized, event) await event.wait() await group.cancel_remaining() @@ -286,7 +287,7 @@ async def test_balance_delta(): api.initialize() mempool = MemPool(coin, api) event = Event() - async with TaskGroup() as group: + async with OldTaskGroup() as group: await group.spawn(mempool.keep_synchronized, event) await event.wait() await group.cancel_remaining() @@ -309,7 +310,7 @@ async def test_compact_fee_histogram(): api.initialize() mempool = MemPool(coin, api) event = Event() - async with TaskGroup() as group: + async with OldTaskGroup() as group: await group.spawn(mempool.keep_synchronized, event) await event.wait() await group.cancel_remaining() @@ -361,7 +362,7 @@ async def test_potential_spends(): api.initialize() mempool = MemPool(coin, api) event = Event() - async with TaskGroup() as group: + async with OldTaskGroup() as group: await group.spawn(mempool.keep_synchronized, event) await event.wait() await group.cancel_remaining() @@ -396,7 +397,7 @@ async def test_transaction_summaries(caplog): mempool = MemPool(coin, api) event = Event() with caplog.at_level(logging.INFO): - async with TaskGroup() as group: + async with OldTaskGroup() as group: await group.spawn(mempool.keep_synchronized, event) await event.wait() await group.cancel_remaining() @@ -416,7 +417,7 @@ async def test_unordered_UTXOs(): api.initialize() mempool = MemPool(coin, api) event = Event() - async with TaskGroup() as group: + async with OldTaskGroup() as group: await group.spawn(mempool.keep_synchronized, event) await event.wait() await group.cancel_remaining() @@ -443,7 +444,7 @@ async def test_mempool_removals(): api.initialize() mempool = MemPool(coin, api, refresh_secs=0.01) event = Event() - async with TaskGroup() as group: + async with OldTaskGroup() as group: await group.spawn(mempool.keep_synchronized, event) await event.wait() # Remove half the TXs from the mempool @@ -473,7 +474,7 @@ async def test_daemon_drops_txs(): api.initialize() mempool = MemPool(coin, api, refresh_secs=0.01) event = Event() - async with TaskGroup() as group: + async with OldTaskGroup() as group: await group.spawn(mempool.keep_synchronized, event) await event.wait() await _test_summaries(mempool, api) @@ -501,7 +502,7 @@ async def test_notifications(caplog): caplog.set_level(logging.DEBUG) - async with TaskGroup() as group: + async with OldTaskGroup() as group: # First batch enters the mempool api.raw_txs = {hash: raw_txs[hash] for hash in first_hashes} api.txs = {hash: txs[hash] for hash in first_hashes} @@ -561,7 +562,7 @@ async def test_dropped_txs(caplog): del api.txs[prev_hash] with caplog.at_level(logging.INFO): - async with TaskGroup() as group: + async with OldTaskGroup() as group: await group.spawn(mempool.keep_synchronized, event) await event.wait() await group.cancel_remaining() From c5d1e802e7a7e98db36fbad79954b3a46b9e03f3 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 8 Feb 2022 11:21:57 +0100 Subject: [PATCH 094/386] aiorpcx: improve exception-handling in OldTaskGroup related https://github.com/kyuupichan/aiorpcX/issues/43 --- electrumx/lib/util.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/electrumx/lib/util.py b/electrumx/lib/util.py index fd6c90d..f64e99f 100644 --- a/electrumx/lib/util.py +++ b/electrumx/lib/util.py @@ -362,19 +362,15 @@ def pack_varbytes(data): class OldTaskGroup(TaskGroup): """Automatically raises exceptions on join; as in aiorpcx prior to version 0.20""" async def join(self): - await super().join() - # FIXME: we should raise the "first" (original) exception found by the join, - # the one that is responsible for join returning. - # However the current aiorpcx API does not expose that, so for now - # we raise one of the exceptions found (not necessarily the first). - # Note that this exception might be a consequence of the join - # finding the original one and cancelling the other tasks. - # see https://github.com/kyuupichan/aiorpcX/issues/43 - for exc in self.exceptions: # in arbitrary order - if exc is None: - continue + if self._wait is all: try: - raise exc - except asyncio.CancelledError: - pass + async for task in self: + if not task.cancelled(): + task.result() + finally: + await super().join() + else: + await super().join() + if self.completed: + self.completed.result() From b10d03caf639c52a97322d92098a4243ac6091fb Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 14 Feb 2022 14:52:35 +0100 Subject: [PATCH 095/386] aiorpcx: improve exception-handling in OldTaskGroup, again if one of the tasks raises, the others should be cancelled --- electrumx/lib/util.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/electrumx/lib/util.py b/electrumx/lib/util.py index f64e99f..02e050a 100644 --- a/electrumx/lib/util.py +++ b/electrumx/lib/util.py @@ -363,14 +363,19 @@ class OldTaskGroup(TaskGroup): """Automatically raises exceptions on join; as in aiorpcx prior to version 0.20""" async def join(self): if self._wait is all: + exc = False try: async for task in self: if not task.cancelled(): task.result() + except BaseException: # including asyncio.CancelledError + exc = True + raise finally: + if exc: + await self.cancel_remaining() await super().join() else: await super().join() if self.completed: self.completed.result() - From c8d2cc0d5cf9e549a90ca876d85fed9a90b8c4ed Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 15 Feb 2022 18:28:57 +0100 Subject: [PATCH 096/386] aiorpcx: monkeypatch TimeoutAfter internals see https://github.com/kyuupichan/aiorpcX/issues/44 --- electrumx/lib/util.py | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/electrumx/lib/util.py b/electrumx/lib/util.py index 02e050a..28699af 100644 --- a/electrumx/lib/util.py +++ b/electrumx/lib/util.py @@ -36,7 +36,7 @@ from collections.abc import Container, Mapping from struct import Struct -from aiorpcx import TaskGroup +import aiorpcx # Use system-compiled JSON lib if available, fallback to stdlib @@ -359,7 +359,7 @@ def pack_varbytes(data): return pack_varint(len(data)) + data -class OldTaskGroup(TaskGroup): +class OldTaskGroup(aiorpcx.TaskGroup): """Automatically raises exceptions on join; as in aiorpcx prior to version 0.20""" async def join(self): if self._wait is all: @@ -379,3 +379,35 @@ async def join(self): await super().join() if self.completed: self.completed.result() + + +# We monkey-patch aiorpcx TimeoutAfter (used by timeout_after and ignore_after API), +# to fix a timing issue present in asyncio as a whole re timing out tasks. +# To see the issue we are trying to fix, consider example: +# async def outer_task(): +# async with timeout_after(0.1): +# await inner_task() +# When the 0.1 sec timeout expires, inner_task will get cancelled by timeout_after (=internal cancellation). +# If around the same time (in terms of event loop iterations) another coroutine +# cancels outer_task (=external cancellation), there will be a race. +# Both cancellations work by propagating a CancelledError out to timeout_after, which then +# needs to decide (in TimeoutAfter.__aexit__) whether it's due to an internal or external cancellation. +# AFAICT asyncio provides no reliable way of distinguishing between the two. +# This patch tries to always give priority to external cancellations. +# see https://github.com/kyuupichan/aiorpcX/issues/44 +# see https://github.com/aio-libs/async-timeout/issues/229 +# see https://bugs.python.org/issue42130 and https://bugs.python.org/issue45098 +def _aiorpcx_monkeypatched_set_new_deadline(task, deadline): + def timeout_task(): + task._orig_cancel() + task._timed_out = None if getattr(task, "_externally_cancelled", False) else deadline + def mycancel(*args, **kwargs): + task._orig_cancel(*args, **kwargs) + task._externally_cancelled = True + task._timed_out = None + if not hasattr(task, "_orig_cancel"): + task._orig_cancel = task.cancel + task.cancel = mycancel + task._deadline_handle = task._loop.call_at(deadline, timeout_task) + +aiorpcx.curio._set_new_deadline = _aiorpcx_monkeypatched_set_new_deadline From 25339328a7468235071d152f728b214df10d4c56 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 10 Mar 2022 14:06:07 +0100 Subject: [PATCH 097/386] session: handle remote_address() being None related: https://github.com/spesmilo/electrumx/issues/40#issuecomment-1048386092 https://github.com/spesmilo/electrumx/issues/168 --- electrumx/server/peers.py | 2 +- electrumx/server/session.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/electrumx/server/peers.py b/electrumx/server/peers.py index dbd53dc..5a7f6e1 100644 --- a/electrumx/server/peers.py +++ b/electrumx/server/peers.py @@ -339,7 +339,7 @@ async def _verify_peer(self, session, peer): # store IP address for peer if not peer.is_tor: address = session.remote_address() - if isinstance(address.host, (IPv4Address, IPv6Address)): + if address and isinstance(address.host, (IPv4Address, IPv6Address)): peer.ip_addr = str(address.host) if self._is_blacklisted(peer): diff --git a/electrumx/server/session.py b/electrumx/server/session.py index 69a0ac1..483d934 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -996,7 +996,8 @@ def extra_cost(self): return self.session_mgr.extra_cost(self) def on_disconnect_due_to_excessive_session_cost(self): - ip_addr = self.remote_address().host + remote_addr = self.remote_address() + ip_addr = remote_addr.host if remote_addr else None groups = self.session_mgr.sessions[self] group_names = [group.name for group in groups] self.logger.info(f"closing session over res usage. ip: {ip_addr}. groups: {group_names}") @@ -1241,7 +1242,10 @@ def is_tor(self): proxy_address = self.peer_mgr.proxy_address() if not proxy_address: return False - return self.remote_address().host == proxy_address.host + remote_addr = self.remote_address() + if not remote_addr: + return False + return remote_addr.host == proxy_address.host async def replaced_banner(self, banner): network_info = await self.daemon_request('getnetworkinfo') From 8198ab99603a7e74083b3569c15dad13850c721e Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 10 Mar 2022 15:50:01 +0100 Subject: [PATCH 098/386] session: catch unexpected exceptions in notify() closes https://github.com/spesmilo/electrumx/issues/169 based on https://github.com/kyuupichan/electrumx/commit/742f4914803d9fec0870996801a892594bd4ae12 https://github.com/kyuupichan/electrumx/commit/72b7a4962916b5d3fa18f5fd665c3533d06eacca --- electrumx/server/session.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/electrumx/server/session.py b/electrumx/server/session.py index 483d934..bb5cfc4 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -26,7 +26,7 @@ from aiorpcx import (Event, JSONRPCAutoDetect, JSONRPCConnection, ReplyAndDisconnect, Request, RPCError, RPCSession, handler_invocation, serve_rs, serve_ws, sleep, - NewlineFramer) + NewlineFramer, TaskTimeout, timeout_after) import electrumx import electrumx.lib.util as util @@ -1010,6 +1010,17 @@ def unsubscribe_hashX(self, hashX): return self.hashX_subs.pop(hashX, None) async def notify(self, touched, height_changed): + '''Wrap _notify_inner; websockets raises exceptions for unclear reasons.''' + try: + async with timeout_after(30): + await self._notify_inner(touched, height_changed) + except TaskTimeout: + self.logger.warning('timeout notifying client, closing...') + await self.close(force_after=1.0) + except Exception: + self.logger.exception('unexpected exception notifying client') + + async def _notify_inner(self, touched, height_changed): '''Notify the client about changes to touched addresses (from mempool updates or new blocks) and height. ''' @@ -1543,9 +1554,9 @@ def set_request_handlers(self, ptuple): 'protx.info': self.protx_info, }) - async def notify(self, touched, height_changed): + async def _notify_inner(self, touched, height_changed): '''Notify the client about changes in masternode list.''' - await super().notify(touched, height_changed) + await super()._notify_inner(touched, height_changed) for mn in self.mns.copy(): status = await self.daemon_request('masternode_list', ('status', mn)) From 7e53936f34d2f1c979c64f91d15d6745fb8103c2 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 10 Mar 2022 16:16:07 +0100 Subject: [PATCH 099/386] session._notify_sessions: gracefully handle notif during shutdown While the SessionManager is stopping, Notifications are still coming in from e.g. the mempool as mempool.keep_synchronized is not yet cancelled at that point. closes https://github.com/spesmilo/electrumx/issues/170 ``` Mar 07 17:27:40 hostname electrumx_server[454057]: INFO:Prefetcher:cancelled; prefetcher stopping Mar 07 17:27:40 hostname electrumx_server[454057]: INFO:BlockProcessor:flushing to DB for a clean shutdown... Mar 07 17:27:40 hostname electrumx_server[454057]: INFO:Controller:shutting down Mar 07 17:27:40 hostname electrumx_server[454057]: INFO:Controller:shutdown complete Mar 07 17:36:32 hostname electrumx_server[454057]: ERROR:electrumx:ElectrumX server terminated abnormally Mar 07 17:36:32 hostname electrumx_server[454057]: Traceback (most recent call last): Mar 07 17:36:32 hostname electrumx_server[454057]: File "/usr/local/lib/python3.8/dist-packages/e_x-1.16.0-py3.8.egg/EGG-INFO/scripts/electrumx_server", line 35, in main Mar 07 17:36:32 hostname electrumx_server[454057]: File "/usr/lib/python3.8/asyncio/runners.py", line 44, in run Mar 07 17:36:32 hostname electrumx_server[454057]: return loop.run_until_complete(main) Mar 07 17:36:32 hostname electrumx_server[454057]: File "/usr/lib/python3.8/asyncio/base_events.py", line 616, in run_until_complete Mar 07 17:36:32 hostname electrumx_server[454057]: return future.result() Mar 07 17:36:32 hostname electrumx_server[454057]: File "/usr/local/lib/python3.8/dist-packages/e_x-1.16.0-py3.8.egg/electrumx/lib/server_base.py", line 129, in run Mar 07 17:36:32 hostname electrumx_server[454057]: await server_task Mar 07 17:36:32 hostname electrumx_server[454057]: File "/usr/local/lib/python3.8/dist-packages/e_x-1.16.0-py3.8.egg/electrumx/lib/server_base.py", line 102, in serve Mar 07 17:36:32 hostname electrumx_server[454057]: await self.serve(shutdown_event) Mar 07 17:36:32 hostname electrumx_server[454057]: File "/usr/local/lib/python3.8/dist-packages/e_x-1.16.0-py3.8.egg/electrumx/server/controller.py", line 134, in serve Mar 07 17:36:32 hostname electrumx_server[454057]: await group.spawn(wait_for_catchup()) Mar 07 17:36:32 hostname electrumx_server[454057]: File "/usr/local/lib/python3.8/dist-packages/aiorpcx/curio.py", line 297, in __aexit__ Mar 07 17:36:32 hostname electrumx_server[454057]: await self.join() Mar 07 17:36:32 hostname electrumx_server[454057]: File "/usr/local/lib/python3.8/dist-packages/e_x-1.16.0-py3.8.egg/electrumx/lib/util.py", line 370, in join Mar 07 17:36:32 hostname electrumx_server[454057]: task.result() Mar 07 17:36:32 hostname electrumx_server[454057]: File "/usr/local/lib/python3.8/dist-packages/e_x-1.16.0-py3.8.egg/electrumx/server/mempool.py", line 366, in keep_synchronized Mar 07 17:36:32 hostname electrumx_server[454057]: await group.spawn(self._logging(synchronized_event)) Mar 07 17:36:32 hostname electrumx_server[454057]: File "/usr/local/lib/python3.8/dist-packages/aiorpcx/curio.py", line 297, in __aexit__ Mar 07 17:36:32 hostname electrumx_server[454057]: await self.join() Mar 07 17:36:32 hostname electrumx_server[454057]: File "/usr/local/lib/python3.8/dist-packages/e_x-1.16.0-py3.8.egg/electrumx/lib/util.py", line 370, in join Mar 07 17:36:32 hostname electrumx_server[454057]: task.result() Mar 07 17:36:32 hostname electrumx_server[454057]: File "/usr/local/lib/python3.8/dist-packages/e_x-1.16.0-py3.8.egg/electrumx/server/mempool.py", line 263, in _refresh_hashes Mar 07 17:36:32 hostname electrumx_server[454057]: await self.api.on_mempool(touched, height) Mar 07 17:36:32 hostname electrumx_server[454057]: File "/usr/local/lib/python3.8/dist-packages/e_x-1.16.0-py3.8.egg/electrumx/server/controller.py", line 67, in on_mempool Mar 07 17:36:32 hostname electrumx_server[454057]: await self._maybe_notify() Mar 07 17:36:32 hostname electrumx_server[454057]: File "/usr/local/lib/python3.8/dist-packages/e_x-1.16.0-py3.8.egg/electrumx/server/controller.py", line 55, in _maybe_notify Mar 07 17:36:32 hostname electrumx_server[454057]: await self.notify(height, touched) Mar 07 17:36:32 hostname electrumx_server[454057]: File "/usr/local/lib/python3.8/dist-packages/e_x-1.16.0-py3.8.egg/electrumx/server/session.py", line 789, in _notify_sessions Mar 07 17:36:32 hostname electrumx_server[454057]: await self._task_group.spawn(session.notify, touched, height_changed) Mar 07 17:36:32 hostname electrumx_server[454057]: File "/usr/local/lib/python3.8/dist-packages/aiorpcx/curio.py", line 204, in spawn Mar 07 17:36:32 hostname electrumx_server[454057]: self._add_task(task) Mar 07 17:36:32 hostname electrumx_server[454057]: File "/usr/local/lib/python3.8/dist-packages/aiorpcx/curio.py", line 150, in _add_task Mar 07 17:36:32 hostname electrumx_server[454057]: raise RuntimeError('task group terminated') Mar 07 17:36:32 hostname electrumx_server[454057]: RuntimeError: task group terminated ``` --- electrumx/server/session.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/electrumx/server/session.py b/electrumx/server/session.py index bb5cfc4..131799b 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -786,6 +786,9 @@ async def _notify_sessions(self, height, touched): del cache[hashX] for session in self.sessions: + if self._task_group.joined: # this can happen during shutdown + self.logger.warning(f"task group already terminated. not notifying sessions.") + return await self._task_group.spawn(session.notify, touched, height_changed) def _ip_addr_group_name(self, session) -> Optional[str]: From 83835279cc125eb07beba1138bfc930e649cf0ab Mon Sep 17 00:00:00 2001 From: Nilesh Londhe Date: Sun, 20 Mar 2022 21:21:07 -0700 Subject: [PATCH 100/386] Update electrumx.service --- contrib/systemd/electrumx.service | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/systemd/electrumx.service b/contrib/systemd/electrumx.service index c2a819d..04d5b20 100644 --- a/contrib/systemd/electrumx.service +++ b/contrib/systemd/electrumx.service @@ -5,6 +5,7 @@ After=network.target [Service] EnvironmentFile=/etc/electrumx.conf ExecStart=/usr/local/bin/electrumx_server +ExecStop=/usr/local/bin/electrumx_rpc -p 8000 stop User=electrumx LimitNOFILE=8192 TimeoutStopSec=30min From 265a5a87b8ad01f739049c0b1e80923aab318f58 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 29 Mar 2022 20:05:25 +0200 Subject: [PATCH 101/386] increase resolution of responses to "mempool.get_fee_histogram" related https://github.com/spesmilo/electrum/issues/6474 --- electrumx/lib/coins.py | 6 +++++- electrumx/server/mempool.py | 8 +++++--- tests/server/test_mempool.py | 2 ++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 9f1b893..c65f21a 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -82,7 +82,6 @@ class Coin: HEADER_VALUES = ('version', 'prev_block_hash', 'merkle_root', 'timestamp', 'bits', 'nonce') HEADER_UNPACK = struct.Struct('< I 32s 32s I I I').unpack_from - MEMPOOL_HISTOGRAM_REFRESH_SECS = 500 P2PKH_VERBYTE = bytes.fromhex("00") P2SH_VERBYTES = (bytes.fromhex("05"),) XPUB_VERBYTES = bytes('????', 'utf-8') @@ -93,6 +92,11 @@ class Coin: GENESIS_HASH = ('000000000019d6689c085ae165831e93' '4ff763ae46a2a6c172b3f1b60a8ce26f') GENESIS_ACTIVATION = 100_000_000 + + MEMPOOL_HISTOGRAM_REFRESH_SECS = 500 + # first bin size in vbytes. smaller bins mean more precision but also bandwidth: + MEMPOOL_COMPACT_HISTOGRAM_BINSIZE = 30_000 + # Peer discovery PEER_DEFAULT_PORTS = {'t': '50001', 's': '50002'} PEERS = [] diff --git a/electrumx/server/mempool.py b/electrumx/server/mempool.py index 5f83105..5667976 100644 --- a/electrumx/server/mempool.py +++ b/electrumx/server/mempool.py @@ -139,7 +139,8 @@ async def _refresh_histogram(self, synchronized_event): await synchronized_event.wait() async with self.lock: # Threaded as can be expensive - await run_in_thread(self._update_histogram, 100_000) + bin_size = self.coin.MEMPOOL_COMPACT_HISTOGRAM_BINSIZE + await run_in_thread(self._update_histogram, bin_size) await sleep(self.coin.MEMPOOL_HISTOGRAM_REFRESH_SECS) def _update_histogram(self, bin_size): @@ -165,7 +166,8 @@ def _compress_histogram( '''Calculate and return a compact fee histogram as needed for "mempool.get_fee_histogram" protocol request. - histogram: feerate (sat/byte) -> total size in bytes of txs that pay approx feerate + histogram: feerate (sat/vbyte) -> total size in bytes of txs that pay approx feerate + bin_size: ~minimum vsize of a bucket of txs in the result (e.g. 100 kb) ''' # Now compact it. For efficiency, get_fees returns a # compact histogram with variable bin size. The compact @@ -174,7 +176,7 @@ def _compress_histogram( # transactions with a fee rate in the interval # [rate_(n-1), rate_n)], and rate_(n-1) > rate_n. # Intervals are chosen to create tranches containing at - # least 100kb of transactions + # least a certain cumulative size (bin_size) of transactions. assert bin_size > 0 compact = [] cum_size = 0 diff --git a/tests/server/test_mempool.py b/tests/server/test_mempool.py index a10f0ef..594e034 100644 --- a/tests/server/test_mempool.py +++ b/tests/server/test_mempool.py @@ -343,6 +343,8 @@ def test_compress_histogram(): } compact = MemPool._compress_histogram(histogram, bin_size=100_000) assert compact == [(19, 78000), (18, 900000), (13, 5000), (12, 10000000)] + compact = MemPool._compress_histogram(histogram, bin_size=30_000) + assert compact == [(22, 1000), (21, 75000), (19, 2000), (18, 900000), (13, 5000), (12, 10000000), (10, 101000)] histogram = { 1.0: 10_000_000, From 338c10275f155f6dc8c17027ea3eae7ce83fb473 Mon Sep 17 00:00:00 2001 From: Daniel Gonzalez Gasull Date: Thu, 28 Apr 2022 09:43:33 +0000 Subject: [PATCH 102/386] Update electrumx.conf Point to the right docs. --- contrib/systemd/electrumx.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/systemd/electrumx.conf b/contrib/systemd/electrumx.conf index 48d66c7..bf51db7 100644 --- a/contrib/systemd/electrumx.conf +++ b/contrib/systemd/electrumx.conf @@ -7,5 +7,5 @@ DAEMON_URL = http://username:password@hostname:port/ # COIN = Bitcoin -# See http://electrumx.readthedocs.io/en/latest/environment.html for +# See https://electrumx-spesmilo.readthedocs.io/en/latest/environment.html for # information about other configuration settings you probably want to consider. From 5f4dd2cdb414464484407affbbaae6b7407696cb Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 28 Apr 2022 15:17:39 +0200 Subject: [PATCH 103/386] LocalRPC: add --timeout arg To allow running local rpc commands that might take a very long time. The server-side `processing_timeout` is a bit ugly though... :/ --- electrumx/server/session.py | 2 ++ electrumx_rpc | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/electrumx/server/session.py b/electrumx/server/session.py index 131799b..0a0529c 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -1528,6 +1528,8 @@ def set_request_handlers(self, ptuple): class LocalRPC(SessionBase): '''A local TCP RPC server session.''' + processing_timeout = 10**9 # disable timeouts + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.client = 'RPC' diff --git a/electrumx_rpc b/electrumx_rpc index e270397..302b642 100755 --- a/electrumx_rpc +++ b/electrumx_rpc @@ -85,6 +85,8 @@ def main(): ) main_parser.add_argument('-p', '--port', metavar='port_num', type=int, help='RPC port number') + main_parser.add_argument('--timeout', type=int, default=30, + help='timeout for command in seconds') subparsers = main_parser.add_subparsers(help='sub-command help', dest='command') @@ -110,13 +112,15 @@ def main(): if port is None: port = int(environ.get('RPC_PORT', 8000)) method = args.pop('command') + timeout = args.pop('timeout') # aiorpcX makes this so easy... async def send_request(): try: - async with timeout_after(30): + async with timeout_after(timeout): async with connect_rs('localhost', port) as session: session.transport._framer.max_size = 0 + session.sent_request_timeout = timeout result = await session.send_request(method, args) if method in ('query', ): for line in result: From f5582b29792625e8cca7cf137a6718c2520bb9cb Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 25 Apr 2022 15:24:17 +0200 Subject: [PATCH 104/386] LocalRPC: add commands to help debug memory leaks --- electrumx/server/session.py | 37 +++++++++++++++++++++++++++++++++++-- electrumx_rpc | 22 ++++++++++++++++++++++ setup.py | 1 + 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/electrumx/server/session.py b/electrumx/server/session.py index 0a0529c..63c016f 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -26,7 +26,7 @@ from aiorpcx import (Event, JSONRPCAutoDetect, JSONRPCConnection, ReplyAndDisconnect, Request, RPCError, RPCSession, handler_invocation, serve_rs, serve_ws, sleep, - NewlineFramer, TaskTimeout, timeout_after) + NewlineFramer, TaskTimeout, timeout_after, run_in_thread) import electrumx import electrumx.lib.util as util @@ -167,7 +167,8 @@ def __init__( # Set up the RPC request handlers cmds = ('add_peer daemon_url disconnect getinfo groups log peers ' - 'query reorg sessions stop'.split()) + 'query reorg sessions stop debug_memusage_list_all_objects ' + 'debug_memusage_get_random_backref_chain'.split()) LocalRPC.request_handlers = {cmd: getattr(self, 'rpc_' + cmd) for cmd in cmds} @@ -593,6 +594,38 @@ async def rpc_reorg(self, count): raise RPCError(BAD_REQUEST, 'still catching up with daemon') return f'scheduled a reorg of {count:,d} blocks' + async def rpc_debug_memusage_list_all_objects(self, limit: int) -> str: + """Return a string listing the most common types in memory.""" + import objgraph # optional dependency + import io + with io.StringIO() as fd: + objgraph.show_most_common_types( + limit=limit, + shortnames=False, + file=fd) + return fd.getvalue() + + async def rpc_debug_memusage_get_random_backref_chain(self, objtype: str) -> str: + """Return a dotfile as text containing the backref chain + for a randomly selected object of type objtype. + + Warning: very slow! and it blocks the server. + + To convert to image: + $ dot -Tps filename.dot -o outfile.ps + """ + import objgraph # optional dependency + import random + import io + with io.StringIO() as fd: + await run_in_thread(lambda: + objgraph.show_chain( + objgraph.find_backref_chain( + random.choice(objgraph.by_type(objtype)), + objgraph.is_proper_module), + output=fd)) + return fd.getvalue() + # --- External Interface async def serve(self, notifications, event): diff --git a/electrumx_rpc b/electrumx_rpc index 302b642..a087fb8 100755 --- a/electrumx_rpc +++ b/electrumx_rpc @@ -74,6 +74,22 @@ other_commands = { 'help': 'number of blocks to back up' }, ), + 'debug_memusage_list_all_objects': ( + 'Print a table of types of most common types in memory', + ['--limit'], { + 'type': int, + 'default': 50, + 'help': 'max number of types to return', + }, + ), + 'debug_memusage_get_random_backref_chain': ( + 'Return a dotfile as text containing the backref chain for a randomly selected object of type objtype', + [], { + 'type': str, + 'dest': 'objtype', + 'help': 'e.g. "_asyncio.Task"', + }, + ), } @@ -125,6 +141,12 @@ def main(): if method in ('query', ): for line in result: print(line) + elif method in ( + 'debug_memusage_list_all_objects', + 'debug_memusage_get_random_backref_chain', + ): + for line in result.split('\n'): + print(line) elif method in ('groups', 'peers', 'sessions'): lines_func = getattr(text, f'{method}_lines') for line in lines_func(result): diff --git a/setup.py b/setup.py index 258c0a7..bb782e3 100644 --- a/setup.py +++ b/setup.py @@ -9,6 +9,7 @@ install_requires=['aiorpcX[ws]>=0.22.0,<0.23', 'attrs', 'plyvel', 'pylru', 'aiohttp>=3.3,<4'], extras_require={ + 'dev': ['objgraph'], 'rapidjson': ['python-rapidjson>=0.4.1,<2.0'], 'rocksdb': ['python-rocksdb>=0.6.9'], 'ujson': ['ujson>=2.0.0,<4.0.0'], From 9c8640c60150f46628bf3c0feb18c30dc5b90bf5 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 28 Apr 2022 13:15:02 +0200 Subject: [PATCH 105/386] monkey-patch aiorpcx.TaskGroup._add_task to fix a memory leak fixes https://github.com/spesmilo/electrumx/issues/172 see https://github.com/kyuupichan/aiorpcX/issues/46 --- electrumx/lib/util.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/electrumx/lib/util.py b/electrumx/lib/util.py index 28699af..83fb3b9 100644 --- a/electrumx/lib/util.py +++ b/electrumx/lib/util.py @@ -381,6 +381,18 @@ async def join(self): self.completed.result() +# We monkey-patch aiorpcx.TaskGroup._add_task. +# This is to plug a memory-leak, see https://github.com/kyuupichan/aiorpcX/issues/46 . +# Note: this breaks the TaskGroup.results and TaskGroup.exceptions APIs +# but we are not using them anyway. +def _patched_TaskGroup_add_task(self: 'aiorpcx.TaskGroup', task): + self._orig_add_task(self, task) + self.tasks.clear() + +aiorpcx.TaskGroup._orig_add_task = staticmethod(aiorpcx.TaskGroup._add_task) +aiorpcx.TaskGroup._add_task = _patched_TaskGroup_add_task + + # We monkey-patch aiorpcx TimeoutAfter (used by timeout_after and ignore_after API), # to fix a timing issue present in asyncio as a whole re timing out tasks. # To see the issue we are trying to fix, consider example: From fb037fbd23b8ce418cd67d68bbf8d32e69ecef62 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 16 May 2022 22:31:32 +0200 Subject: [PATCH 106/386] aiorpcx: adapt TaskGroup._add_task monkey-patch to upstream fix - upstream fix is at https://github.com/kyuupichan/aiorpcX/commit/a0be71c5671b07e02bedc51d7efaecda86f05128 , but it's not yet released - prev monkey-patch was causing issues if used with aiorpcx that already has the upstream fix: ``` INFO:SessionManager:TCP server listening on 127.0.0.1:51001 INFO:SessionManager:SSL server listening on 127.0.0.1:51002 INFO:PeerManager:peer discovery: ON INFO:PeerManager:announce ourself: True INFO:PeerManager:my clearnet self: None INFO:PeerManager:force use of proxy: False INFO:PeerManager:beginning peer discovery... INFO:PeerManager:trying to detect proxy on "localhost" ports [9050, 9150, 1080] Exception in callback TaskGroup._on_done( result=None>) handle: result=None>)> Traceback (most recent call last): File "/usr/lib/python3.8/asyncio/events.py", line 81, in _run self._context.run(self._callback, *self._args) File "/home/user/wspace/aiorpcX/aiorpcx/curio.py", line 147, in _on_done self.tasks.remove(task) KeyError: result=None> Exception in callback TaskGroup._on_done( result=None>) handle: result=None>)> Traceback (most recent call last): File "/usr/lib/python3.8/asyncio/events.py", line 81, in _run self._context.run(self._callback, *self._args) File "/home/user/wspace/aiorpcX/aiorpcx/curio.py", line 147, in _on_done self.tasks.remove(task) KeyError: result=None> INFO:PeerManager:detected SOCKS5 proxy at localhost:9050, auth: none Exception in callback TaskGroup._on_done( result=None>) handle: result=None>)> Traceback (most recent call last): File "/usr/lib/python3.8/asyncio/events.py", line 81, in _run self._context.run(self._callback, *self._args) File "/home/user/wspace/aiorpcX/aiorpcx/curio.py", line 147, in _on_done self.tasks.remove(task) KeyError: result=None> ``` --- electrumx/lib/util.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/electrumx/lib/util.py b/electrumx/lib/util.py index 83fb3b9..ad93484 100644 --- a/electrumx/lib/util.py +++ b/electrumx/lib/util.py @@ -385,9 +385,12 @@ async def join(self): # This is to plug a memory-leak, see https://github.com/kyuupichan/aiorpcX/issues/46 . # Note: this breaks the TaskGroup.results and TaskGroup.exceptions APIs # but we are not using them anyway. +# TODO: this monkey-patch can be removed once we require aiorpcx versions that +# have the upstream fix for #46. def _patched_TaskGroup_add_task(self: 'aiorpcx.TaskGroup', task): self._orig_add_task(self, task) - self.tasks.clear() + if not hasattr(self, "_retain"): + self.tasks.clear() aiorpcx.TaskGroup._orig_add_task = staticmethod(aiorpcx.TaskGroup._add_task) aiorpcx.TaskGroup._add_task = _patched_TaskGroup_add_task From 714058794a69ec56563009509a1d6df54ff482f2 Mon Sep 17 00:00:00 2001 From: Jeremy Rand Date: Fri, 23 Sep 2022 08:29:18 +0000 Subject: [PATCH 107/386] NameIndexMixin: Add 1RT name lookups --- docs/protocol-changes.rst | 9 +++ docs/protocol-methods.rst | 136 ++++++++++++++++++++++++++++++++++++ electrumx/lib/coins.py | 15 +++- electrumx/server/session.py | 48 ++++++++++++- 4 files changed, 204 insertions(+), 4 deletions(-) diff --git a/docs/protocol-changes.rst b/docs/protocol-changes.rst index a13ffbd..d4d0abb 100644 --- a/docs/protocol-changes.rst +++ b/docs/protocol-changes.rst @@ -166,3 +166,12 @@ New methods ----------- * :func:`blockchain.scripthash.unsubscribe` to unsubscribe from a script hash. + + +Version 1.4.3 +============= + +New methods +----------- + + * :func:`blockchain.name.get_value_proof` to resolve a name (with proof). Name index coins (e.g. Namecoin) only. diff --git a/docs/protocol-methods.rst b/docs/protocol-methods.rst index dcf3ae3..3151ac0 100644 --- a/docs/protocol-methods.rst +++ b/docs/protocol-methods.rst @@ -1248,3 +1248,139 @@ Returns detailed information about a deterministic masternode. }, "confirmations": 984 } + +Name methods (Namecoin and compatible coins) +============================================== + + +blockchain.name.get_value_proof +============================= + +Returns a name resolution proof, suitable for low-latency (single round-trip) resolution. + +**Signature** + + .. function:: blockchain.name.get_value_proof(scripthash, cp_height) + .. versionadded:: 1.4.3 + + *scripthash* + + Script hash of the name being resolved. + + *cp_height* + + Checkpoint height. + + +**Result** + + A dictionary with transaction and proof data for each transaction associated with the name, from the most recent update back to either the registration transaction or a checkpointed transaction (whichever is later). + +**Example**:: + + blockchain.name.get_value_proof(bdd490728e7f1cbea1836919db5e932cce651a82f5a13aa18a5267c979c95d3c, 518353) + +**Example Result**:: + + { + "bdd490728e7f1cbea1836919db5e932cce651a82f5a13aa18a5267c979c95d3c": [ + { + "height": 607853, + "tx": "00710000000102e3d236710b0a21cb9bb4c11d2a2ac6730e6e3c776773f688a53c14fc89ced5ab0100000000ffffffff261a3e3b04326e0dd33208d23e3119144f8cce8394de5757374a37323ee1b6060100000000ffffffff0240420f0000000000415309642f626974636f696e1d76657269667920323032322d30342d313620393a30353a3030204345546d7500144e5cafde0d7fd4f31323e14182a9c3412e026d88978d4e1400000000160014d1a61d11d434ca9197ae47b59a5f8933682140eb02473044022018129841dbd0e9700702b94dfaebee9f00a5e5dc847156faf992195c384a73a502204802754d4a7f406b3fb538d7f2a6c96967deb61eb87597e27dd30410b25e363401210261c92aa521e0e9660d605fff64129ada7faa6f2a201d9f82fd71b4a7ccd0cc230247304402200495ff83f6d2edc697abd3530eb16a315366cf4383225708826ab98c471e8a4b02207ef49122589aaf009e6c7515e04a2e5179263d6efb0f7043d871d3024aae136e0121020138a32ab1be1ae4ee0bf08a2dd6e7208f4e39f98312eebb09016a3fdf31978100000000", + "tx_merkle": { + "merkle": [ + "5b08bf9b8e8bff946da742641fc27c107096af5a05c0dcdec1848c3f7423a746", + "11c88190df88b334f2ce38e7034c1d1caec2e9fccb226ff3229c6440b7a94e6f", + "ce68ac63e88da5554218cdd7e6811088b1189ab8635bda557044c0518d174d1d" + ], + "pos": 1 + } + }, + { + "height": 597196, + "tx": "00710000000102c575b325030f89a7515596c2661967b02ff5a2594ba0a4774833d6bd6bdd41ca0000000000feffffff8afb31ffd43385ff4477c28d81f0dd43e6fc6cc63fe56f7d87733c1de7d67eaa0000000000feffffff028327374a010000001600142715d587d4c8e3edddbac11417b736964c519c6440420f00000000002c5309642f626974636f696e0872657365727665646d750014e893563837316a53cb00c99e13a8079f311173a9024730440220597930ab6071d009ec05e14a9247cbbae4f31c7e3c4e812a995e873eb31646ac02204236ad5e2ee4b72e25428c3e5d70dd9dc387e96bd5dab1f921c6a3dbe7b3f4a8012103549f0ee91a8ce86087fc05862fb06247affca4688d4c6ab166eb9d1531357da90247304402204f48be0eab589fb08acc3cd9281f2c23a742c7745f374176978d7ff1ccf3fd39022052c66eaabab908d7c332083fc88cf863bcf0aea574a1aba6b2dc8106bf0c455a012103877c7f65da9ecc3bd126ee9126b9a03569c2eaebd9b9ef7a30989327321231a7cb1c0900", + "tx_merkle": { + "merkle": [ + "bee7bcd954879e9110b019617f443a31f51210b4c841a577abc84dae583a115e", + "e412f9f6832238fc209fddb2f7aeeedb939e252e0b4a8f7df4b3c509d2b0233e", + "63e144e0a0726cf8cc9a8c776e6411fc5dfb7f225730bddba0c6a3ab656b3bcd", + "b5c75bfaaec4c2029b5b175168f9b91af766581dd8496d1184a44e432b20d1fb", + "4905bf1dc9fbedb16ccacd26a0f406fc62c53b860ac099369010ec610521c4af", + "3e74dbd7730487df73e07c1fa03b25355c82fbbbd81b35bf28cc8322dda9f3bb" + ], + "pos": 38 + } + }, + { + "height": 563168, + "tx": "007100000001026bfeb4ee7d7a5ab62b2e16abf439173a460ac013ee19a0115c26569673eb71eb0000000000feffffff6cee0ee75d60d1a02671d7d2e15e8a8c19045e15d9c6738f76395e8de63ccca30000000000feffffff0240420f00000000002c5309642f626974636f696e0872657365727665646d750014b1cfea8dbd1de3e2e0640b42838f1078f73c050d44d13f4a01000000160014d40f8f5b62c73e2167540001aa1634e60c27fa5a0247304402200207688a4c8e1ca3c093a81f862a0192a94590b2ff4e829564b164ca82b9fada02203ff970582e49ac3d0a8452d211903ba8405153ac556f0a46cfea2d0f3faff182012103cac6fecff4c3a06c71e429dc29a6bea58e42adcc283e5f98e66cb550d6d145df02473044022009e2ec71addf23c21df3034561997aac6e5c7700ac6dd44fcba6157afb865ca202203ad3ff0484f94a405c175184fa9a9ccb6fc8ceab67edc1dba314432e76757d6d01210289706af81a776c60a088ae35021142325f17f81da5778667a172dd1a92e1a644df970800", + "tx_merkle": { + "merkle": [ + "9ed4efd8e6b1cf9773db60ef0ad7213ce4282f56df709dd8c69ab2caf466f23b", + "5cb725f78154e33288b724a59cd6c0146ffb8d0e39ace6f1333479a6eafcb76c", + "596f00ade273423324821e88c8b99fbe7274470d04c062a0453c7ec1db1086d2", + "7dfcb41cabce612dc5ab7c4309ada78dbab9dc977321147b2a82ddaae7eb5d02", + "45d7679dc5e8f92f314b9776ac93e0dcb92015c989c076326511d5a5be5e4a17", + "ef5096cd39456fe5850c221ad6ec722e11820aa4f6e1773c380e6718e68625e7" + ], + "pos": 26 + } + }, + { + "height": 528949, + "tx": "0071000000010234f902ef0f953862bb9ba5b6e9fc1a8a914d8b5b1b9af89e843d85176318c5ff0000000000feffffff24802c9ae06cd5af3dd11bdb653b5df4e9cc6a7bb27b4f1cac13a16fd5249e9b0000000000feffffff0240420f00000000002c5309642f626974636f696e0872657365727665646d750014004b1245eb3ac8724d0bc684e15859c9d5eb9ddabfa39e4a0100000016001440fb148f07ff3c8b1d801628754d8ec5cbe1f7e70247304402203ec1e1907ecefd90180b1f7f9fec4a10b75fdb21a85f0df28ea8fe48419338760220549bebe6b96869eeb3175de8149cd127a42bdb344945bd32571950a1e5df40210121020d0bb22a3730f1de67b87af7e2e5517c7649932e4ad8105817372fd99ca1ce8102473044022013d238876040649429f29f5df08712a3b7439db436e7c7a42e38a2d8c733d05402201475420bfea26e684a4bd488ba6ef0b954ca703234d1536b7a545c55d53759e501210321307ece7f4731fef37b0208807ce1a04d1013c765fef1567752df06e5d7bfe233120800", + "tx_merkle": { + "merkle": [ + "0c1a965febb58cd57e8019e6b437f5124968b15855c3066c60a36ae863a900a6", + "643623318b6409171edb487dc6f14ea56db8956c0bc958abf524ddcf845fd805", + "1eeb4f63067c4b9ccba24eb717887b3b1f7b95f60ab461b04a785b57251d62d9", + "94baa94fe1153cc616420bb17bbad79591b409405f8d670b8a1f188e1eda3bc8", + "bdc4cc1c882e039866939b0ac0c923d1581de93fba1ff1140d3ddc616d1a768d", + "ac9dbfac19c216f593a932dafe813c3a9ad1d706fdeb2406a0c39db0209cba2c" + ], + "pos": 42 + } + }, + { + "height": 495000, + "tx": "007100000001020489c88ef8f718c9ac94bef1424986bfeaa79ec1b66e28841d3de4c3e8238e7f000000006a47304402200b3c3b859fe978f69cadb516e563122ef27fb68dcfea641cbfe69b4c71866cda02206a56c2e1d113e2032cb67a3b506a74ebbdaabca51b3601d9e269042ccce2c3ba012102184feeaa11d4a2ecc0f5c34c10222f6c858d33148c330ad115515d697c77d01efeffffffda3ec65cd0f1d8a5e3b56237993f53c5e1cacc9adec82a808a718023e3592c9a0000000000feffffff0240420f00000000002c5309642f626974636f696e0872657365727665646d75001417e6be040f4b08e4e161db3c27c1dc46b235bba8135a750100000000160014b9667261ba2f8127240b1de27515734bdd5761c00002473044022077d5fc03bdcdd0ced102c2b04ef67d8d9398cc03243f70cef59cbcdae6df0bc202200510a7bc5dc8d8a89dcc16575041912e9fa8913846c1c4ec796c95400ef346d301210330b9946e3a7aaf15858119423ca8cec8bb71e71267e8531feccbeb941abe2e17978d0700", + "tx_merkle": { + "merkle": [ + "b87f44bf986dccd425caf731ab403f5d423689977ac15a8d6204e0c8bbf00cf7", + "f1182408914fa2c86f935ed14a9925a4a20f077b56610131f995b45230155713", + "78d090050c52d44bf36a99ee4b884a95b6da8fb764fc6e383c6f7b5a4a8ff8be", + "523dc9ce6021858807700836f65b2b3bf8ea701d723b979afff6ea0cf096815f", + "0c7c131ab0bf11ba122af39d63a4483b4dbc845a33b507bce7cc0bcefee6d724", + "a2e5f09a5663c567471571d91c2f8cbd4270d286e772067573439f93e8b4befa", + "820fbb93b424b3e67602ce2b4136764080f5329b46bc671b97553e2340588e0c" + ], + "pos": 11 + }, + "header": { + "header": "040101003c59eef5329201b1f077d6f8b832b8200cbe9aff82feb75cf5e5bf3240eed8750768161b62133b7b758fe6a724362e3e5948ff4ba3e59098336d057a24d8bd4c37a7535e2394131700000000", + "branch": [ + "c28a169077edcec70fbb74dac6c64229e862a286fcc22f8695eeb268e7b454a1", + "7de3af1dce3faf8c93f40608f257a00c042cd13a2b7f5635454079931254602e", + "9e1f18e383256e49872da0ed2affac378baba7566f2ea0abf6b06d156b331d91", + "52f09f8d938efcc083643da4171ff0eb77a48dfbbe6ba801494caeb33dd8f2f4", + "a591862302cb03aebf74fcf9bf552d0ebe50634816719459669532e1f5122d19", + "2f17efd7c29f3e5f2fc10d494a4dc8cd1e1346fc6848b5f8f053d6133b56af5e", + "6b1656ebb4b82071b85a6bb6bbf64106f7f9cbd60531d8bbfa665d2d02eae9b0", + "1841b9a4bc6782948ec92bdbb93c0c8a330728052547f1b2ecd38a2adcff310b", + "8f5675ecfbdc76c4516f6b110e43b3dd66adb73d2d7f01957055973b832210fa", + "ede6544bcc09e29229bfaf7d099503172a7826174473816805891b5381dcae3e", + "9c9a8d5d2d3a7888bedc7906d22ebdd73f684cbc14c435aa4543ad18d56a4ef7", + "98f0e303e64ddfce18ee0ea9aaf95cecc0d6b849c2d6eb772aba3bf984660601", + "96c3e6226d5608ccf0a8f3e02d20a258044c9fedb726b041eb94d1d3112cfeb0", + "f71672e8b1d7621fbf2462e89b0c8d5af91efee7c9de54587acb7d705a8d4659", + "f9188789442cfeff1955b5030d82317fd77133524bcc4be4e3c24bd857a4925e", + "7b920859b50d0030c18725888aac05e7edea5bb4f4d3cadd5d6e71fe52a9ebac", + "e1185d87453a4392ff4d4aea556597bc1b0a1a6320714f364410a73aa136d7aa", + "345a13ed174cc91593862162fab8610ed84d5dd9cfc1533fd604d812cb13a699", + "88191322193a9f6ae984009b694edd97670795c8afb130082cf90cd4640e5701" + ], + "root": "476a138d228b66a094c20d5bbaea3230ea60201ba16af81500c5f368b06dc48b" + } + } + ] + } diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index c65f21a..05d8dbc 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -49,7 +49,8 @@ import electrumx.server.block_processor as block_proc import electrumx.server.daemon as daemon from electrumx.server.session import (ElectrumX, DashElectrumX, - SmartCashElectrumX, AuxPoWElectrumX) + SmartCashElectrumX, AuxPoWElectrumX, + NameIndexElectrumX, NameIndexAuxPoWElectrumX) @dataclass @@ -484,6 +485,8 @@ class NameIndexMixin(NameMixin): a name index in addition to the index by address / script.""" BLOCK_PROCESSOR = block_proc.NameIndexBlockProcessor + SESSIONCLS = NameIndexElectrumX + NAME_EXPIRATION = None @classmethod def build_name_index_script(cls, name): @@ -529,6 +532,10 @@ def name_hashX_from_script(cls, script): return super().hashX_from_script(name_index_script) +class NameIndexAuxPoWMixin(NameIndexMixin, AuxPowMixin): + SESSIONCLS = NameIndexAuxPoWElectrumX + + class PrimeChainPowMixin: STATIC_BLOCK_HEADERS = False DESERIALIZER = lib_tx.DeserializerPrimecoin @@ -1166,7 +1173,7 @@ class Unitus(Coin): # Source: namecoin.org -class Namecoin(NameIndexMixin, AuxPowMixin, Coin): +class Namecoin(NameIndexAuxPoWMixin, Coin): NAME = "Namecoin" SHORTNAME = "NMC" NET = "mainnet" @@ -1194,6 +1201,7 @@ class Namecoin(NameIndexMixin, AuxPowMixin, Coin): 'ulrichard.ch s50006 t50005', ] BLOCK_PROCESSOR = block_proc.NameIndexBlockProcessor + NAME_EXPIRATION = 36_000 # Name opcodes OP_NAME_NEW = OpCodes.OP_1 @@ -1232,6 +1240,7 @@ class NamecoinRegtest(NamecoinTestnet): PEERS = [] TX_COUNT = 1 TX_COUNT_HEIGHT = 1 + NAME_EXPIRATION = 30 class Dogecoin(AuxPowMixin, Coin): @@ -3400,7 +3409,7 @@ def header_hash(cls, header): return cpupower.getPoWHash(header) -class Xaya(NameIndexMixin, AuxPowMixin, Coin): +class Xaya(NameIndexAuxPoWMixin, Coin): NAME = "Xaya" SHORTNAME = "CHI" NET = "mainnet" diff --git a/electrumx/server/session.py b/electrumx/server/session.py index 63c016f..4230c21 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -977,7 +977,7 @@ class ElectrumX(SessionBase): '''A TCP server that handles incoming Electrum connections.''' PROTOCOL_MIN = (1, 4) - PROTOCOL_MAX = (1, 4, 2) + PROTOCOL_MAX = (1, 4, 3) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -1835,3 +1835,49 @@ def truncate_auxpow(self, headers_full_hex, start_height): height += 1 return headers.hex() + + +class NameIndexElectrumX(ElectrumX): + def set_request_handlers(self, ptuple): + super().set_request_handlers(ptuple) + + if ptuple >= (1, 4, 3): + self.request_handlers['blockchain.name.get_value_proof'] = self.name_get_value_proof + + async def name_get_value_proof(self, scripthash, cp_height=0): + history = await self.scripthash_get_history(scripthash) + + trimmed_history = [] + prev_height = None + + for update in history[::-1]: + txid = update['tx_hash'] + height = update['height'] + + if self.coin.NAME_EXPIRATION is not None and prev_height is not None and height < prev_height - self.coin.NAME_EXPIRATION: + break + + tx = await(self.transaction_get(txid)) + update['tx'] = tx + del update['tx_hash'] + + tx_merkle = await self.transaction_merkle(txid, height) + del tx_merkle['block_height'] + update['tx_merkle'] = tx_merkle + + if height <= cp_height: + header = await self.block_header(height, cp_height) + update['header'] = header + + trimmed_history.append(update) + + if height <= cp_height: + break + + prev_height = height + + return {scripthash: trimmed_history} + + +class NameIndexAuxPoWElectrumX(NameIndexElectrumX, AuxPoWElectrumX): + pass From 2d4a9cad93e9342dcdad2037b6db98aa56aae962 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 18 Oct 2022 14:47:43 +0000 Subject: [PATCH 108/386] more aiorpcx monkey-patching see https://github.com/spesmilo/electrum/commit/12e628e2e6eb39709361b587a87e35f291200d16 --- electrumx/lib/util.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/electrumx/lib/util.py b/electrumx/lib/util.py index ad93484..f400af7 100644 --- a/electrumx/lib/util.py +++ b/electrumx/lib/util.py @@ -425,4 +425,23 @@ def mycancel(*args, **kwargs): task.cancel = mycancel task._deadline_handle = task._loop.call_at(deadline, timeout_task) -aiorpcx.curio._set_new_deadline = _aiorpcx_monkeypatched_set_new_deadline + +def _aiorpcx_monkeypatched_set_task_deadline(task, deadline): + ret = _aiorpcx_orig_set_task_deadline(task, deadline) + task._externally_cancelled = None + return ret + + +def _aiorpcx_monkeypatched_unset_task_deadline(task): + if hasattr(task, "_orig_cancel"): + task.cancel = task._orig_cancel + del task._orig_cancel + return _aiorpcx_orig_unset_task_deadline(task) + + +_aiorpcx_orig_set_task_deadline = aiorpcx.curio._set_task_deadline +_aiorpcx_orig_unset_task_deadline = aiorpcx.curio._unset_task_deadline + +aiorpcx.curio._set_new_deadline = _aiorpcx_monkeypatched_set_new_deadline +aiorpcx.curio._set_task_deadline = _aiorpcx_monkeypatched_set_task_deadline +aiorpcx.curio._unset_task_deadline = _aiorpcx_monkeypatched_unset_task_deadline From 5ea65bd4c98559811efd8a800338f4b16e7d297f Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 18 Oct 2022 15:38:28 +0000 Subject: [PATCH 109/386] CI: migrate to Cirrus CI closes https://github.com/spesmilo/electrumx/issues/195 --- .cirrus.yml | 57 ++++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 5 +++++ setup.py | 6 +++-- 3 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 .cirrus.yml create mode 100644 requirements.txt diff --git a/.cirrus.yml b/.cirrus.yml new file mode 100644 index 0000000..9ffdf86 --- /dev/null +++ b/.cirrus.yml @@ -0,0 +1,57 @@ +unittests_task: + container: + image: python:3.8 + cpu: 1 + memory: 1G + version_script: + - python3 --version + tag_script: + - git tag + before_install_script: + - apt-get -qq update + - apt-get install -yq libsnappy-dev zlib1g-dev libbz2-dev libgflags-dev liblz4-dev librocksdb-dev libleveldb-dev libboost-all-dev libsodium-dev + pip_cache: + folder: ~/.cache/pip + fingerprint_script: echo $PYTHON_VERSION && cat requirements.txt + populate_script: mkdir -p ~/.cache/pip + install_script: + # test framework -related deps + - pip install pycodestyle + - pip install pytest-asyncio + - pip install pytest-cov + - pip install coveralls + - pip install Sphinx + # deps for e-x itself + - pip install -r requirements.txt + - pip install "python-rocksdb>=0.6.9" + # hashes and other altcoin-specific stuff + - pip install tribushashm + - pip install blake256 + - pip install x11_hash + - pip install git+https://github.com/bitcoinplusorg/x13-hash + - pip install xevan_hash + - pip install quark_hash + - pip install groestlcoin_hash + - pip install x16r_hash + - pip install pycryptodomex + - pip install git+https://github.com/Electra-project/nist5_hash + - pip install git+https://github.com/RitoProject/x21s_hash + - pip install x16rv2_hash + - pip install git+https://github.com/VerusCoin/verushashpy + - pip install bell-yespower + - pip install cpupower + script: + - pytest --cov=electrumx + - pycodestyle --max-line-length=100 electrumx/server/*.py electrumx/lib/*.py *.py + - sh -c "cd docs && make html" + coveralls_script: + - if [ ! -z "$COVERALLS_REPO_TOKEN" ] ; then coveralls ; fi + env: + # following CI_* env vars are set up for coveralls + CI_NAME: "CirrusCI" + CI_BUILD_NUMBER: $CIRRUS_BUILD_ID + CI_JOB_ID: $CIRRUS_TASK_ID + CI_BUILD_URL: "https://cirrus-ci.com/task/$CIRRUS_TASK_ID" + CI_BRANCH: $CIRRUS_BRANCH + CI_PULL_REQUEST: $CIRRUS_PR + # in addition, COVERALLS_REPO_TOKEN is set as an "override" in https://cirrus-ci.com/settings/... diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..958e29a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +aiorpcX[ws]>=0.22.0,<0.23 +attrs +plyvel +pylru +aiohttp>=3.3,<4 diff --git a/setup.py b/setup.py index bb782e3..bca35c2 100644 --- a/setup.py +++ b/setup.py @@ -1,13 +1,15 @@ import setuptools version = '1.16.0' +with open('requirements.txt') as f: + requirements = f.read().splitlines() + setuptools.setup( name='e-x', version=version, scripts=['electrumx_server', 'electrumx_rpc', 'electrumx_compact_history'], python_requires='>=3.8', - install_requires=['aiorpcX[ws]>=0.22.0,<0.23', 'attrs', - 'plyvel', 'pylru', 'aiohttp>=3.3,<4'], + install_requires=requirements, extras_require={ 'dev': ['objgraph'], 'rapidjson': ['python-rapidjson>=0.4.1,<2.0'], From 42401f34c5b44d2cc075db061c6c36546a8734ad Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 18 Oct 2022 16:28:31 +0000 Subject: [PATCH 110/386] CI: split tasks --- .cirrus.yml | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index 9ffdf86..032d69b 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -42,8 +42,6 @@ unittests_task: - pip install cpupower script: - pytest --cov=electrumx - - pycodestyle --max-line-length=100 electrumx/server/*.py electrumx/lib/*.py *.py - - sh -c "cd docs && make html" coveralls_script: - if [ ! -z "$COVERALLS_REPO_TOKEN" ] ; then coveralls ; fi env: @@ -55,3 +53,23 @@ unittests_task: CI_BRANCH: $CIRRUS_BRANCH CI_PULL_REQUEST: $CIRRUS_PR # in addition, COVERALLS_REPO_TOKEN is set as an "override" in https://cirrus-ci.com/settings/... + +pycodestyle_task: + container: + image: python:3.8 + cpu: 1 + memory: 1G + install_script: + - pip install pycodestyle + script: + - pycodestyle --max-line-length=100 electrumx/server/*.py electrumx/lib/*.py *.py + +build_docs_task: + container: + image: python:3.8 + cpu: 1 + memory: 1G + install_script: + - pip install Sphinx + script: + - sh -c "cd docs && make html" From 92d9594e96a9fc72031af0874844a0664687977d Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 18 Oct 2022 16:38:42 +0000 Subject: [PATCH 111/386] tests: tweak server.test_env.test_MAX_SESSIONS apparently value was too small to trigger intended behaviour on Cirrus CI --- tests/server/test_env.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/server/test_env.py b/tests/server/test_env.py index 84595bb..cc0f7f6 100644 --- a/tests/server/test_env.py +++ b/tests/server/test_env.py @@ -324,7 +324,7 @@ def test_LOG_LEVEL(): def test_MAX_SESSIONS(): - too_big = 1000000 + too_big = 10_000_000_000 os.environ['MAX_SESSIONS'] = str(too_big) e = Env() assert e.max_sessions < too_big From 399cc81db84a38703f92601a28245bf01b7260c1 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 18 Oct 2022 16:18:45 +0000 Subject: [PATCH 112/386] fix pycodestyle issues --- electrumx/lib/coins.py | 3 +++ electrumx/lib/util.py | 17 ++++++++++------- electrumx/server/session.py | 3 ++- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index c65f21a..bc0e76e 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -2777,6 +2777,7 @@ class GroestlcoinTestnet(Groestlcoin): '75dycxl6lqxujplls3qkhkzffptzdfohv3y5um7s5nhyu6idayqmk7id.onion t', ] + class GroestlcoinRegtest(GroestlcoinTestnet): SHORTNAME = "GRSRT" NET = "regtest" @@ -2785,6 +2786,7 @@ class GroestlcoinRegtest(GroestlcoinTestnet): TX_COUNT = 1 TX_COUNT_HEIGHT = 1 + class GroestlcoinSignet(GroestlcoinTestnet): SHORTNAME = "SGRS" NET = "signet" @@ -2795,6 +2797,7 @@ class GroestlcoinSignet(GroestlcoinTestnet): TX_COUNT = 1 TX_COUNT_HEIGHT = 1 + class Pivx(Coin): NAME = "PIVX" SHORTNAME = "PIVX" diff --git a/electrumx/lib/util.py b/electrumx/lib/util.py index f400af7..c92a35e 100644 --- a/electrumx/lib/util.py +++ b/electrumx/lib/util.py @@ -392,8 +392,9 @@ def _patched_TaskGroup_add_task(self: 'aiorpcx.TaskGroup', task): if not hasattr(self, "_retain"): self.tasks.clear() + aiorpcx.TaskGroup._orig_add_task = staticmethod(aiorpcx.TaskGroup._add_task) -aiorpcx.TaskGroup._add_task = _patched_TaskGroup_add_task +aiorpcx.TaskGroup._add_task = _patched_TaskGroup_add_task # We monkey-patch aiorpcx TimeoutAfter (used by timeout_after and ignore_after API), @@ -402,11 +403,12 @@ def _patched_TaskGroup_add_task(self: 'aiorpcx.TaskGroup', task): # async def outer_task(): # async with timeout_after(0.1): # await inner_task() -# When the 0.1 sec timeout expires, inner_task will get cancelled by timeout_after (=internal cancellation). +# When the 0.1 sec timeout expires, inner_task will get cancelled by timeout_after +# (=internal cancellation). # If around the same time (in terms of event loop iterations) another coroutine # cancels outer_task (=external cancellation), there will be a race. # Both cancellations work by propagating a CancelledError out to timeout_after, which then -# needs to decide (in TimeoutAfter.__aexit__) whether it's due to an internal or external cancellation. +# needs to decide (in TimeoutAfter.__aexit__) whether it's due to an internal or external cancel. # AFAICT asyncio provides no reliable way of distinguishing between the two. # This patch tries to always give priority to external cancellations. # see https://github.com/kyuupichan/aiorpcX/issues/44 @@ -416,6 +418,7 @@ def _aiorpcx_monkeypatched_set_new_deadline(task, deadline): def timeout_task(): task._orig_cancel() task._timed_out = None if getattr(task, "_externally_cancelled", False) else deadline + def mycancel(*args, **kwargs): task._orig_cancel(*args, **kwargs) task._externally_cancelled = True @@ -439,9 +442,9 @@ def _aiorpcx_monkeypatched_unset_task_deadline(task): return _aiorpcx_orig_unset_task_deadline(task) -_aiorpcx_orig_set_task_deadline = aiorpcx.curio._set_task_deadline -_aiorpcx_orig_unset_task_deadline = aiorpcx.curio._unset_task_deadline +_aiorpcx_orig_set_task_deadline = aiorpcx.curio._set_task_deadline +_aiorpcx_orig_unset_task_deadline = aiorpcx.curio._unset_task_deadline -aiorpcx.curio._set_new_deadline = _aiorpcx_monkeypatched_set_new_deadline -aiorpcx.curio._set_task_deadline = _aiorpcx_monkeypatched_set_task_deadline +aiorpcx.curio._set_new_deadline = _aiorpcx_monkeypatched_set_new_deadline +aiorpcx.curio._set_task_deadline = _aiorpcx_monkeypatched_set_task_deadline aiorpcx.curio._unset_task_deadline = _aiorpcx_monkeypatched_unset_task_deadline diff --git a/electrumx/server/session.py b/electrumx/server/session.py index 63c016f..1ba6bb2 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -618,7 +618,8 @@ async def rpc_debug_memusage_get_random_backref_chain(self, objtype: str) -> str import random import io with io.StringIO() as fd: - await run_in_thread(lambda: + await run_in_thread( + lambda: objgraph.show_chain( objgraph.find_backref_chain( random.choice(objgraph.by_type(objtype)), From ea1b6ae83c1a0d812f7f9766af52c1f4df712acc Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 18 Oct 2022 16:32:58 +0000 Subject: [PATCH 113/386] docs: set language explicitly ``` sh -c "cd docs && make html" Running Sphinx v5.3.0 Warning, treated as error: Invalid configuration value found: 'language = None'. Update your configuration to a valid language code. Falling back to 'en' (English). make: *** [Makefile:20: html] Error 2 ``` --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index ba4c166..8d43b7c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -58,7 +58,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = 'en' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. From ef26caba3749170f80b6ce21f02e9c79f2c3f626 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 18 Oct 2022 17:10:36 +0000 Subject: [PATCH 114/386] setup.py: requirements.txt needs to be included in sdist --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..f9bd145 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include requirements.txt From 54b1787d4e791dda4ede67942584cc0b07eb24dd Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 18 Oct 2022 17:18:20 +0000 Subject: [PATCH 115/386] update docs re CI migration --- .github/pull_request_template.md | 2 +- .travis.yml | 49 -------------------------------- README.rst | 4 +-- docs/index.rst | 8 +++--- 4 files changed, 7 insertions(+), 56 deletions(-) delete mode 100644 .travis.yml diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c91881c..a41b48b 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,4 +1,4 @@ \ No newline at end of file +and (2) make sure the CI tests pass. --> \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 96dea4a..0000000 --- a/.travis.yml +++ /dev/null @@ -1,49 +0,0 @@ -sudo: required -dist: bionic -language: python -before_install: - - sudo apt-get -qq update - - sudo apt-get install -yq libsnappy-dev zlib1g-dev libbz2-dev libgflags-dev liblz4-dev librocksdb-dev libleveldb-dev libboost-all-dev libsodium-dev -python: - - "3.8" - - "3.9-dev" -# command to install dependencies -install: - - pip install aiohttp - - pip install aiorpcX - - pip install ecdsa - - pip install plyvel - - pip install pycodestyle - - pip install pylru - - pip install python-rocksdb - - pip install pytest-asyncio - - pip install pytest-cov - - pip install Sphinx -# hashes - - pip install tribushashm - - pip install blake256 - - pip install x11_hash - - pip install git+https://github.com/bitcoinplusorg/x13-hash - - pip install xevan_hash - - pip install quark_hash - - pip install groestlcoin_hash - - pip install x16r_hash - - pip install pycryptodomex - - pip install git+https://github.com/Electra-project/nist5_hash - - pip install git+https://github.com/RitoProject/x21s_hash - - pip install x16rv2_hash - - pip install git+https://github.com/VerusCoin/verushashpy - - pip install bell-yespower - - pip install cpupower - - pip install kawpow -# command to run tests -script: - - pytest --cov=electrumx - - pycodestyle --max-line-length=100 electrumx/server/*.py electrumx/lib/*.py *.py - - sh -c "cd docs && make html" -# Dont report coverage from nightly -after_success: - - if [[ $(python3 -V 2>&1) == *"Python 3.8"* ]]; then - pip install coveralls; - coveralls; - fi diff --git a/README.rst b/README.rst index d738420..6d973e2 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,5 @@ -.. image:: https://travis-ci.org/spesmilo/electrumx.svg?branch=master - :target: https://travis-ci.org/spesmilo/electrumx +.. image:: https://api.cirrus-ci.com/github/spesmilo/electrumx.svg?branch=master + :target: https://cirrus-ci.com/github/spesmilo/electrumx .. image:: https://coveralls.io/repos/github/spesmilo/electrumx/badge.svg :target: https://coveralls.io/github/spesmilo/electrumx diff --git a/docs/index.rst b/docs/index.rst index 0adfd27..cd3d3a2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -2,8 +2,8 @@ ElectrumX ========= -.. image:: https://travis-ci.org/spesmilo/electrumx.svg?branch=master - :target: https://travis-ci.org/spesmilo/electrumx +.. image:: https://api.cirrus-ci.com/github/spesmilo/electrumx.svg?branch=master + :target: https://cirrus-ci.com/github/spesmilo/electrumx .. image:: https://coveralls.io/repos/github/spesmilo/electrumx/badge.svg :target: https://coveralls.io/github/spesmilo/electrumx @@ -22,8 +22,8 @@ Source Code =========== The project is hosted on `GitHub -`_. and uses `Travis -`_ for Continuous +`_. and uses `Cirrus CI +`_ for Continuous Integration. Please submit an issue on the `bug tracker From acd0b61a07e82dcc83b6ba6cdf4be80f89f62723 Mon Sep 17 00:00:00 2001 From: Jonathan Chappelow Date: Tue, 18 Oct 2022 10:35:31 -0700 Subject: [PATCH 116/386] litecoin MWEB tx deserializer (#196) * litecoin MWEB tx deserializer * electrumx.server.mempool.SkipTxDeserialize * move SkipTxDeserialize to lib --- electrumx/lib/coins.py | 2 +- electrumx/lib/tx.py | 107 +++++++++++++++++++++ electrumx/server/mempool.py | 7 +- tests/blocks/litecoin_mainnet_2265985.json | 71 ++++++++++++++ 4 files changed, 185 insertions(+), 2 deletions(-) create mode 100644 tests/blocks/litecoin_mainnet_2265985.json diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index bc0e76e..6c31a52 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -1000,7 +1000,7 @@ class Litecoin(Coin): WIF_BYTE = bytes.fromhex("b0") GENESIS_HASH = ('12a765e31ffd4059bada1e25190f6e98' 'c99d9714d334efa41a195a7e7e04bfe2') - DESERIALIZER = lib_tx.DeserializerSegWit + DESERIALIZER = lib_tx.DeserializerLitecoin TX_COUNT = 8908766 TX_COUNT_HEIGHT = 1105256 TX_PER_BLOCK = 10 diff --git a/electrumx/lib/tx.py b/electrumx/lib/tx.py index faf210b..1281605 100644 --- a/electrumx/lib/tx.py +++ b/electrumx/lib/tx.py @@ -44,6 +44,10 @@ MINUS_1 = 4294967295 +class SkipTxDeserialize(Exception): + '''Exception used to indicate transactions that should be skipped on account of certain deserialization issues.''' + + @dataclass class Tx: '''Class representing a transaction.''' @@ -306,6 +310,109 @@ def read_tx_and_vsize(self): return tx, vsize +class DeserializerLitecoin(DeserializerSegWit): + '''Class representing Litecoin transactions, which may have the MW flag set. + + This handles regular and segwit Litecoin transactions, with special handling + for a limited set of MW transactions. All transactions in a block are parsed + correctly without error, however certain MW transactions in mempool cannot + be parsed and will raise a SkipTxDeserialize exception. + + When litecoind is run with the latest/default RPC serialization version (2), + a SkipTxDeserialize exception will be raised for any transaction that has + the MW flag bit set (0x8) AND has a non-null mwtx section. + + When litecoind is run with -rpcserialversion=1, only pure MW-only + transactions that have no regular inputs or outputs will raise this + exception. This is a workaround for a bug in v0.21.2.1 that lists these + transactions in the getrawmempool result even though when served with the MW + data stripped they are invalid. Presently, such transactions are of no + interest to electrumx, and they vanish into the MWEB when mined. + ''' + def _read_tx_parts(self): + '''Return a (deserialized TX, tx_hash, vsize) tuple.''' + start = self.cursor + marker = self.binary[self.cursor + 4] + if marker: + # We could call super().read_tx here but the call stack is + # expensive when executed millions of times. + tx = Tx( + self._read_le_int32(), # version + self._read_inputs(), # inputs + self._read_outputs(), # outputs + self._read_le_uint32() # locktime + ) + tx_hash = self.TX_HASH_FN(self.binary[start:self.cursor]) + return tx, tx_hash, self.binary_length + + version = self._read_le_int32() + orig_ser = self.binary[start:self.cursor] + + marker = self._read_byte() + flag = self._read_byte() + + # Work around a bug in litecoind v0.21.2 with -rpcserialversion=1 that + # returns invalid stripped MW-only transactions with no regular inputs + # or outputs, causing this to look like a segwit transaction, but it + # really just has zeros for the number of inputs and outputs, which we + # incorrectly interpreted as a marker and flag. If what we parsed as + # marker and flag are both zero, this is such an invalid transaction. + if flag == 0: + raise SkipTxDeserialize('invalid MW-only transaction with no regular inputs or outputs') + + start = self.cursor + inputs = self._read_inputs() + outputs = self._read_outputs() + orig_ser += self.binary[start:self.cursor] + + base_size = self.cursor - start + + # https://github.com/litecoin-project/litecoin/blob/948e6257aec15b52ef68b4e1ee9d73f7c740fae3/src/primitives/transaction.h#L299 + if flag & 1: # witness flag + witness = self._read_witness(len(inputs)) + else: + # A MW tx is allowed to not have the witness bit of the flag byte + # set, indicating no witness data encoded for the inputs. Perhaps we + # should return a normal Tx here instead, but there is a flag byte + # we should probably not just discard. + witness = [] + + if flag & 8: # MWEB flag + # If this transaction is in the main block, not the MW extension + # block (MWEB), this should be the HogEx/integration transaction, + # which has no mweb tx, just a single zero byte. + # https://github.com/litecoin-project/litecoin/blob/bb242e33551157e0db3b70f90f5738f34b82cc51/src/mweb/mweb_node.cpp#L17-L20 + # + # If this is anything but a zero byte, we cannot (yet) deserialize + # this transaction since the mwtx at this location is variable + # length and we cannot determine the locktime that follows without + # accurately parsing it. + # + # Only in mempool should we encounter transactions with mwtx data, + # so for the time being we will simply ignore these transactions. + # When such transactions are mined, they will either have the MW + # data stripped when they enter the regular block, or if they have + # no regular inputs or outputs (MW-only) they will only be in the + # MWEB, not the regular block at all. + # + # Note that by running litecoind with -rpcserialversion=1, the MW + # data will be stripped from transactions and this tx flag will be + # cleared. + if self._read_byte() != 0: + raise SkipTxDeserialize('non-null mwtx bytes are not parseable') + + start = self.cursor + locktime = self._read_le_uint32() + orig_ser += self.binary[start:self.cursor] + vsize = (3 * base_size + self.binary_length) // 4 + + return TxSegWit(version, marker, flag, inputs, outputs, witness, + locktime), self.TX_HASH_FN(orig_ser), vsize + + def read_tx(self): + return self._read_tx_parts()[0] + + class DeserializerAuxPow(Deserializer): VERSION_AUXPOW = (1 << 8) diff --git a/electrumx/server/mempool.py b/electrumx/server/mempool.py index 5667976..8385ddf 100644 --- a/electrumx/server/mempool.py +++ b/electrumx/server/mempool.py @@ -19,6 +19,7 @@ from aiorpcx import run_in_thread, sleep from electrumx.lib.hash import hash_to_hex_str, hex_str_to_hash +from electrumx.lib.tx import SkipTxDeserialize from electrumx.lib.util import class_logger, chunks, OldTaskGroup from electrumx.server.db import UTXO @@ -328,7 +329,11 @@ def deserialize_txs(): # This function is pure # mempool or it may have gotten in a block if not raw_tx: continue - tx, tx_size = deserializer(raw_tx).read_tx_and_vsize() + try: + tx, tx_size = deserializer(raw_tx).read_tx_and_vsize() + except SkipTxDeserialize as ex: + self.logger.debug(f'skipping tx {hash_to_hex_str(hash)}: {ex}') + continue # Convert the inputs and outputs into (hashX, value) pairs # Drop generation-like inputs from MemPoolTx.prevouts txin_pairs = tuple((txin.prev_hash, txin.prev_idx) diff --git a/tests/blocks/litecoin_mainnet_2265985.json b/tests/blocks/litecoin_mainnet_2265985.json new file mode 100644 index 0000000..1a1ff0c --- /dev/null +++ b/tests/blocks/litecoin_mainnet_2265985.json @@ -0,0 +1,71 @@ +{ + "hash": "9bacbef81b42f777fabc19d08bceddb56d219fd4585f85946cb27470c6f4f596", + "size": 26030, + "height": 2265985, + "merkleroot": "79d964404d4350f4dcfa2ee08f6469130512998ac42a3a997d1f735d2f9174be", + "tx": [ + "9efd56c7048de96df7ae7bd72cf0398d2817b9027f3ef0259af7bdf376c857e8", + "2b682b5b7c07b2d65928a0c6e374497afbe53fe1e90a93255fdd2eea1b94e63e", + "b9ce847ba8e16e838e904f6a3b298d695048044b74db2add90b75344d3e6a026", + "1494ae566aa3d3498a5cbb90b8f305a46467f616cb2bb62e3713c6ce3d80bbb5", + "5624f00f73c9e626967d84b0ca532cb19f0bd91ea3c399c884808983915bdfc0", + "ca2515d27fb127b1ac88e62ad15a8b45c985c4136aee45ad428de23e409b2c82", + "f2f3669ec6ecfb621624cac163ddb633857591c90bb4d364fb11c0ebf525b58e", + "dd7b8589a7accdb12bec566dac7954fa6f6ca89dd0c26c76c6434d8a74933bc4", + "928c5ed77db0bd3665ed7e83420ef2abbff81463cd073b4afca25b75cadbd42a", + "630bbcadc80ceed96250d17730af711cebfb7e523b2c87611a86e78482b8050c", + "a28e0cb5d8063aef7f6acd4823753c6274d12ca5318ba65ff612c9a3db6a5180", + "1b17684cb8bceeed1c41376b662370eb9ba102a3151171dbc17cd766b0ea011d", + "65663da47f0ed5fd7bcce5373b1f01063b38e2472dc5c9dad2013b3c972e552e", + "fe9589df802ce4c25281d1db915a057c291a4c0db3a7cae4b66596d21c84413d", + "9584fe010b5ea2d9b16c147d712f4ccc0532a03d4847ded09c0baf419742d647", + "3e5eb32ffb48df46b929073d5dd23086cdd6479dd19f3dcce3f124056fca1259", + "8c250fa5e73d32f3bf3bdc957591083ce3fd147fc87ff7050575d700e5439167", + "b69acd7b64a6888464fa6d87abb6bb36dc4b9c8fb2ab06f036790efbf2de0d83", + "c0d02740807f009ac8f0851594536c3f0813c2eb5db2756fb66597b2c7ba78e3", + "3ab1220dee54729f12382ad4b7589286cd09737bf4541de89e024bf1f54bd7e5", + "4bebccc9f2152833931b38ddd48f24ae1b8c17a6d3955ec20e28c86ed1aabb40", + "5d4fda84e3e58b5f1d5fd84dd8a1e956ce3de185a9342d954b44400865131443", + "44b623303edba8210082c9dd18f6d2bebfbcb7520d4c1c81177430eed375950c", + "9dd4ec5dfd4bf813a547fc1e2444417365ab77620b72fa4a3b906fdcf56a3763", + "ef76e99bbeae77dcea0caa7e1f37c06ef67704a03f5bff9f07cc4acd83a6c964", + "8f9c778abf866fdd9a11f59620c9376ecdc4dd14aa28ad121552b2de2b49b06f", + "a5aebe1993cf05ed7770fb409cd02b0e6a0e7fa876bc6b97e554a1238b4fbdae", + "152ceaf88eba56b9e0ce65de105d57c9f8fbfede8697bb57d47172d8d9e106cb", + "5dce9f04c330f3ca59d980e64641390d2e90f0f7d138cf264baa03338015cdfd", + "df1985350291a9cfea049238761bbf2a70d080b01464a0982682f2dd5ac4dc2d", + "94ad13570ffa8af3c6d578798402bbb45db2dba39ba82cb3688b9c5583c28203", + "b45dd03a8c49915fb7708b883142e5ddb9293f07d29c8629bee63589dc65e50f", + "cf34e74e28c93359af3b851d576aa0d041110d1a2478c39c8bfb1f09cbcca62d", + "471a8449fd00ffed0ae02c5abc3ab7627bcc87f767463d903c912386910b892f", + "105970a5b5624fb4e3be5f30017db896cc3f61a063f36aac03af1eff7dab3f37", + "de2f617efcf0effe85abb5b2803f2e004641c01f0cc3a4aed535cf2bfb8a8889", + "c27f48387a0d01fe1ecfccccccf1d50a343512599e2bf64c9f877b77038be989", + "3925bd4997da9ae7f10c71b5bc04108d5aa8125e97f585672fe4e635523e8aac", + "75664b29bc9695b7cd37e56ab24c2595a4ac34eb3389867106fc4619e4bbc7af", + "91788113756404beacc58378be59150c54d945d67f4885b4431597094ab38ab3", + "f1405e48350f7302b378124c6e6d8fa6fcbfbf345bf7d0a67ffdb4b5e1ed78f4", + "ac6d11be2d591ed843b69316dec6360a3c38e9fd6d4fe4668c2aeb12fcfb7eff", + "6daeecd3df6e1f340b7faa0bbdfd9e32454ebdf8929a9e9121ed722522408aff", + "c18127a02ea4fb6b8adc19158093d6a9ff93444db8fcd6b2bfe180e2608affff", + "e9afa72f06089722f9db019465a8faf96611675a95475ed7a8070da0145429c8", + "44a90db9c3c702bcf63bd91fe45bf21ef4cae9a55e8a5ce978b29ae1035538c0", + "7d5e7169eeb4a0b5b2ffe5bfbdfb13a6aee78251fe4ab747867988a5b8e7e2f0", + "3389dea5a732b71f917d03d01577e41b41aee4ba959096791d07230c28bf002b", + "fa89081a94e6b341153571b359c77e54a10039fcfa3017d44add6654b379c402", + "bf4a4010c02327b86aa6988bbb0cc033d020159b055df7db4771236dcc28857a", + "9d9db8e7afe7cc2ba37566ba4c06431f6008ccbab92165a0383a556586f8559a", + "9eca8143abff670b4267896e47955f65c734e7793e3c5d0cfe2c18945f1c64a5", + "8d8f64330115917215b330284d4c26d07e8e54fc997c6ee99a00974d2e5877f3", + "3f5065cc0de6d8db88c8a0d2cef6941c330ccc6dae7f1ddd4a56ba0d748aedd4", + "da2f03c5d7098482a82fd3a8f526381d320ee9c1fa429812cdfc036e29b5193b", + "b8cd5b9064755ed9f90e47e7374adbcaba9ca9c8889f75b4cc3d084be84ade63", + "2390b60403d8d64e18e9b10f0216a2f109bcb0360fd8f7730aff71d0c1cd471f", + "b0a867c72f417dc0a904f708ba37fa2d94c3b25a288ea6e8fbe8cd14d8ca7119" + ], + "time": 1653023116, + "nonce": 456582002, + "bits": "1a010ccd", + "previousblockhash": "418dcad142cd2d94da49f4d0025efa73adde9d7ac971392852f7bce7c716d11c", + "block": "140000201cd116c7e7bcf752283971c97a9ddead73fa5e02d0f449da942dcd42d1ca8d41be74912f5d731f7d993a2ac48a9912051369648fe02efadcf450434d4064d9798c218762cd0c011a72e3361b3a020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff5b03819322048c2187622f706f6f6c696e2e636f6d2ffabe6d6dfcd99927297681566a7214d4d214bc5d80dac8987d46e0501ad5dce21407530f0100000000000000bcd080c257fcee363e4ade85785fc3b702000300000000000000ffffffff023f07864a000000001976a914354039ba9a6666f6b729a2570fb8b660e86f142288ac0000000000000000266a24aa21a9ed9ed9bc6a21568d565c7d3e0cf5573c0df6a91877ce5ab32e4ce18c2b6e5432cc012000000000000000000000000000000000000000000000000000000000000000003cde0c870100000001036ddf803529e44a7d33eb5fba1e2e0860d4f667b39f6dc0bf9e30a6843ccb00000000006b483045022100833a1065de39f603b727bc1acc07c43f51d0151b0bb9e03a7621a5dd323ff38c02200af44c4a5cef37c049781c1903513ac504a132c097f74ed1702fc890baa5d65e012103519c5209bf15b55a0de8c092384291751a6122a04d50a5c4ba570eaf2b5c0928ffffffff020065cd1d000000001976a914ca6be635c230a3c8f8383a12167cacd29f391b3088ac80bdb42c000000001976a9145b7b1e73d71cbd7a6d31b393ae14c4a3c6b7715688ac0000000002000000000101efac35bd87d8b3ee8db98daae78355b349e17337f55cd8c73e5d4d8325d875a60000000000feffffff01c3594000000000002259201ee4bc92eda6a705f7504239ff8e5b6a05746278faa6d6e40afd5c97840609b90247304402201cc095d6612181959137e138fdefd5b92dc35d2d210267d9390561ffb81bf6d90220405c6b47040c1d6d180713f48f9d4792ab28fe7e59cee15aacac4cf0ea423aa60121023a0f5e00dc6fbb651811cecaacabd255b464e5bd5536bbe1443252621ef4e9577f93220001000000000101bbc28b64d1b9f74a6e90c5f85973fffb402a4a86c08dc1f1b9c585acf5bd2f030000000000ffffffff029e846d000000000017a914f301c7b1cdcfdec91ffd07fee16c8ef35c6fa84487b609510000000000160014c5b903cd88b99db3eab427c644f183de9f31f51f02483045022100fd726ab09ccc21b2c3c4373001a1785de579894a37f0e8b404b6b9438d91d48a0220633db52459593208ab80a60229b49b0712235ac61358bcf96aadbf662ec74627012103b2cb61c415b7ef5570f68438b96cfef9a5bb610c79484a5b6321f2ff4d56ca0b000000000100000000010113d757a224a89ba225e6051968cf92b243b3191d102f5fb116de9e24048d9a720100000000ffffffff02bddb138d00000000160014fc845dc3ee090fa40899b226abf2b25aafa64a42003e87490000000017a9143970fc776725ba1c9658c9a9b69ea890043785578702483045022100e0e316921bec2e7e8a270cde14da0078af89d788d56594567c67f7f4339233fb02206a63f3155acfe80794695f581eda8d6997f89c9728d6fb72f7919df0f39a1506012102fabd99c14a36bb6a1dc07f10b733bace2d09716c630c655ad81ce5c75769a44c0000000001000000000102c7dfd65444625474fe96c28554d1866e1939be07f230db94d63c88c71c003e3d0000000000ffffffffa2447beec7fcde3d997cc83cd9a6f8b5c02289ee6e3005a04ed082c4983030db0000000000ffffffff01245038010000000017a914dda060aa6f31ade32bc90be3747297a9ca23c0658702473044022001a18342f3148b69249b80432e06b58e695bcdaa9a3a2d98562ba438aecb1b5b02206458a6cf656bf2452a61b2a0aaba15ed9544ac28fddce5ea04151d3c8b1b935e012102a079a0a09867d88c404d313ea85e109b4fa24079c0f6a372bb4f4d6ff4f48d9e02483045022100d0746748f9ff5371ab3c5e0f9f15db8e6ab2885045510701b6dfd65a3e49e40f022053ea81d27fd22479f43a588e3515c1828511eb510a358021ff39a7709f82fb500121021cd443aea91c208fef729ebcc6658cfd4854e05a1a093a7fe8fc9f8d85f0bf1b00000000020000000235e4c0b1696efa2369b2985e4dd84bfc09e32834abf5b9e9d8ec24f056b2e88d010000006a47304402200b69c9934e924822d4631ad35e06b7a72642976e412820b34a0cea06a0460a9a022005a3015074eda9349c6d6f729f6b9a80eaed42b2ce1b341f2e079920c2b24f4e012103a677de3f904cb245f69f9e38c64b4b282d7753e3b64e8747c6de9b68c5b710cdfeffffff39e9d92c9eea966ab669cbabf281b7f52bc21e0ca2d12dd13e64bbdca0feb3750c0000006a4730440220228e6196834cf62b4570b6e5ac0961d1bb226d3722c420fcd2b7149a58aa841b02201b75f43da2820fde7adc21cc66dd48c4527d56e0be34b1fd33955f4d1be7ccc301210396457514e75781ac0b70fdaa419562741f96a4ed355c99082873c5dc48f27866feffffff02c74e1b00000000001976a91411bdc05cbca10f5d0f3ccb07e1926c593465118388ac48d299000000000017a91490ef9659243846e5d9aa7a380538fadab9e5d484877f932200020000000001017c2ff14de2143617d0b92e6cfcc5f70eac4cb38425896fefc18563cfb3b06b980000000000feffffff01424c7f69010000002259209b23ec52393f6c48d5bb45d218b6285378abfa5ca493564f79c736341512e0ea024730440220231157332faaafa6c2b1e1b9c2f3a99096f0ec4844e77b218b9f2ed47576ddda02200de5110f9d8189f78a3b2d4dbf8e10e366a610d4d796febd579f2f4d6ac6790901210270b55ab41d988d37e3afa6aa26aebd9e9056098fa63ee55a00a782a6983c50fa7f9322000200000000010112e0b018ec525a89ee5517be1f7d323a2784e31220ed39deb708cfa4d53ccec409000000171600148f8696e4fd9272ac6ddf4f2f005a04e02baa4386feffffff015c09a1b200000000225920b265a18e4aebf7efa980f9a67adfd1963bd6b42b4ec5c8d1308b90fd73ce93de0247304402207d673046e38e1541d5ef75513214fa3d9d0a807555b74e8b83368aa1ee2d76d002206ff5266712ce5e1106986377459597d60b9349b118768babeafcd078c0a57864012103348c364f954849b49c679474873b3ac55671534064ab0e1000f6fd7fb038964d7f93220001000000012e61192cd0437a4cbc538c71135a406651a9d7f0a21f5e218ca3085522c6803d010000006b483045022100f770acb984dd1ef194e0a4eea010302db592e0a3554df11ca00dea829f85b819022048f8942a41807d16b9d2580feb8949ba8bd08746232acc97dbf41d5b947a5db40121035c70e8d4e9af255170b0d30f3a7c1b6e2e4bf5c0e6c7c6420eb501be9e0f3284ffffffff0230d56d000000000017a9147d9c4697bb08e2fc723641645002ed7be409892e87c8442302000000001976a914698c57e536458ca8947ff9bce0371368638f93fc88ac00000000020000000001083904256b48052cfc073a92ddf881293e499df4d57bfb4095b33d6f4e917f99c90100000000feffffff3904256b48052cfc073a92ddf881293e499df4d57bfb4095b33d6f4e917f99c90500000000feffffff3904256b48052cfc073a92ddf881293e499df4d57bfb4095b33d6f4e917f99c90b00000000feffffff77b34b4e06aa251b88acf1a878df3ac650cb90e9102ddf9f54dd610b4237604a0300000000feffffffdc75c3780af4366564a64c4a48830db610f2e61c70b3645310bc93b4b314729e0000000000feffffff3904256b48052cfc073a92ddf881293e499df4d57bfb4095b33d6f4e917f99c90e00000000feffffffb25633e45154a87c90aeb178ecd7e99b5545a307d5f7107f0713e1612b83e34f0100000000feffffff77b34b4e06aa251b88acf1a878df3ac650cb90e9102ddf9f54dd610b4237604a0000000000feffffff02a34899110000000017a9146ed836fdc6683b96f350735a961b6d070a749c00873685a9040000000017a914b189a66c3aea3aca11f4679f3897b56207d93cb287024730440220212290eed04e6d1e42ab87ecee9723e6fb86012e004ebc434c001e65e7bdfce902205612f3a7e857736430647f1a534a2159f80d629e35db19a23d278e27c26b76c8012102b375fcc98d37acc25862df8484fe4b6b6c386bb5b78e762d80bd636414b51ea80247304402206f2a900499ad13705ae4750cd2ab81545119f346874878bf3e9a4eacf8e5d6850220100294f6b59f75dfc7f0f31e0cda8a03550d9b99fb0bcac82f015b69e35b48620121024eaa18c24dfa5b71978b75e32941d4bc6c7f5941609a9c6d21ad0fe31a9051250247304402207f0dedfae0726aa501dd03c7275d81392550e0525ecdbb07533431e8d974a63b02201b3dcc6ee0f976ed5ee2d7fda44a9f962d62b0e8446ee25f0ea2f4f58ca610780121028d3beb6359fbca6704a1f1bbc3c83992ecb26bdd170e5e29dcb3d840204eb4780247304402205094abe57f8a477065dd13cf761972abb59b2c46fa569cd74b900ade5bb187d1022062d9fe6329b6788edf5ef44cd01d0d4a6e002cd185582da41b73b3da48af1d3f012103377a406db548897155d3a42d0772798014d58d0c057a9b3ad2bf91986421afbc0247304402202c8e4d7b8332f72e9bd35c7f45091e1624815b19bffb5f206724a7fdcbf479fd022019bdf21b2b5d87618f5757f2e789114155956cccb24f721b9e0b66ac14c5a6b80121025de7727b71224051c9ac62b4220c13bc9d3caba5aa85577c2eb062002b3005ee02473044022047c0bc02efe03a75373b092386fbe6cfe2b3cb2bf4c4121a0ee3ce658672f5ba022051bb6d15429a2697060ab209ab0a21bb0ba2b6aceca5f1946629fecd62e666ae01210353d296b9acdb48bdd8b7305d1be5b7da4cb0d510f405881925073c3432489aa102473044022078e66437d8542ccb8518213a437552be500a1da52a746af016f34400064588c802204e7dc2c2b102f9a0520e02141c250528a664c76671f5f187a49f14bca8b6620a012102b731af134efadafb69eb9193ef36d3c561a3e92d512b21bd40c7f6331e63be1402473044022013303f6f242969c34e0f1ed002db227cd9ae12d33892f010b8d233c905b1059202203589b19d9ad243f6911980eff9fbdad916ee07e6215fd462bf4e0d9af018aa80012103d4f9cd13793e8c9b01dee16ca3769a8fd4cfbb5c326fb105ee678c5c71455e787f93220002000000000101bf96c7b4de781402f8dbcf11d9c46a00d1a65a39c90b51e958b52da1b5168d740100000000feffffff028c18e7020000000017a9147c26449d4015deccbe8d5fec41ca7ac6a5ab21308770ff97101300000016001400c019e6171e59d8f971a9f2c619a3ffd86d052d0247304402205491c0cda127f32eabda9c2621527e12fb2cbb358e24f3fcd37c191d398184f80220636325f1b572d1af2c0dec653c6d2c368b6afdd381f307c33dcedc62f39726640121023dd608755b7604ab1e01c301fd64891b6e592c4d93da9b3a37d6c746857beefa000000000200000000010101da469e5efcb7f61b2ea992a1870d81de5cc0967fa8fa0fe458a831443bef51050000001716001444592d37b2d035e51abc6dab299b28f46ce43780feffffff6770170000000000001976a914c5c099c441eeff062e89e031e1f69d0affbc910488ac70170000000000001976a914e797fded43dfccfd3882d539d43f9741bae5652688ac70170000000000001976a914167647b4c2dc332632b2eff74fb64d8b2605309288ac70170000000000001976a9143d0ea0d6d7750d9811fc56e09648504e85796b9388ac70170000000000001976a9141b764e547edac61044a34a30a919ba2ca96ec5e388ac70170000000000001976a9144d9f1171c1a52919e85563bf4e9c18f1c77ddda488ac70170000000000001976a914a98ba3113a9a1822da8a674e30c83f59789aca0388ac70170000000000001976a9145161a56fa4e436ccd5e55422408ef31f1bd0c73788ac70170000000000001976a914444004cdc217ef515832443d3c0f8af11740676788ac70170000000000001976a9146394fa3ae0d6fa35c84ab6059f923fd78d35553988ac70170000000000001976a914d80bfc04d36b1c5c35d1e6b5589b2a39b4a78b3688ac70170000000000001976a914369e22d80f5b92a74537d995ca646a7d2e15ff7b88ac70170000000000001976a91471ef013af71c531a5cb100f814cc78e34243df7588ac70170000000000001976a91416d45943d5cc0222d95428ea99333de3850a50dc88ac70170000000000001976a9146cf0c3d02054c239eb6a1a63464dccb2fb6a111988ac70170000000000001976a91450d2d604803e5f62a6636f640b2f112619261b6c88ac70170000000000001976a914a6e78f661a4416ec7930fe7e3ce336b2ad1f2a9388ac28ab290d0000000017a914fe62bc96f334da011168bf348f22ff237a756c438770170000000000001976a914f76ddd0071764cbda6156cf057bbd8243b378af988ac70170000000000001976a914ef7aa7f31083d7a69e5c408cde2dadc7f3a78dda88ac70170000000000001976a914f15570cf5775c4ee91b8d67c5d96096099e4a38888ac70170000000000001976a914111bb32e8a17e266133972782818dd428dc8edd488ac70170000000000001976a914cb37fd9d9027d5e97bb66a8ba52c4f9b7371332f88ac70170000000000001976a914ab75195ee43d958e445e6b41c6c761c3a58b75b188ac70170000000000001976a91457782520ad6e38cfaddce7613ea9cf5c37c45c2388ac70170000000000001976a914de0943fca89cbac158c0e45e3a4738dad497c62f88ac70170000000000001976a914bcfbca032322705454e5b62d9ad067f7d83896bb88ac70170000000000001976a914bf39aa4658a91981e26dc0474e24b5d86a52627d88ac70170000000000001976a9147dd4ff51f38077b55fcaa0fc6746ccdddcf3000088ac70170000000000001976a914ed7f8e36fee0bdd4bd161df3d2446c393b34eaaa88ac70170000000000001976a9147267e1eb2dc151da7893a6da23bd774152973eea88ac70170000000000001976a91459cf99d7d836f13f3452fde02dff9675d20a4b5088ac70170000000000001976a914928336a528fa03c0453ee4b22dc8ce4339d21a8488ac70170000000000001976a9147be164b5860c416cc260242ca171c17ee57ead2788ac70170000000000001976a91450f375daf860918d320458d9e65174ac4d8f3f3588ac70170000000000001976a914a5bfc4dced2d1bf3b821272b9d77a9abfc9a2a0e88aca08601000000000017a91498d1eae7d7203586059ab08a5b4e4b75cb9c2c398770170000000000001976a91465e0cff6943ab4cd085bafa47d212a9eee5d018d88ac70170000000000001976a91404b1e630124681d41ee2b99ee244e6764981741788ac70170000000000001976a91437973420d0638c9f0c63bc00dc0c3cc130be4b3f88ac70170000000000001976a914752eca2cba0d840caf1926da635ed367ec0d5f4a88ac70170000000000001976a91410afd3e2f4a5a07dc923d0229c43b25021ad75e988ac70170000000000001976a91403ae1125548a57ce5ab17d845adc90fc86f2833288ac70170000000000001976a91495d143b8606ccdf299403a01ace96a6de87d142888ac70170000000000001976a914c00c134b5b97796b7cfadcdd79157b59c47a754888ac70170000000000001976a9140bc5477ad441ec7ca5c8d95b4c159dd5189b779c88ac70170000000000001976a914390fb04779b87c878db0f51b21a087875aa7290988ac70170000000000001976a9146e011939966aae32bf8f3ad0eff398a591be7f9288ac70170000000000001976a9143790278f3fab27da89220398d0834e02a16d7cd488ac70170000000000001976a914f38a13fcf62030fc282543f36fa3fe9303275b8688ac70170000000000001976a914fdfdd96b5a80f63f459f63fb09e6ae71069e65e188ac70170000000000001976a914bd5cbbc03bf410fc54681541844c344509f35ea088ac70170000000000001976a9146271a68b1440e3cdcc1e8f0f904f61ab0ab7790788ac70170000000000001976a91419f8363ed5d3465a7e8a69aa0041b021ce885aad88ac70170000000000001976a91401a67f87d94b500874e679025b73dc8d8703960388ac70170000000000001976a914e26a491a30896dd773e8b6fa2b2914e12453157d88ac70170000000000001976a914cd46dedb618560e1407163302ff4f775a0a69d2088ac70170000000000001976a91448e8d706ff45d748a1fdd9c47de7ea63c5dc057688ac70170000000000001976a91486e13bb6f157394e0efb1142da0130b37171b38588ac70170000000000001976a9141a6277a72fe4fa98538459aabbce9f0e93fccf2e88ac70170000000000001976a914738501de40e2e12d862d9c5599db3536f2e7439788ac70170000000000001976a914fa0071882481b92cc29052108a196bfe13ba369188ac70170000000000001976a914bb27a2f8dc9f3fe108c041f3244be3a362d88dfb88ac70170000000000001976a914c795ea3c4f1a612637e9499a4d2dfebca9f3316e88ac70170000000000001976a9148fb56c24e4cdfd5a744cf0a4002515e506ef869e88ac70170000000000001976a914c27f4ff29e902a071b8a59a431f46de13b1066ba88ac70170000000000001976a914dc21f0115af9fa0940319afcdaba9062618574ea88ac70170000000000001976a914d2016cae5d44ef18756de01e1032ba03192b4e5588ac70170000000000001976a9148f8482f1a866a86e1286f4fc6c1b3ae6e340eab388ac70170000000000001976a914ffe0ce40e1ed478bea06ae45792246d8762b3f4588ac70170000000000001976a9145259f610a95dd2fb02493b612ae0881fd49c3aee88ac70170000000000001976a9148a301d333a9fee2aab54c9d8a62cbbde160c028988ac70170000000000001976a9149addfd3cf9eec9cd592d80543c7db6f12349d7fd88ac70170000000000001976a9145bdd18144d7eb637b85d6a72a77868b3ab0c74c488ac70170000000000001976a91471b6e5c492fa0a05ee7c3efafad157f1654d2ac088ac70170000000000001976a91408750ba4bd6af0a038d427b816ab40f35171721d88ac70170000000000001976a914be7cd084463042834f654d1f33f02941ad1f635f88ac70170000000000001976a914dd62a986993e6ddc26012ed0ea45e365e60314b688ac70170000000000001976a9143db4b6afdd9a5903e323b579657511ca8718546d88ac70170000000000001976a914df93f26e82bead1b19b58299ba7a539b39fb43ad88ac70170000000000001976a914f841e3c1406e7c270fcafe0bdc82c807c1c1b9c388ac70170000000000001976a914b6dccedf333dc334126303aa53c4ca7163145cc888ac70170000000000001976a9146d5cd69ee3177cd5df466a487f83a42b5197c2e588ac70170000000000001976a914f7ef58443446d158c2a6cfef123cccaf4554175088ac70170000000000001976a914c6b146ddf6c1444956aea857bb10850418231c0b88ac70170000000000001976a9147f756e26dc8ba2b7fa1daf5f4c28e1c8f9c914fe88aca08601000000000017a914f6a5735fc6a1b5f1c7c33d88b0c68113ea54ba9a8770170000000000001976a914f2b7c8f2e0aa61d109a07a1d9c5f893837ce615b88ac70170000000000001976a914f3e2efd9fadef6984c4ee860d206b9cd470295d688ac70170000000000001976a91402b02d3ac4ebf96f3c2b0b2ec88b5c627365cfdd88ac70170000000000001976a9143f3cddf15dc7987168e42fc9cda6dfaf92cf885288ac70170000000000001976a9144f2a2b87e79ec758ce59917e4fdb4d346980dfd588ac70170000000000001976a91407cd4bc0b4a529db74b76ed751a8ccf3b937f52b88ac70170000000000001976a914b2a42e3597aa0535f05e22c3668432bdb162bd6288ac70170000000000001976a91451d2bf986a17169e175c3336df3c67e31071730788ac70170000000000001976a91493cf13db01fbd0efdd94cdeab7a4c8411ffa2a2288ac70170000000000001976a914c6e04bc1e849d1ce3c2f976f5acbb3c2360d7b9f88ac70170000000000001976a9145711e53c8002bab52c15639e5621d53a3ab8c4d688ac70170000000000001976a914d3de6145ebf8c16ea7d7ea58fccbeb8d94aa992088ac70170000000000001976a91479c4845ee6a359cd5722f51b2a9b4929d7d4d3b688ac70170000000000001976a914dbfc8394f8d01d15a64bfcf0df8270a8b4d3484588ac70170000000000001976a914ba7408b64785f9487c8ee13c4a4b5e0bcfad673288ac70170000000000001976a9148a875a49725842f148800b6fcdd1f6dc3cb2824288ac02473044022077a78a2e1c4ed18c64d7802176fdea709d5653a212ae39bd5fdbdc3ef62f18af022061bc7f757fff31d2f79e545853d7008f90a4214b4979d52e25e1c7927e77f2850121031b19ea5ba26e8dcaa21cbd35c9f60f48e815d6b618d47ae8fa9bd63add7d538c7f93220002000000000101477055f526770944a83eaa6f2a339d3a7f561e23f68d4a85ae98d5611c7f7136000000001716001456f64e182ba7049f38d9523081496ede1924ed08feffffff02598622000000000017a914e950f8a177669903cebad6db67165c35259760f087ef7a00000000000017a9147698dd48ac959f2453869b94163e142f0df3ddfa870247304402205729d83dc742bc2458ef15587553e55fac96c57f98d1701ee2ad8ecdd7cf8022022036839e80e153d4bc57e2eef87f7d92805a9c5d76e9b81dc6d7699f97cf91c0420121021cbcd65956a219c68289066e2bc9dd85bdbda5ccf90180af1bfa79da525c61a17f93220002000000000101ee4d69e80c581206f70f8e2e46430ceb8483a546bcef171f9b59193548703e44020000001716001434bcfbc4ad5cf2b943f9299343c81cde0859731dfdffffff07ba84ca010000000017a9141116e3949547511380b7d666e9a56be3d4cd3dea87f5b3e2020000000017a914eaee23bac6200770ba9c136f2f5c87f9d60314c9873155bb110000000017a914542880ed5d68a09680dcf217ee6a71d51482c6ae87c39232020000000017a914c03eb096a40a14e1d3d57a1da82cf5c1800461d58703cc5cd80000000017a914a985e24c703678220ff95b362ce69fc4c9a3aab187af14ae020000000017a914087ee54eabd8461e1106f7ef657a295434b6911f87af7e17020000000017a9147b8fb49474b0c985fb9a5c73899f89d323bbeee4870247304402201050db610d789e343d70585519059d3ba9091e192a926aaba86bf26091458f8202206bc9ed6480c8b467f489ca21b223b7f72db09c87db06d11f1b167fae20c9ce54012102b2c59de2801b54eb12c323bdde904e55d9d91a9df769102a533af401affeaff27f93220002000000000101e255caea039b548d783c8cb4f086f8fc4d181e6ed509e6531f693b9b21b13ab10100000000feffffff02a9efb8cb040000001600149c3b2e8903c9db6eaa02c29aa40644dc162665b340e6ff0b0000000017a914d84b519e3d49d9ae76270b6fb8f65e14aff62369870247304402207a9f14a2d28a8b2d5294a6e94c011fe188c92efc571f85722c6c454e84388b0d02206ff5c088b21f0565cae6c0f32ae9e05e720d129c52e6cf5c2dbf00fbe88abced0121031dff88cd16c4ab485b162539b19cea307a11b5fd89555c43864b22f0df4d50402a932200020000000001018b1f5b68597b1dc0ed14968d2270ec191e09de98db4dc24564cb29d06d5de9df07000000171600140b6090fb6bd251abc848036986fe72623b334019feffffff02801915010000000017a914c07f8d57e5f0b6c86224c6543bd7d1ee82ff24fb87a08c0f000000000017a914fe905e6494fbe533a85a5e6d3c3c03cfeb151fca8702473044022038b6781d101e7ffb90347c08c064f9fdf081e5bca58dbf9518895aa365f5acb502206f9c96586ce7aaf0cd2fce59515d4da12f311453dc2f11d7f34c7e0528d543ea0121035da8b55e0d2068a45ad492686e8b099feea7823b81ccb81245dfd11b93f37f5d7f9322000200000000010189ffa7e98bdde6ab73b5aab69b2c19a8bbdb7d1dbde7960c61b877b72bea20c8010000001716001436ce58d8242fc50217513017a64f02ecdf0e8bd1feffffff024c1d0000000000001600144d2859013702c99b13b95eb28594d2f53894a3c889cdc70000000000160014334491a85408b328f46f5746df4591b53033addd02473044022047a75a4ced2482aeb9ee90529445078f910b53059c955854422b0b25dfbf911902202657b63395c561ff7cdc7a27cedeaec3e82db83d8992868a9868406bcbe0b7db01210286a1b0a0ff0026f3921e6b4697d06798a81004a99c332a5ea59ea1f562f87f677f9322000200000000010178ce1823a816c6433ae3e2f81442193e9d9f753a2498ed7f66d943ae39f0a8b00000000017160014fb1d9d5d8a0fa6edc98a2e13377048e1387c102cfeffffff02d0e9f9050000000017a9143afc11de765839f2af23c16092de36fd08e25e6e87861177250000000017a91405a4fab68f8981f0a2102abef6c92829e0157092870247304402207997efaa3edb85065238fb7b31fe449994c304bd1a712055d50e3c02885452b7022011caded93e56eca09e3e7b0b6657794b3f205833201d15a66285441e334b499601210347e3e5b3702a11bc6d3097ce0604bb6c7e194119c429593f7d394c5b9a7d31021d93220002000000000101472601e7c7a40d1d0b5d608a07aa23f91540957fbd3d9353a615b81786beaade0500000000feffffff022900a2020000000017a914a6dc6bc6ad6ed3599d94312ed36ac58cc9163df987b7f0dd0100000000160014080c820b0aee3b0c17b92158dfafaa384bdbda4d0247304402202c60b16f2ab7016115c2eecf5d10f5e20e0c980993f584d58ae847352da10185022017c5bbdc11d10e681594915d84ef0c41c3e9db94d5ba699103d7b96cc853aa58012102e9fe9ad281f797ce19be5a433bb18eb5a9c56a2e89676636cdff7d92f5fe5a107f9322000100000001c34221f2f3308bd399ebec6139fbbb05d87bc4ee13f738cf61f6620be4f94a6e000000006b483045022100836fd614093cab4ed15d9602e96f9846ee5f98fc8bc6afe22f5b8345b5ddaaca02207916e2bd427e60aceb36d1ae00c1fc3aa24794145dad40fc90424e7f5671e37c012102d3a0cad33a567850db6ec19ab74caed05642df62ef978f4aa0085d91baff391effffffff0170697801000000001600140ae6016ca45956e4ce638e2f64d05e9c0f25b3c20000000001000000015746d71fe941382e50663721d60f2b3ce4cd251fb5eb01d5394b548f8c66f3e2000000006a4730440220177d3e9b8b823b36ab1564e0cebf4101742d1fc03dbb00f995612a2d3f98654d022030b580d902040b69b7deb218ee6e1c25b7189663f16913acf4842d16a24e8641012103be68489197b4796eba5a9fda1d9c48507e41ba50877092fac5e5b40c99d4ff74ffffffff0110eb6e090000000017a91438aef51eab778dbe5ff2b83ed625988a3658a9aa87000000000100000000010100b71392d2d1c62e494d61c18fc8a57bd017ea36d843c30ff6f2171a962696500100000000feffffff02701700000000000017a91449e4b7656a636bd17bc6ff795475e7ff389a9a5d87b289050000000000160014305a8a4045cba94be567a6630887fe3277d85e9e0247304402206d449b486ace1f302d7068a57f86ffbe5a7606a4d7f4ee83ba4971c29acb8a0902202b79f95f3ffabd06dfec3b5e6cf2ab7695e43f3dec90f095b9566eb6bf80b99e012102f0a27626c7a8a2ac07a8140583777d83c2cc34e5262f58bfb3ba9dbe04c5bd3600000000010000000001012002afbda1b5329379c34a0419c685b13fe0d9c6476d8221e1361df0641c14e70100000000feffffff02701700000000000017a91455f13aea59ad86ac117c560c851f81d6313c38d1879a8d0500000000001600142e70e1444b159329b61d770f834aecb47345f9f60247304402203d4bbdc67608a05a6bcbfc055aa86831fcf17f629c220165537c28ed4b6f35e202207ead5df9010b4c845419ede1b41d89c741b49bfb7c112038eaa04de492f6951201210397b748cb890ab038bfada2eb697ffe764053079a2daf96cc7b0d9fc5d6274b32000000000100000000010132a4c34ad52135b75f8b78c64fb8263b32a59d9e93254211f6e09d3f0b911e0901000000000000000001a29bda600000000017a914a41d6d4cb9db207ad8238773b558999be6816376870248304502210086c59e03c06be5c9638aab7a100d418cda53508a855eadba714892e5918b100702206cdb707923fa3b0e4adac505f190b2ccc41bce702158274ff8d831a7f2679677012103fac8b82306a98a58ac27350c6c9d2f3ae1d1ea915ec2409efcc2f6d7f5f929620000000001000000000101816c3f679b6bbb6ab7bbaa9a14b1943e99e45adee0e8d7e6d616173a3deae0f40100000000feffffff02701700000000000017a91439f038837c566b1188100c9fe2be8881a2ebb5e6877114110000000000160014829d33eb95af9655f0534e6b8c0e5b784ef4579402483045022100e7eb278b95d3d3d96715bf0987e5e210ab67db209a491c525c26a15bf9c977b9022002cc88a0e89651505b2e2c6ff0bb0db40f1265512e0eb9702c07e4ab515d6b68012103d926c43419010d7c8f8aa6b083742a7750260d750945aabc1f5138368f6d3da60000000001000000011a5d39a4ac057ca75ac4ef20d89601eb67db78061224ad2852bb80f064a723f9000000006b48304502210097ee6f45a5958048f0c2d23eb42006077b8fd828999ba426fab8c06dfb7a2c2002200bae333665e37c018407d57cbce75e8fed646a6bf0cece600ad9c26c43606237012103002f7cea99f9799c2dc5e5d63afa12ee845bb57ad07727bd3e6659df3891c2d9ffffffff012bb0dc00000000001976a91490e25c844670f7aa3a7f245f05e602f6bab6ee5188ac000000000100000000010152c115b5739f2666fbebe38de6523cc9438c843f883f92f0a0d5ec8d5a572b730100000000feffffff02701700000000000017a914e8201ea25367514d5bd22f7690b50c07f9f96c7587d254060000000000160014dcabf3391f09efbce91dc4500885c925a0614d9c02483045022100a18e4ecefa7b6aaad14abc1c470c898d5d5bf9ce6fb884043ce2e44bc93f8b0a02201aa3a599e0a04b9777d6ff413245f7c0ff6553e1e029a0b32185e2a22115aa4901210261ef5f2c207f51d73afbae7a9f861d1585e15b03c65c778eacf2f648b092ec8f000000000100000000010112ce53465ca7b9dfe637f4c7440ddfa2c27d3145543b6e3e807016b8d18db1360000000000feffffff01288f980000000000160014b5e90502ecaca6275efc6423356ec82e49034006024730440220186e7cc67ffa5cbd85b8a9d4c2bd0368ce8ea1ca6a31caada4f4be2ecf1e83b202206216e4f59a376e50fc1dc543ad84ad23bd7ed0b821c81af1fd7ca3dc7102ad430121032a7628341c1c5cc4cc61d67076cb8c0c99d54419f78b553ea6b365c3f4c254590000000001000000000102b2737cfe363b9f010ebba3f493922ca8e5a918dd096cf9bd7528039cfdc510ec0600000017160014edb7f5dc2fac35d4b436d284f1cb14ac85bb04c4fdffffffb2737cfe363b9f010ebba3f493922ca8e5a918dd096cf9bd7528039cfdc510ec0500000017160014b8cbcb716e9ba2509de28fa435683f3500802011fdffffff063de53d510000000017a914b055c58362cc29ad413cbe884ab3c6bd66588fcc878ebeaf040000000017a9145cf2ebf2dd67cd71b38c68d9c3d1ef983d84534887b7beaf040000000017a914f66497f8c0f6637c953ce8de64766c08e30bbdf28760beaf040000000017a914c8806516e6fc21240bd64d8f18d64fd54d0431ff878fbeaf040000000017a914b94ef952cb88b067dce958643a37be9eb79b7bbf8774bdaf040000000017a914ac964d33a10e0493c68148c5105692d0bae4f43687024730440220199ac99e178ef33a1c0d02b28c4cfd4c68a5a0a4f9900d45aa464ea5e90fcaf202204ad6489c94effaf2fd873e837504f36d425a008290e16d06f5029ce6ae8b6ea8012102d2891418afaff80abf077458297c7b235765cf3bc148e5857c0c2ac3a4e05a040247304402201fd69c75aaba45e42adb397b1d5aa9ee022ffe062c2288e47f080471592c7a4002201497441aa4f7cb6756c6e28ffc2b3b8e7cbb033778f7d6f529c22a3769533c6301210279e6d95411a35b1bfa4f5713e8a38a8be3b5f7de201919143b712d90497c9d9c0000000001000000000101d5529e857c15d27b1ad3caf993f5559d3cbd6aacc2ae0ab202e2de5fa54e46fc0100000017160014339dc41a6c7e82a3dae2748e7cb4e27eb15b5f79ffffffff024602b603000000001600148195f9e6354c49da683e65ec87881ef04b06c6b73ddc19090000000017a9149591c20e6d1c3499996e195ebe21bd37aabd6d868702483045022100ee176b3ce072ee346a9ba3ae0ea25b68838c870f5425f516b05f313260faf30702203a8c904398de4575c95d61d3997bd69f92126353c0a709f4c01a6b936b23daa6012103be4f0be028d7a192ad0b948dd160b692a1b6583f87d8309c44ac8d44c6cfa7fb00000000010000000001016c8e5fa2acfe169de122cb4a26da8b08ea8d56af7e503e216601d4cdfc87d5db0100000000ffffffff0203b4cc000000000016001438b4626b38db0dcf0dfb2c562934662edd84c277efad2905000000001976a914bc21910356f156816e968559e9a0e9bc64de32f988ac0247304402205547acd2fd4af3c1ac42ebb76d4944f05a6e0c26458eef190cb28c6577e9ccea022055cfb79030722d39b63797a7cf51d200430c6080f061470139d2f802f27fe13a012102f1a819538f077eb62ea9044e5ebedf9065a01e1dc12e05d514ae80fa02b89d3b000000000100000000010109bccbd7d6cabba9c4f876ae5dd01c6d5712770d456ef129effa82a4a2138f940000000017160014582d6c38220d3bf5e3a95075c618bc81062ad60cffffffff02e291b200000000001600142dcb07113ca082e67667eb6fa9425d84c6b460a0d54c12040000000017a91492b2114a672e1207e0af48d7c902cb7e1497e1e78702483045022100a55286bc28734f8b460404f98e67640df9d3e64c3289baacaca91c84ed519cdc02206d239222313e9e218e3a36e7498b58f9dde23dc469855b222854bb95943971f501210250483e0b355f4172c78a64c71a847b755031673e123c35e9cf7b3ae8a7d344f20000000001000000000101221c200225880b9381388c617fe8fcc19ebc78ab5ae3d575649d1c45c783ae240100000000ffffffff027cce5e010000000016001439d216b9fa3a95860a3c64a4f248751461f25eb6853d440c000000001600146a4e82a2a765df1b46ff93c46ce7892ef52831b002483045022100b85a30b0ad94454fcf742fed04fb0fcf1711ba9e1e11622a1430431bb27618ed022045579182291a08618b6fd7f31a930d1ca6f118e9671c7c340967c583cb10a43d0121023184d3723355fb9428d0dfa9b2673891c188deaa63e993b1f0b67b4aaa5d5c4a000000000100000000010107cd1a3029a15a1bc0e747d37b98ec3b164e731d06e2c6c5b152b176bddd2e770000000000ffffffff02187d0600000000001600145366c28197f910fc7ed73254ea40ee5b07046d98a3fb27010000000017a914465049128e61fed689fea491e4af3fff413f7cf187024830450221008aff2950cc35a2a35f7e798b5f7a74d9abf429d1098c14c6bb39d10ec4ffe788022046a5aa81600fc497f0744a3f61a050189f65365957aa655f9baec66464fa1a36012102efed8b0e5c592e7e344ef899952397254f8637bdd48db0af2d3506ea441cda06000000000100000000010156342600c7454a19b19ac6dd4a221d007d6be18fef59fa26cc6663a1623cd1130000000000ffffffff0214bfc200000000001600144de6b182deaab8745b8d02f79a45ad47d50771a9003b58080000000017a914997b1937d6a1bdd4cfd81e2c9f9c29ef0b1558f587024730440220579eb76610c6d1376265a1560735abb332885f7c0cdd1931f39a49e54c4fd4dd02206f36b103e3670d7744fdba01597d6167a6ae24e8fdfd251087cd163ca9a925cf0121036cc82c5c840466bdc9cdf197c5b1a5f91dd659c4786a01d4f2a5383e6925637c000000000100000000010178853c5319037d9b0168853a7825d6d6d3216d8aa6a2f049ce41988a551e16b70000000000ffffffff02931d0900000000001600142071a4d50f5eb916ac97a82cefcf61a59ccae171f87bae000000000017a914c71e349fbc9e684b26e9c6f7fd1f7f6accd16b498702483045022100dd4efa402bc5b9e483999cd63603cef937cf32250a46a25bfafd3a0dd78dc27a0220127855e8a07cd92f87c136992df05ab8722dbfa69f071ff950dba087135f90580121030df35b6ee0916ea7ba4af1ce094657087bcc6e1ecee352df69188e2e69f806bf000000000100000000010164d034cfbe1966d31ac49e9680da771a0f34f7360f968f8bfc2cabd405c71bb10100000000ffffffff02452f210200000000160014ec710e6e9240dcc759fb859e1a7237b884818a0b37284d080000000017a9140c22d2569296fdb4c6c8363768999e63390500f98702483045022100ff3de03d54b9496d8af312fb6ec200edd820f510421fc527af661bb775de994502202d844df288fffef5f1ad519556f3864df7cc1ae1e174e88c4e987846fbb025c0012103a673f67576887cb1157f81e23df9c08a124ab841b7f4f722b5ab75f5ab4aa08b0000000001000000000101c492c2f987977ce32f324d8e290697f1eb7ae83165bbbe27e26379538f68760c0000000017160014df7634a3497dc3648bd77b1a78c063e1e7e55e65ffffffff02cd3b1b040000000017a914213d0c449385347f24c9ec434964b05fa5839b7987f5703001000000001600144053a85a8999d35571251a6d0fbebc100d0f110b0247304402206410ceeee301321b8a6c71e3d39f81da283daf9c978e8ea122b3f4b67f5a18a9022055ccab03441c9ed4d1fa147320a8084bac5e705a0804d60e56c024a6efab7cb401210388874ab00bb4f0b96a44a2d16b4aeb1038b0b0fcc28b63a5666e4603d223c53f000000000100000000010157201cd08add1041cc110e44f6aee705dcea4db3345dd37c2b09926fcff33b480100000000ffffffff0267c51a1000000000160014ca6ad2e139c5bde5114478445eb1b191616917fb06bc3d080000000017a914c2ae34b5ee5588bde85452fcdf4cb866f35c6a8b870247304402203a048a2be934517bbde8df3c163fb8a2dedff7a90a2c39e48182a5e54e29df15022008016e8e83f46a3834c5175860dd82efc427e1ccfca28fadea1440daa5cf6f67012103396e650a725ec474a68b4362ed3f24311443fb7282e7359ec6ea22f9574b05de000000000100000000010183f504c56291a7eb1b68b6063e6834d97a1495acde65db74490ffb4fc3dbef990000000017160014a9d0db6ede2e11e4b91b20bc68a283baa469beedffffffff02101e6001000000001976a9144c4710aae82cdf33a8d71d640fde8c2f12412af888accc0e1500000000001600147c122d26eef578bbd1896e856e6af190cc7000a20247304402207cbf52eb9ab87fec441a7edb427be8bfd421408140544eed3e4bafbbcfecc883022012bb903a967d1b339986453a41746d239ae1cecb6d74848a90536206cb5a448d01210274678e091f37b9ca2139cd408bf1494bcefb981c535b35fc7a9633db54372f5b00000000010000000001012f00bedaf6de394c11848272af652e90f7585fe70989dcad997cfe56a91f0e230000000000ffffffff0216e59206000000001976a9143e528b4bbb5ffbcd2a2032fc9032edcc3e59a22b88ac39270d000000000016001483ed64f7e9e7770c4801ad2b22c5e719162735850247304402204d2f2e038e5fc6483ebfcc3766c9f7750ee402de983fb02439a9591b6af0cbb9022008aa65fcfb93f067bc083caadbd44db802b4b6f2fb30fa7f6e7cda333dece38a0121021947c2bbb3677b5b2fc71a45a00a25b3a6f223165148273a8cd9e1cf6429d02f0000000001000000000101b0c108d5cc35d96f42169ed61d0716c0a1bbf8bfab98c437614487e3dbd276130a00000017160014dcd3e33fcbce349e74ee998ec201825f315f03a0ffffffff02c4d82c040000000017a914e7eb393ea408b748cecef60959bc6035d8983c0587eba0c7010000000016001424dd0e09f7ff2ee5c66c21705092ef9be0c4648e02483045022100b6a0e47de7256d8a58d6fa60bd84ee283d04bbbd8d7f9d00efbad55e9531b45302203138da9708e695ec22515735b938c6ca26053779c2bfffdaf9e8812e18886966012103f4c6b6f86cc7c21e7ddbdca90bbd185d1396c4dd32fb14f6d0404186c0fd3d460000000001000000000101fec823fd122fb306b8fff294990685d255c81cdfbee43178879451dcca68a0780100000000ffffffff022dea030000000000160014c27cc5f157361248e8924a589373f35b17bf47d30fca07010000000017a9148e9bc33b96c1e493af1097ee6de3453ae81063e78702473044022075cbd42d4d32d4c5d36b929705860b3d4e77a23305227daf81908f10f7ccf660022043ba86b4ab2220f6ac21021d0694a6ef22ebf1e7c9db3b4c4a0240bdfcb234b4012102362fb840db2d6d7fc560e74d860df7cf5d93b21b4084289ba39fbad4de440b3d0000000002000000000102855c26a1471ce003c8a0b95a3b948be62b98fb5826d9fc62747beed52db9d7fd0300000017160014bab73926971c01bb3f392b423d0420e77511201fffffffff9ae5f1e73a86924dc28fdc52aafed0d5308eefef577b7790a10dc5598ea073ca0100000017160014402067eb4a2382e5ab435c235db751d41b5f4d57ffffffff021cf768060000000017a914cfe38c3a5b02b947bc7334b3c6c11ea705c56b2587786d0f000000000017a914930472dfe89763754e558b4f69bfa405fa4dfe65870247304402201b6f0462f9edf365f0c5efaab6451b69541f1d2cabb949566f5bb96e92796d95022008d17105aa92b27d77c7d4a7c3cf249f5800fdd1f51c2532e5c30021dddc047401210209a118f38f9ccf0b33f605d8d7d5092a3d944d144bb79246ef4b1c0c3f9420de024730440220405da568dce71c70a3923db5b9b7a24401b5360c00997a282d4fba4a6535a1680220419741fb88e4b0ad67ae334e4a2613f0d8ca14364ae9cee297bbe87b0575f239012103d6124f7ed6b7482012fc8cc85841decc2e451e5486c9ec3ab54b83dbcf69b7e1000000000200000000010118eed8a4f78016c3491111854e092877b50985412255d0c0c2d007f08c1e22c30100000017160014402067eb4a2382e5ab435c235db751d41b5f4d57ffffffff02ba4cd7ff0000000017a914ab3e3df211632a140d99131addc978cd6a331c278762c21a403a00000017a914930472dfe89763754e558b4f69bfa405fa4dfe6587024730440220240731791bdccc47c64b49323f201507b6a77557ec0311498416a3611d46b54e02200c466b45cd7b7ba22657f8e61f06835ecdedb3877d78fc72887e715a1f63d13b012103d6124f7ed6b7482012fc8cc85841decc2e451e5486c9ec3ab54b83dbcf69b7e100000000020000000001012bb8badc17074e19902ca02a6b8f338349c818e464a244a72fd8654188af5d940000000017160014bb607afe3b457e3656857965077be5f5cefeef2bffffffff02deb847330000000017a91416423f80c2def2bb59e81920b28505befa4d5ff1870a3edc050000000017a914930472dfe89763754e558b4f69bfa405fa4dfe658702473044022062b983bf3824a436a99daf12da3079bbdc3682852ff36450694fdb25689278a7022006cb5e33efb74738decc111e4d5ea16a075775a3ac49c30450c5bd4d1db85a89012103996c22f2f29c188ed8ae5d131c6f856a435f60da1a749686dae68da0b734671c0000000002000000000101248be256ce6d887fb24f8ae4f02111e38c4e40bffad1d4a7a4858eefe791cac00100000000fdffffff0220974a030000000017a914af5b3d8b8e2f91732761af2d54cadb61ef0d78fa8759727e890100000016001420c7b72622ffb2ab9f8a0cac6c542e26ce37b38a02483045022100dccaa17ac4e8b56ca5286d26d90b33f05ed916dde5b7a4b4bd356b16bfb7840d02206e342e435e0028bee0ea11173dd3df442ccf93ef8f3de1e6282886fd4470849b012103629b446d6fba0b1f80c35e1dc88daf738b8455b2c6d87f7e4f369d0a2783e23400000000020000000001013057e2df020c9c53ad2752610db711c118aa5d02b92b5a41eabacb19f88f31f605000000171600147886c9a492f87f5815eabc139f9d7a32c88d0b4affffffff034efe5f000000000017a91437eb0069643da86d43624d5b3cc397cdee4a135a87679bc7010000000017a9149d30ec21cf6cfba445e1a657eb2359ad50bbf2eb87f3652e000000000017a9149b358c704c0648eac4249bc8817bc7e6e8f9c129870247304402203167fbe22d6f1618105df9801136d91cca88b0efcae163da3d8b4f1a02c572cd02205179da50c28ce3ddd61b02b3f2d7fe5fb50adabe539bfa4fee6d007918e1aa940121021480f5979f8ab782142fd8f37aa224ea3cd3da7a211a55e734d82ce647a9289f00000000010000000112eeb4a6716649be7de6958dc421117390e1db37f7981e5cae5a83b0780e5f8701000000fc0047304402200efe772767265f878150a22b618a6eb903f378c9c1706c1448161c0903f75a330220332aef25615ce3c7e972dc687be4c56c15a5dd1dc045f86634dd8db3971049c801473044022016a7ea5ad8bea1250204a0db8721100ccaabe145b5b4aaf6a37c197508b9a70f02203c9e0a2f8e0beb97fb3707c01d24bed852c03bae0da338ef710f0dd7e9d5c66b014c6952210263086bb4249a2bfe184b9fb83bdf8dc8b1953be84252a89930d602927d92da5b2102814bb2920b6fc389e3fc975162826843160e4474b0419d08cbcc47b4f95085a621039031a698e7e14601c2afd3b09f5af5c793dd7cc2f5a2cfb1db7835dc6129810753aeffffffff026dddd3000000000017a914811a49741abb8b0d1765e58f3242d7d90b43809287f9b210070000000017a914b09e16a6bdf8eecb8c89ef411cf2a6ec59af6e56877f932200010000000124f74eb16ec6a60886db98d99f7975a65c0f2b060f9483fcd62993abe167276e01000000fdfd00004830450221009a759ce6e5e16f0040f4587951b98311226fdf847c434aa52dfcad06fbe839f602204c7f08f0ddf967dc27cfb36be2a35f882cd7032de5430393776560551699d1a7014730440220256cf40d0418a1db98750213c29275d92001f93ed2b783ecc8862a8dcec315fc02206512998affb985386c1d4bff9c43306fc8fcd3034c29a438706f6f94fd27275e014c69522103b258b6e4c69f48daf59b10aca30ad20ce3f8261931e44b633147974e4a8b845421023275fff4ddcd5e1b2d939ea13e42153495626ac0636462ea0e139284b304f6cd21023b17184c9c46c8ae2cba1a06244977958a65805f7e0c00a0c79e2f9730a3e2de53aeffffffff0259a68b02000000001976a914af0d2f359877fac0279b719fa6348a9c7e6b85d288ac0d061e1e0000000017a914a7dfc7a4fc6af54904b7e42d3d3cca73713bb0cc877f93220001000000019c74663ae7eabed4e151b0ca91b7d90bbc96a81e7b0a5d4b5bbde7b8e969b66101000000fdfd00004830450221009a4d82a2cc03a630e57e39fcd5eb2c1e589403cbd87177585a3c47b593427ffa02206e1853accde8fc2f99c3dfb8bc8d1f031987a7dfc7a3b7597ca055e1c5becb7a0147304402207c199e4e5a4074d6633e96711d43e954bea138ae783b49ab70a73107268630ab02200c8135695aeab36cf050045af1f68333b2d51c9493219dd1237fca84a3ba16af014c695221029f52f1fc633dddd8a915ddc3d462e5c1881c5257520d1f94360ce574063c8d6e210347243d392da82b80e4eab3a4e5dbc2228b64d340cd1c861448193a259dc538212103763eb7b122b018971e7089077ae3e557c564aed6fc77de0368e82a095dad172553aeffffffff029dee02050000000017a9144eb0575876b5723a79662cd334c7b4586e7ac41287f7e8ade20000000017a9146f186e90334b4a6027ae0828ef6f8cde11cbe34b877f93220002000000037efb13231a49c6a554f973abc2778fc3c46930976ce29c77ced6aca86ef8ce35010000006a473044022010a397576a817701a1221e1e5fbc036cc8c8d71021cfcf8e638417b7a623cc8e0220409399e9f04cf752d0c7e2f7fa374e0cb2764b2324123c08dcf7792256001a880121029c245b08c8a89287ba031d6c371e95168e017adcd7a54e3532c07167dc30013fffffffff838c35a3a51044c5e2519cadeb74cfa2861fe33bf122534fcd3f6f11a98441a9130000006a47304402207291bf47e6b5202c40c83a122d409c927d5d724dc961f5ad18b28aa65d2cd3fa02204cd47021b4834f18581be9ae416476b2c189db5310dbe1624c952c995452f0fa0121022b9fa10efdfc970c4042d2863d68fe1416bf017ba7b8f15a40ba1609fb2ff611ffffffffa7cdb65822c6567d2b6d39343369e9c897cfcbdb126803e8b8c8e5e319cbe1721b0000006a473044022050a41f6b55d1d331ac9bbd15190210207979fccae8dfc2aa70635f35106772940220124dcda6fbb8d43a8dc772c403bc6055c4cb0e3305e669a4905cab5800626ef7012102105d4aa50faa9f3f9a164e0d22b5dae7463e7538994f6d594e0ac9b96cff5d2affffffff02f09c8903000000001600141610da42f8b4fb542705bee70c64b21a2ff9438ebab43001000000001976a9146200d5c18af5a2ea8e8fe4c29df3165de5d322db88ac000000000100000001df694ce63cccb5b34a6b2af7e5e72e80c081e2de95ca8e45e11ac6b650288864010000006a473044022068e03e0c2c39e66e12c9f2fec83809af5e57f1f9bdf7521ad6da876b8728ed9c022052d11437a61b28a2143c1558fd169958cadac9929bb25c4064a0ded84d90bf4701210374cfa7fbe55217f1e7db36edfd8c4169c6533a0c803a78b01226bc257a80cd39ffffffff125e2c300d0000000017a91455b4c9f58972a438721a40675f31010192042e1c877d298f080000000017a9146fbc9332c0414034fa889fd565de41dc752c205887b0c7c701000000001976a914254c234a5b99cc2ea22baf24e58c88b90672362488ace002d205000000001600147761c096c237d4853997a35221bbc0bda80c24df0ba072030000000017a91403ec902e84adfe9dbd9d68e24c4180bc407302fc87e05ef2000000000017a914f496df21939cd8e6fbaf3b9ce13f388d987e1b438780d4e1000000000017a9149228304de07db5c83989b9a51e870e574f0f5c508701ac2b060000000017a91462c78fce551318ddbf95d4bd955008dea8325cce87c7af65040000000017a914022cdfd4b429784c6d22cfe74d890c50163b1c568758faf8370000000017a914ca2d4de987bd3ce2bee47cbc5b1aacfbcf55b06d8722ae27330000000017a91405f21479a55e49f00e097b64241c364a2f6244bd87566756120000000017a914373e47a7f32e8d136cdb3323c9baaecaeae169468739d4a32d000000001976a914a2e84a5ba3c5e9ff2bf8c5607e1d7b3e3d6f4a1e88ac08b097000000000017a914aafdab0734c16129ce67a1e5344afb054a7c9ef08740ea70000000000017a9143be7826042dfb52a8703c6917c1704006f27ec0687c0018608000000001976a914cc693e7dc90298f814b401d6007591e909f2e1a588ac80030c11000000001600143c1e3571ed9f7a062cdba0abdcd846e1fcba608acf327a01000000001976a91499b79eea83499802d0b60c4e9b7b6df9903e4ece88ac0000000001000000000101c2927eb03453592ab7bce0af3c04ef80dfceca46fd02b72d8852b16335fe3dce060000001716001483221177a8a7b07184024df1c2813ae9bb95c9e6ffffffff03610007000000000017a9140560ed1d2fc9f555ce6a158722628e24f513e57e8721cc30000000000017a91445d723f72cf00c07cac3c90f621ddca09af79cfc87ad5884020000000017a914a0dfe38d84b4329a2adeb4978d9919c020352f2b8702483045022100cd4eef09040d0ca16d997ee7d9df7f4799bc8bf920fcd1704e7c9ca2acd5cbe60220142410de3443d33073f76d2ff13fd6c1fd3f56e73ae944f427da4d57bac6b149012102b046303ae34befbf993e4ed7a6e26589a4a502d9e7ab093568854fed578f9cae0000000001000000019a1f85cfcac8d916443aeb7bd8ceb76e51ad2f50c88ade5a00eada180ca9a56e06000000fdfd0000483045022100cf9c4ada999119e8284ee3d95d28117f1ac477d09c2098ef646205479003c32002205b4afa2104fd6179ee9cfc3f3a05c091fe1785fc1ac44d946cef8f2736616ab70147304402200cc59b9b85b3b05e4bb6b3e00d8350975559ef9dda9ff0627e0a8e1e9fc2d0bc0220427fc25c4d4eaac680d7ce79a99fc599fde82b93b4a98ee7cd925cb48b47fccf014c695221029da8a8278a20dc1b334cdcca58ad2b50e7d722b911fc903639f788714a26486a2102247f84368444b165883063d283c32ba72500a796dc7fac496fa823737f9bd44d21037d86562c35a7064911b6620c99b89538c229726a1ccdc5ba9e0ec015cbfb007c53aeffffffff015a8f300c0000000017a9148b5b26127e132bf460aa6e2812e52727965a1b9c870000000002000000010c0e27319bfca2b628ce467cb6f865541b98e8ca02bd6f4d59123254b1ab5b86010000006a4730440220035a4b3ce3631b5bd17df495a9547b7497cecba3778d0154eeca5cc6aa5ec39b0220794f53c134eaabf8f0ac71c46d1319640c928c4e1091f2f7990dea1c24e5f60a01210250d70c3a4c784926e417ba26d79daaff2ca16e6412e3cd00e89a7f0ac3631b2cfdffffff02d04628020000000017a914219c58662d28a6746e2b3d5081a85252c81b94768791753832000000001976a9145c8e270ceac52e33eb6382ed54e6453fef51293b88ac7f932200020000000008049b40c1082041a5a72b62e895d0b2d9784cb346f2cf5e792be82206089d7bb5690000000000ffffffff26a0e6d34453b790dd2adb744b044850698d293b6a4f908e836ee1a87b84ceb90000000000ffffffffc43b93748a4d43c6766cc2d09da86c6ffa5479ac6d56ec2bb1cdaca789857bdd0000000000ffffffff2ad4dbca755ba2fc4a3b07cd6314f8bfabf20e42837eed6536bdb07dd75e8c920000000000ffffffff0153c5d3230200000022582030e8a2e9fe715e7c607cd711cb683d25388db2d4db6fa94aaea35d194b5953700000000000018089a601dde19fbbfceb2d12535fd5c92fb310abdc84ec651ad28a93478c80253014d5fc8638aef067cd30005c5a3f85de20255097999c7fe8c007d6ac4f08c6bc9231fd99d44d377bc5936d8cb7f5df90713d84c7587739b4724d3d2f9af1ee0e4c8efdf8a94c134f8cbaeb68bce01e809247a798abe8764b5eb1df40d0c93ce37a3aed6a6c23e4d60ba17aee4108166b68db58e3e8090de34f79d2788e3bf2b994ffa108030006086be91f9b14e652593005964d3689cc9f2a8509437f2e9e978d52c34c3c5eeee002624889a6ade4461bf730c6bbf577a87e5c2e2f9f30e49a67fa2f3206eaaa4c2b03e71c47748476551209d8eaaf3eceaef752fc4a195718cfcc873bfbc30fbf77350103c2f5eb94815d8aad30a40770c7c4adfec6611427818c59479af23255f21e6b3eb6759ae833fa393133a8f8120b78cc725690e656fd858031aa2926faf55df7334a472543d2e073a3d182e1f414dc58169bd39c41c79ad0b2e7ec2f11e20d8f5553bc17240b6501ac028e632fa84843cb653f283f867e6d7a400a4e477410742591958b5120e04e6b58b1951950e3396fabc5587fa8986259e3aef3c71520434bf20529f9d62e38ad7e37ff775dc4e8a86e98693c029d9f342bb3c0bed2f9ff6bbfc8bfdeb68d74a7fb331a52c8578627d8e8327d7032b5380ee073c37d3d296cdc12cc488c7c5b896e49eb70ace3182c79b905c969196515a47c634f81e3583c101b0ababb159b7fc69c6261d4ca22ed1a23fe56220e8bd31fc4fb334fc6cbc17f2deaaeab3a1be22c67e8eac0a05684d440faeee166bfd9ed4cf94f0af36b2ee457fe2981d2198aa8755daa485951fe6d1688abdfadfdf03d309ec42946173757390cd596c37143f3c6274905fd05551ba65150da2db7a17294b58fb98d36db506309d215e0a132ee7fab9925e0104157986a94f3e3b1ee642eed0334168430e4741e2e5be5aa31cd0ab5fa7dea1d2fa1b2a87584df2a69526af265b2abcbf65c9506d47fad0d6886aac1fb1724461ba33c5c7fc62d8cd9aaa3821b338ef9c9a8d37cd18432559bca932bcc87a1430edc84a8dad78d6d5df14d6ea56592ebadb0a44d439fdbf2134bbde386cfd474224d0de18e38f1b450b6cd520997344066866173706259bf9e88c9e7a1325e84e891dd5b4dc43275e3909d4f926aac582365b5b55e2eaebad07029dc97b7139fcd6ce35dddc31d8c4cdb3470c9f3ad92a7115948fdb8c8f967aeb5c6cb7fa9e8fe5f0d71faf919210c80065828a7f743fb8fb08b3baa3cfb54f78f5d53c6f9c0e27aebbbfab984f28db2905a29b699a8146e38c776d825c90105b2915cb02720638eac59efbdff7ef9caad1d4ff1ac9a10ddf63af56a68eb0e71136416d7a92a32d4da83aae52bd2a3d905704169856426d85fdec200ec4f3fafb586a6ae7c32b5e94fcfe5062c934bc93a2cdfc437d83921fd5218b9662fba9cf49f8d9fdcc7d83babb9a19d07130fe56d16bb08c971cacfc10258b1844dbb1543803afcc44d11e0ba727a576c65680703eb27d703549568ad112281d420e787f6e36b733c9f4dd6f8ff3b82a8241f84c623e0bd3e02f5e63b93b2a9c4d4b73a970fd569145253fa6f0ea4899205363175a4325eac2e0103362f93759cd6a850a1b5afaa67819d05ff9bf9d273eb58a591cac978c440543be9d3a5be3bc7292d0c199d6e5b0d5a68214ed76860e8759599cb7c7ba495e8444f2e65545531be23fea2bd6071e3c8b0a686c711d016ada422298b961a07f6b61639edf281beae77aee0f9252e8df3013082cab16529e0614b008875e3b38a9d0aece13cf7ed0e47043820304bdba0ec06a72724328e70a6724392fdb1ff7ddf6e58c5ae277a623a6e47773787e6d35e2f9bce8de99fad9ddf42b6b41232a6ad0d11904df3ed51368b530d08920659c93a451ce6c9e67906220e9709c4b991688d54bcf713cb15d2120509b7973cddbe6eb8062e8528763b12bc39a7c1d79e9e3edf6fb001d792923baaadf5db40649ae445423162ffeaaaf9069ced11359f9525a2487493443eeb67140898b00c958fb76d71f000e2ccc3955f5ececb6a9949252e8e7f1278255ed20a0e3ebda4e0bb36a078f2c901b62a23428f86dc62e4dc222716098ee2dc03dec78321acb4cb4f3efb3c76305363087e195ddd7d49303cbc9ea651b9bbf083e2e189e6c39fd8e84de7624d60042aed727c0503d1f64f4a7caa517e2865b9c770f04a63b7629416ba1c342763042e4164b9248213116ab671bf119f8c9a5665e5e2f97f60bf90a17ad49f7e605a0a7df95e4a82f842d6dc9dab1478603aadb44ac486a4a934129084c05e7c5711f49751e81eb2faea77e07ed57e654cb139b067960e302626e977ac684b88e100ef8cb3a9d8e2f2ee795ec99ee4e9c79f50ff7d3b8f62cb66ba9e123ce619bdb1568bdbfd7c61c330950c2561ac683e854b58fdf5a68ff8a9a937fc034eacc3c9030fdd7131eed7c0f147ed688c8e8b7bb38e5a744a9c6de70512febb4f97ab1816608901b6005bddb05ebeebe1b40a22c4cd80943833797ca3cb552e2f41cd0b0ec7460359ca451105aec94e3c230ecf68d067e89c93c5f7133dbcaf6da0a1c5c725e647fd14166683514bf5a3c090c2651523591c34e53bd4decf6ca460458b16c959d4a38574ab31949ab6a4a123422819fa7c9d18af2e6255eebcc7dacb8368e09433d724350a99a7b4a936f0f2a3a26e714cf2c6b1537f42a872fec8a71d855a461d41fb089d8d6dcac12cb35d7e73b709227649c6adc6fc6584ffbf318e6bdb49d0c90e4703004980d71a338ef0073ac6a9a229ae8ae264d4698054735c607bb61e5ff24288035452eec608493438c1486931524556b99a2c86918653b7ba5829df141f9e9be90102923b3f4d5af3c1d4204dce4e4d1325f6b2053835d2ea897638c5ddb7c229dbb67faa10870be87a1d00c58e95108a3ae668decd5152fe72451af5b075b9e9b346253fa308234e51c2c3b30310e04ab68ef3746eedcda83e42361ea3abd3f2203bae283a0f47c357b143cf4a8ab2f599cdecd268eed15cb5855200fb5a095d30a8eed6c86a3a7708ca0ed167686b92e1b9db307b5d4a03daefae47725c67a1e5ce392991c894b4df0a101e370b84d87f8c2bbc83f8a68bf9958a923f4aa4d2ca9f826d0e98be800f3fed41e052cd46f8ac13f1e39c0cce1489d4730e521402db25b275ced1941d576de6d447565b0c39ff7b9a9092c6746aa09bff443070c60638ad684539290e290e3d5448a20766188c31bb37248590c370dea79cb6ff66784d096788fa7b68b52f11cdc58ad1c746e7604a3525fcf23d6bb176c2dc71b382826d3397c6e2c6e252b977ea3c4901c36d4df5371029d854297b6e98041ba2e13a871347aea588341ac123d6f8030e49f77b451cf4b3917ba7877f0b5eae9a53921b44b91b8fd4e671ae13455a71482c53e8ee2c44303189ef60e73401bed5f4fb87e69117a93cefbe44e56c32ae377aeaa3f2c8479116c3ef8d015c1bc0a2ebc45a47eabbd906bf92e59777924e0dc599d316ee7ca8f7bcd7b084f6910f1782cf036fb603c70d55abdea6d0695b15774f00a12d308c6cd1ce082ac20589a50aa524641a25997b36df0ccf785dbe89e6b285ab1469d2e0a0de6b2b2dc65bee9e5541df600a700778b4adca359d6ab8d132b7703dccfe0e8ab916159472e41f8cda24d29f5077585be3849ac25fe6680851dc2ce11abcbe2950e24215f603dac0a0114a5bc852573cd9df2269d5a80105cac5dd89164a21c4e49c16c1c015bce6767c5cdc9dba900063351169b3625fb59b086403ac6adc446edf0ffdacc1a5f91c95985e5d509af8ba45dcb67ca32e93b0c3e6ca411c81cce3f93daad3a560efa9f8a6f4320d358510482b2e0104788a1b6e59f17f97d7268b2cadee361e3715a7b69176418a8311d080201aff32aadb95387461940ff5031f1d72571368005809c83a6ea2e4529d318757b4b21908783aa6397915155794f36203fde0086301eae68568378116ca8339297d9974798594a1a88b78ef42d22332080240c502b0ad9adf7a379fcb5d371c75b47dc926749bd85f38bf3476fd5fd77c7aa5cf750290e4b6e579e69ca635e002923f8d8d5f9c25dc8af92eead28ac8d4d05d18bda901027444f2aa0f6f932133d275dde10e4bfecfce796b2691670c4bff1281690780ddd1472449fdca9d413a636e9f21f7916de6a45be59d85087fedb8f924f57c83719bc07978a7e9020bcb50d965137e1fed05d7e9ab59c70e1d20427a4a62bcbcdc8d295ad0af3d0f720083ec4b4b36c6f66f07338d5a9d70ff9106d15d68d1016ae95fdd0a190101bfc80ac95a837fd1e659ec6ef8fa2d518cd25425bcfc683ab04f4df24a0a177d68f06638e6a5a66010b142c486c7740f34cb2896fb1f38210dda1d6cc55150537c391ff9729df5cdf9a747e7c32a77fd07a5978046a51e0c34596ca3e28b1fd4abd8c8fd7e2dc861c20c8404291fcad31aa90cc6ce0cff3c7f52974f72a71c105feedc738ee67e4cb69e3bd67cbd8b7b3a0ecd9affddab274153803e4eb3ea9b6c44d202094f0b034ff91cd6a2236b3f911ef59f1921e7777ca620a39dd937d2ee07ece7cfb78abfe2f1f78593502ad674d378efd4a5353f4fce04620062381678d5bc17cf14b713e0f2890466057b942075b28d0759387d087134cd33f8c45c68a88bca5737d6019511f0a983a985b4a17289bd00a7ee06dcf358c1310e45b23661efd2a2c037a85be67e381e8d944e1bab099b5ce6e66b529cc150dde23e0904b2c51519f497fa3319aef6a6395830660a046a94f8fa4d78d986f507a8b4dee4dca7fea791fbaced8b5de1c5f422220ec7c8d0e381faf308951adcd8c234ae6c58ad760eec8878e6f0acb40b008029b030053ab9412799978355764f51fa7f41db8b01b4f93397ffb8c81197dce1a2b1704e950e31690de1e81de1f1860fa8d96b2c904ae30c69f700375e9c1177a1cad14d018d97df9384b4aa9a481c399c44e8675640ab94e9aa47030250425a20232600b4a544a82b5952cd75796457fe0b2398a61d6f8417349a7b5a4723a4f291b0b3de958d924519afda221238e61837081a9b8398ebefff8fd011b1034a942fe077a62658ad17e9136e3e65a9eb54b0e4f038072665eee37981e4d715f183b6560c7e2302c6b6b9e8951119288bbbf665137d073925435d4534e27e44998eca63731f8a46b004679629ba335a62da24aadf5063edc7eca0ab08c9f2b5361a1ca12776e908b1be4c516ba0136ef1a3049444bd445d9c1f9285136af825f04e7b4f24adb268020ba9eabc6e7c6744d977c112cc40cfa5d8aeb1674afdeaf43b64491e36e88a9f02f8750af0e57b1c2922faa3e2081d2fe74ee4d78380adf9d367ef00374a6b3d8d010302c5be8c64a36a3abf84d07e6efd069e26c27e8204b2ec15d3e8d6a85f4b3091e649f114dcc9aa57ece84d2cdd92ae076a8b42adc0043eecd301a345b4018c60fa1fe902401e6d13f18d768246fc4e2cf1c8c53105fe8f8232f03ea90b4403ff12bdce4ed1f66d0c5b71e62d56372bb558d23f7554517ae684074bec3fa96635a2b848ad5953f083e9e5324a6599f53c0edc8119b02cf59eddfc209b0e2d6e228c7b983b8e8fdfe3263cbfd5efda095b407f15271bca7cec0b431aa990a3e4e5b1a07572d7666dbfc71085bc7fe0845ba277657d98fe275641bbea2925182ce61a4f06000000c6868ff744a91ae1c85c437e30f172961416cc948f902e9beb90a4b21cac645327eb7aaf39b6088485a9f8a18fe84f7a2d4a941c90d2eab4a07f0f5a91381e3ade0df23e543fbfc169fabd8d05687cc380e7f2c9dd30de0a4fefef350c87ecef76a7be623e5f083936d5e81552e04d327353fe38254aad99ef6152356ec18f7b8505682a3a29bf0fd90ea3dd8924515c50b19b984d9dbf60e15c24b1631ab873fa56fbf7e8c95a2185c19ab54c3d505fce9b60c20c00ce2117d5e95bf59e761ebbb323cf1ab7e24b52742020bb27c6be5d4c3cc0b468ac3bead7d4057228af3f7544be5336cad3f6d01e2518cacc3e828c1eba8a89bc3d58e5537eea722274fde0e95d312f6639c03b8dc00674293d4b1f136cef0385ea2b851996bf1e944ef49d4667f82cb6010ea6cf636a6d33af4e7d7ee3cb4a3ec01206219671de083d77988f90c4f65839fb0005504132f351745b7074d9d3f8ab1b356eec2cbdcebe25947a105371db19e178e9fea5ab60b3b4ce8c78cf4e5f64b22afececb5b3687914e333bfc74a7b6daa4eb17503467a6cb44e32e6919d8f7968e97985032a5daaa47bb112122707888c18f1d277966ff9fb2dcb04f4880d11507fcf883adf31009dc35b975cd37d122d0dc5fd7823ce87a5f897243e60be939db47b733b7dcff65969b359b7ffcdc81e5cfc26f8620148279e8e061aed55c3331181914b09319e6095c4d1987a400fb6ef370e219057a343708c44101d5ceb50c63796872d593970d2a2501771f200bf8cc5a63e1a9371df4f70ec2d3b209c0186969e1cbff1b07b45437e13cfec1918ecb8e515f4de34f44ec6afd24167a03ec1ca0132bebe69968dc046e0dd2af8737294206553c8d298c01b28028f104b703f5434d7b30e76f676eb931657dc307e7d95abea451b109b1c5e0a3add0f1aa260102f576ccb3a67ce5b7a50b4779a4042f62ee4b6cbb34675855306efda986b0c1fc21445181b362f85d8d9b0de110e97ee1e4d4cd1886e8a46aa90c65cf3ca424e8bfabec2153dd525a3bdb09b29b0f1d245414ff437dcc56257e1516111956f2fa1d13547d6e70477681a3934bce49f76c49f9ba707dd1b2d4370254b07d08ee6faa958d85ec4ee244f1eed225a6cc2113574630424ef9790d04df93b657245a27257e0657ab342bd64c369484e181eed232e1ef2f6ec2694e081ebd15d459d0834ee3a5ea82032521d6ec5f977abe0ce8e1fe801ff62ae670ee7f3476295ae2e92f9dc80f4f7422e6c1ea6507be5bf7c0eae94f6171de6edadfb082904669ab337a56dde3eafe9598b881e0209d7c63b6970b15ec403f12e3b8595533be00ad64c596177bde803324755ed172f66b2900a7f10c7364895fcbb117fa84a2875acd63cead1dc06cfaab8b8f652f16a658793f5413655fe316e8184b061eabf82ae0cdd3cdc93df0998d35665b89c4187076ff720d14ddf773a8043c62e86f279fc46dffab7e1a7bdce65ff41fa132e45acb37e1f618b14a27f4f40a4a035103d118fd8bc6e90f140cc69a1a7580465b58e293c4b3ac171d62005f0973c2a7d0b4a358e69b605b5fa802642b1282008d28be17225fe41b90da22a451b5b7d7b3d82e3cf2e8f1c21512e0f6485cde1eaed06932f4711eb33516be6628e3080922f7ea6e9284ea5aa1428b8d8a18aae0a8ce86efe0d71dc76710d71ad78f8e2bf33a5be5214fad5b84fe44fd66482d6166aac8c31589bd39aca8481105b7dab737e340b2fd492b64364757fed15964bb845ff0fed2716cfa1048fe3f344710e4cb8112dff9bb79a64173bdaff8098a1fa2c66638c4bbdce07aa29966017a6498c0a5f15c2b9e50dc4d8ee06b0b937492723cde52329db097ff13efb219880daf6560b968d195ded16c5cb674d616e91270864e1b9dd4414c42d763df8164438b5bc7e687fe38ef5c8c62a874a6304610f4e69b6435c04348a7b1eaefbca7321fed554e52cf2ba527dc2ac14ebe4481f77646e3712a6e11f724fa8d2c521689300048d03d81498c9310b04b88434383f514ee82aeac6ee20b3e047b582ba7ca03139d3c95cafc974203ae5f5876829bbfb64b629e8790ffae8068c013aab9fd0c1843ec43aff0d57bae0939a00f9e45d9344c8f29cf541080b51c66363483a0656e6ec4483720620df012a811dcd87463b5451803285422152134cf4232f3843c4de131d4f81dd962e9390980c7701fe4bf2240f3ec6eea744190365e11516a3390a63ed38d56207edd60139d3c8a9483915c02606f9206dad2495fd87596b039937f57afa8e7ef440e152783dfab4483199b46081b144aec655a54a16e08425d32d500e1f2bcdecc1932cb08b6e29d21f2666f86a62b1b7463ae1733afb9a2bb5c482640346b64c841534b9292e61bb6919dfe1c05787b69aefbe90471ffe3fe77e7ba3a2364d3d587bb1ac014668e91512708bb139d3c8180b243031b6a50ec07d6fcd446a6e9b23cdcaae429782c2c3c6eaff12e82fac00cfbb79f081d13d51cdd2741fa88ccd015a919a4bd2ca444dad5014f4bf1fd707f0a5c5f910a53fc6e1803131a2e15f8393d9e7c6783cfa7127e56a71a87a069733eb383cf8f1be029525c55469a2218f024f2a0ef1d8ffa4e3a16df8b76295f74eed24868" +} From 2b5a43eb61abf9c48c9ad72ef2d069ab19aac098 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 18 Oct 2022 17:39:55 +0000 Subject: [PATCH 117/386] fix pycodestyle --- electrumx/lib/tx.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/electrumx/lib/tx.py b/electrumx/lib/tx.py index 1281605..cdb2525 100644 --- a/electrumx/lib/tx.py +++ b/electrumx/lib/tx.py @@ -45,7 +45,9 @@ class SkipTxDeserialize(Exception): - '''Exception used to indicate transactions that should be skipped on account of certain deserialization issues.''' + '''Exception used to indicate transactions that should be skipped + on account of certain deserialization issues. + ''' @dataclass @@ -312,16 +314,16 @@ def read_tx_and_vsize(self): class DeserializerLitecoin(DeserializerSegWit): '''Class representing Litecoin transactions, which may have the MW flag set. - + This handles regular and segwit Litecoin transactions, with special handling for a limited set of MW transactions. All transactions in a block are parsed correctly without error, however certain MW transactions in mempool cannot be parsed and will raise a SkipTxDeserialize exception. - + When litecoind is run with the latest/default RPC serialization version (2), a SkipTxDeserialize exception will be raised for any transaction that has the MW flag bit set (0x8) AND has a non-null mwtx section. - + When litecoind is run with -rpcserialversion=1, only pure MW-only transactions that have no regular inputs or outputs will raise this exception. This is a workaround for a bug in v0.21.2.1 that lists these @@ -366,9 +368,9 @@ def _read_tx_parts(self): orig_ser += self.binary[start:self.cursor] base_size = self.cursor - start - + # https://github.com/litecoin-project/litecoin/blob/948e6257aec15b52ef68b4e1ee9d73f7c740fae3/src/primitives/transaction.h#L299 - if flag & 1: # witness flag + if flag & 1: # witness flag witness = self._read_witness(len(inputs)) else: # A MW tx is allowed to not have the witness bit of the flag byte @@ -377,7 +379,7 @@ def _read_tx_parts(self): # we should probably not just discard. witness = [] - if flag & 8: # MWEB flag + if flag & 8: # MWEB flag # If this transaction is in the main block, not the MW extension # block (MWEB), this should be the HogEx/integration transaction, # which has no mweb tx, just a single zero byte. From ea9fe20c62d461ca0d475c5c7ce66a60e5000cfd Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 18 Oct 2022 17:50:55 +0000 Subject: [PATCH 118/386] fix pycodestyle and docs --- docs/protocol-methods.rst | 2 +- electrumx/server/session.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/protocol-methods.rst b/docs/protocol-methods.rst index 3151ac0..29d6e8b 100644 --- a/docs/protocol-methods.rst +++ b/docs/protocol-methods.rst @@ -1254,7 +1254,7 @@ Name methods (Namecoin and compatible coins) blockchain.name.get_value_proof -============================= +=============================== Returns a name resolution proof, suitable for low-latency (single round-trip) resolution. diff --git a/electrumx/server/session.py b/electrumx/server/session.py index 562726d..b38c369 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -1855,7 +1855,9 @@ async def name_get_value_proof(self, scripthash, cp_height=0): txid = update['tx_hash'] height = update['height'] - if self.coin.NAME_EXPIRATION is not None and prev_height is not None and height < prev_height - self.coin.NAME_EXPIRATION: + if (self.coin.NAME_EXPIRATION is not None + and prev_height is not None + and height < prev_height - self.coin.NAME_EXPIRATION): break tx = await(self.transaction_get(txid)) From 4f5ee39dc2c96e7babbe48e83d64571962aff248 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 18 Oct 2022 17:59:11 +0000 Subject: [PATCH 119/386] setup.py: de-dupe version number, and expose `__version__` in module --- docs/release_process.txt | 1 - electrumx/__init__.py | 5 +++-- setup.py | 18 +++++++++++++++++- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/docs/release_process.txt b/docs/release_process.txt index 792d830..52c8a52 100644 --- a/docs/release_process.txt +++ b/docs/release_process.txt @@ -3,7 +3,6 @@ // 2. Update version number, in: // - docs/conf.py // - electrumx/__init__.py -// - setup.py // 3. Tag $ git tag -s VERSION -m "VERSION" diff --git a/electrumx/__init__.py b/electrumx/__init__.py index 5a4a6cb..88f1ae8 100644 --- a/electrumx/__init__.py +++ b/electrumx/__init__.py @@ -1,5 +1,6 @@ -version = 'ElectrumX 1.16.0' -version_short = version.split()[-1] +__version__ = "1.16.0" +version = f'ElectrumX {__version__}' +version_short = __version__ from electrumx.server.controller import Controller from electrumx.server.env import Env diff --git a/setup.py b/setup.py index bca35c2..afba5d9 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,21 @@ +import os +import re + import setuptools -version = '1.16.0' + + +def find_version(): + tld = os.path.abspath(os.path.dirname(__file__)) + filename = os.path.join(tld, 'electrumx', '__init__.py') + with open(filename) as f: + text = f.read() + match = re.search(r"^__version__ = \"(.*)\"$", text, re.MULTILINE) + if not match: + raise RuntimeError('cannot find version') + return match.group(1) + + +version = find_version() with open('requirements.txt') as f: requirements = f.read().splitlines() From 934441ab35bfc525961a4a6d723a9b204196631d Mon Sep 17 00:00:00 2001 From: cipig <32116761+cipig@users.noreply.github.com> Date: Tue, 18 Oct 2022 22:53:59 +0000 Subject: [PATCH 120/386] add LBRY Credits (LBC) (#186) --- electrumx/lib/coins.py | 20 ++++++ tests/blocks/lbry_mainnet_1000000.json | 89 ++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 tests/blocks/lbry_mainnet_1000000.json diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index b5c182f..c305d9e 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -4027,3 +4027,23 @@ class Syscoin(AuxPowMixin, Coin): RPC_PORT = 8370 REORG_LIMIT = 2000 CHUNK_SIZE = 360 + + +class Lbry(Coin): + NAME = "Lbry" + SHORTNAME = "LBC" + NET = "mainnet" + XPUB_VERBYTES = bytes.fromhex("019C354f") + XPRV_VERBYTES = bytes.fromhex("019C3118") + P2PKH_VERBYTE = bytes.fromhex("55") + P2SH_VERBYTES = (bytes.fromhex("7a"),) + WIF_BYTE = bytes.fromhex("1c") + GENESIS_HASH = ('9c89283ba0f3227f6c03b70216b9f665' + 'f0118d5e0fa729cedf4fb34d6a34f463') + DESERIALIZER = lib_tx.DeserializerSegWit + BASIC_HEADER_SIZE = 112 + TX_COUNT = 50792939 + TX_COUNT_HEIGHT = 1178612 + TX_PER_BLOCK = 43 + RPC_PORT = 9245 + REORG_LIMIT = 5000 diff --git a/tests/blocks/lbry_mainnet_1000000.json b/tests/blocks/lbry_mainnet_1000000.json new file mode 100644 index 0000000..049fec2 --- /dev/null +++ b/tests/blocks/lbry_mainnet_1000000.json @@ -0,0 +1,89 @@ +{ + "hash": "066d89a69aab2c1e3cb805aa4b2d1526d54758db8b657fa6ce22240afddb80fc", + "size": 27483, + "height": 1000000, + "merkleroot": "57d5e9ee479f8568aa93fd438cbcffb742a2c9b9346fcd4df821d7987f60b00d", + "tx": [ + "d5354e79eca345a22d930c412e8d05543dafc202c57158d6715260f5924a17f0", + "5a5230c56a1b7f7fc826563ceb0f31d1bf1d129c8a258f563d4deb18d2573efa", + "c0796e5a56e2eb05b15c9e2ba8b66e1e04ee0aef6f0286278766a11b5def5bf5", + "8cdcaab6459af2c02a4f7e98a02709ae7221b5db75effce56c74d311e48aed83", + "79fda5a3fd134ccf50684b29c87c70ac7e7b31c29d33b0c9b7456373f89f791a", + "ab7d8b23f47ae933f4e5196ae66ac560396cf44eed0db53259e0ad56cda4368d", + "4cda10b49dc9c6fed9802899bd0506bfc9e9ae8ecf3db81b3838fb69a409d469", + "6fee7fb5de7825b73b7963e4a5deb2b2c1ed0d3ea219cd0e024f95aec3724b78", + "1da6b2213ae6d51097011606d22801804d1b2c01700e401f2d1ef1c7a1a325c3", + "4ee0da8d57abce6b5c660444b36e7446a6df322538ab70027543c9104660bf8d", + "d93c1505a8de7471fcee23be920b6bf97a065a1c6f7ee6afbd46afc21c75f086", + "9066179a6e67c2ae6a4e52d8808f7d1dc434e1f93455006db3ab3b762e19ba2c", + "bf3de9020a58f0f512bfa783459d53a26d670eedccc9cf49186d210f5e69db65", + "d686202c57bbcb353d0e349939506fd181206843b3be84e0c5e64bde79faa41a", + "a902f1b4335a7d4aab808c7ad026e5130c8bac91ec32182bd02c9331ab7401eb", + "5d56e977edc9b4499809cd34f619fb0be756409eb3c683da2681d3f4d9a216f1", + "729e689a468da941e9400118769e76e80f673801d78547fdb8ec7eaf5e83596e", + "46de6c6bcf7a711145628af266951bb4573e756b066f932c976bd33fa3b771b5", + "b30b5a1994830915eb6f7b30bbcbde8f0453ecbca891db8dbf1c90187ddd4c90", + "4832d5265ab3bb18291c47b62d8dbbcd7df624dbf3214ef2f39f293167a1b601", + "0b30426d55ef689c82092aca12639201452ec56624cf7e9b6d15dfd6b89a5a03", + "9f38a9947e8752348a41c3bc227f20dcd85f001da03f76cfa8f59e2455a2cd05", + "84b266ca47d43214898cb43ce98cab983b5113dbed9d4b0630aa58cf482e440a", + "796ff80b92891145ce87e3d2d65e91aebb7f29348896798f5bbce6263f26620a", + "b4adb6d8a96110c4b6f68b281c737f67e809b9422f10d9284ba1cd5f2c066312", + "96ca7c4bb95442fa88fb6d1073ba7f9828abc7de8422120a2e9868e19fbdc524", + "40111a20286ebe8849150be8bc78fa48b2cda8a861a3de9502a90d4f5401cd28", + "27a868be26fa10fa77ed3bb40ff9fd8a838d58a88319fb003edae3fa6a37042b", + "b06ff0d183f6b525756b86dd66572f9a8a1a317eef7e303274b505d84fd23f2d", + "df9d73aac50d77e05ddc27a967b5c0ea480de9af4ca90e182cb02a695d8c1a2e", + "c2e519302af6cea3b85cc664ae77918d07a910332ab1361ce70e7e5448515f35", + "9f01b1da6010333c16f9107f1ca44b36a88176a5398bb288ae4bc7fac3477935", + "9f09f50f06b04028cf1de9aa304bcc4c26c0158a3883264be6acdf76f1dd4642", + "3d6a3924b113126c0ed6f925ff990a2466a70fda3d93a4592454118283476f4b", + "b5f175db99778fa074024f312fdee79ee29d78ac8ce22a274b155ad8808fbd4f", + "38b1d79e0d2d5f91b324f0ac8902060cfa991548263400fdc56ce03090913c5e", + "ab11df7fc16ea7a951ea2820723879b5d1888f37b61b3744d064e46196bae160", + "026d0b8cf55954cfd2dd3c1b947cbe2c8411f6a1ba18423099236a090c5e0b67", + "d993a0deb9b2265b7e2cfca255abf6d2bfe33082a9cd41045ee4516e9de13377", + "a0ead0f98c4cf399b4194fd2aa541f299efb50dbfa20a43bf6ebb08271eee277", + "cfda399533fe2c847c18b274c6f3ea746f6c89ccd73ee10e3f304ccd4ead4279", + "1fbaa6419eb09871a69979bd1042cd2ebc9a2d2f194a0b49c71ad14b6354047c", + "0a2efce88ab7e4b82f6974ee4ecd5d0fdfabc7715c3772157a6a4a0d44031d83", + "5799a3866363c604ec7b239a2690d87cacf4bf31268be4d017bc2a6c93ef4086", + "a1d520dfc5d40dcf77d62aae61e5b87fb26cd11845ef23612980f80012fca28b", + "3e79b63252fc61182a903639e001b73d1db14dc8581e190bd56590a4ab7fd18b", + "07b40d457c3d4142a6022bd235d963e7a4d745fe7e13b0fab330c27aba3d328d", + "2cec1c0eecda5d25a91b95c2afc9ac9f24db9d3d750d9e1904dc17fd9b7f7893", + "2da968ddb89ee76c98f4a5aa21cc30465d3360762ad2a821f744b23837a4e593", + "1043a9d4d245d322e318931c6d1f2b30acc4e3a162d30fc78dbacb35b1f36c96", + "b4633499f4cd298a1abe612840699d7e77691abc73f9b90a55877ba75e037c9f", + "dbfe4e3d4dd59bfbf2e173ad2231d99f0f152c66aaf4be40a2f763715662a9a6", + "690d34f069b83198df989909891a9a8c1230cf59cf819289738c012d20f627aa", + "aa92a138430ae69e65df387721f90619edd332bdb10c63c82ffe5d3a455739ae", + "05e54035979cbf6087f18c930773eca18fb337d5ee7c338ac8a0a29e17ddbcaf", + "069d2bee399b6d06fa8e121df0710128be664e7f547a2d9b5ae5164181b92bb4", + "114197e6312001fc3870ecf383d3805d896fd022c54ef711f97f8343c0bf75b4", + "3924b30a015b8fae8668eadfaac6e7d0bb50bef1550f0ebbf30de0f0e6b7fab6", + "f99368085ebbfd667df7989c68059b40d6896203c6c219c92113150b25ccc1b8", + "6fdb0a920d8642215eab7c78b9abe6d735b3b2ebd4fc985f9d571b9a6de3b0ba", + "a239d81f7894afdfd4b474cf6ad7a1c6914d07c9c7785fdc7639d58fbaf999bb", + "399661cb231198a0a8b6982f8b50c085e9cdc0661015e8665c09c18af8259cbd", + "f3181e1e500a05a041cbf71b74163bc906d763e5c6a3cc67227c0d2d0b96a3be", + "6ae3fbf76c00cb2c0e2ae084eef007a6fbd9705da1fe6cf67a0cfc521b1a07c2", + "986d8725baa72e4c05034feb70f022c862c65654b54eaa493a6142104f7154c8", + "150e83a81ba7b018e08bad8bcb2e2f1ec5b701ba8b0597577fe42afe39015fca", + "607fd5c1ccdfbcf8f0edf6a20f58ced84442fc319d05ee95ab98c1070aeedecb", + "30edd96024fde70593276c09304d0d653a2fd79b59d211ba8396fbe003280acd", + "15d457f21090c8c13939c5a9bbd1134ca8754320ca9345ed7b952715ba8267ce", + "5154fc959b4fe7fa8cf3f5c5d36249c8ec2f6f577e1ded4e34a99b902124bccf", + "37ab77571fa0545f80b27abfd91dcca809224f42b520065865b7702c6ef86be8", + "62a97cfc4d7b957a761832f87f1f607da9c9dfdb680e92c4a051f36ee3942aef", + "c976614d7e9cf081aef1b3ab7ac381c2d9efabed153dcb485613d975e87a48f1", + "61092bc75031789955d142a0bacd8b31e83101e747d61c1f3dd56ef0114ab0f5", + "6b6a9a29eae7263967aae253521264cbd4fe769b9708c57f384bffcb10ae14ff", + "a7d9bfbc5fbb6890b8481158d71ee08b65287fec044ab0c0c66a4d242a8e56bc" + ], + "time": 1627051164, + "nonce": 74682161, + "bits": "1a015eab", + "previousblockhash": "e13c26bf391ec0388ba7c530d18e224a2fa6416717d36befe11d6d46e5f37ca1", + "block": "00000020a17cf3e5466d1de1ef6bd3176741a62f4a228ed130c5a78b38c01e39bf263ce10db0607f98d721f84dcd6f34b9c9a242b7ffbc8c43fd93aa68859f47eee9d557a5b674c24351b99d809c60483bc4da8ee2998c7a6979217a6bd31655ea16ba3d9cd4fa60ab5e011a318f73044c010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff470340420f0494d4fa6008fabe6d6d00000000000000000000000000000000000000000000000000000000000000000100000000000000700000e702000000082f4478506f6f6c2f00000000020000000000000000266a24aa21a9ed6b97f536acd48e1d15e44f4ccc80eb71566c692c71f5a4ddb2be94a27fb1367d7e227005060000001976a914e9dd1437538ca9ab0afca5a8d64d1940cfa3a44388ac01200000000000000000000000000000000000000000000000000000000000000000000000000100000001dba858914c51352e91f0f93e5963ddfa8e471a19c361b8948126c45c03ca41b9010000006a47304402202863bdc56c59ea6b378c6993d5dfe13926051061c6c0587d74334a25cd128fed0220436f603078bdcbac3ac7487ffa3f1e1fe9e65cf9c6799e403519ee58e9f0cc780121033de3486e06c0e02f13d5b306ed82bd88d707ffbf9d9e6daca772932aed8aed34ffffffff0240420f0000000000acb52070726f6a6563742d726576656c6174696f6e2d616e6f6e796d6f75732d6d70344c6d01de4f2d900c57102e0de2dd3d11196d0acc55dd2e496b56461ae9d94bfb9f048148d7b24b42dbd37e7a08adae03809fafd2a0e534c9dc2c9df01a5cb4be22d94f8c430abdcd4ba666f8e0f5b583c53d37fc179fa922160a14a7932b02a127dde335d30798eb2221cb31ecb5106d7576a9147aff023a9e3ec6d58c6e0030db253921eb0c5b2088ac68b53c00000000001976a9147aff023a9e3ec6d58c6e0030db253921eb0c5b2088ac000000000100000001e1e4f8d9a77abf972f38cb6966f97a08538065cb174d91a7465e2bd3b7702e0f010000006a47304402200b5fa3624798911f2138ba52b34a99488d7554d0b1c9455278658daeab0c2f49022056d5ebc9c4946e196352c1613774c8984b37e96113e4a837f66ab138f05cf06a0121026b01811ba0213e23988bf42f2e2384d1b91bab6f534b10f87f4bc5e9670b39ebffffffff0240420f0000000000fd8203b5305069657272652d4b6f72792d42726574742d5765696e737465696e2d697665726d656374696e652d65787472616974314d32030189fe06c954ca36f5e4fe81269c4efd57b96b357f5bb0d595b1c631a79c68327f222e3405c34831ec9d6fcf31d6e02d4a820afa70a4abffedb6069f81c6224d73bcd26ea934fd8d214679dd55da619bba43ba1bc80ac4010aaa010a307d9d92321ed8493a5a700d6b46489b5851bc6ae92c6c1295859585e91b469d5a2c1cc765504f05d14331b43688248f571234506965727265204b6f7279204272657474205765696e737465696e20697665726d656374696e652065787472616974312e6d703418cda194102209766964656f2f6d70343230cb026c8837ff6d51a93271731076fdade8425ec6338638b1fcf106d48ae44e82de63afbe622230262d5394fca7c86d311a044e6f6e652880a9eb87065a0908800f10b808188a014241224c27697665726d656374696e65206120c3a974c3a9207370c3a9636966697175656d656e74206578636c75652064657320657373616973205066697a657221224aa203224c27697665726d656374696e65206120c3a974c3a9207370c3a9636966697175656d656e74206578636c75652064657320657373616973205066697a65722122200a0a506f757271756f69203f0a0a456e2032206d696e757465732032302c20766f75732073617572657a20746f757420737572206c657320726169736f6e7320706f7572206c65737175656c6c6573206c27697665726d656374696e652044455641495420c3aa7472652063656e737572c3a96520c3a020746f757420707269782e0a0a45787472616974206465206e6f7472652074726164756374696f6e20647520706f646361737420666c657576652028326833302920646520506965727265204b6f72792061766563204272657474205765696e737465696e203a0a0a68747470733a2f2f6f64797365652e636f6d2f40697665726d656374696e652d636f7669642e63683a352f6c652d44722d5069657272652d4b6f72792d7061726c652d64652d6c2d697665726d656374696e652d617665632d42726574742d5765696e737465696e2d64752d4461726b2d486f7273652d506f64636173743a3352282a2668747470733a2f2f737065652e63682f392f396335653730313930393434376232312e706e67620208306d7576a914bb7c3aa96b83e30fe9641d0fed709ef2bd2041f588acf4448f00000000001976a914ce5464eb45baff7369b14f339b6b4657e0e0691088ac0000000001000000010405a9ca36c32a0e87b40e12db5b5e73355e1024161a77fd361ec37ab822a8eccb0000006b483045022100ba618c0c48f437a18227be2c49f8298d67b538c971d9230d8ad6319cf57435780220103e7aaf5ebf447f7bfb5df8297fdb0a3bf087807668a6cd22f6524730ed374c0121024a899c6d84b60bf57a50fd2cfe6c384a48c794e13bf64583371cf1b82dedc60dffffffff0240420f0000000000fde502b52577617665792d7468652d706f636b65742d626561676c652d7368652d63616d652d66726f6d4da00201f92e5b16472c91308fa387b12cfca383ae6fd83abf053741e8a9014d342f63e9f663c434ffae5126c4107dabab2dce060e6d9471795ca8581a6c9de3b95a11922a5c47939d07ac16e738934ee1d12996e325a1700ac9010a95010a30e3c3f8cbd552eddc4fad8a38ac6c81b1af02896d4d65a5009cae99761c53e6b0f70e0c3563625d182717f86c609fc708121f77617665792d7468652d706f636b65742d626561676c652d7368652e6d703418fbdc85052209766964656f2f6d703432307b25f34e2bccf00461e1925f503944c94291ed7a0e3ba832fbd50d9bfc2b634b1709ff9e3340219540f194b9f842d5941a1f436f7079726967687465642028636f6e74616374207075626c6973686572292880decd87065a0808e00410b808183a423a57617665792074686520506f636b657420426561676c65207c20225368652043616d652066726f6d20746865204c616b6522202373686f7274734a8801576176657920506f737420436172643f2068747470733a2f2f7777772e70617970616c2e636f6d2f646f6e6174652f3f627573696e6573733d48575a5254463857384d5747532663757272656e63795f636f64653d5553440a2e2e2e0a68747470733a2f2f7777772e796f75747562652e636f6d2f77617463683f763d55456e6931667a694f546752292a2768747470733a2f2f7468756d626e61696c732e6c6272792e636f6d2f55456e6931667a694f54675a066e61747572655a0562656163685a06626561676c655a0c626561676c652070757070795a13646f67206f6e2061206d6f746f726379636c655a0b6772656174206c616b65735a0d6c616b65207375706572696f725a0d706f636b657420626561676c655a0673756d6d65725a12746865207570206f66206d6963686967616e5a06796f6f706572620208016d7576a9140090fd3ef07e7fd8f44728844045d4cac24c8daa88ac34401800000000001976a914b1b7c8792eb000fd4c5ba1a2512e9e94bba1885f88ac000000000100000001b128e8c3a661ebbbd8e906667d67e638c2449c0351b118bfd48118c69ce696c7010000006a47304402204762e815666d3d07546fcf0c3df9a011d28c56e6e71bdf6facda4ba8451aef2902206e2f152f95b6e7f662cb350414cb6c00b0bb6febaffbdff62ece66183e298de0012102fc00843d1d4a716eb07fca507fcf1b8f750d2ab782abe8ce0a984e930d3ec004ffffffff02a086010000000000fd4d01b510396335653730313930393434376232314d1d01018007d9416280f79fbb2dc25d17709d6bb843cff228985723438eccb2ffd09cd1efb9687eb88a3b35aff87056c7e7dc772b71a6f69b4433a63b8b2d6b5d201bd789eb582b145be287895702f79794d0603e71c0230aab010a89010a30e067a165c87e59f86e307eb42065e97dbf0709a3b426955c4bea5342c07778b1cf77ff9e9fcfdd816408ba01ae1632a61214396335653730313930393434376232312e706e6718bac1622209696d6167652f706e67323077afe30eee142f09346ad4e943e6c8ad584fc1b5cd0f4ecc0547b8ec2a9d6adfd5026f8d7ddae523aaede795e2e696111215766f696477616c6b6572207468756d626e61696c73520608f50a108f064218454532624857706e4274623963794669696459734b754b4f6d7576a914543f24037516ace9bd1dd0a875d2cd31baafe64488ac30656f00000000001976a914543f24037516ace9bd1dd0a875d2cd31baafe64488ac0000000001000000018f8c0035a892bbb53580887d58ac64fffa4aa98624388b4eabd229682f78a0fd010000006a47304402205bc2ded7f2ddc4c252c546bd02f95bad954865c5bc81a1215914c6f25d3562bf0220658265c355417460c451073e9924123884994dba22b6f47fb21bcd1b7b5040b1012102fc00843d1d4a716eb07fca507fcf1b8f750d2ab782abe8ce0a984e930d3ec004ffffffff02a086010000000000fd4d01b510303233346663623133373231663439384d1d01018007d9416280f79fbb2dc25d17709d6bb843cff22e3128a26ba8e169032529f7f25f99910c7135a2b43393e175c8d865c0737cca6b6f03e0c929d5f53942ec67effbc3491d83d3f36e3676d46660a247630cb69b0aab010a89010a308761b1886bc2b221527b475f07554aeeca9c9bca8da314a8442602c72c9c946c9b160ef56aa615fede9005a747e15e2b1214303233346663623133373231663439382e706e6718b6df0d2209696d6167652f706e6732303a7c5839272e061dfe24655884175e58b975c4f9081099910a3a54acd01d34ca8d3238bb29dee72c79d11505771efc951215766f696477616c6b6572207468756d626e61696c73520608b60610ce0342184464566b4768634b4b6e574b4f344f324a394d3547356f706d7576a914543f24037516ace9bd1dd0a875d2cd31baafe64488ac086ed400000000001976a914543f24037516ace9bd1dd0a875d2cd31baafe64488ac000000000100000001a1ed94274b728b6ae6e2358a17a2457afffe8ebfbc0fe4d6f613263f87bf4747010000006a47304402203d19d41a239eb6c0dff5eaf50a2cf0cbf76720a79aa7e422565dbd059adc6aed022028a6392dcd3fffabd611668695befa3a85ac673ec1ef72e56aa8cdf27e93a336012102fc00843d1d4a716eb07fca507fcf1b8f750d2ab782abe8ce0a984e930d3ec004ffffffff02a086010000000000fd4e01b510343232336432313764653530613433304d1e01018007d9416280f79fbb2dc25d17709d6bb843cff29b6ef45869fd6bb03fc6a8e341980c41e4d2e7b1d0903d5e9f6910157055c46c5060558e4de7204875ca262637eb3125774d0b0a91c2d8204f45262f813f38240aac010a8a010a308385d2662400d38413a6d125b3d4bb16982718d81a815ed60b995465e64b8c633530931f243ec835ceaaf158d4d8b5011214343232336432313764653530613433302e6a70671892ac3e220a696d6167652f6a706567323020f7e00d5b7964f3bbc3ef01544fcefaf3959affbd62ec84d419bd3609d002f46a046acf51d216bc58bee52a2a57403d1215766f696477616c6b6572207468756d626e61696c73520608800f10b80842183966504f6f615037536d4d454e6757524f6c36684e4a43576d7576a914543f24037516ace9bd1dd0a875d2cd31baafe64488ac4cfb6b01000000001976a914543f24037516ace9bd1dd0a875d2cd31baafe64488ac0000000001000000016ac8b83caf3ba6b9076bb924d2086e760ee4397fc185a04eb17da8b38e020e00010000006a47304402205bef3e74db980c7c1da9eca8815925050b3432885967246ad594b51b9d3ca73a02200d1d6b56da81e63cce4141f7dd88f898263038bbf3b8b4be9bc7d2763163e993012102fc00843d1d4a716eb07fca507fcf1b8f750d2ab782abe8ce0a984e930d3ec004ffffffff02a086010000000000fd4e01b510643163663039373234616563393866334d1e01018007d9416280f79fbb2dc25d17709d6bb843cff2d7781be2593e9beadaf0210e440945886a0b5a10d8cb79903a2c867f094baf9e1fb915d191a2616da60d54fb079729fd5ee003076596360c07d6061776f20dce0aac010a8a010a30748268ae1afb0f9d3fd068463ce7c9a928e15bbad20ef84649901d0ca3ffaa194d39c70a5b7084b1d87ccc2bcf40f87c1214643163663039373234616563393866332e706e6718d5f192012209696d6167652f706e673230ae55ecc23c5f7537b3fce67e7b924b591fc974c9468971b6034396a9ece4d5cb503748e6c994e30a3344a40523e3d5a41215766f696477616c6b6572207468756d626e61696c73520608f00910a10542184779754530313353366733784e487565465972384e78796d6d7576a914543f24037516ace9bd1dd0a875d2cd31baafe64488ac086ed400000000001976a914543f24037516ace9bd1dd0a875d2cd31baafe64488ac000000000100000001f737d84b3d5f21055f7967708cf460e8ba33186acc3038fdded41ce141c0553b010000006a473044022055c30e2eed3daed691a9e86139cb401224ab0bcb7cb2a93bf69d695b48e20fd9022049937ff5ea0619c9c15178176dc56ba58dcdcfb0dd165cf3d261eb2f722be38001210319e46a64f04cc68f929a20e0d0b84c6323d88aef1496459b1806045390b7695cffffffff0240420f0000000000fd1a02b512637574657261726570657472616e6b696e674de801017edb6b681772a4eb6fa1672bef22207eed501653b7bbb54e303a28e21d33cba4453558939300aea6a55f50e3f77bf4c0452e82d72c81929547323864471edc4f3708d6fdb63be44ae8a58c9ad15c683824ec10910ae0010ac6010a3021c1f82c489338dbf14fef553f61fb3251ed7dd498529789fb16bb63532f2cec26223ff4099cf5058fd40fde46ab66a3125079326d6174652e636f6d202d20e9a3bce381a3e381a6e381bfe3819fe38184e3818be3828fe38184e38184e78f8de3839ae38383e38388e383a9e383b3e382ade383b3e382b05f31303830702e6d703418d1e8eb4f2209766964656f2f6d70343230170fb308607be070f3b45d7f1cf489a7355f35f485fb597b74850482b85b3ed2929730d67f0aa4e7801edc5be440cc231a044e6f6e65289da7eb87065a0908800f10b8081880044228492077616e7420746f206b6565702069742120437574652072617265207065742072616e6b696e674a435369676e20757020666f72206672656520616e64206d616b65206d6f6e65790a55524c3a2068747470733a2f2f706169646572612e636f6d2f3f723d3230383834373452282a2668747470733a2f2f737065652e63682f382f663834343135353165653830383331302e6a70675a037065745a06616e696d616c5a0472617265620208016d7576a9143a2cecd82cb6f89fd2611a883b0ac2c829d395c988acfcbc4600000000001976a9143a2cecd82cb6f89fd2611a883b0ac2c829d395c988ac000000000100000001d87499e1ba61732139f6da47aa069c80c8265c8fd3436312cb57eaf934b19e37010000006a47304402207e99654707a55588fe2e57e5babdaec723522a8d997279b864126bd39601aed102206c5cb67c0a9f9fd2342f842e4f924f1cd581160a9a777e8ba98c2f508411ef6b012102c6785b987e93c71b63f68831eef6fb38bdb4ad4ba1675b4d558ef50c95e1f7aaffffffff02a086010000000000fd7e01b50d70632d6573637269746f72696f4d510101aef4dbca1fbe9ec2db1a2626bf368e0c3ee75a82e7d32462923c68967c6ad67e46ef805a8e29ed0164f52c0854cd3495b85911c4bea266a62f756032f78b4e14488cd508e57ba77e196840d95e4763bb05444d560aa6010a8d010a30c7b9f66b2c2db79a3f6f35c393bbbae3e92addd441fa0bfb5c1ba8531bc7666b36739e9e225acfa426e4bad26058cc1e12175649445f32303231303732315f3137313432332e6d703418e986a7062209766964656f2f6d70343230e5f8a436659c0468adf7fe6100ebe19436972579b575aa909853119d868e8f8c5095f8263ad26d7d6477a4b8084e8c3c1a044e6f6e652885a9eb87065a0808800f10b8081818422377696e20313020656e2064656c6c206f707469706c657820373535202373686f72747352282a2668747470733a2f2f737065652e63682f372f623537343734336162396630306662652e706e67620208016d7576a9146dacb84b1cb841c52188cb9815ff5f0693d4655688ace063a200000000001976a91402639c792cf664d7e989b03d95604df83fe1923988ac000000000100000001de96c3eae198fabf46bc6496d61dba3eb47f16e3b68b4cfcd97d7b0c5881f0b3010000006b483045022100e2a8994721bd56a6ce0d45be356ec007989ad046c1ca7a5068d4ba80ccdfa30702205eb4df74997aea14d53a5046931f258b753f40767307735e2da23d12678c80ca012102682d828c5b80bc90d647ffbea938e45e1828b3ffce1f9fcc63cf84de70fa0b3dffffffff028096980000000000fdcd08b5353133372d626974636f696e2d6261736963732d626974636f696e2d776861742d69732d646563656e7472616c697a6174696f6e2d334d780801c4d820581846eb57ab2d6dbb7a3b7c0b43a5ac001774bf56883261e6a3731563b5d3f9dee69aa0e0e0532e39eb3a7a2fbabbbb98adeb69d438a0258f1e4ec23a03493d5ae37cb393f91e225cebbf05abada5e1660acf010ab5010a30ebbd86ac368dce16163963218d435a12a6c46a9dd081b4200cd50c35f1d34adcee174c40331e2a81fc297007da0104df123f3133372d626974636f696e2d6261736963732d626974636f696e2d776861742d69732d646563656e7472616c697a6174696f6e2d335f66697865642e6d7034189fc3c01e2209766964656f2f6d70343230d64e5ea82806189332b7cf725fb805d1410bbb7a37cf3d34d43455331c85bfc176b5a1fdb4b8526049cf175dfcd512571a044e6f6e6528a0a7eb87065a0908800a10d00518db0642395768617420697320646563656e7472616c697a6174696f6e3f202833206f66203429207c20426974636f696e204261736963732028313337294ae60c2a204e6565642068656c703f2068747470733a2f2f626974636f696e6261736963732e68656c700a2a2041736b2075732061207175657374696f6e20666f72206f7572206e6578742051264120657069736f64650a2a20426573742068617264776172652077616c6c65743a2068747470733a2f2f626974636f696e6261736963732e68656c702f636f6c64636172640a22506172742033206f6620342e2057687920646f65732074686520426974636f696e20626c6f636b636861696e206e65656420746f2075736520626c6f636b732c206e6f6465732026206d696e696e672068617264776172653f20486f7720646f657320626974636f696e277320646563656e7472616c697a6174696f6e2072656d6f766520746865206e65656420666f722074727573743f220a53686f7720706167653a2068747470733a2f2f636f696e636f6d706173732e636f6d2f3133370a23426974636f696e2023426c6f636b636861696e2023446563656e7472616c697a6174696f6e0a0a4e4f5445533a0a2a20446563656e7472616c697a6174696f6e2073657269657320706c61796c6973743a2068747470733a2f2f796f75747562652e636f6d2f706c61796c6973743f6c6973743d504c38315830624a55526d78583565415f74337755736656547330614437736557720a0a4f555220574542534954453a2068747470733a2f2f626974636f696e6261736963732e68656c700a5265676172646c657373206f6620796f757220756e6465727374616e64696e672c2073697a65206f6620696e766573746d656e74206f7220746563686e6963616c206162696c6974792c2077652063616e2068656c7020796f75207769746820616c6c20796f757220426974636f696e206e656564732e20576974686472617720796f75722070726976617465206b6579732028626974636f696e292066726f6d2065786368616e67657320616e6420696e746f20796f7572206f776e20637573746f64792e204e6f7420796f7572206b6579732c206e6f7420796f757220636f696e732e2057652070726f766964653a0a2a20426974636f696e2068656c706465736b0a2a20467265652027426974636f696e20426567696e73272065426f6f6b0a2a2046726565206d6f6e74686c79206e6577736c65747465720a2a205265636f7264206120706f6463617374207175657374696f6e0a2a2044495920656475636174696f6e0a2a205265636f6d6d656e6465642070726f647563747320262073657276696365730a2a206574632e0a0a444953434c41494d45523a20416e7920636f6e74656e742070726f766964656420627920436f696e436f6d70617373206f7220426974636f696e204261736963732c20696e636c7564696e6720627574206e6f74206c696d6974656420746f20616e7920686f7374206f722067756573742c20697320666f7220656475636174696f6e616c20616e6420696e666f726d6174696f6e616c20707572706f736573206f6e6c7920616e64206973206e6f7420696e74656e64656420617320696e766573746d656e742c206c6567616c2c20746178206f7220616e79206f74686572206f6666696369616c206164766963652e20446f20796f7572206f776e20726573656172636820616e64207365656b2070726f66657373696f6e616c206164766963652e2041742074696d65732070726f647563747320616e64207365727669636573206d6179206265207265636f6d6d656e6465642062757420746865736520617265206f70696e696f6e732062617365642075706f6e20657870657269656e63657320616e64206e6f7420656e646f7273656d656e74732e2057652074616b65206e6f206c696162696c69747920666f72206f75742d6f662d64617465206f7220696e616363757261746520696e666f726d6174696f6e2c20736f66747761726520627567732c206d616e75666163747572696e67206572726f72732c20746563686e6f6c6f6779206d6973757365206f7220616e792069737375657320696e766f6c76696e6720746869726420706172746965732e2056697369742068747470733a2f2f626974636f696e6261736963732e68656c7020746f20737461792075702d746f2d646174652077697468206f757220706f64636173742c2066726565206e6577736c65747465722c20766964656f206775696465732c20736f6369616c206d6564696120616e64206f74686572207265736f75726365732e526e2a6c68747470733a2f2f636f696e636f6d706173732e636f6d2f77702d636f6e74656e742f75706c6f6164732f3133372d626974636f696e2d6261736963732d626974636f696e2d776861742d69732d646563656e7472616c697a6174696f6e2d332d796f75747562652e706e675a07626974636f696e5a0d626974636f696e2070726963655a036274635a0a626c6f636b636861696e5a0e63727970746f63757272656e6379620208016d7576a914cfb64b607f1931c3ab7e717b2f208fd6f338642088ac324e3612000000001976a91400005ddcff91ee12521be352f1f09db53c63398d88ac0000000001000000016328499bcf414d0b12fbabb128905fc15882716a8b669c3d672c475d12a7a7cf0b0000006b483045022100eec2a8ce6ae831cbe8a7072fc149a410ed905fed16645e10fa135ad5caf9380c0220745d9a9c5ec40826c446edd32853186e6de670c7889758e14ab1403e439e79730121027e7b53881807d16f2fb13603ecc0743ba0c06c92b9021f08f810c1707fb11c68ffffffff0240420f0000000000fdc30ab52670656e74616772616d2d6d657a61726b6162756c2d7265616374696f6e2d746869732d746f6f4d7d0a016d21ca1272ef657b4b7fcd359a7614879f0d7b7521b480d1372daf42e73e8cd5195a09214e0ffd2681f4d719f017c51000799f56a8b654af79e7be58ab27db454cab825a0b6c7115464e4e9b2a4ee713608a5d790acc010a97010a30d25c8eb3ff35bca80a4f50d8f69ee06b69517b69c783cedc18d12624c186218ec59f312d1aaab9357d7d197724f1d5c8122170656e74616772616d2d6d657a61726b6162756c2d7265616374696f6e2e6d703418f28cf8382209766964656f2f6d70343230fccd1ec24cd7ec375ae07e7572c9a17c990b3bf70c133b0531cb38d7ef0b45cd18c7745648a9108bfcc8116ffc26f6001a1f436f7079726967687465642028636f6e74616374207075626c69736865722928cea0eb87065a0908800a10d00518d303425350656e74616772616d2f4d657a61726b6162756c205265616374696f6e202d205468697320546f6f2057696c6c2050617373202d2046697273742054696d652048656172696e67202d205265717565737465644aaa0e506c65617365204a6f696e204d79206368616e6e656c20746f20537570706f7274206d6520616e6420436f6d696e6720536f6f6e2c20456e6a6f792054686520476f6f64696573206f662074686520566970204c6f756e67652048657265200a68747470733a2f2f7777772e796f75747562652e636f6d2f6368616e6e656c2f5543526732776535372d74354967747868305f39537250672f6a6f696e0a0a23526f626b52656163747320234d757369635265616374696f6e73202350656e74616772616d4d657a61726b6162756c0a4920526561637420746f2050656e74616772616d2f4d657a61726b6162756c202d205468697320546f6f2057696c6c205061737320284c697665206174202742474d27202f2030342e30322e3037292048440a4f726967696e616c20536f75726365204c696e6b202d2068747470733a2f2f7777772e796f75747562652e636f6d2f77617463683f763d544a35546c597a4a5376550a0a4d79204c696e6b730a0a4261636b7570206f6620616c6c206d7920766964656f7320626c6f636b656420616e6420756e626c6f636b65642e0a68747470733a2f2f6f64797365652e636f6d2f40526f626b5265616374733a370a0a4d7920436f766572205265616374696f6e204368616e6e656c202d2068747470733a2f2f7777772e796f75747562652e636f6d2f726f626b697374756265200a4d79204f61736973204368616e6e656c202d2068747470733a2f2f7777772e796f75747562652e636f6d2f726f626b7265616374736f617369730a4d79204d61696e204368616e6e656c202d2068747470733a2f2f7777772e796f75747562652e636f6d2f726f626b7265616374730a4d7920596f7520547562652047616d696e67204368616e6e656c202d2068747470733a2f2f7777772e796f75747562652e636f6d2f47616d696e676772616e6470613432300a4d792054696b546f6b202d2068747470733a2f2f7777772e74696b746f6b2e636f6d2f40726f626b7265616374730a4e657720526f62206b20526561637473204d65726368616e6469736521202d2068747470733a2f2f6d792d73746f72652d323735323730352e63726561746f722d737072696e672e636f6d2f0a526f626b20526561637473204d65726368616e646973652048657265200a68747470733a2f2f6d657263682e73747265616d656c656d656e74732e636f6d2f726f626b7265616374730a5477697463682047616d696e6720205363686564756c6564204c6976652053747265616d732068747470733a2f2f7777772e7477697463682e74762f536d6f6b656469736c6976650a547769746368204d7573696320205363686564756c6564204c6976652053747265616d73203a2068747470733a2f2f7777772e7477697463682e74762f526f626b5265616374730a446973636f7264204368616e6e656c202d2068747470733a2f2f646973636f72642e67672f7073575170795a0a547769747465723a2068747470733a2f2f747769747465722e636f6d2f526f626b5265616374730a446f6e6174696f6e20537570706f7274202d202068747470733a2f2f73747265616d656c656d656e74732e636f6d2f726f626b7265616374732f7469700a50617970616c20537570706f7274202d2068747470733a2f2f70617970616c2e6d652f726f626b6973686572650a496e7374616772616d3a2068747470733a2f2f7777772e696e7374616772616d2e636f6d2f726f626b6973686572650a4e657720526f62206b20526561637473204d65726368616e6469736521202d2068747470733a2f2f746565737072696e672e636f6d2f73746f7265732f726f626b7265616374730a0a57616e7420746f2073656e642073747566663f0a504f20424f5820393939303020484d20393730203338330a52504f2043414d5042454c4c0a57494e44534f52204f4e204e3942203042370a0a436f7079726967687420446973636c61696d657220556e6465722053656374696f6e20313037206f662074686520436f707972696768742041637420313937362c20616c6c6f77616e6365206973206d61646520666f72202266616972207573652220666f7220707572706f73657320737563682061732063726974696369736d2c20636f6d6d656e742c206e657773207265706f7274696e672c207465616368696e672c207363686f6c6172736869702c20616e642072657365617263682e204661697220757365206973206120757365207065726d697474656420627920636f7079726967687420737461747574652074686174206d69676874206f746865727769736520626520696e6672696e67696e672e204e6f6e2d70726f6669742c20656475636174696f6e616c206f7220706572736f6e616c207573652074697073207468652062616c616e636520696e206661766f72206f662066616972207573652e0a2e2e2e0a68747470733a2f2f7777772e796f75747562652e636f6d2f77617463683f763d76717a354f694349456c6b52292a2768747470733a2f2f7468756d626e61696c732e6c6272792e636f6d2f76717a354f694349456c6b5a056d757369635a0b706f702063756c747572655a04657369725a07657572617369615a0567686f73745a0b6865616462616e676572735a0b6865617679206d6574616c5a0b69726f6e206d616964656e5a0b6c6174696e206d6574616c5a056d6574616c5a0a6d6574616c2062616e645a0b6d6574616c206d757369635a096d6574616c686561645a0a6d657a61726b6162756c5a0d6d6f6e73746572206d757369635a0f6d75736963207265616374696f6e735a066dc3bc7a696b5a136e6577206d75736963207265616374696f6e735a086f70656e206169725a076f74746f6d616e5a0970656e74616772616d5a1170726f6772657373697665206d6574616c5a0572656163745a087265616374696f6e5a097265616374696f6e735a13726571756573746564207265616374696f6e735a07726576696577735a0b726f626b207265616374735a04726f636b5a0c746872617368206d6574616c5a0d7475726b697368206d6574616c5a107475726b697368207265616374696f6e5a0c7475726b69736820726f636b5a0874c3bc726bc3a7655a0f7761636b656e206f70656e20616972620208016d7576a9147ee9752cf48959fb94de59ac2f93e2c7bd03c61e88ac7ed81400000000001976a9146373efd935bf46b3b4a825b673e3658b7ef34e2888ac0000000001000000010b83498546f4d0ff85a9eeaa8ba27d28d662271358643918aee789a9e629adc5010000006a473044022044aa85a966158319f0f16dc3d8fcc08a6cb9d6312544b401858f0860c7463a9002201ba05bd58e474f1e215fc0a8ac9b8d6daf087c14162488e0dcd75e6a4a35036d01210314e15d020bc13d0eaa8e086be482f79991e7636b3694c9aa55fcdfd3633eac69ffffffff0300e1f505000000001976a914a7692b1702b212d1466cec4ea14113f6c17aba9988ac0000000000000000196a17500a14eaddbede804c121aebbd5caf432c647da7591883eca9f505000000001976a914508be1f3fed576e613c82fc8015c266a5e1c016788ac00000000010000000163b5df6ee6947b8e55623d7112f0ba8c12511b14cd13504f7a6d8d02363ea937010000006b483045022100c8951e14944718c3608ddab6356bdb1b7ea3a249a126a25a23779eeb6f7bc14b02205c632ec785de997839f2deb2a93d65466d6401a8102d56c0c8e7e3e3397509cc012103edcace45c5d65f8478b5e348855351213d08e591b9455c0827c58e144c69d286ffffffff0300e1f505000000001976a914a7692b1702b212d1466cec4ea14113f6c17aba9988ac0000000000000000196a17500a14eaddbede804c121aebbd5caf432c647da75918836cb9fa02000000001976a9148594c8bacf57269593ab18b3fd70a3021078a97388ac000000000100000001ff8cbbb657d104e2aa3762568dadc7d259c179e694bdd9dc90fe842f20007bf5020000006b483045022100d8eba2064fb262f6264cc2a9bdaec8b1a772483afb421d14230e628f8d17596b02200b65a9988c3969059931ea3ddada5461dca3a432ffcf4446f5361d9ac18017b7012103fad7c054c3339c35e3247f0dfe60ac3119db330b5db792893ce82ec0b6554325ffffffff0300e1f505000000001976a914a7692b1702b212d1466cec4ea14113f6c17aba9988ac0000000000000000196a17500a14eaddbede804c121aebbd5caf432c647da75918834cf1f305000000001976a914b9120870ccfc350f724713460c6abb0018cffa2188ac00000000010000000116a23c452a4e88490e84269a233b267ea839de72093c377e1f03045ea663d4f6010000006b483045022100daa89731329ef479b3d0ffaa45429f6fe86d85a236275ab280551ff967c890fa0220554571563f18abee4a540b997a717449a3dfea6382855801b64028ee225e37f801210207c5e72c79f5d8e9c9bc763db56c68dca6202ed426b1a44dbcdb915cbc799acaffffffff0300e1f505000000001976a914a7692b1702b212d1466cec4ea14113f6c17aba9988ac0000000000000000196a17500a14eaddbede804c121aebbd5caf432c647da7591883ec739041000000001976a9148a6e70ff142bc98d58f95184c2e2d60b082af44488ac00000000010000000268b9a3f11525fec52b17c3616eb14e9e1a7343a8469bb17ca03bd4d89aa3c274000000006b483045022100feb1f6481d1613878a4df37dec34f7c9d2c098e9493e964bbb6b3170f233217a022075ac29106415eac0d775b89405257640df07e118d84180544a781e7045cadc840121023332e9208f3f1cabfbd023f01f3e81a9a21206c941724a51ea7fe6493f42f252ffffffffe295ea6f228d8474ab8ac31ec883f665d869ff9090316e66f4334747b86e1797010000006a47304402201a85ecc7df72ef1a60e363955b2f08cca7b9333e8345fbfc5a3430890c7c09960220756e34b4f1257f36d623c3ef4d3368fc7842866fe4dd1a0ee1e8069daa43582f012102306165daccd5dcf88127ff966b6d080a527a69f47222cffdd0b6934347a2d667ffffffff02a086010000000000fd0801b708406e6f7468696e671444d298af12db90c70a9d98cc51f649d6320a57704ccc00125a0a583056301006072a8648ce3d020106052b8104000a03420004e63ef5335203c09a20b023276e54a3126815c31175f3be69f0ce08986c374aad11fdd701722bade2f777c56e079d5fb6f634cf33fbb7766c1848c2b1be8e7fea42117369676e2074686520636f6e747261637452282a2668747470733a2f2f737065652e63682f352f626630373537313963633637336465612e6a70675a076e6f7468696e675a0b6e6f7468696e676e6573735a05656d7074795a08656d707479696e675a09656d7074696e6573736d6d76a914aaa0e8531e948368092e556506a5a124511a3a8788ac28e50100000000001976a914945f708389a968cacd35d7db3ab86dbc5d52686d88ac00000000010000000346fc4ddabe3081dfb3510b0566c26528e1ace5241cf2528ab8fd4ced8a4666e3000000006b483045022100b2059c3c5ee7e2f8f34a6320f7b48504727d029e2505ad62524fa1fae614db2402203660b45279fa00bf55be6929b8f221dc960905cf455d7562f561157329ecc91d01210335da8706473b34ca599b6679e846270cdf630c36b1709fed847195735ec7b61affffffff4c5b8f5347315dc06c83bdfe2c3d5565a17f72fa04d0f7ff1439081d7c61e63c000000006a4730440220089c8b55c699bf7d27be3738bf6ed8f26f9d3492f0d899c3d0cd4662187acb2602202323cf5692d92a0448d5c99eba911a4f7848c1ad3e10b3a60f19de6626d98f48012102806a9c08ebdec8004eb905cff6770cbc6ef5ff42fbb98945c0b7a34581ee8a6bffffffffa2145eb64097ce3d02ad42910bf9b51f6ab8141c7d60ad5cc9e77de8b3cf82dc000000006a473044022058fe9c987b2ebaa303f877118242ba835a94bceb1b931c5fb36dc358578f209f022045674d9568a0bb31206a5e117e69488c8131ec382fd0fc33e05d8a4d96e48c50012102ce20239c1f9f4abed518d72cfaf1ff71a0e74fccd0116a958e5c949c14e2c471ffffffff020068f3c961000000c5b63c44494f534553594d4f4e535452554f534369656c6f73616e6365737472616c6573457069736f64696f31446f63756d656e74616c556e69766572736f148dfbffe3059160d309f5cdf95e0a8d16ca793ced4c550100684e30bc9e64d25856a2bc7d7f1da8201f59c2f1d349b82395f5183991b9658117ab96504605f39d5958268ecdd93aca4ec672fd45f02b31301e9110d19a0f14e16ebd45c5f917f0c2557a8e7aa8c85577ebd76d6d76a914abc74c61bbcaee2e9a7b5acb524035d945c6455188ac5c0ceb0b000000001976a914abc74c61bbcaee2e9a7b5acb524035d945c6455188ac000000000200000001f48846b52cb65e3b980eaeced2db45b2a68a2c252790cee93973deabf7f5a80d000000006946304302200e571df979b020ad2416231aa20ace7d40b007fc74d3a09695dbc99109642fd1021f4c37e597e6147b57cb592032cd93fd5e62c1f6611155b984d168d1889bf10b01210243d1210f20f0b3594e4c706e6ea5c0a3971192527e051df6889b86bf61cf77a4feffffff020008af2f000000001976a9143b41bb1e5c01d2338833a74a11a1223f19280bd988ac3d42de79000000001976a914dbfcfb16d9661bee1b002c664d9abe47fa9a7ca988ac3f420f000200000001fd184d2e5810c6bc1946c75dce47ed82845f846304b64c61d7a2926ab87e633b000000006a47304402207edff82c3bd86e98614f390f4d2b293e8c58d9d097e1578d814c64f0d485de8e022013ee305cfa168218b8c8592874243c5ba024903b8f1d3b0de06c2db29e9dc4b50121035968ddc08ede56b59ac6ab00998d926ddcc0abc5b88b1a00fe64fcc46187f4a7feffffff0280f0fa02000000001976a914a9c00675ab780af9f7ccedbee8c456d55ac31e2f88ac3439bd7d020000001976a914847d7b577115918c6e89d410f6a3b288e2b8e0d888ac3f420f0002000000012b10909629767d0d42898061075b2a98c55dcdbdf8eef360c97c8a9380acd227010000006a4730440220234585900069c05c38778ac29386a2d5a4674b404b1f6ef9620d2bea86a9e0cc022065a1f65421b756e5aa061169bff3abe12470574dfc751ecd47624a7771b5746201210275586ae23e02e8c8aa4344a185511b3ab027c601b58ec3540a73e6dffe446b41feffffff02002d3101000000001976a9147757fc86c30a45f5197d9fa6edd3432b241e7d9a88acb00e1586000000001976a9142c52f49097b16b8bb2c9f41399e3cacb8996bdd788ac3f420f000200000001b46169d4560d3964fda00b708d545b858cbac405a774e34a7e14581fd3093988000000006a473044022028574e7d29ef5a67506bced343d6c277f66b32d4a7f284e36002419aff5a1fbc0220596e9f0b10aaabf7d51189e11b9ab771f9e7a2e6df62ae52c06b86cce6afe9580121037f2fa14addd9d9a8a34cba397c7437d818497d953ef9f27e5becb31445d12e11feffffff02002d3101000000001976a914d60d40c6dd06def67e0215d08e8044ae4da80d9d88acfab48f79010000001976a914cd6d5946446b8426d20cb3ed64e5ce76333d58e588ac3f420f00020000000198acee4e517877378d3b3677657927e1aa3fd9a19b2a11db65c645fd9df1cc19000000006a47304402207d7bb9b18789b9297fa49d36480e885be4375d63a667f06e10394b825d846fd402207d61e3383b092da87408caf84c9cf23215e53c73041101a579b58c090ba46db801210370782b6e5d21fc228777d6455c1ef73955df542d7f824b4e1d19a1ed3510e1a6feffffff025c8e3a8a000000001976a914a1e9b5acab992af6ef67076bab95999b278d6fc988ac801d2c04000000001976a914483456ef1b88151b987807d68d6005f0475ee0e688ac3f420f00020000000139355a20fc3420d801f4a693cb71322b27b6215c88ee242c4f16e1d5747642cf000000006a473044022054da25aabad59fa9790c95dd1fcddbe4a062311af692a7f5e2f0fecdc934f8780220346c2926d534d8c8a37bc93d27a4e9e052e2e627846976536cc5d8c9429efb800121036e522591402a3f0e5be5bf93dfcef75356f1f192e57297e8efc2095174edcefefeffffff0249e7d09e000000001976a914a935f59fcfb005d7ba3a327d748f3d16589ba13e88ac80c3c901000000001976a91422e82237a553c5ed666a2a7279283f9169143c9688ac3e420f000200000001fb7bf68422b5571bcda378e3b169ac9fc4ab73a80ebb0d8df597e132fd0518db010000006a473044022032da5c8c49ceaaeb5ff6248436494b09e0d86f0e0c25e40e404358444432afe9022008266ddb9dd31bab41be61d05ea6dd10fd265eead0a04c62f6e4248530bee11a01210336b04381a04dfc4a880b536b6a16e4262ad05a2dae54a468d3f2911067f26dd2feffffff02002d3101000000001976a914a3a48a3b356c10cc5087f1ac5058ba054437d54088ac401007bc000000001976a914696ab4811751d8af0f5cfd007fbadb63245539e888ac3e420f000200000001a500a5ad656f5e15a941279d4ef574d2ffb71c4ecf65272566dd7563e4107e60000000006a473044022015e60e3ec1d9d9bfbfacf7d28f18b0ccf18da20543e00e3cbc3b9a830564eea9022064222e62ee6c96a6349696865a6f6ea856acfcb1278c5f109f185610513900250121020580da932eff3374816b6394c6dbafa19b9018df1d0071b53d7e023933d7a12dfeffffff02519add76010000001976a914fc406b7d6e22d62a0a2bc5f57f9db8ff2198d71888ac40787d01000000001976a914bc79803342e1ad60a7a2a3c7e6892c9ba0197c0288ac3f420f000200000001705aeef1faa457c95bc9a14b033057232fb52a4edd1409b43474d23aaa9f0aac000000006a473044022060b6b3bcf91b56b84cefe310eca8a9fc427a44b94bac22ffd6e413009c3e9858022031a569d75e2e1189638b4d8b555f58df375d1b941ed722fa607ab604ed8682c601210220f9cdf87a40ad3b5e905a99e18d87a97138071f9f25b8c98f00b16af4939730feffffff021d881918010000001976a914b10761f82118fe4dc9e0533577b2b88b8ee563f588ac801d2c04000000001976a914202d08f37528feac41fc82f9dce0620883c035ef88ac3f420f000200000001ac1cecde9efdb1d126350aa7ddd3e9e1b4d0c20cb341c2967a15f275457fc0fc010000006a47304402206c7fa765999e42e63935d6cd8fb56edf67d46994ae321e528349245a1818a9ee0220735ba7d1c488b7050fc67575d658b96729f7be9b37d7794be370688702fb372c012103fccb3128df19ee416ccd29dc9e4ee83c5473a7ef2d5339105aff13fc09f81d75feffffff02f1dbbb6f020000001976a914a071b967dd17c4e9b275983b0c45136df68093c188ac005a6202000000001976a9143de5c9c83627949a3e9ba121128385871a2a148588ac3f420f00020000000134f0817afcaeb9f177b1aa3dbead8d29fb8a1606b265777fdbbd956288ba57fe000000006a4730440220269730fcedb0c1a071af732b1452e860540f48b1b69a6ee048b6f66db5bb6a3602203def92127c49c0d47308ee45460022eea435cf8e16612c2d281cc858525bad360121028a38e96f80f0f66a2c52f8a67c5e9d6a11df584999865ea0d94db70042c6433cfeffffff02d11c64cf010000001976a9144091df934ae1270c6ee94a380f5f00798eb58e5188ac002d3101000000001976a9146976acb8c8f7b449a80be20a2a31200a4096f56888ac3f420f0002000000017b409e72e2071c0d81a98224fae2e34225a8fc75162c9c3cb954f2adec474f32000000006a4730440220669e176fe68d1dfa16513007c1868f9dde755400913d521803e5642839a2a70802205350e22283aa93ac1c80830c7c51049b2606413fa83c17c411f4d692b8f495f2012102c4c266961e7464f7870a65ee026e371dbc8f42b7eaf103f6d8d5f26128f00a11feffffff0240787d01000000001976a914ce42e1bfe398702875528d2c5abf7ced7b20768688ac6120d3f5000000001976a91440086bc9c9914bb5e4e60038a9de0565ec475f6088ac3e420f0002000000019605635bc770bada4a14224d4041733076eeea1e2edba3e0ee6af1985b217aa0000000006a47304402207944cc88db6bbb6639be83e7e77e8be189e7ade065189ec591ca44f559cf3ef602200096cdc568349b59aabe0335a1b240ad47e66acb20e7dd806a5f8d612c067acf012103ae2c551b526f7519435ac26d1fcb520988aac87f547592d98335d9754c6e383dfeffffff02002d3101000000001976a91439870bede14dfa1ac61b02409d45e44682598dc788ac49cbf559000000001976a914bd27cb9a3e59c83228eea1eff46753907ae8ca1b88ac3f420f000200000001f620ee758d0a834dc8526e8cebc10bb9566cd28c0e200dfb1b911b5013a70e40000000006a473044022073b0bbb7a078aff8f8a30202484596d0c1dfd78126d51a8b76c21cba044679240220671123543c801ffef7c722a46f00e4ed0639037740e13a298e26432af9748e2801210237d8d2bbeee62c22b1689089caeaf7211a0d82abad967fe397d4cf8715030522feffffff02005a6202000000001976a914221de2ed548b29813a61d3b2254f489971c61bf888acb8780254000000001976a91449307d6728272ee89fef3c37da283b6d36d2065a88ac3f420f00020000000111d5b4a40e9fdd7cd100055b24e34ebac51cd7b31b8776a3a87ee7760ba01ef7000000006a47304402202a445b246159ae71e92f60a48569c29fee143cf865a1b5f7cd01a4b18cc2a9c90220747875bc7eb53dfc262afe63169c08d1bf250233204ca700880ad1bfcaa8edbd012103457bf3e470b4afb89f5a7fd05badab9722bf7ac564575ba99147768c84d180b8feffffff02002d3101000000001976a914b03f7abdf26a51da072dbe25e5198cea017bd64c88acc8264954000000001976a91471ce6a86ff752e5709395b8dddf1df0f1e829d8788ac3f420f000200000001c8e3cb52d0e6de1620e7969934f0128abd5aea23ec0ee10ea7fc3c0ab55d491d000000006a473044022078917dfe6ca020ac7d4a3a8e2825515ad43cb9ee8f28dc72155aa5e5b250ddd302205c740364964c82b1532d9ce8fef568715f0dbced483ebc070cc4b3055d7c5361012103ccc255c4ab12e01bd18648d8a44772eb95acaabd1bc28860dd42f58b1a87efe0feffffff024e433177020000001976a914efebdc75df8ba077e3025331a326c7b6680d5fc388ac40787d01000000001976a914e62f0c496bf083370566ee8cab485857a8ed51cb88ac3f420f0002000000011a79c88202af7f1e615396d63473eb66891fed114645b8fc8cbec1f03842e0e6010000006a473044022000be7d40dde7ac8cfb81053f73997492a3004ca0f1d94a5923a30dcbd8b7026c022060947f1daf5110a93579ec45107bd7b8e163a56d29a31903542f06d5a7593f9801210260cf599ae82575f84a78eb52e25d75e6ca34f14b6fcb1a29e7642e42bc8d8238feffffff02c1a5a721020000001976a914a197a8665ba2d3b6e468079b3338b88d43dd925f88ac00879303000000001976a914d4a601ac5b1588f242f8f287d11fdc3bf53760d388ac3f420f00020000000144c349e1e4840c7cf12138945e21e0c16313cf34b8990b4850285fcd8b0e199c000000006a47304402202e15e3318d946bfeca54188a6711c5efa7d5a511655668fa1589fd0679a9b4350220365fa497295b449102c4506891816194c2e5ea560ba6437dcc998749940226f00121023fafef1ce02fe7d09c4e83083490c7daeda339cb70a2d4d2cc9f672e068c7355feffffff023cded522010000001976a914a445ba2defe547f47fe8f8fd1c97adefb8302f1288ac00b4c404000000001976a914b7963b53e9a7f53b5e5cbd8afc6f1a7d530ef5e688ac3f420f0002000000011f77183f55eb61c356a263fd5b4779902555c1973e454308f516fea31ef5ff89000000006a4730440220496996e3600199a7faad9b1c53dbaa24836c38ca105e3797dc9d0169a911a28002207b1a8d32866cf4ed00541d77f0b2aa1ed6d38c47b85921d18f57021355cae313012102a9852bed97ca2e03e6512d06f6bd4224924ee7ed1e7fc307afd1a52ceaeefb55feffffff02005a6202000000001976a914ff68050f1d55a71c337dcdc5900512f8b4c47cba88ac0ab6557e020000001976a914bd4095355b91948ad546bed34f1111cbb55c134d88ac3f420f000200000001535223ff2ba3b45e4dab9aa2d07930d1604e1eb1a6ee778e1e00c827c45a9760000000006a47304402206f77257ade3b3cd777b77d72fc5ace6422124d4160a7a51b4057cc9a81edb31e0220187e276f2aa18f432bb3f7e1e2fbf97328ab9388fb7f2f17de11f3853afedfb1012102fe538e0e4b3a9a56c3d7399d7bba874d2732e3407a440459483ed69a85361bb8feffffff02619e923b000000001976a9144212889e98bbc585541144a2ff59fc6d815655f888ac80f0fa02000000001976a914221842a40e21052ad522c05e76c002dc3dd4c70f88ac3f420f000200000001fbac19dd06c03dc676b0da2dc48d86ba91dfbf3ddaa28784e6cb49f6686b0c4d000000006a47304402200b68bcb1a153bece4bb9e0696e2a9298265b2dfe9be913e0c5c5c26f4d2d512a0220331ab1f34f79f5bace4a47808d6073ca08880a13b0ecc40fded8ab68880a19f5012103e8e67679e4ca2cca5c683625b0313f65b1c6260f5bf9042b016a390b3f5fe7a7feffffff02002d3101000000001976a914a41a5bd2a6d9cc18e785115ae2c5037805f7b7a688acc34192ef000000001976a914d03db6f6c9f7fce937e881e69a923297b9c3b7da88ac18420f000200000001be9086b65117ceb288842be6222ea9b2823ddd4eccfde0b7d6a41f23d5131f10000000006a473044022075c0c2ca84528feff6a2522724e0286cfc7ea75b5418169befe5d60c6e42be0502204e5f62ec1df13e88e0883b3c3fd1042aad63d4ea5a6338b41aaf72052f17961901210295f34c99202b4e5034c64d9233abc92698d868ffc0574a86a79429f9e152364efeffffff0280f0fa02000000001976a91481a778780412e49f7fe4120226dd98d919c11ee688acef4a8255020000001976a91425b3ef4be67c3d89127ad5680cac84521f8b064288ac3f420f000200000001251591e663e2e6bc64e0d087dfae5ab46cc3689712e5daf466103e26d56065b3010000006a47304402200ae76eb1832dd3981d10871bf55942d810b4221f9d8701f515445bc9cd58d6b8022028ce40b5860aa46742105ca3f30493af4ef1d9e508bf0461d09ac9d8675322ce0121030a6e20e35e8632e7124229e51d595dbf65365f4edd3522ae4fba03f4552495a7feffffff02e4752e36000000001976a91406b484477ab4aea7f69a10e191d6e76ab2c3274688ac40787d01000000001976a914c0c98d7597f0de96cdfcbbecb1ebbf6d5b144d5d88ac3f420f0002000000017337c8f1981ad8f4ec9b5553be69d39c2bf4caf012c72982c2c334944b8cab41010000006a47304402204eeaad78159a5a500153d500ce39b376a994083f56bb646a19476b5cd496d11602207d89bd281b16cb383aed3bc05f59a70350be4e6bb90680cc6d7e29022d012c9c01210315590ec61237430b5a1538c4a74ff55202af8b3f2ff53fefd3b4e30fce2b576cfeffffff0280c3c901000000001976a9140e01776c7771a686a6df516225c1946501e1c16188acb63e9c81010000001976a914cb3d5e05a916f6039942a6f1ce8dcfc7fff1a46188ac3f420f0002000000016c35f584ba5a8da2b8b8aa229fabc853649095ed42039fbd474af8b7a8d79b2f000000006a473044022075f1a537c792da9edc3a5658b4e435dafd6c46f9eced135168f2ba3fe033c4da02202c6662c24d9f0fe6f9e2ad5c83edb23eb178c5e8398304fb08f610ff33d6e99d012103012b154512d96a8a9a077ef4a79c9591c00d14f89ac3d79e5e3470893e6504bafeffffff02002d3101000000001976a9145114a1315b5057a4bc0ad36e58c5e48b4df26d0488acc37bc569000000001976a91489ea505ee0952fc4932b69f9c3cb93376f1f1a4f88acf4410f0002000000019211c666e850cfbd26ddea64b49772a513abab03a93f2abff9413f38d1ab6749000000006a47304402204878cad829ab0634523a28bae802a82b7b8613d2ea388c190c75b2ff055dd35b022057a990a5e0f51223defb1f956249b54054564fbed5c8fd97cef20cb5b9ae10e9012102e8e6a748d078849b87e587329203ca6a84795b28294d12a89913c4b9b193c055feffffff02e502b445000000001976a9143d3b440dde2036061d466399446c2bfadf4091ad88ac80c3c901000000001976a914ae94a6bab593c037a1e82de85a5236d2123cc05288ac3f420f000200000001093f50aa550814b2fb2cc4060bf38ea85c205b052387d696fea4c0bfacccfdfc010000006a47304402203fd6d397029fa5cf6cb80d2fbe0bcb001e4d78902f593adb00e573fb69e582f8022067c61b34f6fd1c5a96649c80620cdbfc7bb6900209b0910ee29ffbf41db384410121031fbdba3391dadac73e83ff94217a41ade9e8460f397ae37c25e7315d0deb540cfeffffff0200e1f505000000001976a9147757fc86c30a45f5197d9fa6edd3432b241e7d9a88ac3280eb43000000001976a914b8970b4141753bba384509441b5719974653136388ac30420f000200000001c963ddada5e97ddb2fe8a45a34121883eaaa708e0f67825dd8b2b8eb2920f12e000000006a473044022031180f5ac8d811aaeb24fa0ceea7117b90385866a6e2655d4f844e36aa84828602204adc93242ef53651d2f58eb507e28a42658992e32df6e319d3906003535c8e50012103bf8adc90eef7d628f77d0ed7040877b629b188581449418ba0c080640cfbd87cfeffffff02a150c944000000001976a91423b71cfceff395bf11d53885c7e7e37bf70d568388ac002d3101000000001976a914ebaf524d4a8a28a2a70bac6c634cf43dd832c51788ac3f420f000200000001b23192f1fb3efef00093f6a6d56564c943f43f15df22766cd17ae35a6ac44992000000006a473044022064ca1532217a98f94457de2e29e8b4003a3be7d657995dbe04ea7c838efb6f7402204becc8fa226fa1660509e91c4e6184dbdea8bfd9407b8849fd9a1007aff9081701210303b9be17766944f9921d350ed37dbf91c849dc0f858a9edacc8003ff6c31261ffeffffff02801d2c04000000001976a914c3276cd2a43b4e491e98857686ab96b313b4583088aca1aae5ae010000001976a9145ba9a149bca0246685d90c3094e2a341fa5ec49f88ac3f420f00020000000177d789863894bc6b9bc52485f0f03661c28e97def6447912454a020cb2445dac010000006a473044022077393e7b3bf3e6e185b43c554f4d020e5558efd593481d050eeab28a3e9ae050022054429aecfe405aaddb35f075a6a06689bda430468981e7637258baa9cc3108d30121029120c84f71e4b4f6a3d8a329b2e2daab39dfe337ba4fdc30d3c2ddc16d9ce302feffffff0285973d6e000000001976a914946cd4aadf9ec21bf7a74173be5099f5b22e26ce88ac40787d01000000001976a914917c475f3bbb10154d216203343d23b2c730fa8788acfc410f00020000000160d795916a398d90e0997b4c201d2a29f03e4a4e28a5f3c8744d57670ba8eca0010000006a47304402200b6d89c2d6c91444b5d6a7f3f78be96b43ad85eb47a87dc3cde87de44240efd502205f28478b8285023c84f507b4756edbb5b576d5d65437c152619f5145de0335920121020eaadb79bf25576ed53321b587dc5084f4184bb6c5ba6c2768081cf06ac61a80feffffff022d67ec05010000001976a914abe5b0d41fd02e94246f2a2615fdcc4dc5db5ece88ac002d3101000000001976a91417cb9a0b1e90122518863aea9d137eb445774d4e88ac3f420f0002000000011b76d3d5289cdd57d7d9794a33c63046b17fe2e108b4877a104f3d96735b4810010000006a47304402201e15f8c97f096f50c6cfb1f259ae69ae4377d23fe7af56bb345fa4dee8bfd18802206ec13304a3b61c5bdd4437e6d0a4d3aa1ef733f6a46c31e296f231f0d9e76b4e01210222c6f6dd5336525501ccbddc97cef72215c8a87b1d0be3a654fd6c4cacf6b472feffffff0200b4c404000000001976a91417afa2efd4b01b80ac3fe8d473015607ba10533c88acf4658bba000000001976a9145c9c54a80ca2d9c2c47c6f4f83a77905464afc0788ac3f420f000200000001b5730cc09fd963d4562036619b3f1389af7c8f6b35cdd330b254a58a3de52775010000006a47304402204ad16585f71e0401ed90d43e5e4d42f51346ce5cf1e7c9bce6f6c584c4c2ff8102200869981eb9ea92f898d7679e25c11a3702429034178b4e8fcffe374c616b4774012102636e504aa7e170470c8b3d1ba1d47db464e8356197ec5f97e95e7b9b53168de9feffffff0280f0fa02000000001976a91412ab983d5851a1fdfc8e70746c0a1d34d46b50a088aca7075eb6000000001976a914cbd06fdb3f1db95a74f998e54f52efb1eeadacef88ac3f420f000200000001e2885a3313a450e74cea1efa97d0eb263b6828e792420bf2026bd8669e587816000000006a473044022060459606175a9cd3750f266e88a042264bba27f4a29141e2d8da3a1e23c46a7902206b9bb87c4675267d7829fc58224633f7f8e510a679cca27b39d1a7177e4d00b1012102bea8ee1ccdca0a54bb86024fb1de4907c21cef188f6426bd0b42d28be8a37549feffffff02801d2c04000000001976a914fc160a2b721d3bd90d10fce41a92204ef438e43b88ac645f190a000000001976a914f687d7ddacbba9acbde82cfeee75e15eea63cf4988acff410f000200000001d83aba8068701901801bc99ed982ac77b9d12666d89d306f9ee8f7c495aed35b000000006a4730440220209a18d6a4a0ad79080a073b0cc7c8a8dbac2ed4dc50ca9757ee2e900b3ef57102204386708832a05edf11b0eaccfd34e82432a9c21f6dfaf6896642ff18fe99f465012103d745a40ff6d15a0513d9d90616bc50869afa8eca2493d7ae94034a6709735867feffffff02002d3101000000001976a91487d71219d723411f6ec417b7f4b8dea960dba14e88ac3de87b77000000001976a9144fa6ad290e29e565d942a6ddfebc9b5d176c5af988ac3f420f000200000001989056ad4d4c5aae463b771bacfb1a76cb65f1f678d701d2e62e656b66a4cd6a010000006a473044022001c590fb0c59e061bc76c6a31b20d375583ef23e3692993eab8cb7229adc5921022041ae5ba637d217a4e49d28b170dfe9e1663e66e01cce2ca2853fc6587ffc617a012102623b035f79b5b96c6ce77019ccce144ba99bbcc553a0440094b969d7d9567684feffffff02cbe86e23000000001976a914d10a2cfe50cf0bc7e8417fd3c110417ec1d7671188ac00a3e111000000001976a914e9a99f3b2cb538ef70fb714519d1ad3f11a15a0e88ac3f420f00020000000134f0960a4ce8c2d80636a3895917475a44f495f3a0faa0ac660749492ac1ebd3000000006a47304402203f4dbc9c650554d9730ee7857eb15a05ba0b0959d532d54272050e7a237340b002201cd67e61c36e73059aeae5fd174416be8a34463d8811cb08ec8b698f93d0bbf8012103f37489b46151859443ba0996ccf24905a56ddc3725d85991241fde1b67f4506efeffffff02c784a67b000000001976a914379f5407b2e1cc8e70daeb2ae2251e09a883432f88ac80f0fa02000000001976a914b2cc76c1ac9acc7d88a058f9d7be30db46dbdb2e88ac3f420f000200000001e0a18d1109c511f7c72ba430767bb3ab0c884d2e1c3deb8ed661fd4e220e6ced000000006a473044022002c135a12623c7c6933ddd254dab2212f95ca7c94f50733a1d5cdb55ad7aca810220129756166f1493f70bdc96857f14cfccf35e74d42b6366c84a82dd0ea328e025012103c7a24de682130a2c6669743baa7b4447f109097193cb0598b3d3948f58dd99fcfeffffff02d1e9baf6010000001976a91466fb6a3a2b065dfa48a64d7e0333d89f69b3c7ad88ac002d3101000000001976a914917c475f3bbb10154d216203343d23b2c730fa8788ac3f420f0002000000016eac093e0836d8892a8559b1936db4bd06d3239f4c25d8f057558e35a4f56986010000006a473044022002f9cfad60a3a7458e94303809fdfd685e8a40f1adf3e89d52d1cc42b3d2c63c02202d8fd1eb8b8dcfee35809355c76074656dfba6aa6f8990ee0c435c97fa53e084012103153a9196b39ea911316d327dadf9db9c2f03ebf25d322823970914080240a1e2feffffff0200e1f505000000001976a91447926291dd6323d1ee2b1b5155019b9379b5896c88ac4e1b9957000000001976a9149d4ac1600a44358f88d0b3318856a26261d8904a88ac3f420f0002000000013538a24667e63a78a6b0b7fe67e7e58327150bf399b4fac10a0a8d44fa9cc4d9000000006a47304402203240ed80b80b3294fb6eb5bab3c297258a706488911d4ababbfab5f8a4383ce0022042a84cef5987a1c7ff8ebd9f8b69a250b990bcea63ca84ceb8035e704dee7feb012102ade5008210a388027940d41788b54db4dd3742ab907b704ecbc241dfaece0ec0feffffff02cf192eed000000001976a91407de97538982554fd6f1d18043edca4671aca3e388ac00c2eb0b000000001976a91487d71219d723411f6ec417b7f4b8dea960dba14e88ac3f420f00020000000173c045286342ddefec5ed9b31337958750c1cacadac24af2f15dbaa29ec397fb000000006a47304402201039a5fb4d3f836017fdf229d74588fe348daa207376b28b5aebec82d0a11eb6022058aecf12329df0956565f0a99a237123bc4113332b6b9a41876726be50ce56b8012103345cfc8994b6460b0d57af9bff58d4e8027b0b668a1762d7cd066520536ad7aefeffffff0258882fd1000000001976a914c0474ed5cf12503a421eb0176ceda4c9e75540c788ac00879303000000001976a914a6941273bb04f997321cabcac86b8ec53add8f2b88ac3f420f00020000000145a1dc50ba3e887298bf5157934e60d00f0132ce5c80faf4d24e5b6fd663076d000000006a473044022057a72f5f1e2afca503252d15b1f288c26838e7d2e11c9656e4f4abf164b643b3022054eea6ab25699c4d8219754d639ea8c42280bafae0f60cf76e26fb6f07b0ce73012103f95849aa944b8609a01c31a9641bb5baac18a5e459e315a8566f71928030279efeffffff02d500b04b010000001976a914e855a384898a61a3c4f39cad5f8bbf6c3bade3ad88ac005a6202000000001976a914077bf84c9c0674373b0e4cb1c4635db30482e52e88ac3f420f0002000000018869e8594e650d1136669463f2d70f3f228159f277abee1854259787ba9097ad010000006a4730440220488694424dfe324a4e08c6f0642dcf29f619355b5dfcad748ef34753ee583a4402204db50e3e538c185f28359a579450a7edabf93722cdfa949b33c1f808822042a8012102ef7a60827413270ceb34269e6462bced795ed1e17934fe982743a76e8ac8042cfeffffff0280f0fa02000000001976a9140b3f86c513162437896c4f12fc1c9213cc39665f88ac6c733fb7010000001976a914d7d2216bc3de8853b46c538f3e445f948172cb9288acf6410f0002000000012ee004ba551b0f6c5881dd6466511cf076dabdf265302fa781c60c9776e802d3000000006a47304402202753bd2d723465734626e640f7266b90c07437d13e0e3d1d959035ac1ca93b63022025a8e160123f608bb5cf019089ab7761a028fd058ae9d0f14e99fe99fb497a3c012102f5a1b8fa4c68cd45be036ccad1b35e3126d6cb17e7d40d5425cd4b75e75edcb6feffffff02005a6202000000001976a91461764d102c9c8ccb9c5c46ceaadd4649dc7dc06688ac47a58e6b000000001976a9148917b3d017de8295a385dd5fa3a148b8e339b68d88ac3f420f000200000001156527980653b87bd26237dc4a64746dc33430ab7b8e9369b618f0af55a53b58010000006a47304402205c71ced80c546bc4374daa4dbd9fb7217f47a6864daced9dd883dd437f0952e00220018a050996bed68c4390d00bb4e0c0c52985bd704deb7a10e3d6c932f693d43401210342a5b61df08878cdbe58ddcb13cac5f75a02693f62e86deedbdbd6810ccc31c2feffffff02dca19a73000000001976a9141c8e662cccc3104d859ad1ddbd96f8e02247ea4288ac002d3101000000001976a91400d122c1939c23d2cdca3a747a0aa882244f1ae688ace2410f00020000000108cd6bc73b04bf31af48a5b58c044feaed4d1068e9bf6223ec1aee9aade82ad3010000006a473044022059e3dd0e64a8ff71755f00b001a16f200dc2e523f002455fe52467be8c52af7a02206f34ea45b6d264375a63e845820c0d394318f8875d57418cc83c68ee02503e46012103e7f5306355f2af23c70e92bd445464a9a71b77c57fadef1580deeb588e5caa20feffffff0280c3c901000000001976a914519ab694968faaa846e3698f4e30de6e4f6ae01088ac3b9e9dd8000000001976a914ac8ae62bf6790ca62190aa7df82c9b201572f13a88ac3e420f0002000000018f69dbbc6b8bbc8d4a9aabbb7ee9966460b2515acccc7301c2aaf9f47531b57b010000006a47304402203b2c7910639c826decf51f2921e0de043233bab563b9e2d640b5419a29d0e05e02200eeb6a78ae975aa9f6d3431f2736eeae059271bf4fa708eee3dd91d61cb9b846012103c6f912d8b7bb28bb742750a7e048d19d41ae86f890bed61e0d6eda5bf9235a89feffffff0282d9728b000000001976a914bc70fa8d5a078e14e6fbebbce73a02174729e4e288ac80f0fa02000000001976a91456ccdb31909e92ba6543fdb8d8b5deb1867ad01f88ac3f420f000200000001eb4d5e23628a203a47c406c6b70c5b5d74807872f7bd2762f0c39d5ee52542a8010000006a47304402206ce732134df30784693346cba7b98ea72d8f3e391042a02b1dd835a336dc19f2022047a2bb15838f96ef674128d7187a6f2037b3ca8a611ab801381599897f0ecebf01210223a9d9ffb37b0a2b0856b6523fc9a696b7adfbc372a02d788a148fb5cd8b2895feffffff0240787d01000000001976a914b9a0fc15d2df74ed478d65138a974f5a1e28920a88ac63e99568000000001976a914c2b6c66e140dac3443248e51780bf3b4f5009d3488ac3f420f000200000001d9ab6f289ffcef735fbbafcb8cde76c3b7ecf444cd18c1aee53c16372f2f1455000000006a4730440220254464e5d309f5e8c5dc73c5c45dc13113d8286970612fe8da3e0b8d6c178a4b02202c3022ddcce115fed34ac7e05282688cc9bb37ea6e0958d5f18bddda6089e6970121038bb5ef0c9383d3fa3e343f28e04314a3fe1f2452386ef765e1e0ee67b7565557feffffff023fe49a81010000001976a914403a01203bc8282580c33290d25f83b36704878788ac002d3101000000001976a9146016da3260b4faed60b061266d151d0f3822681188ac3f420f000200000001dceb67c6b96e707f655e6ccdae161de16341fcab999349db37a6d942a0bd48fa010000006a47304402202c99bfe1de64617cf290489371ac3bf30c124a7f2299bf7d7d59df176156c81602206e5b12a75aec80b1a441ed8be41568124f5472430bff06bb917a614700cb4652012103dc1aa2280bc5af099d287a2e5c5a6598ba70ae5113cd82d9e68ff92b9abec3c5feffffff0280f0fa02000000001976a91409cb24620ee27f32951f817d62b5622acb6d4fc288accc47c42b010000001976a9149969941588f6a68877ad692d2f8663232bc4fe1d88ac3f420f0002000000013c78c416df30c0ccb3fc07f95aa9f1d257382ded075388cd3f439c960844b3b6010000006a473044022004d23a3bb32c9c9bbcdc73f572cc43b6c0ffb8b935ddae0f8b26cca2954b85440220093457e877ea3e7702df0674f5d28a7c51cc93576e1b670e900ee0d520e57c9f01210209d326afaae10398e05332f3f0eb170f2ca15ef0bc979c8f6bdd5c53b84ad56ffeffffff020acc714d010000001976a9147e0117b74b3596814e817783f6f33efd1e5c762488ac40787d01000000001976a914d0bd0ded93f5f062ea6fd2b873e820092f1d263688ac3f420f0002000000016eda599406b3dc6d8f4914c63bf050d05ca68cb4183e09697cef76b4e3e591ac010000006a47304402205b86ff987f1b83c50d5182171986dba2babeb8e22299cbb0d3a0720657188ed402204658b4cecb721cd90023a152030bed9c4393581772e3a10c48b6bd8128398686012102d6c99d3fc3c07bfb1584a007c9acc9771401d2cca331a97db44ffe9ec7c03edefeffffff022abeb85e000000001976a9149d4ed2cac12dba61d871faa95fd71dc70a7b85fa88ac80f0fa02000000001976a914a0453252a56fda98509df71b18de52e3cdd28ae988ac3f420f000200000001983773132d3a22ed542e712702a3efb018816ea6cfbada5aecb12cbb726b787f010000006a47304402204a333541094a2d71265177f926ecab6cefe0d702f228af8d420baaf18a56f24802206bb4283b6214b76e566a6f3ce903609d106eb583e550d02d41c93da4310734eb012102f9a0ec7f610b0734e56276bc2f4d886039cbc067a6fb75d3852af13fd821c483feffffff0284a0116a000000001976a91409247c03630587751fb18929309215c5a89b3f3b88ac80d1f008000000001976a9148594c8bacf57269593ab18b3fd70a3021078a97388ac3f420f000200000001fb2084c2096921b7d18d56a0c83443c90ad56d5fe0dee0b6827c335edcffd4aa000000006a473044022005f79a5f0a92888800e6f541aee5db4b2dc339800b78f2dbdab0849b74989dc302204c3f41aec3bbdc64bbcdf97a512420d0d15a8fec481a950a0bbfb12da3dceb3801210237f61b9c077b9e5221ed19294480d7f69435ccc7f6e5094a96469a7355bd5165feffffff02e90af656000000001976a91422b4d077d40a7df7723923757da98a5016169be588ac40787d01000000001976a914a6c2a1816d240b7b2129b048ea30f67ac79dbea688ac3f420f00020000000199249d8198e121e490b0f94bdcc5bd20eec4780587fa413c2784477dab60d5c9010000006a473044022001c9726d3f1d3a837fd6a6eb9b3a4935f988681ae201027b1ac17dfe5e2381af02203cc419bd791a99a63a886b006536f4825f7605f925db7806d8497c73474c84090121024f3af9c24b68ca6e64490c77a19444beffd656a1c4d1b57a07d6cfdd7dbeb44dfeffffff02f63d8055000000001976a914a79fad62123d14c7f0742ddb198fc951db66190f88ac80f0fa02000000001976a91463777aab074147381481a0028994662bcaeae8d988ac3d420f000200000001dd0633ba64c3d9e42bf73b752481508f5747680d84a63318387fc82ebcdd1298000000006a473044022004bc2f23cfe2984d1a36a35ea86be26bee8fc7cdaef369dea254f51b4d48273702202d595aea1055eaa26eaabf4f1b14ce6aa295ff9f5e2a25e6554368fe933d9f540121033107ae3ee35bec92751f1684cb4b0d23a714ec6d3c5892c1521d3b9b6817dd6afeffffff025a5df5b6000000001976a914d3812b740e4af2eb365161a13412a094bac25bb288ac80d1f008000000001976a914bd9cf8747ac0750a5bb13cf67ed63b29a2a6244588ac3f420f0002000000015b578193c83e7fb0260ffa2bcdda75e40eb1e76e3cdaedf51053258c8253d099000000006a47304402206fb0cbf1d5ab6ccb0eecf18c2fa01431f9a760f9af9f672eb34a439448ce7d9e0220026553240c8b50339b5ea8535249924520eead803a47851f21903aa72a09ad2d0121026116cea6b5a68a5209978331acb0d44ea3bccfd676080417b75ee45a397209b7feffffff026b329cec000000001976a914244a19da46c693db52dff42cd83766ca354d61c888ac40787d01000000001976a9146e42e400cf412a71433fcaffec06a7cb6bbb3e9d88ac3f420f000200000004be431bb99efa8814b91ca713ae3d1ca875d99353a9170f71ae418b7367b36b53010000006a47304402201f398dda1b833edf50ec9bc50736824eec9d7fe92a71e06d7c68179ed07f07aa02200e06264cefa9b3cb8348c9741d4eb92adcccd6dee216b488f5ec01b8170692b001210279ad49a2a9abdd46bb77f1a677bc78ba466df0d6c4f8f2d8d86173b7e2cfb8f1feffffff19ee11654cdeaa9ec1bec904d6573ce4ecb597a765b26861f450bb37672c5c23000000006a473044022062df81131d415b8683fa2621cf0c0f383d0dc77c329880d036fdfb8313f6f20602200277f265d6ea5b19a1eb0078c9d8656b31da783f3a5943e4691439a1c742333b012103f64373f4cd6817c270fbee9629bb19da5c3c0a9725a9c3a6c1df2070394a6a4ffeffffffd27c7d8719fce22f4c84245efb5d944e8fab4fe86a1647e215956c6329a0b91e010000006a47304402207e7d1af66ce867e73e61fad64f895391bb8ab164e052170fb746e05488c28b21022072039509355a346d3784b1ad9eb2da203924348bc14853217d0ecbc00e4fd29a012102a88b228d6a8413c1472dacc76ec72e29e8039ad9f6f59023b493c8dfe5215f63feffffffda646c69bef035f7343c87398a1ba8515e79468545f0aebc27a6232ee858bd10010000006a47304402206b81c3771a7f354990afb458f9c2a8676102edf7e128935dd77cc50b72852e0a022052ff30d6898beae9b9bf63a92464a3402fdd7d94d52a80a926d9cb4b74e4701b012102f18c0ec4871f611a74c99c7dba4158e3bedb472dbb489595436cb85b599e7125feffffff0280c3c901000000001976a914340a88ef739fed9423669bee1cf9330f16b7aee688ac33b41f00000000001976a914e8a47b0011f97baa2717b0138a31b13bf165590e88ac3f420f00" +} From 8c56c0f6918e9bdb859d371a48a84ff3a138f321 Mon Sep 17 00:00:00 2001 From: vertiond Date: Fri, 4 Nov 2022 19:28:28 -0500 Subject: [PATCH 121/386] vtc: add mainnet peers, testnet and block tests --- electrumx/lib/coins.py | 20 ++++++++++++++++++++ tests/blocks/vertcoin_mainnet_1849000.json | 14 ++++++++++++++ tests/blocks/vertcoin_testnet_545000.json | 14 ++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 tests/blocks/vertcoin_mainnet_1849000.json create mode 100644 tests/blocks/vertcoin_testnet_545000.json diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index c305d9e..9ead9f7 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -1851,6 +1851,26 @@ class Vertcoin(Coin): TX_PER_BLOCK = 3 RPC_PORT = 5888 REORG_LIMIT = 1000 + PEERS = [ + 'electrumx-vtc.cryptoverted.com t55001 s55002', + 'electrumx.javerity.com t5885 s5886', + ] + + +class VertcoinTestnet(Vertcoin): + SHORTNAME = "TVTC" + NET = "testnet" + XPUB_VERBYTES = bytes.fromhex("043587cf") + XPRV_VERBYTES = bytes.fromhex("04358394") + P2PKH_VERBYTE = bytes.fromhex("4a") + P2SH_VERBYTES = (bytes.fromhex("c4"),) + WIF_BYTE = bytes.fromhex("ef") + GENESIS_HASH = ('cee8f24feb7a64c8f07916976aa4855d' + 'ecac79b6741a8ec2e32e2747497ad2c9') + RPC_PORT = 15888 + PEERS = [ + 'electrumx-tvtc.javerity.com t15885 s15886', + ] class Monacoin(Coin): diff --git a/tests/blocks/vertcoin_mainnet_1849000.json b/tests/blocks/vertcoin_mainnet_1849000.json new file mode 100644 index 0000000..9bc0278 --- /dev/null +++ b/tests/blocks/vertcoin_mainnet_1849000.json @@ -0,0 +1,14 @@ +{ + "hash": "6801da3f2c22df0f727cd022e1d6d741cff728f525c8b74ad27fc7b5504b7f7e", + "size": 280, + "height": 1849000, + "merkleroot": "5ebe6edf2496dce7c5791df2d02984c4f61f4a5ecf7887192dbf8884cc5a67cf", + "tx": [ + "5ebe6edf2496dce7c5791df2d02984c4f61f4a5ecf7887192dbf8884cc5a67cf" + ], + "time": 1664373939, + "nonce": 13185771, + "bits": "1c012d9d", + "previousblockhash": "4f545a8d85c48449312417742233d8f572e133bfe2a61e5a611740c56eeb7090", + "block": "000000209070eb6ec54017615a1ea6e2bf33e172f5d83322741724314984c4858d5a544fcf675acc8488bf2d198778cf5e4a1ff6c48429d0f21d79c5e7dc9624df6ebe5eb35434639d2d011ceb32c90001020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff1f03a8361c04b3543463088100931d000000007a657267706f6f6c2e636f6d0000000000020000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf9807c814a000000001976a914e83d9c3ea18fc88a3d8182a5677395f3e5b04e6a88ac0120000000000000000000000000000000000000000000000000000000000000000000000000" +} diff --git a/tests/blocks/vertcoin_testnet_545000.json b/tests/blocks/vertcoin_testnet_545000.json new file mode 100644 index 0000000..1f2763a --- /dev/null +++ b/tests/blocks/vertcoin_testnet_545000.json @@ -0,0 +1,14 @@ +{ + "hash": "be6d4ea2d3087000c0b8f95bcfe891ff56b4af76ed0b2a8e09331a4b2e01a2c8", + "size": 268, + "height": 545000, + "merkleroot": "1ce5b13dba69366f693a52c24c605b3574f9cb0c23fa62f27256d8086c7310b1", + "tx": [ + "1ce5b13dba69366f693a52c24c605b3574f9cb0c23fa62f27256d8086c7310b1" + ], + "time": 1664377874, + "nonce": 300006115, + "bits": "1e07fd44", + "previousblockhash": "66ee036202fec366656dc2c2fd0c5638d5ed88f1991e99bd1c55e13bd9d14da5", + "block": "00000020a54dd1d93be1551cbd991e99f188edd538560cfdc2c26d6566c3fe026203ee66b110736c08d85672f262fa230ccbf974355b604cc2523a696f3669ba3db1e51c1264346344fd071ee3bae11101010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff1503e85008000f2f503253482f56657274686173682fffffffff0200f2052a0100000017a914f06ef13b9ffd1c56f7e40f61b17b6fec6c860907870000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000" +} From 178a873b54c554bcceca628c68c3776d9000da6a Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 10 Nov 2022 17:26:53 +0000 Subject: [PATCH 122/386] coins: rm obsolete v2 onion addresses from default peers closes https://github.com/spesmilo/electrumx/issues/140 --- electrumx/lib/coins.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 9ead9f7..628ebb4 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -643,8 +643,6 @@ class Bitcoin(BitcoinMixin, Coin): 'electrum.vom-stausee.de s t', 'electrum.hsmiths.com s t', 'helicarrier.bauerj.eu s t', - 'hsmiths4fyqlw5xw.onion s t', - 'ozahtqwp25chjdjd.onion s t', 'electrum.hodlister.co s', 'electrum3.hodlister.co s', 'btc.usebsv.com s50006', @@ -909,7 +907,6 @@ class BitcoinTestnet(BitcoinTestnetMixin, Coin): CRASH_CLIENT_VER = (3, 2, 3) PEERS = [ 'testnet.hsmiths.com t53011 s53012', - 'hsmithsxurybd7uh.onion t53011 s53012', 'testnet.qtornado.com s t', 'testnet1.bauerj.eu t50001 s50002', 'tn.not.fyi t55001 s55002', @@ -1020,7 +1017,6 @@ class Litecoin(Coin): 'electrum-ltc.wilv.in s t', 'electrum.cryptomachine.com p1000 s t', 'electrum.ltc.xurious.com s t', - 'eywr5eubdbbe2laq.onion s50008 t50007', ] @@ -1195,7 +1191,6 @@ class Namecoin(NameIndexAuxPoWMixin, Coin): '82.119.233.36 s50002', 'electrum-nmc.le-space.de s50002', 'ex.lug.gs s446', - 'luggscoqbymhvnkp.onion t82', 'nmc.bitcoins.sk s50002', 'nmc2.bitcoins.sk s57002', 'ulrichard.ch s50006 t50005', @@ -1294,7 +1289,6 @@ class Dash(Coin): 'electrum-drk.club s t', 'dashcrypto.space s t', 'electrum.dash.siampm.com s t', - 'wl4sfwq2hwxnodof.onion s t', ] SESSIONCLS = DashElectrumX DAEMON = daemon.DashDaemon @@ -1897,7 +1891,6 @@ class Monacoin(Coin): 'electrumx1.monacoin.ninja s t', 'electrumx2.movsign.info s t', 'electrum-mona.bitbank.cc s t', - 'ri7rzlmdaf4eqbza.onion s t', ] From 75d0e25bf022355941ae4ecb4d5eba41babcf041 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 10 Nov 2022 17:38:34 +0000 Subject: [PATCH 123/386] coins: add some default peers for bitcoin and testnet --- electrumx/lib/coins.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 628ebb4..2edf706 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -653,6 +653,15 @@ class Bitcoin(BitcoinMixin, Coin): 'electrum.jochen-hoenicke.de s50005 t50003', 'vps5.hsmiths.com s', 'electrum.emzy.de s', + 'electrum1.cipig.net t10000 s20000', + 'ulrichard.ch s', + 'hodlers.beer s', + 'v7gtzf7nua6hdmb2wtqaqioqmesdb4xrlly4zwr7bvayxv2bpg665pqd.onion t' + 'qeqgdlw2ezf3uabook2ny3lztjxxzeyyoqw2k7cempzvqpknbmevhmyd.onion t' + 'kciybn4d4vuqvobdl2kdp3r2rudqbqvsymqwg4jomzft6m6gaibaf6yd.onion t' + 'explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion t110' + 'v7o2hkemnt677k3jxcbosmjjxw3p5khjyu7jwv7orfy6rwtkizbshwqd.onion t57001' + 'nf365b5sbzk5j4jreimskffwnfpka7qtamyni5doohoom3g63o5tldad.onion t' ] @classmethod @@ -911,6 +920,11 @@ class BitcoinTestnet(BitcoinTestnetMixin, Coin): 'testnet1.bauerj.eu t50001 s50002', 'tn.not.fyi t55001 s55002', 'bitcoin.cluelessperson.com s t', + 'blackie.c3-soft.com s57006', + 'electrum.blockstream.info t60001 s60002', + 'testnet.aranguren.org s t', + 'explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion t143', + 'electrum1.cipig.net t10068', ] @classmethod From 9ebbfc446068d1da285041e1749a847a318b3b5e Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 27 Mar 2023 16:31:27 +0000 Subject: [PATCH 124/386] protocol docs: clarify mempool.get_fee_histogram closes https://github.com/spesmilo/electrumx/issues/209 --- docs/protocol-methods.rst | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/protocol-methods.rst b/docs/protocol-methods.rst index 29d6e8b..e08afcb 100644 --- a/docs/protocol-methods.rst +++ b/docs/protocol-methods.rst @@ -766,12 +766,20 @@ pool, weighted by transaction size. Fee intervals may have variable size. The choice of appropriate intervals is currently not part of the protocol. -**Example Result** + *fee* uses sat/vbyte as unit, and must be a non-negative integer or float. - :: + *vsize* uses vbyte as unit, and must be a non-negative integer. + +**Example Results** + +:: [[12, 128812], [4, 92524], [2, 6478638], [1, 22890421]] +:: + + [[59.5, 30324], [40.1, 34305], [35.0, 38459], [29.3, 41270], [27.0, 45167], [24.3, 53512], [22.9, 53488], [21.8, 70279], [20.0, 65328], [18.2, 72180], [18.1, 5254], [18.0, 191579], [16.5, 103640], [15.7, 106715], [15.1, 141776], [14.0, 183261], [13.5, 166496], [11.8, 166050], [11.1, 242436], [9.2, 184043], [7.1, 202137], [5.2, 222011], [4.8, 344788], [4.6, 17101], [4.5, 1696864], [4.1, 598001], [4.0, 32688687], [3.9, 505192], [3.8, 38417], [3.7, 2944970], [3.3, 693364], [3.2, 726373], [3.1, 308878], [3.0, 11884957], [2.6, 996967], [2.3, 822802], [2.2, 9075547], [2.1, 12149801], [2.0, 16387874], [1.4, 873120], [1.3, 3493364], [1.1, 2302460], [1.0, 23204633]] + server.add_peer =============== From 32a149500ebbfb67249c00160ad3f928627c2126 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Mon, 27 Mar 2023 17:05:39 +0000 Subject: [PATCH 125/386] maybe fix readthedocs build --- .cirrus.yml | 7 +++++-- .readthedocs.yml | 13 +++++++++++++ docs/requirements-docs.txt | 1 + 3 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 .readthedocs.yml create mode 100644 docs/requirements-docs.txt diff --git a/.cirrus.yml b/.cirrus.yml index 032d69b..10c882a 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -65,11 +65,14 @@ pycodestyle_task: - pycodestyle --max-line-length=100 electrumx/server/*.py electrumx/lib/*.py *.py build_docs_task: + # note: this build task is not related to read-the-docs; it is only here to sanity-check the docs can be built. + # For the read-the-docs build, see .readthedocs.yml + # and the webhook set up in https://github.com/spesmilo/electrumx/settings/hooks container: - image: python:3.8 + image: python:3.11 cpu: 1 memory: 1G install_script: - - pip install Sphinx + - pip install -r docs/requirements-docs.txt script: - sh -c "cd docs && make html" diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 0000000..c20ab16 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,13 @@ +version: 2 + +build: + os: ubuntu-20.04 + tools: + python: "3.11" + +sphinx: + configuration: docs/conf.py + +python: + install: + - requirements: docs/requirements-docs.txt diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt new file mode 100644 index 0000000..2806c16 --- /dev/null +++ b/docs/requirements-docs.txt @@ -0,0 +1 @@ +Sphinx From 427c2c1f5e9f76aa4e96b1cb6d4963c4a5d83f90 Mon Sep 17 00:00:00 2001 From: st3b1t Date: Sat, 13 May 2023 23:11:13 +0200 Subject: [PATCH 126/386] dockerfile --- contrib/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/Dockerfile b/contrib/Dockerfile index 11cbe37..f308495 100644 --- a/contrib/Dockerfile +++ b/contrib/Dockerfile @@ -1,7 +1,7 @@ # example of Dockerfile that installs spesmilo electrumx 1.16.0 # ENV variables can be overriden on the `docker run` command -FROM python:3.9.4-buster AS builder +FROM python:3.9.16-bullseye AS builder WORKDIR /usr/src/app @@ -15,12 +15,12 @@ RUN python -m venv venv \ && venv/bin/pip install --no-cache-dir e-x[rapidjson,rocksdb]==1.16.0 -FROM python:3.9.4-slim-buster +FROM python:3.9.16-slim-bullseye # Install the libs needed by rocksdb (no development headers or statics) RUN apt-get update \ && apt-get -y --no-install-recommends install \ - librocksdb5.17 libsnappy1v5 libbz2-1.0 zlib1g liblz4-1 \ + librocksdb6.11 libsnappy1v5 libbz2-1.0 zlib1g liblz4-1 \ && rm -rf /var/lib/apt/lists/* ENV SERVICES="tcp://:50001" From 3326b11c4d34bef393824b4c70bcddffe800b9fc Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Sun, 9 Jul 2023 22:08:05 +0300 Subject: [PATCH 127/386] chore(DASH): switch from no longer maintained x11_hash to dash_hash --- .cirrus.yml | 2 +- docs/HOWTO.rst | 4 ++-- electrumx/lib/coins.py | 28 ++++++++++++++-------------- setup.py | 2 +- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index 10c882a..8868ca1 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -27,7 +27,7 @@ unittests_task: # hashes and other altcoin-specific stuff - pip install tribushashm - pip install blake256 - - pip install x11_hash + - pip install dash_hash - pip install git+https://github.com/bitcoinplusorg/x13-hash - pip install xevan_hash - pip install quark_hash diff --git a/docs/HOWTO.rst b/docs/HOWTO.rst index 4ec30fb..2681e27 100644 --- a/docs/HOWTO.rst +++ b/docs/HOWTO.rst @@ -25,7 +25,7 @@ DB Engine A database engine package is required; two are ================ ======================== Some coins need an additional package, typically for their block hash -functions. For example, `x11_hash`_ is required for DASH. Scrypt coins +functions. For example, `dash_hash`_ is required for DASH. Scrypt coins require a Python interpreter compiled and/or linked with OpenSSL 1.1.0 or higher. @@ -444,6 +444,6 @@ You can then set the port as follows and advertise the service externally on the .. _`runit`: http://smarden.org/runit/index.html .. _`aiohttp`: https://pypi.python.org/pypi/aiohttp .. _`pylru`: https://pypi.python.org/pypi/pylru -.. _`x11_hash`: https://pypi.python.org/pypi/x11_hash +.. _`dash_hash`: https://pypi.python.org/pypi/dash_hash .. _`contrib/raspberrypi3/install_electrumx.sh`: https://github.com/spesmilo/electrumx/blob/master/contrib/raspberrypi3/install_electrumx.sh .. _`contrib/raspberrypi3/run_electrumx.sh`: https://github.com/spesmilo/electrumx/blob/master/contrib/raspberrypi3/run_electrumx.sh diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 2edf706..9643718 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -1311,8 +1311,8 @@ class Dash(Coin): @classmethod def header_hash(cls, header): '''Given a header return the hash.''' - import x11_hash - return x11_hash.getPoWHash(header) + import dash_hash + return dash_hash.getPoWHash(header) class DashTestnet(Dash): @@ -2402,8 +2402,8 @@ def header_hash(cls, header): Need to download `axe_hash` module Source code: https://github.com/AXErunners/axe_hash ''' - import x11_hash - return x11_hash.getPoWHash(header) + import dash_hash + return dash_hash.getPoWHash(header) class AxeTestnet(Axe): @@ -2544,8 +2544,8 @@ class Pac(Coin): @classmethod def header_hash(cls, header): '''Given a header return the hash.''' - import x11_hash - return x11_hash.getPoWHash(header) + import dash_hash + return dash_hash.getPoWHash(header) class PacTestnet(Pac): @@ -2656,8 +2656,8 @@ class Polis(Coin): @classmethod def header_hash(cls, header): '''Given a header return the hash.''' - import x11_hash - return x11_hash.getPoWHash(header) + import dash_hash + return dash_hash.getPoWHash(header) class MNPCoin(Coin): @@ -3156,8 +3156,8 @@ def header_hash(cls, header): import xevan_hash return xevan_hash.getPoWHash(header) else: - import x11_hash - return x11_hash.getPoWHash(header) + import dash_hash + return dash_hash.getPoWHash(header) @classmethod def genesis_block(cls, block): @@ -3303,8 +3303,8 @@ class Bolivarcoin(Coin): @classmethod def header_hash(cls, header): '''Given a header return the hash.''' - import x11_hash - return x11_hash.getPoWHash(header) + import dash_hash + return dash_hash.getPoWHash(header) class Onixcoin(Coin): @@ -3328,8 +3328,8 @@ class Onixcoin(Coin): @classmethod def header_hash(cls, header): '''Given a header return the hash.''' - import x11_hash - return x11_hash.getPoWHash(header) + import dash_hash + return dash_hash.getPoWHash(header) class Electra(Coin): diff --git a/setup.py b/setup.py index afba5d9..927f504 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,7 @@ def find_version(): 'groestl': ['groestlcoin-hash>=1.0.1'], 'tribushashm': ['tribushashm>=1.0.5'], 'xevan-hash': ['xevan-hash'], - 'x11-hash': ['x11-hash>=1.4'], + 'dash_hash': ['dash_hash>=1.4'], 'zny-yespower-0-5': ['zny-yespower-0-5'], 'bell-yespower': ['bell-yespower'], 'cpupower': ['cpupower'], From 0ec9d8217b12fb6cddbf739df5905cacd1fc972f Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 11 Jul 2023 10:10:58 +0000 Subject: [PATCH 128/386] maybe fix build_docs CI task was failing with: ``` sh -c "cd docs && make html" Traceback (most recent call last): File "/usr/local/bin/sphinx-build", line 8, in sys.exit(main()) ^^^^^^ File "/usr/local/lib/python3.11/site-packages/sphinx/cmd/build.py", line 313, in main locale.setlocale(locale.LC_ALL, '') File "/usr/local/lib/python3.11/locale.py", line 626, in setlocale return _setlocale(category, locale) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ locale.Error: unsupported locale setting make: *** [Makefile:20: html] Error 1 Exit status: 2 ``` --- .cirrus.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.cirrus.yml b/.cirrus.yml index 10c882a..3288e84 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -74,5 +74,7 @@ build_docs_task: memory: 1G install_script: - pip install -r docs/requirements-docs.txt + env: + LC_ALL: "C" script: - sh -c "cd docs && make html" From 939973442c014cc96d3ca437a212a5b4d4fdf8c4 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 11 Jul 2023 10:38:22 +0000 Subject: [PATCH 129/386] fix "unittests" CI task by pinning debian 11 - workaround for rocksdb - debian 11 oldstable ships librocksdb 6.11, and debian 12 ships librocksdb 7.8 - "python-rocksdb" on PyPI is unmaintained and does not support newer rocksdb>=7 (git repo: https://github.com/twmht/python-rocksdb ) - note: there is a somewhat newer fork at https://github.com/NightTsarina/python-rocksdb , which still does not support rocksdb>=7 however. Another fork at https://github.com/HathorNetwork/python-rocksdb claims to support rocksdb7+. However it does not have wheels / is not on PyPI. (though we could try to build it ofc) - for now, pin the CI to debian 11, and keep using twmht/python-rocksdb --- .cirrus.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index 3288e84..b53ce59 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -1,18 +1,19 @@ unittests_task: container: - image: python:3.8 + image: debian:bullseye cpu: 1 memory: 1G + before_install_script: + - apt-get -qq update + - apt-get install -yq git python3 python3-pip python3-setuptools python3-venv + - apt-get install -yq libsnappy-dev zlib1g-dev libbz2-dev libgflags-dev liblz4-dev librocksdb-dev libleveldb-dev libboost-all-dev libsodium-dev version_script: - python3 --version tag_script: - git tag - before_install_script: - - apt-get -qq update - - apt-get install -yq libsnappy-dev zlib1g-dev libbz2-dev libgflags-dev liblz4-dev librocksdb-dev libleveldb-dev libboost-all-dev libsodium-dev pip_cache: folder: ~/.cache/pip - fingerprint_script: echo $PYTHON_VERSION && cat requirements.txt + fingerprint_script: echo $(python3 --version) && cat requirements.txt populate_script: mkdir -p ~/.cache/pip install_script: # test framework -related deps From b4bc32a7c34445ae611fa882437c77270e882a3c Mon Sep 17 00:00:00 2001 From: mraksoll <32709596+mraksoll4@users.noreply.github.com> Date: Tue, 11 Jul 2023 14:42:20 +0300 Subject: [PATCH 130/386] altcoin: Add bitweb. (#205) * Add bitweb. * add raw block * fix comma. --- .cirrus.yml | 1 + electrumx/lib/coins.py | 24 ++++++++++++++++++++++++ tests/blocks/bitweb_mainnet_941159.json | 15 +++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 tests/blocks/bitweb_mainnet_941159.json diff --git a/.cirrus.yml b/.cirrus.yml index d659501..e39fa62 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -41,6 +41,7 @@ unittests_task: - pip install git+https://github.com/VerusCoin/verushashpy - pip install bell-yespower - pip install cpupower + - pip install bitweb_yespower==1.0.5 script: - pytest --cov=electrumx coveralls_script: diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 9643718..2427d3c 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -4074,3 +4074,27 @@ class Lbry(Coin): TX_PER_BLOCK = 43 RPC_PORT = 9245 REORG_LIMIT = 5000 + + +class Bitweb(Coin): + NAME = "Bitweb" + SHORTNAME = "BTE" + NET = "mainnet" + XPUB_VERBYTES = bytes.fromhex("0488b21e") + XPRV_VERBYTES = bytes.fromhex("0488ade4") + P2PKH_VERBYTE = bytes.fromhex("21") + P2SH_VERBYTES = (bytes.fromhex("1E"),) + WIF_BYTE = bytes.fromhex("80") + GENESIS_HASH = ('00043e9c6bc54d9bd266c3767a83a7b9' + 'da435dd7f84e485a2bf2a869be62f1f3') + DESERIALIZER = lib_tx.DeserializerSegWit + TX_COUNT = 31788 + TX_COUNT_HEIGHT = 29511 + TX_PER_BLOCK = 2 + RPC_PORT = 1605 + + @classmethod + def header_hash(cls, header): + '''Given a header return the hash.''' + import bitweb_yespower + return bitweb_yespower.getPoWHash(header) diff --git a/tests/blocks/bitweb_mainnet_941159.json b/tests/blocks/bitweb_mainnet_941159.json new file mode 100644 index 0000000..d712669 --- /dev/null +++ b/tests/blocks/bitweb_mainnet_941159.json @@ -0,0 +1,15 @@ +{ + "hash": "00000018fc3ab59f3318eb2e37e400abe36ea13c99e561c752ace5bd209fe671", + "size": 327, + "height": 941159, + "merkleroot": "1896758bbfb882d55aa0fd30da2630a6a8ee986db1ec367b9242e59a881edb3e", + "tx": [ + "1896758bbfb882d55aa0fd30da2630a6a8ee986db1ec367b9242e59a881edb3e" + ], + "time": 1676845553, + "nonce": 2431333171, + "bits": "1e00a726", + "chainwork": "0000000000000000000000000000000000000000000000000000468f1e183cfa", + "previousblockhash": "0000007bc372c53a5df1d29da21214f0278c4e5293d07a01ec656bd29d362e87", + "block": "00000020872e369dd26b65ec017ad093524e8c27f01412a29dd2f15d3ac572c37b0000003edb1e889ae542927b36ecb16d98eea8a63026da30fda05ad582b8bf8b759618f1a1f26326a7001e3333eb9001010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff4e03675c0e04f1a1f26308fabe6d6d00000000000000000000000000000000000000000000000000000000000000000100000000000000d91d54e5977286980f706f6f6c2e72706c616e742e78797a00000000020000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf900f90295000000001976a9142d8c740dfee1fbfb4830e54e7875a772d872cfbe88ac0120000000000000000000000000000000000000000000000000000000000000000000000000" +} From 41de3dc81c94553cc4536789d58d10242c45ab57 Mon Sep 17 00:00:00 2001 From: lateminer <9951982+lateminer@users.noreply.github.com> Date: Tue, 11 Jul 2023 13:43:28 +0200 Subject: [PATCH 131/386] altcoin: Add support for Blackcoin (#199) --- electrumx/lib/coins.py | 45 +++++++++++++++++-- electrumx/lib/tx.py | 26 +++++++++++ tests/blocks/blackcoin_mainnet_3984683.json | 24 ++++++++++ .../blackcointestnet_testnet_1088263.json | 25 +++++++++++ 4 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 tests/blocks/blackcoin_mainnet_3984683.json create mode 100644 tests/blocks/blackcointestnet_testnet_1088263.json diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 2427d3c..266f8ea 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -1659,17 +1659,54 @@ class Blackcoin(ScryptMixin, Coin): NAME = "Blackcoin" SHORTNAME = "BLK" NET = "mainnet" + XPUB_VERBYTES = bytes.fromhex("0488B21E") + XPRV_VERBYTES = bytes.fromhex("0488ADE4") P2PKH_VERBYTE = bytes.fromhex("19") P2SH_VERBYTES = (bytes.fromhex("55"),) WIF_BYTE = bytes.fromhex("99") GENESIS_HASH = ('000001faef25dec4fbcf906e6242621d' 'f2c183bf232f263d0ba5b101911e4563') - DAEMON = daemon.LegacyRPCDaemon - TX_COUNT = 4594999 - TX_COUNT_HEIGHT = 1667070 + DESERIALIZER = lib_tx.DeserializerBlackcoin + DAEMON = daemon.FakeEstimateFeeDaemon + TX_COUNT = 10802426 + TX_COUNT_HEIGHT = 3431329 TX_PER_BLOCK = 3 RPC_PORT = 15715 - REORG_LIMIT = 5000 + REORG_LIMIT = 500 + ESTIMATE_FEE = 0.0001 + RELAY_FEE = 0.0001 + PEERS = [ + 'electrum1.blackcoin.nl t10001 s10002', + 'electrum2.blackcoin.nl t20001 s20002', + 'electrum3.blackcoin.nl t30001 s30002' + ] + + +class BlackcoinTestnet(Blackcoin): + NAME = "BlackcoinTestnet" + SHORTNAME = "tBLK" + NET = "testnet" + XPUB_VERBYTES = bytes.fromhex("043587CF") + XPRV_VERBYTES = bytes.fromhex("04358394") + P2PKH_VERBYTE = bytes.fromhex("6F") + P2SH_VERBYTES = (bytes.fromhex("C4"),) + WIF_BYTE = bytes.fromhex("EF") + GENESIS_HASH = ('0000724595fb3b9609d441cbfb957761' + '5c292abf07d996d3edabc48de843642d') + DESERIALIZER = lib_tx.DeserializerBlackcoin + DAEMON = daemon.FakeEstimateFeeDaemon + TX_COUNT = 1401229 + TX_COUNT_HEIGHT = 698611 + TX_PER_BLOCK = 2 + RPC_PORT = 25715 + REORG_LIMIT = 500 + ESTIMATE_FEE = 0.001 + RELAY_FEE = 0.001 + PEERS = [ + 'electrum1.blackcoin.nl t10011 s10012', + 'electrum2.blackcoin.nl t20011 s20012', + 'electrum3.blackcoin.nl t30011 s30012' + ] class Bitbay(ScryptMixin, Coin): diff --git a/electrumx/lib/tx.py b/electrumx/lib/tx.py index cdb2525..a753e70 100644 --- a/electrumx/lib/tx.py +++ b/electrumx/lib/tx.py @@ -810,6 +810,32 @@ def blake2s(data): return blake2s_hash.digest() +class DeserializerBlackcoin(Deserializer): + BLACKCOIN_TX_VERSION = 2 + + def _get_version(self): + result, = unpack_le_int32_from(self.binary, self.cursor) + return result + + def read_tx(self): + version = self._get_version() + if version < self.BLACKCOIN_TX_VERSION: + return TxTime( + self._read_le_int32(), # version + self._read_le_uint32(), # time + self._read_inputs(), # inputs + self._read_outputs(), # outputs + self._read_le_uint32(), # locktime + ) + else: + return Tx( + self._read_le_int32(), # version + self._read_inputs(), # inputs + self._read_outputs(), # outputs + self._read_le_uint32() # locktime + ) + + class DeserializerReddcoin(Deserializer): def read_tx(self): version = self._read_le_int32() diff --git a/tests/blocks/blackcoin_mainnet_3984683.json b/tests/blocks/blackcoin_mainnet_3984683.json new file mode 100644 index 0000000..f56bca1 --- /dev/null +++ b/tests/blocks/blackcoin_mainnet_3984683.json @@ -0,0 +1,24 @@ +{ + "hash": "229f7148da76eb8918d9b425199058fe8d9c048983a6057c6bb95113df4243dc", + "confirmations": 1, + "size": 401, + "height": 3984683, + "version": 536870912, + "versionHex": "20000000", + "merkleroot": "b580ab1962166b8436f3377ffebf7c5a29b5b543ead21fa9a44ad0499bb239a0", + "tx": [ + "791f63185092338369773c113517985fe490129d6b65b8b8a426a51c2504d32c", + "7db87e15e32317a7fbe9848395cf84a9263c6a66ab826234486d788fa4b6834b" + ], + "time": 1652008848, + "mediantime": 1652008848, + "nonce": 0, + "bits": "1a0f1c43", + "difficulty": 1110292.444583575, + "chainwork": "00000000000000000000000000000000000000000000034b3eb36e3e93c370e9", + "previousblockhash": "1226fe62a31b90cf70ed31dc506ed838b1e96764ed8e39e0a1273026f40284a7", + "flags": "proof-of-stake", + "modifier": "26d169f660b2a2f7900b2f4806eedae02f469ef61db62563907a794525cf98d1", + "signature": "30440220575c167687053d1caff65425821eccc17384b9595f45437cdc2b4def8d2bc0ac02207645a8e90709ea6a7054656d3a8ca9b9b85a2fa9b7b3f2fe12bcee52c463e746", + "block": "00000020a78402f4263027a1e0398eed6467e9b138d86e50dc31ed70cf901ba362fe2612a039b29b49d04aa4a91fd2ea43b5b5295a7cbffe7f37f336846b166219ab80b590a77762431c0f1a00000000020100000090a77762010000000000000000000000000000000000000000000000000000000000000000ffffffff05032bcd3c00ffffffff01000000000000000000000000000100000090a7776201a122a1a39f49c525116c8f14aaa6d8f0fba6d9101485b24326127bad59d4edfd010000004847304402202bc9520109482122307d635e14989463d1a63bda5221e7b02a238c7cb1cdbedd02203c725f3b97749eb022ce00b51ea8a9fa1e13b33b85438388649ae976310a16cc01ffffffff02000000000000000000c069f19e1300000023210384136439c98a00fd4b05d747856dcd465989c69281758a6438152279284f4e7dac000000004630440220575c167687053d1caff65425821eccc17384b9595f45437cdc2b4def8d2bc0ac02207645a8e90709ea6a7054656d3a8ca9b9b85a2fa9b7b3f2fe12bcee52c463e746" +} diff --git a/tests/blocks/blackcointestnet_testnet_1088263.json b/tests/blocks/blackcointestnet_testnet_1088263.json new file mode 100644 index 0000000..5fa5cf8 --- /dev/null +++ b/tests/blocks/blackcointestnet_testnet_1088263.json @@ -0,0 +1,25 @@ +{ + "hash": "2249fe3390f2919f07d7db3ccb8c2aa326cc7872dbba83c6e529a3d80b14028f", + "confirmations": 3, + "size": 402, + "height": 1088263, + "version": 536870912, + "versionHex": "20000000", + "merkleroot": "4395cb2ab717d0dcce7481a107118a7a806a40a0e5eb9bc795e91dda5ad068ce", + "tx": [ + "0a009bc84b7760689a915530b0da9413557bf2c78ff9d07c47d6d26b4f72c0de", + "3bcc6d66b37abf396234313356e89023995de8db03dc82dd2aaced1660cba8e3" + ], + "time": 1652009040, + "mediantime": 1652009040, + "nonce": 0, + "bits": "1b00e7ff", + "difficulty": "72315.7003586402", + "chainwork": "000000000000000000000000000000000000000000000030756d73c0b041ff84", + "previousblockhash": "4651bb5523257a2d6a0f456d094bdc78381e6106bc17ceb48a34e01f4f3c7b87", + "nextblockhash": "7e4e1fc80690c1e8596c6dfbc7e21439c7bec061dbca26b41857e0808d9b0882", + "flags": "proof-of-stake", + "modifier": "3a2bb90bc48c1809b836babe9b9e78c447b894974f181d8aedaf490329a15b99", + "signature": "3045022100f5df71b053e99b265c992e169b17b8180f1651b264010488b3ad226b4e10139a022003bc838f1c073e9671348a819ed7e590330f9b6d0ff71ef46b2e2a63354caa88", + "block": "00000020877b3c4f1fe0348ab4ce17bc06611e3878dc4b096d450f6a2d7a252355bb5146ce68d05ada1de995c79bebe5a0406a807a8a1107a18174cedcd017b72acb954350a87762ffe7001b00000000020100000050a87762010000000000000000000000000000000000000000000000000000000000000000ffffffff0503079b1000ffffffff01000000000000000000000000000100000050a87762016e584995b3266c02c56bcbbb7f3c5c24edb772d6c0f7b64330afa93a13d2aff00100000048473044022015d66f6b3d08e97e14eebb11c5e3132a59ffe6106e6d22e9845b818f66ab7d9202202c350085f14b2c42e8524a0d2047a314e3df1d4a6992745384b36fef6d3aec1c01ffffffff0200000000000000000080f8d7fa0e0000002321022dbc07d2ce8e25f0e7d19113bec991d1a6c41a3f9a6a29978bea78eb1fe11009ac00000000473045022100f5df71b053e99b265c992e169b17b8180f1651b264010488b3ad226b4e10139a022003bc838f1c073e9671348a819ed7e590330f9b6d0ff71ef46b2e2a63354caa88" +} From b8accd78a986b94404107a3dc770dc7e5d067e4a Mon Sep 17 00:00:00 2001 From: MaxPuig <45133421+MaxPuig@users.noreply.github.com> Date: Tue, 11 Jul 2023 11:55:28 +0000 Subject: [PATCH 132/386] altcoin: Add Garlicoin closes https://github.com/spesmilo/electrumx/pull/211 Squashed commit of the following: commit 7434d686325c6fa9a4c4a4759b5f0e5de1faa741 Author: MaxPuig <45133421+MaxPuig@users.noreply.github.com> Date: Thu Apr 6 10:19:44 2023 +0200 Add missing block parameter commit 62682fdd75928f7810becb58e75daf4d1e866ce0 Author: MaxPuig <45133421+MaxPuig@users.noreply.github.com> Date: Thu Apr 6 09:28:57 2023 +0200 Add Garlicoin --- electrumx/lib/coins.py | 32 +++++++++++++++++++++++ tests/blocks/garlicoin_mainnet_42069.json | 27 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 tests/blocks/garlicoin_mainnet_42069.json diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 266f8ea..be8cce3 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -4135,3 +4135,35 @@ def header_hash(cls, header): '''Given a header return the hash.''' import bitweb_yespower return bitweb_yespower.getPoWHash(header) + + +class Garlicoin(Coin): + NAME = "Garlicoin" + SHORTNAME = "GRLC" + NET = "mainnet" + XPUB_VERBYTES = bytes.fromhex("0488b21e") + XPRV_VERBYTES = bytes.fromhex("0488ade4") + P2PKH_VERBYTE = bytes.fromhex("26") + P2SH_VERBYTES = [bytes.fromhex("32"), bytes.fromhex("05")] + WIF_BYTE = bytes.fromhex("b0") + GENESIS_HASH = ('2ada80bf415a89358d697569c96eb98c' + 'dbf4c3b8878ac5722c01284492e27228') + DESERIALIZER = lib_tx.DeserializerSegWit + TX_COUNT = 1031869 + TX_COUNT_HEIGHT = 2343659 + TX_PER_BLOCK = 2 + RPC_PORT = 42068 + REORG_LIMIT = 800 + PEERS = [ + 'us.node.garlico.in s', + 'ca.node.garlico.in s t', + 'sg.node.garlico.in s', + 'de.node.garlico.in s', + 'fr.garlium.crapules.org s', + 'uk.garlium.crapules.org s', + 'pl.garlium.crapules.org s', + 'ca.garlium.crapules.org s', + 'au.garlium.crapules.org s', + 'electrum.hotgrlc.com s t', + 'electrum.maxpuig.com s t' + ] diff --git a/tests/blocks/garlicoin_mainnet_42069.json b/tests/blocks/garlicoin_mainnet_42069.json new file mode 100644 index 0000000..b26e0ed --- /dev/null +++ b/tests/blocks/garlicoin_mainnet_42069.json @@ -0,0 +1,27 @@ +{ + "hash": "1c8d7a8b72c448faeb0663a7a07cc1f0f9096c08be44b71d74c359e57ff1421d", + "size": 797, + "height": 42069, + "version": 536870912, + "merkleroot": "e544f42e449ecdcda3e6104e4a3141a3c17ac2c16c7458bc3beb898d015ac2bb", + "block": "000000207758c8aa609e2f767f78eef4bcec0b8399c517196a593b351c66247eb475fbdbbbc25a018d89eb3bbc58746cc1c27ac1a341314a4e10e6a3cdcd9e442ef444e542e87c5a79ff031c001318d203010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff200355a4000442e87c5a0810000051000000000d2f6e6f64655374726174756d2f00000000030000000000000000266a24aa21a9edab5e83b091b348ca9879df10d7a6fe6f97a6d5be673eabaecde8fea15c27e30a2741c628010000001976a91462f80734f8a48eae677bb850a512c4c5a6a3e01088ac0d704001000000001976a914f56321f5d09235061ce3373b3ee30d62a91d481b88ac0120000000000000000000000000000000000000000000000000000000000000000000000000020000000116f9bc92bc59b66b2db0ca5b08bfccc3dc7b5a375e89c47e6bee6bd18fd745d3010000006a473044022036f63f04c72e801e28f2be8fb82e169de737c9f41e16506f2e790a08cca9c61d0220160fecfad75f302f090130d213ae95efa3c1c39f4d6845a0ac541e66630bcb010121038361a42f7b0e5150960195437ec1d9db40060a33257b70a5404a0ce26519ade3feffffff02e0930400000000001976a91473f8bf1ba8168b1383f9eec85cce59b1ddfd724388ac5e0784a8000000001976a914001bf6a4a197ce7b5780656a614f2668ee9220b588ac53a400000200000001a4e790d0db6f3d42d7442bc33af09ca4aff9b8616cc208703da4d730396f5ffc000000008a47304402205f67684c99f10c9d8e40c665f19bf9c60a26186fbfaa7cabf8994925b658ec78022059585c94e453c7477f70e5b91be1df00da374c7aa451f4eb4155e16faa832ec5014104592343545a32bb648a37bb79f6d5bd5e5b45ba04ab21489722470557e329932e51a46d8fbdfda18899780c2ee0da4d6c9912ac1feb99974973effca8a743e25afeffffff02d808c6d0030000001976a914454124f7ea9a2ed118e92e6dfd5b1234920b5e9388ac00ca9a3b000000001976a914ccf4891a4900c028b0b462d0942b740fe5de8eaf88ac53a40000", + "tx": [ + "f498246bced61abf66154146182f99cc48e46850a633f2087d3e8deab4396161", + "3a60feb186cd060e554b71398c3fad6fd4fc531b64cb8af7176b2ff3d7dc8b02", + "9371a4dda250fb24235ee37d5fd98acbedb1f0b3ca2016a7862596b15269ffa7" + ], + "time": 1518135362, + "nonce": 3524793088, + "bits": "1c03ff79", + "difficulty": 64.0319989, + "chainwork": "000000000000000000000000000000000000000000000000001656ccaa310788", + "confirmations": 3953850, + "previousblockhash": "dbfb75b47e24661c353b596a1917c599830becbcf4ee787f762f9e60aac85877", + "nextblockhash": "3fd40439236e6af0086d9e7a064a47ae384f8199aa64ce347e39f9de4d11b303", + "reward": 50, + "isMainChain": true, + "poolInfo": { + "poolName": "Anonymous Pool", + "url": "" + } +} \ No newline at end of file From cf6123e4e48190915cbfbcac95dd8774b31b209c Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Tue, 11 Jul 2023 15:35:58 +0300 Subject: [PATCH 133/386] altcoin: Support DASH v19 hard-fork changes (#221) * feat(DASH): Update DashProRegTx and DashProUpServTx to support extra version v2 implemented in v19 * test(DASH): add unit tests for DashProRegTx and DashProUpServTx with extra version=2 --- electrumx/lib/tx_dash.py | 132 ++++++++++++++++++++++++-------------- tests/lib/test_tx_dash.py | 114 ++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+), 47 deletions(-) diff --git a/electrumx/lib/tx_dash.py b/electrumx/lib/tx_dash.py index c564fe4..be016f2 100644 --- a/electrumx/lib/tx_dash.py +++ b/electrumx/lib/tx_dash.py @@ -73,7 +73,8 @@ class DashProRegTx(namedtuple("DashProRegTx", "version type mode collateralOutpoint " "ipAddress port KeyIdOwner PubKeyOperator " "KeyIdVoting operatorReward scriptPayout " - "inputsHash payloadSig")): + "inputsHash platformNodeID platformP2PPort " + "platformHTTPPort payloadSig")): '''Class representing DIP3 ProRegTx''' def serialize(self): assert (len(self.ipAddress) == 16 @@ -81,71 +82,108 @@ def serialize(self): and len(self.PubKeyOperator) == 48 and len(self.KeyIdVoting) == 20 and len(self.inputsHash) == 32) - return ( - pack_le_uint16(self.version) + # version - pack_le_uint16(self.type) + # type - pack_le_uint16(self.mode) + # mode - self.collateralOutpoint.serialize() + # collateralOutpoint - self.ipAddress + # ipAddress - pack_be_uint16(self.port) + # port - self.KeyIdOwner + # KeyIdOwner - self.PubKeyOperator + # PubKeyOperator - self.KeyIdVoting + # KeyIdVoting - pack_le_uint16(self.operatorReward) + # operatorReward - pack_varbytes(self.scriptPayout) + # scriptPayout - self.inputsHash + # inputsHash - pack_varbytes(self.payloadSig) # payloadSig - ) + res = ( + pack_le_uint16(self.version) + # version + pack_le_uint16(self.type) + # type + pack_le_uint16(self.mode) + # mode + self.collateralOutpoint.serialize() + # collateralOutpoint + self.ipAddress + # ipAddress + pack_be_uint16(self.port) + # port + self.KeyIdOwner + # KeyIdOwner + self.PubKeyOperator + # PubKeyOperator + self.KeyIdVoting + # KeyIdVoting + pack_le_uint16(self.operatorReward) + # operatorReward + pack_varbytes(self.scriptPayout) + # scriptPayout + self.inputsHash) # inputsHash + if self.version >= 2: + res += (self.platformNodeID + # platformNodeID + pack_le_uint16(self.platformP2PPort) + # platformP2PPort + pack_le_uint16(self.platformHTTPPort)) # platformHTTPPort + res += pack_varbytes(self.payloadSig) # payloadSig + return res @classmethod def read_tx_extra(cls, deser): + version = deser._read_le_uint16() # version + ntype = deser._read_le_uint16() # type + mode = deser._read_le_uint16() # mode + collateralOutpoint = deser._read_outpoint() # collateralOutpoint + ipAddress = deser._read_nbytes(16) # ipAddress + port = deser._read_be_uint16() # port + KeyIdOwner = deser._read_nbytes(20) # KeyIdOwner + PubKeyOperator = deser._read_nbytes(48) # PubKeyOperator + KeyIdVoting = deser._read_nbytes(20) # KeyIdVoting + operatorReward = deser._read_le_uint16() # operatorReward + scriptPayout = deser._read_varbytes() # scriptPayout + inputsHash = deser._read_nbytes(32) # inputsHash + platformNodeID = b'' + platformP2PPort = 0 + platformHTTPPort = 0 + if version >= 2 and ntype == 1: + platformNodeID = deser._read_nbytes(20) # platformNodeID + platformP2PPort = deser._read_le_uint16() # platformP2PPort + platformHTTPPort = deser._read_le_uint16() # platformHTTPPort + payloadSig = deser._read_varbytes() # payloadSig return DashProRegTx( - deser._read_le_uint16(), # version - deser._read_le_uint16(), # type - deser._read_le_uint16(), # mode - deser._read_outpoint(), # collateralOutpoint - deser._read_nbytes(16), # ipAddress - deser._read_be_uint16(), # port - deser._read_nbytes(20), # KeyIdOwner - deser._read_nbytes(48), # PubKeyOperator - deser._read_nbytes(20), # KeyIdVoting - deser._read_le_uint16(), # operatorReward - deser._read_varbytes(), # scriptPayout - deser._read_nbytes(32), # inputsHash - deser._read_varbytes() # payloadSig + version, ntype, mode, collateralOutpoint, + ipAddress, port, KeyIdOwner, PubKeyOperator, + KeyIdVoting, operatorReward, scriptPayout, + inputsHash, platformNodeID, platformP2PPort, + platformHTTPPort, payloadSig ) class DashProUpServTx(namedtuple("DashProUpServTx", - "version proTxHash ipAddress port " + "version type proTxHash ipAddress port " "scriptOperatorPayout inputsHash " - "payloadSig")): + "platformNodeID platformP2PPort " + "platformHTTPPort payloadSig")): '''Class representing DIP3 ProUpServTx''' def serialize(self): assert (len(self.proTxHash) == 32 and len(self.ipAddress) == 16 and len(self.inputsHash) == 32 and len(self.payloadSig) == 96) - return ( - pack_le_uint16(self.version) + # version - self.proTxHash + # proTxHash - self.ipAddress + # ipAddress - pack_be_uint16(self.port) + # port - pack_varbytes(self.scriptOperatorPayout) + # scriptOperatorPayout - self.inputsHash + # inputsHash - self.payloadSig # payloadSig - ) + res = pack_le_uint16(self.version) # version + if self.version >= 2: + res += pack_le_uint16(self.type) # type + res += ( + self.proTxHash + # proTxHash + self.ipAddress + # ipAddress + pack_be_uint16(self.port) + # port + pack_varbytes(self.scriptOperatorPayout) + # scriptOperatorPayout + self.inputsHash) # inputsHash + if self.version >= 2: + res += (self.platformNodeID + # platformNodeID + pack_le_uint16(self.platformP2PPort) + # platformP2PPort + pack_le_uint16(self.platformHTTPPort)) # platformHTTPPort + res += self.payloadSig # payloadSig + return res @classmethod def read_tx_extra(cls, deser): + version = deser._read_le_uint16() # version + ntype = 0 + if version >= 2: + ntype = deser._read_le_uint16() # type + proTxHash = deser._read_nbytes(32) # proTxHash + ipAddress = deser._read_nbytes(16) # ipAddress + port = deser._read_be_uint16() # port + scriptOperatorPayout = deser._read_varbytes() # scriptOperatorPayout + inputsHash = deser._read_nbytes(32) # inputsHash + platformNodeID = b'' + platformP2PPort = 0 + platformHTTPPort = 0 + if version >= 2 and ntype == 1: + platformNodeID = deser._read_nbytes(20) # platformNodeID + platformP2PPort = deser._read_le_uint16() # platformP2PPort + platformHTTPPort = deser._read_le_uint16() # platformHTTPPort + payloadSig = deser._read_nbytes(96) # payloadSig return DashProUpServTx( - deser._read_le_uint16(), # version - deser._read_nbytes(32), # proTxHash - deser._read_nbytes(16), # ipAddress - deser._read_be_uint16(), # port - deser._read_varbytes(), # scriptOperatorPayout - deser._read_nbytes(32), # inputsHash - deser._read_nbytes(96) # payloadSig + version, ntype, proTxHash, ipAddress, port, + scriptOperatorPayout, inputsHash, + platformNodeID, platformP2PPort, + platformHTTPPort, payloadSig ) diff --git a/tests/lib/test_tx_dash.py b/tests/lib/test_tx_dash.py index c4a9ddf..18fd60c 100644 --- a/tests/lib/test_tx_dash.py +++ b/tests/lib/test_tx_dash.py @@ -67,6 +67,24 @@ '97400d3196109c8cd31b94732caf6937d63de81d9a5be4db5beb83f9aa') +PRO_REG_TX_V2 = ( + '03000100013d7f654493ff3c9e7ea26c326f435e72cf1ba88d687dd7532d686760221e' + '0b27010000006a473044022076fbc6187b0e966faa4ebc6b06414317e1c1af11ca0a57' + '4dace4b03abec1bec00220466d7d2d1adf396b8df5a8cc6afd064bd984e3676bd93f55' + '4d1c7bfeeb67591f0121026d1a91dea1a6d6fcd9d998212c0914a903197bc3f39d4065' + 'c01857b6d379a8dcfeffffff010adff505000000001976a914beedf8a3a2f385046e11' + 'ac4606f42bec505d4e6888ac00000000fd2a010200010000003d7f654493ff3c9e7ea2' + '6c326f435e72cf1ba88d687dd7532d686760221e0b2702000000000000000000000000' + '00ffff7f0000012afd6cfacb0f86aa7f86200512b346504952b2ecdca2b3c39049cc46' + 'a565e8f2e91c9ec68b58379b99c506f6701e7bd5c8864d8954480abaa2c4ea53af9f31' + 'd60155e3df936a69e6f2b2014e943402adfdc16d8785971bd4ef4bf4011976a914d4bd' + '717bc0ff02c9aa34cd72fcd73989f68d941288acdcc55e86aaad9f59533a5cca568bad' + 'bc04aa68e81ac61d5e2fa5ac732714c4b70319c63d1bf01d6893b01d1bef62f08f79d9' + '6816622b632b411f8f268e41905bab8ad36115f1fdff74ce05bc53688a529fd93b797f' + '4850c8250d73315814b8973b35d8633326269564b2ffb1469c806c8110ac9e0810aa52' + '2f14') + + PRO_UP_SERV_TX = ( '03000200010931c6b0ad7ce07f3c8aefeeb78e246a4fe6872bbf08ab6e4eb6a7b69acd' '64a6010000006b483045022100a2feb698c43c752738fabea281b7e9e5a3aa648a4c54' @@ -82,6 +100,22 @@ '8ffa60e46c365a38d9fa0d44fd62') +PRO_UP_SERV_TX_V2 = ( + '0300020001e3afeff168a2977a303f87d9a42ec7fc6f1e8fa814b11ea1088eadaea500' + '51fd010000006a4730440220349326adf8f022189798cbe4eddfcff06405c3fe9fe28d' + '701e0e1ef6cba8d100022018e64ce0bf5cca3b61a69909f54159ef3e87ca3b5103e587' + '289f1f6150e1c3ed01210321a9da59a58b786f41ad7e012f52d229f125afcc25636e62' + '00cc7d848129fe56feffffff014edff505000000001976a914633bb62fc65f43091ae6' + 'c319b7e4a85f10586b4588ac00000000e802000100d299fbe08bae8863e482ed70c4c0' + '78ff0cbf05204f934ecf0fc114c6bdbd03d200000000000000000000ffff7f0000012a' + 'fd1976a914b7f5148c7f8b29370255b655be5ae9c7404b18c188ac4d3c4a39662eff4c' + 'b7bb00531b9048a81a9500cee046693a893ac928a46fb210af1bf741032fdf87ac1153' + 'dea88c0039a390fdf21f622062a4b4d6df357b3331924cb20e290979d3586b7a2c26b7' + '95f9bd347374bb9d1cdb392ef265fa9255d7b2b5cb66251ce2ed01ce6a80ddb1d63a10' + '982212a3b3ff35e6b682bbb32c889e88369a489447177867b8a1de78efa75cf9cf93ea' + 'd3113650') + + PRO_UP_REG_TX = ( '0300030001f8f9a27ca1c727fb971d45983c9a08a0bbd76753f8eb7913130c72d94218' '8d32000000006a47304402205d530dc4e9e34b44fdf58f06fff0c225d80490be2861ad' @@ -272,6 +306,51 @@ def test_dash_tx_pro_reg_tx(): ser = tx.serialize() assert ser == test +def test_dash_tx_pro_reg_tx_v2(): + test = bfh(PRO_REG_TX_V2) + deser = lib_tx_dash.DeserializerDash(test) + tx = deser.read_tx() + assert tx.version == 3 + assert tx.tx_type == 1 + extra = tx.extra_payload + assert extra.version == 2 + assert extra.type == 1 + assert extra.mode == 0 + assert len(extra.collateralOutpoint.hash) == 32 + assert extra.collateralOutpoint.hash == bfh( + '3d7f654493ff3c9e7ea26c326f435e72cf1ba88d687dd7532d686760221e0b27') + assert extra.collateralOutpoint.index == 2 + assert len(extra.ipAddress) == 16 + assert extra.ipAddress == bfh('00000000000000000000ffff7f000001') + assert extra.port == 11005 + assert len(extra.KeyIdOwner) == 20 + assert extra.KeyIdOwner == bfh( + '6cfacb0f86aa7f86200512b346504952b2ecdca2') + assert len(extra.PubKeyOperator) == 48 + assert extra.PubKeyOperator == bfh( + 'b3c39049cc46a565e8f2e91c9ec68b58379b99c506f6701e7bd5c8864d895448' + '0abaa2c4ea53af9f31d60155e3df936a') + assert len(extra.KeyIdVoting) == 20 + assert extra.KeyIdVoting == bfh( + '69e6f2b2014e943402adfdc16d8785971bd4ef4b') + assert extra.operatorReward == 500 + assert extra.scriptPayout == bfh( + '76a914d4bd717bc0ff02c9aa34cd72fcd73989f68d941288ac') + assert len(extra.inputsHash) == 32 + assert extra.inputsHash == bfh( + 'dcc55e86aaad9f59533a5cca568badbc04aa68e81ac61d5e2fa5ac732714c4b7') + assert len(extra.platformNodeID) == 20 + assert extra.platformNodeID == bfh( + '0319c63d1bf01d6893b01d1bef62f08f79d96816') + assert extra.platformP2PPort == 11106 + assert extra.platformHTTPPort == 11107 + assert extra.payloadSig == bfh( + '1f8f268e41905bab8ad36115f1fdff74ce05bc53688a529fd93b797f4850' + 'c8250d73315814b8973b35d8633326269564b2ffb1469c806c8110ac9e08' + '10aa522f14') + ser = tx.serialize() + assert ser == test + def test_dash_tx_pro_up_serv_tx(): test = bfh(PRO_UP_SERV_TX) @@ -303,6 +382,41 @@ def test_dash_tx_pro_up_serv_tx(): assert ser == test +def test_dash_tx_pro_up_serv_tx_v2(): + test = bfh(PRO_UP_SERV_TX_V2) + deser = lib_tx_dash.DeserializerDash(test) + tx = deser.read_tx() + assert tx.version == 3 + assert tx.tx_type == 2 + extra = tx.extra_payload + assert extra.version == 2 + assert len(extra.proTxHash) == 32 + assert extra.proTxHash == bfh( + 'd299fbe08bae8863e482ed70c4c078ff0cbf05204f934ecf0fc114c6bdbd03d2') + assert len(extra.ipAddress) == 16 + assert extra.ipAddress == bfh('00000000000000000000ffff7f000001') + assert extra.port == 11005 + assert extra.scriptOperatorPayout == bfh( + '76a914b7f5148c7f8b29370255b655be5ae9c7404b18c188ac') + assert len(extra.inputsHash) == 32 + assert extra.inputsHash == bfh( + '4d3c4a39662eff4cb7bb00531b9048a81a9500cee046693a89' + '3ac928a46fb210') + assert len(extra.platformNodeID) == 20 + assert extra.platformNodeID == bfh( + 'af1bf741032fdf87ac1153dea88c0039a390fdf2') + assert extra.platformP2PPort == 25119 + assert extra.platformHTTPPort == 25120 + assert len(extra.payloadSig) == 96 + assert extra.payloadSig == bfh( + 'a4b4d6df357b3331924cb20e290979d3586b7a2c26b795f9bd347374bb9d' + '1cdb392ef265fa9255d7b2b5cb66251ce2ed01ce6a80ddb1d63a10982212' + 'a3b3ff35e6b682bbb32c889e88369a489447177867b8a1de78efa75cf9cf' + '93ead3113650') + ser = tx.serialize() + assert ser == test + + def test_dash_tx_pro_up_reg_tx(): test = bfh(PRO_UP_REG_TX) deser = lib_tx_dash.DeserializerDash(test) From 5714f69155a83f1160f09bc73557bad08dfbf868 Mon Sep 17 00:00:00 2001 From: koh-gt <101822992+koh-gt@users.noreply.github.com> Date: Wed, 12 Jul 2023 17:27:31 +0800 Subject: [PATCH 134/386] altcoin: Add Ferrite (#224) --- electrumx/lib/coins.py | 50 ++++++++++++++++++++++++ tests/blocks/ferrite_mainnet_143580.json | 38 ++++++++++++++++++ tests/blocks/ferrite_mainnet_150213.json | 43 ++++++++++++++++++++ tests/blocks/ferrite_testnet_6282 | 48 +++++++++++++++++++++++ 4 files changed, 179 insertions(+) create mode 100644 tests/blocks/ferrite_mainnet_143580.json create mode 100644 tests/blocks/ferrite_mainnet_150213.json create mode 100644 tests/blocks/ferrite_testnet_6282 diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index be8cce3..ce49f4c 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -4167,3 +4167,53 @@ class Garlicoin(Coin): 'electrum.hotgrlc.com s t', 'electrum.maxpuig.com s t' ] + + +class Ferrite(Coin): + NAME = "Ferrite" + SHORTNAME = "FEC" + NET = "mainnet" + XPUB_VERBYTES = bytes.fromhex("0488b21e") + XPRV_VERBYTES = bytes.fromhex("0488ade4") + P2PKH_VERBYTE = bytes.fromhex("24") + P2SH_VERBYTES = (bytes.fromhex("23"), bytes.fromhex("05")) + WIF_BYTE = bytes.fromhex("a3") + GENESIS_HASH = ('46ca17415c18e43f5292034ebf9bbd10' + 'de80a61fc6dc17180e6609f33d3b48f3') + DESERIALIZER = lib_tx.DeserializerSegWit + TX_COUNT = 164010 + TX_COUNT_HEIGHT = 150251 + TX_PER_BLOCK = 2 + RPC_PORT = 9573 + REORG_LIMIT = 800 + ESTIMATE_FEE = 0.00001 + RELAY_FEE = 0.00001 + PEERS = [ + 'enode1.ferritecoin.org s t', + 'enode2.ferritecoin.org s t', + 'enode3.ferritecoin.org s t', + ] + + +class FerriteTestnet(Ferrite): + SHORTNAME = "TFE" + NET = "testnet" + XPUB_VERBYTES = bytes.fromhex("043587cf") + XPRV_VERBYTES = bytes.fromhex("04358394") + P2PKH_VERBYTE = bytes.fromhex("6f") + P2SH_VERBYTES = (bytes.fromhex("23"), bytes.fromhex("c4")) + WIF_BYTE = bytes.fromhex("ef") + GENESIS_HASH = ('7a9f43d6e86eefa66e2b79918b2235c9' + '362106f3d9f11f37f7a33450ceae73c1') + TX_COUNT = 12445 + TX_COUNT_HEIGHT = 6290 + TX_PER_BLOCK = 2 + RPC_PORT = 19573 + REORG_LIMIT = 4000 + ESTIMATE_FEE = 0.00001 + RELAY_FEE = 0.00001 + PEERS = [ + 'enode1.ferritecoin.org s t', + 'enode2.ferritecoin.org s t', + 'enode3.ferritecoin.org s t', + ] diff --git a/tests/blocks/ferrite_mainnet_143580.json b/tests/blocks/ferrite_mainnet_143580.json new file mode 100644 index 0000000..6005581 --- /dev/null +++ b/tests/blocks/ferrite_mainnet_143580.json @@ -0,0 +1,38 @@ +{ + "hash": "e7c515861a86e94f0825eb258abb8d97cedc41861d4fedf703fdffd58194beb5", + "confirmations": 6672, + "strippedsize": 29097, + "size": 30572, + "weight": 117863, + "height": 143580, + "version": 536870912, + "versionHex": "20000000", + "merkleroot": "cd075467fa40bbea77ceac3470aae0ad6d726d2dc0c623403c3c5ee90074f114", + "block": "000000208e0183cdaace8239f637798fb99f0a8cfd6b828bc59e0ab8ee1fd392a2d133de14f17400e95e3c3c4023c6c02d6d726dade0aa7034acce77eabb40fa675407cdcd3e7264a721031b43b958330f020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff1e03dc300204cd3e726408810000111e000000506f6f6c4d696e652e78797a00000000020000000000000000266a24aa21a9edd0b712ac480dcc5f1d53b85401ae09e96176e0beed963252c90437a63faec9d228560c54020000001976a9142e4eb268145a9862dc110e1b6e406b558398b61388ac0120000000000000000000000000000000000000000000000000000000000000000000000000020000002f4d11a5de2dd9a275ddd8c05690f185af78b661f362d5ab3c24672afedbfa4dec000000006a4730440220377efad1a94239836109ea8240553674081f6e5b5168c65b33ff596e10fc92c002207d2d8f6e8ec09b27740c304a23e814a67c46461e0a3d766f0e6889bd70d63b4b012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffffc0fe7875fb7df5d094bff5b68c0beb27e1481ee0792fff4be2e4b4a3a0defdd9000000006a4730440220283459736b8b4c170d9333d38f4fe1b0540807d9c0621d78e78d4cba94e44dce0220184e5076bde9738e1190dcb72b86e2b2967532563a07b3785b2a5dddea9f7743012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff4e9dad9c8c84e7728f7ab0ed40ec1ce435c3cc482424f62edec8e3caf77673ef000000006a47304402200c8e45e9ea7c7a50b3377e645d95c147b2a90fa21f02914dfb92f9fdba0ec03202207095f1a89b3c24e10a2020521280601c4d72086ce716b7b41f5e3aad453443c5012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff518917c1fae72452f095d6332245327e07adbaa5c7b99cb758862f7d8c4ce74c000000006a473044022002e881f93a2599e1f9a069c5b4bfbf4e62201ee1d39ce947312721c366060df7022074805e9f71aae97f253208cb3c61abd91b2a35145fc0f98d81cdfbee8e94467a012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff72224eb0a41540eb886a478649d412d73a103d2e19bb3fb9dd5e0cdd1da9f2d6000000006a4730440220288fc9e18f2ea1cf809359739f3c528ee69c4489b138a9a8121a03a29c9ebe4302200ae191fc87033d2327cef678bc0dda23156b01aba34a420ac7c07c7e816392cc012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff437d1b76fc29444423a8e476aaf5ecdf3d540567170a3abab064e154b29536af000000006a47304402202fca7c306a747a99fd8afcb0855e43f4cfbce08ed31ec3aba43cc5d09d55034f02204378e995f98d523e421e20da73b6227eab14ee2821619c03b200f889be1ab3e1012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff38558ad05ecc7b8f1fc245a683985367b79b72e8613f88fd15e86255de654b79000000006a473044022025da2e03ca8e1e0ba7a1d0babd234e68ad1fb5623cda0cf534c05efe726bbd4b02201afc7aa8704b86a65b4fbf74d816cb13293392a95b39fe2c3a5070b43362bff4012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff4bef102552d12d9de0b150ca115ed1b4b6bcf5f54fe816e94646c295673a1f56000000006a473044022011944c11db68c2df38f901b3eccdf1c28cd3c0c09fde29deb9e5b7fe12c5e8f60220664e7e345cec2bcceab137e3b7b943e3ef30d96311f6003c59b3b935bce3de74012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff769ea54eb6583f6ac673609cb804ea1923df196eb4aa85dba08e3818fa6a5112000000006a47304402206dcd88e8ced8bed7895b798fa40882b98f6ce37ffb8b86ab09ba2a50a8913c1502207f62fc401774338322164d6927a9e44258eb3b6d1788ec72a8c82b2b872a0ae3012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff02e7ae5cb9a31b0f769fa67e3edd5916c5c48d31819da6a0a7e2ef7079172edc0000000069463043021f5034969b8fb757b8027806c831bef98464c6d632e742af6487d404c2c2cf1f02206157457f81061450493d630508f71b5f0b77aa2c7b693d0730ef68e42c6f7b04012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffffc41ef26d851354ee38f0b1f88ce89119c9686a0892fe93b5dc835d809323fc88000000006a4730440220759f7a723fcd723f86c898970d42dfd49d5b17d85589913f815811831b330e2c02204dafe4da2c2f7092195dfbc7a93b33c6690ddef65bfa2b0fa302d68221fe862d012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff3ca6510766d2ececdf4beda09fd6c03b1437586d843cc7fe0f0b248648023d6f000000006a4730440220765c5d2a2763ca11a7382b914a2b6b06c3b7a7b3af0b9bfc8a1e1a43165204bb022072066ba2b013fb6d03d7fe405909930e47c3c0cc4e38c4dd5bc7ceef414ed94d012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff49920ac0c291128f44798827ba798a3cd0e04af697e5fcee67721ac4999e67ab000000006a473044022007c156d5ae6aeab8fbab84dd629fa5522581a3c0176c88c82e00f5e73aa59e4d022032a796a3d3071080a1f2c328ad2db2dd5e41c348c592cf472fa14f2752099197012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff53a49675ab919d9fab7d0abc8b305abb0c411096e49e64c35bc98f7048460870000000006a473044022017d779c74e7257b78a2d8f3807d7407c6bcb53240a5c5b582e3fae6ecf878b5c022042090f55cd5c231462ff9ba60708382c1d0129e84e2affe49d61f74a7cbd455b012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff2573592fd93b3000d0a80f57c7a69b8d7ac4ae736957e59a3f13a8ab86371dfa000000006a47304402200561fc572d2ae3204df66ba6635035ce1e995c5d6847ed98a96084a8cd5f867e02200d9b25c1637db4249c14ce6512625b0ba2cc9068f746300a5abeb8ef17808e54012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff137fa51b3979feb3653b8ef3bc5fecd3f898c0750580963f8d5331b2f8e8101b000000006a47304402207e155375c7c35d41887495945f3746f38ef496f51bf24cd568a57068520fc5ff022073f3f3dfcd5c11bced0852813ec2aee85d5648511926d953310a8cbdf3d77270012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909fefffffff1b996fb17307c83dfb7eca32034e3fa9d220e2edc953ad9dbd5b782e1b12304000000006a473044022028ffd05cde894dfa53e515451f2e6c05d649a4c48161b140caf7bba7ae441b440220137555d5a868a35aa1325045381cff8e3145117162c440f92cc6b8da7a4a0095012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffffcbb0243387219b7d809bbfdac24f23255a54abb4b43bd7ce01d435c5aa24a139000000006a4730440220211241bd0b111428d7336008f10016c78d40902ede245330a64b867cc76385f002203f05f6fae2eec006d14b8d6457e5274da4c1346dee05a07784bd6ddc92c2c2f1012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909fefffffff731ab83215beae1b71302a25621aca798ea2b79a639aecde391144f615c3185000000006a473044022014f2b17217d7ca05491eeb960525b29fb8a126c9b15cb0410c4631ca87082725022066df547267c429f10385e8eb4f5cca630c42b29fe48ac1b5fecb3c2ea5a6b189012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff09d0254120956126e3f5ccc601bf942ab6cf76dbb9861fd2edb538d785d54c6e000000006a4730440220323160d4869d2c3fb3585ca51190997c436f6347661ac2cd8d2aeb703cec2e82022020309765bcc8d9dc8b73dfe4eea4c7bbfd4f17a61e428d714a6ac0ed2a835d30012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffffbd41baf0ccf0f8a9d64663c132c4474fe49742fc966ee32f99c8f2edafb4321f000000006a4730440220499ec5f77d6e6b9a10d24cdf93bbebd9c659b5956557bf7689afe7d3b25bd6a2022071687bda968194995ea5823cdac13f17798bdcb88b58a596f92c558938503162012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff76cc71a8a3a8f22d1d170bb9f5270f486223189adfb0c2247b546f4119e19a74000000006a47304402205ba344fe83a97257c5cec8969e40bd54776f4f114ea3fe9d6d27e8371afdc5a5022065fbfabbfc2002d031363cfa385905538391f106cc9d11aa73b594bfbf5a258b012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff3c4bd612a7781086a01057149a019cfeeca4688bec6d99ddb9fc67ae6c4bf50b000000006a47304402201435da82a6e61a7e6e07ed8d3a1d5f42482d2fe3d0804c21aec91d168fc50bf8022069472491394bab7227a7fb0696edb61a47c746f87228574f1c6284c9ff3cb41a012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff2924da07c1c31e69899ec41a987fc163e179a38b4dbc48638e72ac87f568fb46000000006a47304402203b6673739b30388e824969ec7462dd23e5006d68736f47793b01f088d5eb45b602202e81d7a29b58a46989f47282c847e03d61d4bd6dde22ae15dafe60bc51442b63012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff31f5cb500c582f6241ee1498e641be0a2197c9dcde8692d39854f01f817ca4cb000000006a473044022044cc46205d00ecb9a106dc4063158f29e97d7dda798e3a460c3af927f4dc3b3702200f8aac1a04fde94565c83e1885bce0fa1d3f32eaee2dc6b1d239124531073cc6012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff38a88f4eb655527260aca43b5c0c5acae0f5d1449fa91c8a82e82d84165aa046000000006a473044022065c25c5ea3d65690752df00dc5e920172d73cc02bfd491656dc73a89be857b47022044ea0d3361a54577323dc1c357559d40420bf1642193f9846da70b19ed5ebfa9012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff7284227affce5379c5f05cb1e83a337edec60bba919a6240140c2df8a22b15d3000000006a47304402204e710adf450156ea627ef8d0d0364d61840639c73d80b7f5b6ea915e18410096022060738d23d8a48c42e37b9e10524ec2de4a1b4c1604b922259bbe10bbf2e666cf012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909fefffffff8a571e29ac51d794eeba325881c2324e31fd649777a882bc3e09a2533d5d0de000000006a473044022046eee98789ce98cc8d3424ab9a001cefa4a83dcf9484028786bd9cab0a0c3bd202201138280b0c2081c2cb43a9fef3cf422e6b46d4eb0913af050b40e5b10cfc66fa012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff48c68d6162063f09cf99bfdd0f795e5f96cb4d1f6ee2d5fc167152ea60d1d65e000000006a4730440220732a2099679156717abc9ba8945c7c1da8113900723aa8f1f7026e13bebbd85d022060656465a1bc802f0e66def97205f1b924037b5f2fd4a90a2ed8e04b7e9a77ec012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff5ab53814f1574a5936f8e3c375c6ced2928d5a23b0b649ce6f0dfd0be639f401000000006a47304402202584113d699805919fbbe1b5b947f4d81918df2bd3dbecea1de400f6c6917c4e02207f385e060202aaad7c0ca62c7f704ded74a5353e1dd7c265a7c186e004f20407012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff9c2209c54396646541db294b676e5a094e1d990ff67c99b52552a7699a4f3c4c000000006a473044022031099d9e7fdb2a200240a0031a10704d79bc57c29303959fc748629f2f3ab123022060c9811c01ef18071db3c8e1b9c75232c40c729a35c0dd5f89387120277926ce012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffffd7ca2346a062ff1d91574480a9b80af253bbeacef962d4c461c8d86806064e29000000006a47304402204dd36a09ede34e3cdcde74e68acb96f52a7d1ff549865e909481412c4f5d5bbf022022574bd69bfe024a3012fb94531b61c75a5e3face8e43facb6a3aa4dc06b01a0012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff57b379f9cacf50034c7958ba165476bc94633cae97128b0f173a4e028671dce8000000006a47304402201b3e33c91b28721a4dfd9c4f29b83937166569bb78e7c07bc1fb0f7009a07554022033ead754d018506b6af284558f7c500401631dc37ffbfedd74ee0c72425cc269012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff46984c68beb67d95b8a75a908ad4303c8d3907807e842bb0b2b46d7425344fb6000000006a4730440220440c08d918acf8a5ac434e0ed247a7dded41e3b91d327fb85c1b678021af9df002204dbaece5c163c670c224860430817b413fb9268e44b83e3fb8165d3e1ab68a8a012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffffebd831d3466ba324896ed775b1d8fd5e1a3109517c8b670111428414e0b44484000000006a47304402200b849a4b4c73659de5bd6539fa480366f25804f57a73e561bfd00d12c34ccd4102206a7cb9c528c7be139df54f7b07bce8ef5e9e25b8f4f8e78201448e438eb89f54012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffffb0ec3c57790b52534fa3cd036932ec47bdcbb8400c87bef9b74221e16eba0180000000006a47304402203cb743ccbd3a4c11a379f7a9bd016e0ea4b86b8a6ae7135a0545339bbdf10a4502204b426c6cebc5e746d46fb821e9a492d32dfa1fa57cf1eb94fec1c923d54cc018012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffffdbe2e405bd88c230613d781cbe13a60d2ca903b3926ffde019a758860cfc0dc3000000006a47304402202d8a7840383fc9e91de28db22a3ee580df53cefd882169965d8047763177168802207f7d0d9de33b474c4b7398830f2c8056df4c971b484ebd9ca77a5a8f3b047073012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff675102cb882caf7e326ad0d94ece46232b040027a78169883343bed1b6d41a90000000006a473044022055f81c8359a2cda85426f95492a9f72db70b99fadd0a30a558ed105cb687bb300220034a0fc690c8431d60ccb133a01a975d71698c46ff4354bee1a7f90eb3a36b68012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909fefffffff1675497e223304691b48a41e2815dc53293e2f8606ca566da4359d570e6a46d000000006a473044022072f7b63d4a9b3d13544d3161a8b3d6ea42ba75db40d620d27dd9c7cb23495a720220625afe81165637d4b8df7c484d2eb1d568f1e677688252dc715cb69658c7bcd8012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff391b803d5dfe1771167ffc07bee562857b2fcfb8f04263ef341eed628fd5a9cf000000006a473044022079782d5339f9c722cfa3457514ab442767590b84fdfc31df242d6571b2eb8f70022060900971c9f29a8db1345f6a7f3133ce06b9a6e27cb2996228fff82c5bfa1e9b012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff7d81d5e62b4a9e8ad18ea85082c33860bfd89105bcbc60d9296b94b65f03276a000000006a473044022062ca02288ce43ba0b86acc56c73fe156e0ceb25935bc9829eb5dd7c47e9f4c7e02205763e80045b39fba7283e4a89d9b62e88eb75317a097b6f20c3462eae9b95bb6012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff86d3d54c32049911e529e2c3df3b7c3f437c19d68b93c4344427b4f4cdfde0f9000000006a47304402200c7f3af1d5a9a081b262069ffa537faec937152995fd6eb833f08d722a56acff02204d2cdb9550f87a4da00f8d493aa63164a1893cce5b9fd5dde014cdcb96c596f7012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffffa6539c45660a98fc7468003fab9ff0d3ba9d666d6c99b29e56e8d97d02a70231000000006a47304402201f7c8d781d60dbfe784ceb65f831fc5d4be22dcdd834f80c0711b4f0f6fb40b4022056ed8e10a4212307537499b36a8345136a951c0819c6b26e6845c8b96a09752e012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff33e958018802a8a64ce0b370d0e3245d6273a2b6c370ee43032ec7997a80c33f000000006a47304402207a1cf278dd0b61336e1d7c523c9c45a809d742678db8983ee2c43074eb071ee402207f8a1f64b1df69ed53f7ac9abd92bbdc8cf42a4b96ae6e365bed6febee30723a012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffffd3df9947207a73894f0a179114bb92d39df7fbdc528ba66b037019502470ada3000000006a4730440220725520edd726150fb7c1f5ef749e1759bf200a3d92c5f8c68e1b8001f77db9510220265969a8c4e03f6db1d42b162c1fbc4bc7154de79633e34067f4d7f60c6e316c012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff2b6ea127d10ee7d30c67f6bc7d89da8ec75886623ffa7f498c1c50e58c9de92f000000006a473044022004e8f6d5f619dc4c96c07de6f3f85588854bd4bc7602458cb7bb92db0a6cf204022064476c9adbea67536e4318bfd0df0234511675b502f66845a7d2d552c0b6b704012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffffd5874f84296c362d7d1ea5f5bc15cf36238c5c90c2ef0f88e3585caf04b238f5000000006a47304402201910cc7fee7c239ef0de7ded8f231891ccd4c8f41932fb4f3f28ccfc1cfe1d7802204f84d82a4cbff1fdfd54c75cfc835417274d8d0ccfc53a6a0e4f12ea26077605012102df48760d79538a3480498fb6cfbd213f5c55e38650fce332127b627460c24909feffffff02ec61302e6d000000160014a6fb71e601a04085a3f8fac7e3b4b18646c4a97e5361fe3f000000001976a9145c576710c52ddda7a634350ba497da9e17fbda4988ac000000000200000000018630a3448223564e5c82f542efb07cdd6dd1a999dc002465d6b8cc61422a915c5c000000006a47304402200b4d3979acf588d678dcce32215735a30b51d8e5c090f3bcf3f4f0d12e77be6e022051cb7437611e9eedd482a2f65583049f8d1f0c137c070492c42006e3df82d7310121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffcb39aef81479ec6752b3f3e24aab6f0f3ccc44a4414fb8697164878230b78e29000000006a473044022028712e52a40fddd357c124b8120e96f4a6fc79fdfd0e0962ae38235fd12748570220413cbb237c4c9619007083dfef0b815a4b42c477e5e0a2e5781784c1f18c02d70121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff6dc03c18c4d6d05b637e0f6adb3213868ac0c206126811a1b8f3fc7e5d88cb55000000006a47304402205b3f69bd27e3dc65017c6a2f257527e60a4abbee198c24dba3df76c9337c614e02206d46eb759adfac2c17e7f58400e5bd7db67ef62fad734c01266aa53a467d2a760121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff4ccfc4ccaf07ee6b74906ee850c8720dc31b01f18023dbda45382061f4e1c9ab000000006a4730440220735580ad25c366d8dfcbba5d47a6dd0794738a4405f7b9076d2623402db669b00220132f948f02b7f5eddb08d4a3a9979147419eb8ed52e8580192a2cc29730cd1660121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff858aa2cd604e27622e86c8da0b341e0f1a7a5759bba6dc4af93f0bd5f11c7222000000006a4730440220437a1c930271251cd47cf64a15369325e5120013341b7fc14c4b749e2d09b80502206b0bd1f8dbd46b643d45ecb138450a1c72e8158939a44697a1a92ec1bd04147b0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffdea4c5db7301b3973d19bbd765302da11eeb4300879fd5fec8499d6aa011de42000000006a473044022055900e26695d935a9fbebcb35b32eb8f83479dfc669ed4e0ca69d26df199550a0220019219d705df2d47d0028a8bdcbf50314ab08a2479967826eaec9556d27ce2f30121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff55817799dee2dbf0e0216f0b253d3ab9bc1b6b83cd111a05ad8dc2961abe6169000000006a47304402200c0cd288f10ac4d78691898e003e2ac262cc45cee96180103c4ef83121cc50ca02201a4f0ee4387c3ac353db3b21cf8906ef1dad0aee8e1bd8fa23e6b165f4676bd90121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff4c2a62a7e258f8d890b76444e3862061ef2b5805cc2cc4c3d459588a0729e103000000006a47304402205fc1270096f4f2737d0e3cf864c5d11ebdf53aace6c0bfa34e61c9288eb658af02202e9ee060be769c77fbff9bd24dbc0f31bf50f2166dc0f4402881aa28382d8dda0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffd3728d1f62fae951258c2dec5f86013fc6de86259b99cfeda4feb66be512281c000000006a4730440220790eaf77476bc84a39ebf83de471b7c624e34ef1fb0389817241a1de1f9b4ac7022079b164c85d89511092a6d68af1d88c6c5e1a17175a4b7c6bade0c772a89cfa2b0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff52ddc65541de9d5f860d2b2f42fdc8f37f3b434877a2779aa949802173140a6b000000006a4730440220323053d62e268a27ce97011347f3a9aeec71d2326da9d55287ed010b76b0ab2302205e5435160f30c8db08ba4d205ad784b453b12bfdf2c02c2cba19a6089a2d24970121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff9060dfcc2f20ea1bee11b19226688ff06aa5b334e9f3f7f67284dc8685cca749000000006a4730440220404bd38b3071166deccdc6bcaf11d7c7d605d478009bd8c01ac1aba7652f960c02204c9aff3d24668b25e701375ca0761c43f0115fbdef364f2ae56233e66f8b81470121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff9917e6d76773e30bfa7299a658ed4e06ec64d352b55b845454b9c58e066487f4010000006a47304402207e0eab6e4a0f2a97aeb0b5c8a481fefe1813da604958a2431a301414471810da022009b6ad155287f27b1f2c81df1828403fe0aad8873db9c0752e3a74bae53c74100121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff561fd082cf94315dbe7de8a888535426cd3638cc36397e200e958deb4e9d7270000000006a473044022022d01c7bf0e13ac4970c3a1d1b42d7f2d2756c0b0525d4c183eeff4a90e9a9300220120a7552038b3bcafe27e39cad3e6d3178762b6b8da1151d7c6b433ce21167020121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff7c7b3dde20aab347abd5b37ea96f74bc1b133f362bbe822c8aba72c00efa2f38000000006a473044022036a851bb53c2eb6557c2d42253bbf75805c40d02311ae41027253da5bd831379022073fcc7548108a3f4c89f1c5c15f6419241da7b1d30ddd074bef726943e5af9700121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff920f1257c16661554c6ea0ae766564af7b784454110ee627770f2e01a50ec391000000006a47304402205de62bc5fe0c517a812275509249d6b8006c08eb95a1e2cd707fe03e66ee792c022036f93d016b4dbc594824d1c0a26f48b76bceca86a4dc28e2f7c879d502134d790121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3fefffffff160f2e694e30a61774a53227b1152ac1a8674833b9521536b3111678af30a0f000000006a473044022063bf3978ca2b62047f8c6936a016c702c673445273581a1608532ec7c1832fb0022072e8c8d04b7174f65096b3824ea288eeb87e8e9d3f37d5aec39c1d5c63c2da8f0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff9ef4fc0480317b9409d0c055f1af92c80e344ef5ced1ae178307636ebbf1bf55000000006a4730440220305d93e46a7adb722e6bc046f1638a3ab61b3c3d1cb591dcc1bd1946ba23786902203f87bcbfa70ad07eb4d1942df0e8ce177456897131e7143a595e0ad530c9dce20121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff720344936bbdc6a04b319ed2fbb0aae0478999631b252a0644279e492a7671bc000000006a4730440220412ccf619c62c662bda2873ce4e033d6d335b0e404ce740b38bcac25e8bcd3220220756a573154983f4f2bef7c9c73d6f467c8eea73852e22acd97aa4d9de212860f0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff9d2f4edef769726ccf8ca3b802ee2b8621f5271c4f5b71017c53c3f1d7431699000000006a4730440220795e747468f089c0769d90475827f7951f5ca90a9c71c4efd0ed6c03f6e9a38d022067bd609b1b2356d53712cb505530b5f4dea39ebbdb75df9a41025aa557775b5f0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3fefffffff00ed8657ba1ee21c1d3a40288ba51e9c5e5d7490d58108e5c15ac13db87aa1f000000006a473044022024905d2be38e14a28ad9e31aa93659aafa108063174c606c2215d6a06adb420302200f95fa130973e9681fdaedd74ea35a19d61396764ad0ec53373de2f3af9063a90121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff2162032b6d90fef5955999bbde195172ad2e39cbf450d39d3e4b62057bde82d2000000006a4730440220434f82b02e5773fe21ae62266f886f2a08c126480869da657f30d951b9a3acf402207e81c42897a2d745c805543fa4f8b958f261143e1c32dbec663cf814586da8430121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff3fd2ab808cea63f37d2045ae9465292950be9633c68383fa49559ac42ae15473000000006a47304402205822afbe1058d39c399d05cfb3b2db69f0f9ffcde0e1045a1cea35821153928c022072a01ee8060d534cca46b6cda30ba00bd2c4a9d45b469d9a4179959d9e55d9340121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff8c52d3ae21c2d1b8d7092e7283b4dd4398ef39235a349c8d0b96ed94bb9c5463000000006a473044022066f98cc8b00c59799bd2d39e80d94b70bfd1421ff094e26ea7d39368541410bf022027eca02bd4543a99f0bf3b774f04b63fcc23428e3ad51874a084e4f8b411cbe30121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff837e06ba539027b760c26d632dff5ff9c458c329143f086e6247dade0c6835da000000006a4730440220072d6e2bb483a71affefed0b88bcef3ecf74e5b99f2a4b35c9fec4adaaa126e80220495dce03402f391016e0cb6a5e3db550196af445d5cfa6785aefea178784e03c0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffc4933d2363239df947c6c023bee0daf36c2da22a9452273f4c5bee93f628ffd4000000006a473044022048edccf452de5091b22777b476fcec468c0ce31bba0e1db014713d953d18d63f022058e4fc1e9e85cd3597f92459b3203eb35b1efb149f65f7c3cc312003e2869ec70121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff4af9d2da28b94beaff72c1b02657617db0a8be9724462437188ea3c952b2f65b000000006a47304402203545ecfa2ac62e6dc4ef053c2fd0d29308f7544fceeba26842216b69cd7e27d402200ff97b790d772d0e556c93322bab904ee9f0df08b29f7368496ad95f96c96fa10121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff6e48d5bbe55975904221f75a0e2bcdce6872a2c826a6709ceac15e3dbd605311000000006a47304402202181267336ac123c3a423ee92dd9eeefdaa59654d1a7cb6c027bef8a5bf7694402203224819252331f7c928a9cfc8dd5d0a91f458f78269f567396cf1ec205da57e00121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff6beac48ee2094ebae85eb1c6b28f8f12805a036c0a5927403c926109d6abe61b000000006a4730440220616d9bda5d3aa834cd92cd4a674b8dfe077f9aef877d5f59269428697a39d24c02205c38275733568457fb21af6e53b54e378ccc7746731952e73e815ef8397124e30121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffe345e0ca3374c162000fe5a6dc314a8f5900be8c2dfd4c9273423da4ace5bc93000000006a47304402206beee9f2a2d9271e4a6335e02d7b27c71903e86ab199ec61145f232504c9c1e2022031dd02bbb33796753791aa36be3d74962edf8c5fe8a98aafbb45099762f848aa0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffa686f224e97f2dd65eae981c675d422328eef165510fd0b30034e1c42e73fb40000000006a473044022073fc24064f7d16da35d5487fc9b2f4d01fb463a9d74815678bc057578426f8a0022002eff1a2f7741bf379482d2304cb084e31393c16ede904330d961d03d074cfc50121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff07c1d810f14f75dfb5df3c5d26e1018a923ebcf4866d323542d34a69a1ca1191000000006a47304402205465b95584c81bbf7ef794951059f0e5c94651f505513b947104bc8c0a910c5c02207f3ae0e76ee74b9c0809a87450d4eaf723e2c4d6922ee98e369834d99bf0f8d60121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff59e017bdbc4f2c32b73cfe7e25f1c62d699e1cfba943fc19d994949922ee89fe000000006a47304402205aa4099e9630d43ada77bf35a86b7ab0f4a3729934e504cb9622fb9e7bfc5181022065eab4b38842cf23041b5e49b4cefcf8035d9a5e2c896c7c67ab46b3dc4782070121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff341a8cbf95fcdb3639f9b64b4b079489c965a67998dbc3a870b763c143111061000000006a47304402204f06234ffabf482cba620c9126e6d6ec2d9eb8b84c1b9cbcc5a68f06e3abe3c302204129b10e4121006b7a8d29800360bd306225d0e778d62b2fa2053cbd074b0d390121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff07368670d07acf530123cfa9ae5eb2c8ba33d9d7edbdb07ddf4ba0022ba78166000000006a473044022017e4935a16bda2c6c9ed78671e2aac9e1aeb64082ea640ebebda15b4dc08e03b02204ba79184db39fd851b2bb13b00c41d173545b2706cac5c85582a7aea8f3f02e20121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3fefffffff968670d489d37be4ce3882fc2549396d39c9e820033b659e2bf4b02277c422c000000006a473044022021d1f4625d4cc75a9204fb57144d6f9947794d27b53c295707c8c6d8c9c1c1d002203521e9087c3281eed719fbd153741f8736af4cba64e1bb2afde2ce5d051a1cab0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3fefffffff6eb380947a388b1a2b52faaee0ab9c2531250f7f246cb23c06dc375f1974bde000000006a47304402206dce72c1aea6210b1a96ac75b3b2a92b92c392d9a6d84583333bb2d19be7171602205a6b132adf4ed75cd8c4114c13cece21f1b1b71be4b07409954a279b2a1baeaf0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffbdac86d20745aab87401168d7920d5f1419ea722b45f5460863e53939a237583000000006a47304402201aec6d235a017e66c4adaef2951ff07f1f80c3ef279aea23d3b536974ad53a0202207566d3f4322effc0d7bd29b6acab9e82eb7f1a0b7084f97709c0106246a383350121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffc77af42e7ecbdb72428ec9393eff82f5d1bc1bf367c615139023ef30af25b9ce000000006a47304402201d6952713c2595c97d9b581327f98b72a03b765e0832b250cec17253227c15eb022065dc7fb618f521523f0fee98a7e02ba3752a04e75aae085ac1319cef904837870121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff455fe95bf6689eca15a1a5fa8c915c8c6d66459069598468be62e3e31252ae5f000000006a473044022065c3156a494c6f24aa7b6d3311f5c33aa3b111df9a95a48c05b0c3ebde7b7a7d02201f4d923e21d2487ef7f6573b81ba6393bdfb25deeb0d61c4e1d27990070023650121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff01e06e475c4b28a99ce9a62f8b109d731cdcae4ca3381deee2d213865f9c8e5d000000006a473044022051ce39cb95917cd12ced32ce18a219702f9dc166691a622454080614bc3801e5022010f35fe12db204be3ac7458d8067e5c89222a844a45afe39e95f9b623787de190121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffbec69bc2bdc5d6dbaee78dc3ba1b1237fab92b34e4d086743cbd9c0523a80aab000000006a47304402205b522c0077f82e897a3ff3d6e7488015f392f351f77309d9af65ab51b83cbb4402203f8ab0cca619ca30537d0ccac0737ec9bd85353712f0b6b1ec47dd0aa4e76d2c0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff9e77103ff90e675e69f8c1ddbe2d1f46c76dceba8850289cd7f4b1194fecb5b0000000006a473044022068e2a7e90bce2721a180b6049c4483398cdf2015f0ce818ad93b5ee1857a191e0220565ca4dde43633375b565489aa35dc456dc385af2227a72c9d199b74fbc88ed20121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffdb7b29a1aeaab99c750ad033fc390557be5999244abc663269e07c82d5ff0adb000000006a47304402203a1e4b90c5562daf744ac213fad7a7987e6e88385ae8aa90cf2127b3f5be75eb022056a3dec6f94ae4db4afe6c75c046603dbfc8c645f61c9fb752737e2c63086b550121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3fefffffffe6f9282bac8b9ef8e68046d111f98b2c9be62f5ffbfd8652d0efe185d724ea5000000006a473044022047b67fd8cdc0f49ea44bbd3f35c3e3e73328b499dde6d93f83cfb4fe4152b7d402202d46eb8babe43b59b8ec776070ef9f84a3447f002d76247cd772a7c97958f92e0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff8f133af868b51ac3be25911f86fb1ccf8a41dfb9fb2d27f57a6d29776a19a394000000006a47304402205af2fd8aa1ee6f3532006c94cc432eab78d9feee03acb41c38cd1512fa4ba5c702202d020e8e294c79139e92f1f7951f5a64ef33439a86fb3b0f52444e5f3100eaee0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff16ee940502c20068fb5d130f7dd467d1ed147c550943e755b613f82b59e81785010000006a47304402205313a4725fcc348eed95082c6a244ef171ea0696e7be5eeb3bc4c963e312af720220404e761b27819e5bac8ec8adcc9d3f740610fe8b08ec8f843c9029231410de450121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff30e36e279e6e2acf03f87d9341436fb229a7564d9dc2b7ac6c8a92a8e10d4930000000006a473044022007126b212542ce5ccb6508d9155f02ae9251e7fda12ed8cc052654a4926d61c802206ec41bf7c2cda719a7791e515b50835668991e3526510a4df80e04aedf0cec6c0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffd49d2c3c90a14bf0fd2abb5b8fe2a2b1a4e383b503484338589cb2fc49c547f4000000006a47304402206a05a4a8e718420cc06cf2efb0367b15f084568b75aa0d08c4faa6c55b5dcad40220197b249d8911bb5ad431fd1695a36204b952d065c5b6198b06981495d56cdb470121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff41387ed94a2820986f477e516e40baf65dbcac9f96b48d293027a4088c54d3df000000006a473044022042f76891b9416079e44e343bb8d4d81f5cb983be1d9b4d99af1ba23c16876e3b0220075e8412aa18ae103deda38f3325040be073c240f366f9eafd9198d08e48802f0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff9272672c53b1893dd01cd65c3f0e52cbdbe896883ed29f195b0cc01f70d9169d000000006a47304402206b187bce10435a97592a03a33d5f53452ec7880068a51c925fcdb4da1259b86b0220281fd9c030dc9a32151b67dda21f9de7540bc1851cddb84bf49b7a340a571c700121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3fefffffffad241b49c89f9b05600b635181917ba59d202e845f53d8990ce0b102eaaaa86000000006a473044022034ffafc7caa36675a490da883f527ada989c1c09548f369686198dd29e6272b702205e82d497ad7504ace99b2d125ebfafbb0de968aa88bdfef6e460a09f68080e040121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffcb97f34a67ecc4ea0bc735aae9ef19422fb267bab6e31ab9573245fc7bfcb7af000000006a47304402206116000e7ef63f6b17d62b8e8be8c85529c6023c2dee6c05a4247b87d585574f02207a20b6a64f029fae402fd19722cbaad249a891a0fea1bd87a7b433d42f4120900121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff2ea543808934ae0f9eeead2165ea9d206a60f8aa6de029d3bedf7b3094b2a57d000000006a47304402201e6251e658df15e1d588d52eb11109198dc88092bbff9f284806e602adadeb3102203367e3c7521ce1df2fc8962c6e07d67fa618bec8a76c73dd41e4cb5bd6e6c3b60121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffc4bb5a039f5577f229dc765e8b75550fb5eb44c32277f75024683f09bf68f39e000000006a47304402206367b7e26d073ce97fd772099fc2512af9533d2e1a504601096a0a2abcf4c0210220397e2fab945201920660060eece4c4b2af23a24cc4214ca9936eb39f974879ae0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff851917a2ecc4aa4ed22e2b447802e401e32c6ab72c1df84c361eb0ffd3cd4be3000000006a47304402201411269103fcc02901283590f0c0f941bfcd259919d44fc6b0612aa398bf522a022079fe8e373231df2b00ce29a00486e98e2daf1518d97bffa6a804eea885e94d380121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff063785117312fe6bf10c12d706dd19d1604d4047b574befe788ae3044f8fd8ad000000006a4730440220651800e672a50d6fdc87ff569b04c6b54148e2dc091ab0d9deeeac4d4aa3dca0022013dba260dd58c2259fd6d23e7ee66629680b8cfb1b77a0254b6aa737b5ac18630121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff125fce9ac03f28e0e648a0875f78dc0d77b229c1b59ceba9a535eafb99d455a5000000006a473044022038b5c1c652ff134b75aa45cfce1be3568bd295fc06ad35f1e9a1e1342e4ffe8502200abb4ff71018db9e1509e1ef75c9150505804ccb9ddebf985c2f1ab922acb74c0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff02a9af01e4f3761785767e02e1894e0c492b6c05851557e4729e96c21177fe8f000000006a47304402203c7d8daf64ebe42e6d1e56af2c54be4a17da18b0c1bd05ee82fce7a20706a97802201e56464c25289d4782d2cdfc9e331932f7bf115c714349ebd13881a20950e6630121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffc1deaafaef8355ae403d43ef524e551f44a35605f9d777b5c05497055822d166000000006a47304402206141cbacd96cbc5487d011a80ed74d2f1ac62e23014f37f55e57f97f840ce315022000910c0460224513c92e1fc6a7b3b41715c162f69af788134a7594394b4d3d130121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff868ee69e3a9d8ad8a9519b1d736f841ecb0e153991b4b9b385799ed913363be0000000006a47304402203641052a3a9a5be3482294e27791fef1bec571fe285e4feed398c9c0a01cf79d02200c40ed4ea2faca9c4497b00fdd450dd402ae4d5aee63ca13d61f759734ca81a70121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffc4b31292957ef8df6bb1f353b6a31b578f4846376a874dfd0595636e2788dbef000000006a47304402200f21aef363b19043a3b0e8500ef58e871886ab9045400cb7adbc84ffb2b3b54f02200701d2373c1bc744969b1d114be3bc0cfd0afff7c6144a004f7b987461bbc1530121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff764a58a1d5dc2c3c0cffa8dade68ca2883334034354025e1a596d32435e84519000000006a47304402200aeaba4ab7a6a4fa5b4dc02c63088787b189c915335696308b827392131a4b2302204e03bca3ba29ebe8155c03a9e0687f235d8b9ca8d1ec01cfbd1d1647f60fddfb0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff633a372d8b6033a18ae463a4af6b487cd0fec7a6ef845ca99b8ec76ee9cf9f06000000006a4730440220014e709dea27774bdc76ea338151562e0c0ceab56fa1a5497690f783605a10a302204c7143f7437000ae2fb8da6a38767f692985a93efd2b81a6c6d996be4840fdbe0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff058359d8698107858410610839ec2a799fe6fd46d86b842af981f70194191157000000006a47304402204683cf363df80c6dbca9a0656d56a152ca8972263759daf3b19e041411a8fda902206e7204d0a70b07873364a3e6b39c6de0ab38b87d5075efdea82a35edabaa8be50121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff29f0b11c586c690ea1cb3b5d1ee8b9b69128918147cd6fdadf274c19ee04a9b3000000006a47304402201563d78ca16489fd1e813a4d6cbe664898696c668ccdc9c852dd218387556de70220652d9992613ad28f3a29d9bf433b8fda3317cfb07e60964d2d1eb425b94e44500121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff8bb8776923d11d15d17fdafbd9a53120b9b8a9b590eba81cfb84a19efd231ea2000000006a4730440220585ba2adc11166f33c3c8498c97770321fc8dc1ada03f0c521835433877050ab022038b35fe3e67ad794e8ee58db1ae574afed5004d0b9fdd090bd71499c36c707f10121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff44ad7aa032b9dd56dd8b3c2b6cc2ac21009341b0efb7dbf165d57600c0c09310000000006a47304402203fd881f6345fd8ed7a6d74905db294fc37259e1247f96c877258ba40a1634a40022012efbd2fbcb943f27f3cd29527c31771bdb18ac931c9d76e94ad2119891513580121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffe1bfbc4eda99a6b2a50975b79116b51c247bcf67add8105fd5dda728327ec551000000006a47304402201227bb009d7b96ac9f8c42e29d4fba15867890fd87a853e5485867a3bd841d9202201f14cb9a7ce1b81456face5ea99e261465e124e8a33c70b8b35ab4f1dd09fd100121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff790258074bf21236c0eacde4f7f361c24ac2a7bb4278c547950c53b9902d1255000000006a47304402206977295b3687247a25c9e3795b8614c3a08ed013f121f0be881660528bbfedf10220679fd84dfb48284b0c7c3cfec00ae195a4d6cf01a8baebe573ba3bc433b9307e0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff8e87b7635fbd2cc2e347f6f90a37c6ab0bbc2b7b5c2f75113460da1d5da47c78000000006a47304402201758f7464b8c9c339de44f08eb3c1478dcd587e4fb643f0c929d6495e7d46853022011c7301229b1afbf0cfc10bffc0565505b1d5cef7c540bda384a8adf696a83e70121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff72c4336b0087d10646577e61cff4e3af251f39eed1a2bfd072e97923447efe03000000006a473044022069d8a832d892c5db027bfdcc1d00fc7059a67b3b9af16fd726bece4fc60a3d5102204c1820123ed623bc0980dfae00db470c96d8d78954891fcc0b325a26a8a638f30121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffc0699647d29c8a295c6775dfac7dca9ef143c7120b4021b0e6098032ca22bf29000000006a47304402206b774019f24ec4a95c23e520565606e7abd296297d234fae65367005f216ad1902202b5fef05701e886ddce06b095506d58b903c62000e244cad30e0b963dc26ac860121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff7fc7fc8eba5135045f1317942fe324d5a9edaf78855365a22ce9a74c728b2b8e000000006a473044022053b85d0d7e5896db5a7a4fee6ce72ffbd3553a95ed9064d3b360f509ae1df2840220079eede00216bd1e6740f50294becd8d74ba9f875578fcdff93af84af2bf1c380121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff4d1e96d0eb3bbac7733230ae0eb03cfe77c8b1aa2cdd2225800ea15d61de8d38000000006a4730440220646ed4793004469ac53843444271924566b3a1bfaf66107c5bd7fbc011fa1c08022050093e719ce00a7d3c8df840a06a6e458e7047d88e0cc753838928ebc505c9100121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff9c4dfda939a08794bff2bfb2900a3ba41fd91b74957c1a4abf30a854ee14dc1d000000006a47304402203b7d33d290b0ba9c81df1da0b754128c61a783f2547de33ed105eb9d8020b8eb02200be90af145f3b220ef5b1916744a05d6ca4621c3d6e1edb988ddafff0b8120e30121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff2ba9990794a583459ee711aa0de2a3bc50ea7b35af9a5961a76327707e7f37c2000000006a47304402202d4cf7ab5008dd5ed5e7cf328fcc345112b52d0cfb8b512a5dab02affc0fe7b2022021c4ea0bac397060c1c596823f9d65b9d267ffd11698cf53681fb19e13d6309b0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffe9e47cce22cdb379037131bd4c916eb1bb7bcd8707e45c0b1625df3737035956000000006a473044022040d803dfeec31854f0cb3452dd1ead887a487ed635aef54767dc33a03b38c1e402204bcb98c15103f281801a5b1d84d39ed1c716ba73794a58b163324b566d7ae3120121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffe4efb57c4517ed245a5a28b0dc45a02a6fea825316013e4b7428e5c8c7f55c01000000006a47304402201dd9871920e6f5fda567e04ad0b5e3986e7fe7ff8e649f8c5a90a9cca85a990e02204ca0adbf92d9507281f40a3caa79c70de10ac26807d0df087d896eb803d7c8060121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff9e2261770c9b7eedd1c50c529d8882b31b0c6bcbda2419dab9602e77e69e96fb000000006a47304402207efd3dc3938f5c0ea8c5eeae8156c80d2abbec1f4b382ee84ccc618ea9fb9bc0022048b77001163852975b7a32ab4e4fb57f5682b29291afd44c9996b6d3a887deca0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff2ccb8ed86e2aac582711561f41e796690985bbeed9a8648be7f1cda1378ff363000000006a473044022007e2c7ea089f49772927772cad59db7b07d9e5e93b4abd735d5ef7795aa2be4b02203e8b8258fab9be446bce560a1027b8712722da30319940b49350d5b5e1e0b3820121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff73845ef434651751b1dc49b9641af96d14209f9ee27fc1ed93da4e3ebeb9c984000000006a47304402200bc3c5799e7700d7c2d90576604c574a8a25ed05de44f98a524d970c8f2aa33302205f12c37480fc6c106bd36b0e7cf6651a9c6febe7e0da84d1cd568c18742f11fd0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff0cb64319180ad5ccd4e7a1609f4825e12576d93853a6f8216f1326292861d270000000006a47304402207b0e07e32c901cf94d099685acf4aea732f011164fa26811203f1bda4809d109022001b5aa8bcff76875e5c781b6997b46fd05511ae7e2e2126ff4066159393dc0020121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff9be32331debdf3227b862a21d7b39c84bfe553d420893f16bfe105783af9e49f00000000694630430220420895bb5d9bc0ff38a8df0a605d6207608e18393dae58ca1c0bd93a6b27d090021f2e065f6bf8044ecec912beb0d8cd3ccc2e1b64895e981a7787e6af4ab9f84a0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff134ffee89747abcd677b8bc4ce54dce523b9365d0047538b558700e2a678c9c7000000006a473044022040501a598a3e39fd1e613b1ffbf13b2b6e49379d74b4646ad1c5ad6c88738f560220036fa8bd03272d0d67402b7bd89e782101123e2a8a6f8d4221ee2cfec512e86d0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffa7e1b0042c314f9fd1e14d52dae12efbfd3bf73e2eec7f7aa3d76d760ceb7e1e000000006a473044022040c7820166b41139d53de05c15a8eed188e1b8f2648005cbce27e7674a81f4a602200f449b0cc9b333e984326b2fd9695613d968aef0fd4e427c7fe7fff7964ff10f0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3fefffffff5e574a4ff6e9175a31e18e0071a61174f9842b51900c500e7fedc49cb0af3e2000000006a47304402200a616bb1616871a97f8d3b696ba2dfb30c36023f5153688ce48d5276193bee9202202bf3275ccd00bfed7e2d7fd4e4b4e4120481556944bec14997d3d82df5ef2fab0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff42b7cabf7ecc15d3bd491b38826c0ecd00583df830fa617cf249e79383ab6cb7000000006a473044022037c9ad204e5e8720420eff4d59cd226bdefa960e6e323ece2c65b9040e88c62702206acf99c24ba9598ba44b3b07cbef0c6152aa57b1e68cc6487d6a9da54ca687a60121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff1fe8a7353354083f5b887236ea2a5ed0d25cec7c8acb7c13362eb05bfb3f328a000000006a47304402204a6f04af79e5f5ab093db71b9688edccf1827c4bead023e3f2aa829ad9da236d02205668d24cb791a2b82691f6bf121a098c0518b19e6fe065425d79a6cbe3a7a2fb0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff46b71efe81a1091ab95eb5fa4ae565e1580b79b58a99bfb93234d13e95dab21c000000006a47304402200e782289f688139620d42d50345d8d1b6e331c5daace1cf9fbd6c614d1706a9202201dc710f39ed94a9cc79e5714131b8049724eab4a8a294610d0c12cc4175c70ee0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff44ea866195557dbcc47aa65af5a595e104c0387e5c199880d082d28a944ce2ce000000006a47304402203fe9c299c1beb026d7d938bce2c4c98bfedd466baae8a996c08282b7035e9d9902203eb001c2ce466a0265735d3c8957c513dee1810f925f9e09586a05b85b6fec980121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffc88a8f2c1ac37d787352f17542854c68ef214dfce31027fbfe7f914d649e3c80000000006a473044022052c90982f4faa5d4e2e6814d24384923cbd1f30c219d6b1dd4c65bc89047d9f302200bead49128103cb94f4bf870cf20bd73883fe93f7fcd2994988dc79299cf6c2c0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff628da283f4d2f0542579bf54769ad4dc3ac5f8b11e2337d11b6fea80a44e6ec3000000006a473044022067b06a25ac8f0a62ac907e9ac05d44ed87eb190df3905e13a245f6ab7b85348f02201bea5ba28859653fa6be67dae88938a8ca40ac0076f758c099e52f1ca777a56d0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffc24eb0ee4ac7bf21913cc7ee2fce56edda050b812bf7d6fe82edcd27a79c6a91000000006a47304402203e03a07404ed30e45fb9860cedea5986a29f46c7438dc494ea1aa01ea1a6e1d00220216020de3f94ef90409d5e4cf5313bf78cf7ced032fe143581c85e09cbd24e290121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff9917f27915b61bdbdd921b53bf679dffaca07b302c3462e00a7097410bf61ff9000000006a4730440220211a183bca4a96095f85a3ab3edce55ffd38c7bc61c199ae20459795426df3c2022039062c6cda3de303b123ae249c3ffccd58255df75b8f468eed8a6f03c1b763ee0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff23300fedfd6ca47770f798a1cf14a106be1e7a1eef7265754bdb8bc462afbb04000000006a47304402206002e3d7da0139bc451d1523ab42c795aaa5c942ad0f998cd8e6569e403ad1a202205ead72ad09fbf2a228a2ed7553a55d2564bd600bbb5e14e91748f73deb686a1f0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffd9b76eb9b9dcec5c49fe258b3abb7c6121f2b404cec4b2c20e0333f1d77f3b4d000000006a473044022037491a9b4f9e2800e8793108e758fffd4f9ae7c356fb4cdf9185d292207f721b02202381d2fa6c78d2ea30d88191d2ee9005bb3c2743de392efb66451324839c3ca20121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff2520ba3eff7e8e3879cc5de7383db59089232c56a8860361d577719cc911b886000000006a4730440220215e7759f79aa8a7f81108e126845767e8f86f972e8d3544c3e0f201c8cbc0bb0220600638b536b546ec3dc790257b1377ee0f34e88e96de8eb9e0e38090c27cdb1a0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff63d6e917e166bf20ed56a3868016e5d2e4d6452537434ef720fa22393f20d710000000006a473044022074672d816d2f457c1b9c89089b3102a318100b65a56969fc635402f008c700f702201799d638b768b9986035050a33b22f9b79db74ee717e00ba8da076162c6b08250121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff1e1277e290fa7f10a7189ed2fe602ed980d553fd8adf5e22bcb49702456cca51000000006a4730440220187abe1ae9362866ca7a7810f9431303584f01b5ece682b40e141726525561b602202e54529155b015480aaff4cdcc6ee47f30a88f54a8e7518a311fdd48735b6f730121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff6f36fcd8b8a5b1edd2aede7099b8bbb1c18f747aa8b32070b1e04a08da79e652000000006a47304402202c76e71cfbbb085c0ba46e6a023425aae1e4e8e22d8462f7a0aee956cff678280220316f87bd31ef9f5d25d3def22e99cef71304d95435eb7b18079fa539049af4a00121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff558f74796e8a8a5900dbfbba18008768625b826a16fbf1ffa6d24f3b8f2d0b10000000006a47304402203bfef2d5d734ecc39f85568d12c488f6a54e7c5ad07d08efe5b6407ead32134502204d62a25540fe9ed5e271d3a7ca8bbe6eeaeab48dcd742b079fa28f7983666b700121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff5705a7f79fe9cf90fb4d4653a0192c8bb6c0865d6a620c346f69626c6ec7ee53000000006a47304402206b5fd26d7283aa4a8b17af4d80a652a1b09973eee23b5c353a1725441a33ba6002207334f6f74a3d61ec8eae6742fa2d4032acb8a0c6b29835cddcd9b9d925db800d0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffa0334a76e9280755b75ce3ac549bcc42466a2d95ab4fa2e9bc5de74333f8198a000000006a47304402204d024f9b08b359321f0c91bef6595e3e55a2250cb6da4c0a84ca323493bb783202202558d291bf376c924d2cc5967029a4b1f527add0ea3fa939c87b33e1ccda2a5e0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffd296d77887be81cbf96eb594fe116014a57c87cb301f480e7061bc377243fe3d000000006a4730440220044d26255ba51c60baf4e6dffc86a6d7bfbd6225b4fd7835794454c84795316102206d5bb7a88474be905ffc97f71e5035bf4edd0739541d91215c9c0feee638fe550121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff764b6ccd8d56a18fe7fd76d0cca9190b190ac8008f233313b3f8cb73f19dbe72000000006a47304402207a146bdc607a45f943a9b669a6e2ed6521967de92fb2f24f7ee0b2800a0290b302204fc19f43a86faee44d997a4e1304708d397a12bbfed75ee6495315a9aac0e5310121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff0e9b6490794fbead2bc010269a5cbab663e62ed19696e6ba5ea53d76b4ccc2aa000000006a47304402200ff9cf3399fe14190e8c725f192eb0f715fd5713db1e2fadd1a7889639e4972e0220141eca71166662eeb8eb77311e0738401ed3bec5f688bf1fbad9d54d57a4860c0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff2a627f2435b449b70b1551dd068ea9356b6db0531d02def47bb4c320e71b33e6000000006a4730440220726f54fd5764a3632f7898a6f6a8ab4dc6a266cab4e1f653eacfc5ced71ec0e7022037b81a753d9a03cc96f41e45e33d11123e53c03d5e6e4839b625c6e75a3a93fc0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffaf414d098a5a4615d49a12c5fba89d02a56fc871666ca3dce7027bccde55a8a7000000006a473044022051312ffa900c2646b6d081a5429a56bfc4d15ae517e736005a5eb688daf66ab9022076346601024d4b8b0faa32d202a3f9044cc81e6a9763435eb8095ace874f3d2d0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff62d1c78615b3345be193f224d2deabae41783c53275e7a953f26708f17c41a34000000006a47304402207c65c329fc5edadc69009d303493678b084125657678c843c220f5594f1f4bda02202fd43797e418a233151e5b21a051dbcf3112ba808e024a3b9566fb64c5aef7960121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffc4714184de113d17382a94c780b1ea06604ed22d019f70a088a8decae2bf670d000000006a4730440220660bdc141a048c35ee2464d4b6d106b52bb4af1deaf60a6a3686aae85d2f7f54022019ba95d2f8a2d82bd553889d6692e8cf75c5647e0414404803b717636fcd03dd0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffb9a0bb90dadfc19ad06d5f6b8914a938edf1f6bcc5c688dc1ba31aa7085346c60000000000feffffff767eda8fd2d3347469469b0160837c9f762ffe1e59448380b16e3d98ea3f78d0000000006a47304402200d7b3ac263a8c5bb2d96d139020a37f161ca3fcc651105f817405736f91ad4ed022051f9ff613d3602eea75ec30adc5c1af91bee37730b9875ab091c3be88396da510121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff2eacc27780b96e16e6d98a3af9fe136a04b4fc7e449a82ba820cc5a22e8606ce000000006a47304402205116b64ba26dd2c76c7ff6b0a2fa7339a7770e2e25b747091c1012bde3ab475c02204dbeabd85f9c9c9d999f47aa2e6d5953ce81bf6501969623cef6a68a753c7c210121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff698878a36fd16c190072629b6514b615ada161915af0e99b9f331a35be464adc000000006a4730440220237a536b6d1ab1ef76984018895605bd3963ea01a6a60b5a51f0bbf7c9f403e302207a35b08c0b4a0502687c617f67ea8ddc9d0f9645ffd18c9fd0d1289c5b7595bf0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffdcf26a2305581391e3f99c9e16f7e311d31a61e29e1fb88644815d6a5d42a6e0000000006a47304402203f96828dda2c6a71ee4a564045895dd131a49a4e368b5ea6c6d8de720b73c567022014b692022be6f3cacddff07b21112236c2a238577f9ed1483980909c6c4d38ce0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffbb087167400da5e0ed692118b4ad59b0cfae28b27c474c148a743b1424095456000000006a4730440220634cba1feafc539cdbc13ced178900475ac345f2e57565d8243fa945dcc8ff820220154ef1e7d01a7e76d228c9c63ef9c7c0832e9eb433033b986243c6e34bdcba560121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff30f222601a3555be52cc8a1903117cb19032a7fa78b4a8ca2de2581ecb73affb000000006a473044022068d4d8d8cacbca6c85abc86e8b48ebabc7f72619bb689456ecdd04c7bdd61f8202200a62673465581cd6546a4602d3dcca846455c21c9d6dafb93b93220e8da3bd960121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffbf2136cef8f4381cce1d110b91d2f212344e5d3f2b6a307ea73a6b8237eb6500000000006a4730440220195ca7a7fec293306cb7c10237c91ab9726e140b7b0063ba374ad5558b0a4380022006a07e4dce20526e78b08e42cda4b74234b083a46c22378b75a18fe188b1f6f10121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffc9b5994d34b04e9b0f0dda9e36968eb2c69710bdb339b610bce8890a6a7cfc30000000006a473044022023ec048e4c381a46fdffab98be507948fb21eb63f8f1222d9cf394bc792dc2e7022062fd1374e277b659f73dfe35ad21ffebfd25acf44a41db4887f472baebd350150121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff697b8cfe1a270d4eed2d324fcc7340d4939643f94d10ff93e93c4074cdaf6744000000006a47304402206723fc7ec2baf501b4ad9294653f1efee962e17e99eae0115323e598964947db02200b27063c5514535d09fa5d00a6c9091b56b99b18e3d5da1692b15af4f3e282420121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff32605c0a042d02e43cde00b53600e610756a36f1a582e01efbfe0f186487e90d000000006a47304402202932e07bc94ddaef236e5161936cac76e14bb4f8e0378ae4e1481af5c9090b6e02206c14c690919a194ce2967820c29751df295db446e8acb13ff895d0fba899983a0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff13d7c3cc7ba8b91a03638a2bd5819adc9952d8b78cf11caa4b92892b2dc6f83a000000006a47304402203685896065fa62c3cac4cb02ad1499289f18c5ef5040b6eb2f1087e634ae34cd022040e314d860ddedc5d21f1705aa9d0fdcb1fbc6dc76c3c59b39f7d88842a3a57e0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff2dd407a5153fe991fd9bccb167dccbd7ac873a96a1245ccd744e216d59fc16c3000000006a4730440220274fbc5f8c31e361261c404a5b8b5c1a0aea745832fa9e2dddb21911d1958d86022026bae43343a6fea07f4b7ba3c64773531cc03a66e0963b9ac7d1d164b5efeb6f0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff11172a9b515fca99b080515739637d8320c11b5b948f2387ded4f59c2d6ecd39000000006a473044022063ea3edced14593a63efa60182d90431aba4131f5b79737d85a4b35d49dfdbd7022024fcedec0ff05c41abe4e4f5287c78e4ebb8584a9038013349ed31f6729708550121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff009964b263cf590f7936e4b57c994934df9f485325c6da7fa16646131fa2f0b9000000006a47304402203bb27851615faced2f8add309d0122a982f38476a9e734446f689b4e90b5a44002201952fa60f2e3cadf5df712554ff0c6148946af03c0e123c675068c7bb6dd46f60121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3fefffffff01760e030aa02b6f250fb1b6ebe7c377fdf3dd723a4293accabb6729f920b3d000000006a473044022023b254d970a49b457cdfa136df6bbbc61508cae232da2924f21a5266e291eed502205a770d1d8ac6b0934e7c02ddaef64a035dd423cec68e8e1bacf17cdef55776b70121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff31448685de838a611357c8f4ee06215b6225910f63de8d6d15db45ad75af8bd2000000006a47304402207797a6358dd815cadbf7e0e03add34b45c938fab6598a97ac501d18b4224234602205defbd892c89aeb616619ca1cd796c9a729c8172c3830d032db6d99470186c8b0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffd32b92a42459c588f5244fd478bd60732fd468e07b00224b423c911155a85135000000006a47304402205614ee0f47ebca7e568c616ae3f95f4f5d51c56635221efe58f2527980981df30220637ce84e7cc20cbc27c6704b1e0b8739e4b0a9776084917a2df4fb26052e900c0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff6d305cc6882edaa7ae6daf91dc08c5722b33b599f4191dfd061000696f0bd3fe000000006a47304402202e78a45fedc42b23b9671ae4de182fd0aaf60e350b860e589114540e5a27fd7f02206c3fb504a9d5674f4a1362224314fda589aa864c5542ef23f65bc81ec5c3e27c0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffffdb313f74cd5390619eeaa16861da8688bde3fac599138f5ded7f2d770f07fd1c000000006a47304402203fd680c835ba251cc61d3a366e1ee5aa8cd6ad9726381c0e65e87e2c61e7a9d402201b78fd543f9ae53ebf237578801aa651c7b7a15f40f3c7e67b1d2295b2ef60cb0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff57bacfcb48a10ea57be507191e88bef27ebb681dc1c64769a42a49fc36f0aac2000000006a47304402207d111ea099ddce6980ab678bfec7649a66f8b8bfc6d3cda6f67dededb6f3ee7502200f745ca0f04f82eb26344de21281f78bfc7da52cf11e8fec841949ce3c6760a20121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff738b3a2fbdae42792f9d855adbe3ad396401d4184a61cdffbfb064ef60d77b56000000006a473044022020b0c94b39924a177508fd76bcd660fdee62662d5698730416ad86d0b961f14602205695fb390c9d876dc8d1c23e79eed9e5100477fd70eac5422517df53a601aa3f0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff521b2d20e62fb89cb2ca7cd90190d1aa31b5d27f981db93b114cf62227c6984f0000000069463043021f5fe3889f62426e2969501d4f2d004a11fc547a057e0f82dc427941beae5a98022010b114e6427d27dbb941ad6bf48c267df642f43c68d3da38a360f544eeecec130121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff7d8c26550d90c5f7ff191ad60e71e1bcc33b30f0776475cfcdd01b3b6f76140e000000006a4730440220008358352b3b53e0eb6a93472f337e62174a2381e27c5f18136a5b8c9ac833090220504974eab0871ab9449ded1cc4e0aaac4370824f5d41eaad709a0d897367a63f0121032513dd0819b07843f87c72da9a2dd470052465b07af4dfb2168e0dee9aa18fb3feffffff0200742daa35010000160014b0a0c0793c306d3acf9b7ddc82d81ec883aed1332934ec05000000001600141eae04af95f8bb6d764d6bd8ab1d44c6f9f69f5800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000247304402204e60198def6c42171ec1f8bc94964e5aadf6db6e12b4e17d64f8d28f39823de902207a56df478697982c416f1a6456dcc689602eaaeeaf998eba66d5788d6213016701210235f27c8b66440858f15ff687aa73f832a9749c6836c3a10230e04a5603eb5b960000000000000000000000000000000000000000000000000000000200000000010276445b8d0cb42802f2ae3eedd993d61f89b40e080b8c4ad6edae360470e9ff680000000017160014df298943ed647857d35dbcc715baf6bdea4da857feffffffeb94e6f0b7ec075dec5e082e2c0229067279ddccdba1b420e7b645ca793839b2000000001716001439b324ff1d10a60d68db9e747cc6b0e1dc415eddfeffffff02c0b2acb90300000017a9148b489b3c4768c40cfe38eb77506a77eb7eb50f188704ba5a040000000017a9143a89df35445cf2e8ae1361314f0918140c3f8b5c8702473044022019023831b7d930edba39b0ce003b05adf0c3416a1c1a72b2b81260d8f765f138022011df8a60131a5a1a7ebd65691e64bbcfb4e2972220abc1c42064ea15503ca5ea012102234128d235bfef8c8aa0e9ab3c83341c01fba865f70ddce5f168cbefa59a09ad024730440220236ed0011a681929d9252dcc73997e4262ddca6e378fd072aa36369a2a53ec3502204e9616b0b87731d7a199f6f379c118892acdba7534e8beb16669714d51f6ea94012103c9538598c7bb538698f40459ff4456a87b1a82a9e2a1610929b3038e6eecfa5cdb3002000200000000010164dbdbf3141e4003718e854fc2a1c7cb5db488da01bac291bd68848fe24dd3be0000000000feffffff02e02d92000000000016001416828112ec6e4e26c662237b064dff62d74daf86a8bb25312100000016001469db08ae941d33f162fc8355b7f377e98cc928f20247304402200f55b36d9192fa23b4bbc83df9f55a47d2032d4659aef141f3b0c5782fd7ad3d02203d7b1f7779e2622912a1b69b6905b6b53f2b09618248dc3140fe6f80e0b7d00d01210369c85e1e1c034fb944781a0301168870010aae841a999054faf0f140e880c14000000000020000000001016957f94cceff503e22a61a7c969c9463dc5591e3c313f2305a53486170767b1f0100000000feffffff02478ea52100000000160014c8f3992be8b9d5041ae987071446db4ceefdb21b20e88d000000000016001416828112ec6e4e26c662237b064dff62d74daf86024730440220147fa8c427157a3436f79d0d2f0e7f173b69a02c61bcc87441d5b16fe4a98e950220414931d4cbed56939f6b9fd17017a7bb24b6c472274048c1e7608286d08ebbef012103174ecff8ac9ffaa09ac9713ef35881e1a9f8b5c37d5e236e0321645338c32702db300200020000000001012bd8d93bfae8717a43544635cf6000a6a0337cc4b9710ae71cb8a83ef8efbe740100000000feffffff034c232f3e02000000160014d8ea308a269faea05834c1830ecfb5ee8b1d3bab715d170000000000160014cdb830bb069c4fb24b0ad90e8cfc654a2545c16e39effe050000000017a914b2c887e2a0889e357e9aaa20742291a219de4651870247304402207f7aa2607575d21b2e955e568c5acdfaaaf65f1db526a2b24ab649fd7d4c740e02202829d8d63723d7fe859d9f8dd83ae95ba19c3a99323cb4afba7bde63c0d3d2ab0121034a823ee367acfb9d8499189a1779f39616df23c6b12868cce2cc2343ded1ba67db30020002000000000101c53a812f37fd3ca60eb91f92afd896dd1f9fd7c76a39b2f2f1ca5efe62ce43ce0000000000feffffff0360b05971010000001600144056b48140b1e9cc4ed487e5bc710626a343d40876e5a5a30000000017a914b2c887e2a0889e357e9aaa20742291a219de465187c88c2f290000000017a9147bd0abc3fdda82ba3887eb4433d80d1867bffaf6870247304402202c263a58da5745aad6f3efe7a043eccf296c7b5fa1a152bc9c11dc723a54a9ff022003f0a24a35276dbf6e88a1b53d007645aed737218c4fb797cc1475b85d32f83d012103b82bc63000453a0e41f5c637d3a7da705ab895c890f4bda6340136b19dbc9494000000000200000001abefbeaa1dfa7236ffb18e5cb3d122c1bfd2b2a581ff1fb882782145b883124e000000006a473044022017f2fedaabb72444b8bec2bfc4338c4fa142e22aa98333e90a0707623df38d110220374dd43bb3b2093bd6b79c0e665bcff25b130c78735d073ae3650cd845baf0f70121035a14c069a4146f05ec6977e3e6970da0e0964af501ca8b8f206bf630d8d701e6feffffff028ab0f5440200000016001415fb96547e7e1c89cbb98889e468735353e604d89a32160f0000000017a9147bd0abc3fdda82ba3887eb4433d80d1867bffaf68700000000020000000001018d2da9484357cfcb8cc9871c7ab7476c190a241436a08fd475a0525589a8cc650000000000feffffff02c63693040000000017a914b2c887e2a0889e357e9aaa20742291a219de4651873679624002000000160014c71d2c00e5f0cc68ebee92c406ac14417383ccd80247304402200110afe65de190708b79699c912adc77aef1f574ad9a0443e9f9dc6b2f5bee45022021e36496b5cda303e45c62874ed4603d67c0948b6baf16d5230dcde056d3cd470121026e9a99dd63d32f4862c713b3ae48767e030e9fa513e62e1acdf1959bbdcb715200000000020000000001016c46fc03f077d70069b84975d89b2646c9659784aeab305957fb4bb5c35748760000000000feffffff04e939a48100000000160014500b942e38b38a56888e78fd8d6c2276a7b517cd07eed0000000000017a914ccd3a2fd5006aedd147bd723372524c07c162e37870f2963e40000000017a914b2c887e2a0889e357e9aaa20742291a219de465187935e810a0000000017a9147bd0abc3fdda82ba3887eb4433d80d1867bffaf6870247304402207e69e9e5769817afb923bc3b283c7df106c95cbbdbbd4c35f871849d1c7897810220583a90e3d922072d401290ba1a9d275a789108966bdc4ba8a0770314b35db52f012102162f62b50f5605ee153d09a23191b1369039a7e482f5bf7f3c617955a793939e0000000002000000000101813f8c35cc90c902cf677c21baa9d9ea81245543344f01d55402490b9538768d0100000000feffffff02e092ee794e0000001600148be88cfa48811b52128f5dc441dce209a2cdb3e600e1f505000000001600142752eea78177a0b1614c1e93a1b3c948412811de0247304402201c37c4ac8e183eb8d5e3691bfbf562f07b7d9ec78ca4df3e239495d46bda0d35022062d6fd4d9d9333e64160f42b41771a705af3e2602d4cbed833e15ac7c80507930121028675972cfaee49324578b087e035c98f744fcd57aeeebcb5d0cb46b6c1e8582d0000000002000000000101866bbda1892f342663d939c050b47e2d130c5c8d7b45cf2c672d18ec015cadfa0100000000feffffff02d6f9772f0000000017a914b857a30b16ec9ded2f167483d4c1375388a2a45587d27eea10020000001600140e68296f6d1906859c29f22e8b36a52710361860024730440220293a5348a5393719399d450d23425a41b5b3ecb3547f8d92571a60edbd21d8b902203e4ba74c948937f627355d471d15cda486a633eb11c82731bfd04c8899ffc387012103d13a9adfbb428d512d2ee057d6a01b0cd2f445c08c6711ab262b3295c1eb44f600000000020000000395517c7e024bc8ce589bc4c775eadb9fdfeef7a53381266563f486cf69a0c756000000006a473044022034a8aaf4ade2efcb395cd310a867ce9402378c8ae73e3e7081b8f197f70b3469022067704ee349a4eeaff3f834616b7211a079c3e5f2e6284912b4ecf6a0b4fae57a0121029054d0dd14a76194f072d1208ede88a654229d9e30f1e3620f28ff5796f18544feffffffdd77ff7627d195d7c1972794afecf8dcf6e4580dd8420f3403c3e56f8a4694ba000000006a473044022028bdfd02e330d453ba382eca418acd0044ee3e456e56a619e4e8a9f388181a4e02202b9546343ba637c7afde42699dc91fb308d5748dd2e96406a735d95eb2d159c30121029054d0dd14a76194f072d1208ede88a654229d9e30f1e3620f28ff5796f18544feffffffe1d84ba9a0d7a313fdf473a2e813a8dfa5887071820703a34db99284b256d164000000006a4730440220184dfbcdcaf7f82489caf6870290c8d26c75af6f98cc5af12fa66557382d88f302205c276a12e78ad53d6d3181dec971b1645b57daa0e49df3399857fb07fb5b8b910121029054d0dd14a76194f072d1208ede88a654229d9e30f1e3620f28ff5796f18544feffffff026f9fe11100000000160014aa4a316a33049651cad3cd9701a9c7a80e1e8ba9900a42ea06000000160014b4d57464384047c7b1759c6916e90e4f48c0f0dfdb3002000200000000010122a2e91cc620e1966b2667db5086b1d7f1d48d3ccff6107511b3ca3b7c12591a0100000000feffffff0600943577000000001600143efd967ad4190bbe389698fe77626560990d721900c817a804000000160014271b8cd85d8c26da70102d3a361a9f4f35196f1000c817a8040000001600140892726909fb7f2a913c6bf1952a2e80e232adef00c817a80400000017a91424fb6fa0b657e2a43bfa3e637da50bb0e0870d3b87f6ba1fa437020000160014951a90c2fce81d592548a6d1eb2c06370a356f2700a89c1346020000160014951a90c2fce81d592548a6d1eb2c06370a356f2702473044022064861d092e15fb369440873d5153640baff8e7962f7a37e5af93f8fb178c7ea00220397bbc50189e241dc96add72aacbe9d63b8728f8b8dbbe0433156d2e4eddda3e012103dda7f16aeec33bb97c1c4c6ccde27aa4d040f9ee70e59cac196fdbe87186e090db300200", + "tx": [ + "45b61aabbfd7f0b4fef1ea634cb101bba81c2f6a092754b8bea64ddd5a456864", + "b7ef07b0e90ec71a28de90a9fdf1ea9972e511d5f422a79a5dca19b8f3484b74", + "41d41bce12214251d0e9c15188a8eb6f65d76eae3127a1f3b5f8b3a3513d5193", + "ef958209af53ee6271c3f811df5c227d026adf73db39952a71f61b7b3ab64b22", + "172d9c6aa9eb0bf3aee490b1f6feb3488250552cae0729d2c82ccf26806e9e2e", + "b68fe193e9359988a0e32f926b3e5e695946225867f4b4e03fff3770153f5965", + "ce43ce62fe5ecaf1f2b2396ac7d79f1fdd96d8af921fb90ea63cfd372f813ac5", + "764857c3b54bfb575930abae849765c946269bd87549b86900d777f003fc466c", + "65cca8895552a075d48fa03614240a196c47b77a1c87c98ccbcf574348a92d8d", + "faad5c01ec182d672ccf457b8d5c0c132d7eb450c039d96326342f89a1bd6b86", + "8ec52d042271379a0caf5e554723c0b52116ff2947bc3f7c963db29b1a0456b1", + "f9f8212d279972a6339cbda1b7d89586cffafffa6e34b4656aa4b42a8cf631b7", + "824fe1c2a87b3c7d5bc0c2bd243197c422c0f8e1c6a22150c8ae3000efab4ecb", + "d5b57f3d11b9d78877492fd0e7eb8c29769903dfb696f8cd17ef2bd0770575df", + "b0a4c657995f68b7cbe25ef42953f41a3af7863a683cdb1d0308206566b1dfe8" + ], + "time": 1685208781, + "mediantime": 1685017341, + "nonce": 861452611, + "bits": "1b0321a7", + "difficulty": 20927.97473967343, + "chainwork": "000000000000000000000000000000000000000000000000045cc3538c9f6bc0", + "nTx": 15, + "previousblockhash": "de33d1a292d31feeb80a9ec58b826bfd8c0a9fb98f7937f63982ceaacd83018e", + "nextblockhash": "9ed8fdc8ef871b6b7183ecc685b97ed7a53d4423cf02ee39c3ee7edf62a392a0" +} diff --git a/tests/blocks/ferrite_mainnet_150213.json b/tests/blocks/ferrite_mainnet_150213.json new file mode 100644 index 0000000..391f4a4 --- /dev/null +++ b/tests/blocks/ferrite_mainnet_150213.json @@ -0,0 +1,43 @@ +{ + "hash": "c2506ad0054b701723b1b39261e88d9869d1502f57562f67b6738ffd04d2fe91", + "confirmations": 39, + "strippedsize": 2526, + "size": 7230, + "weight": 14636, + "height": 150213, + "version": 536870912, + "versionHex": "20000000", + "merkleroot": "c09ba83fdc00974cbd5931a9df67ea2801fa0ae7e593a0e0c05660f49058c3bc", + "block": "00000020c41dc4184ed67c0dd14dc267ac7d6906064b95cc93a05e24e93678e392a9ecb8bcc35890f46056c0e0a093e5e70afa0128ea67dfa93159bd4c9700dc3fa89bc032cca864b150061b069c4fd603010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff5203c54a020439cca86408fabe6d6dc1428e8fd6bbab4e5ca1812422acb2d90d835dc0171ca0adee406965b9149dc8000100000000000001476b79940000001f00112f4d696e696e672d44757463682f2d32370000000002bef00b54020000001976a91459c93e2a1d7d1653ca1863425850b53e20283dbb88ac0000000000000000266a24aa21a9ed934a95168f7079fac3691cd7deed5aa45499e39c072ca1d1449d81a11b4ec7f201200000000000000000000000000000000000000000000000000000000000000000000000000200000000012b41efda45d6a7059c900eefc51c8a775f5527b734df1f174646248d867796c4470000000017160014db40ce6a81006888605edb98ad9b6cc9312dd10ffeffffff75fc8cefc80b944e0fb1ec9b23fcb63ae2b877a7cb2951b68830cc513b6fb3530100000000feffffff6c4cf98cedc3e7ba19cca6c99004a82422d99dcfcfbf5fbefac7a8d110667e440100000000feffffff07db465ee82089bf75294424ef1d3f82679a106e68794065544bb8fda653348f0000000000fefffffffbe97868d50f063d62234c056e31c922ed5c398c2027cfff3b1f4070a097947f0200000000feffffff5b9f405b0b92da1b26eede47b9a5e97529fd69782bc24d5e364c14573cc6838b0100000000feffffffa10951b2fc5c1c69862ffcb67bfcdf6a873f87d55239b0c26f5f9446e55cbdef0300000000feffffff55255518eb7d7d919376619ae837f6b69fad084c42a8088cdbee76e8f4fc07220200000000feffffffa8377d773b67ec8cfaf8bc9efc3911f0844e9cc23e34ae3790cbf5eba7f9b41a0100000000feffffff58fb34ad09d283cceea44ec8295a5b26db682384afbd2b9cbb0db21fcb284aa60100000000feffffff3ed3746b790f6250b20583b9ac07545f9010b6da3921ffbf5bc1dabd7947eccc0200000000feffffff21972acfc83e54c4ddd4aed0ed2bc8e4b628286f01575d03add2d56a98773ec80100000000feffffff466f9dcfe74bb9e12d73ab83515210bb8fbd0c82a853bfbb4a8d1cb171fec2860000000000fefffffffd44695241e31c93bf30bef20fea29d653f552462a8b3d6a1d7b2a314cc9be320200000000feffffffdc62d2dce0b9fc0947d6c90c607f9fbf0e362179742198b31bab842bca797d1d0000000000feffffff754cabd9efe49187ef283de57ffe1d4425460b0d6126ab6a60eb085fca2ffa980100000000feffffff60f48cfb81839da3e9dafa2bd447153870d169c7f04c87bc8d95572845f389350200000000feffffff063b3b349729de894b21d883c2bfbb6da9c577312dcc3795345f5a8e30de4c740000000000feffffff353733324b98dea84946b8860e533281c0ab163075f75a7d2350855b8125319c0000000000fefffffff551588479f57d390566a8c3067f84325ef59ae74bbd42eb2f1af6c048ad862f0300000000feffffffca46b7bb1526f2db0a5523489bf66bfea93688693d4d4bc33fa46a287048c4040000000000feffffff400aa9c0cfa0c49a0cd8abdc08c841aac4054f4e0d4a1348ee4b3d12892a5bca0000000000feffffff8567d0202e84017acddf9dbfc745ee6399393074dab2cf76c775099d3e2672140000000000feffffff159e4446aabf7971d379463c8622527fb3c339197c6122eeedb5dbc05351b0ec0000000000feffffff080b6b8447eef1e0b3909b241c4cc2acad748ee74e1e5062b7cb3be5bf3a43de0000000000feffffff9272076a02372cc1eaf57c3e4b54aaf508908e684c4441fe7b0b64ee1b385a2e0000000000feffffffd27dad7df847a415ddc589e8aeaaf0efc67dc4cda1e58e4b73c146260b0605310100000000feffffffde5f225e1d7b1616815cba730703d778288cc83ae99933f2155a6323aa5edb190100000000feffffff47c10e6294a2837025a1579f76f5a308e1604ecee9715f0bf6b6a971c4889ef50000000000feffffff85a332c55997b4175e1e908efd61d46dcff80bf52198b0e424c9fa69675dd6a40100000017160014db40ce6a81006888605edb98ad9b6cc9312dd10ffeffffff912a79402dc26f1a63032b2335225b5518d9819ea5ce9ea014a4de8906397ab3010000006a473044022064112f7aafac4374111ae45901ae5ecd2c327488340af4106c1a29722608a2cd0220609781507774307b4f21b4c76d7398dba75fcab214cb9382a0d3ae0c47acbdbe012102d50640454326f6856dd5032097652e0e3c94f1f2eb94cd0f06faae2c4d7eeb53feffffff30949d56d546c44cdcb6db8a5503c78be5dc4ac742618ac20fa607fc71c58ca00000000000feffffff908e5a025f4aeef8897c46dbf0f90b4fff4b11257f8dcb4befdbaf445959c6de0000000000feffffffead34f52a30cf3d6babca555bbd6f26796812a16e7151b6d91342816cd93622e0100000000feffffffab8de1fda2bb082732a0bc94ddf20bfbf937d840e30c0c00cd6b1083db1d89b70000000000feffffffac7d90a2fa7de2c35f27d364c73777e27bfd0ef608044cc4f60be7cedff6be260000000000feffffff185d51d4f88e7892f29d200c90c8cec4a35e7a1c60a87dfafd042be6fe7072500100000000feffffff1d7889a917785bfe72fbb6bf3c2ea9268d5b73f3b64c397bb5802889f11ca6500000000000feffffff4d8ba3fd3ab8cf6e6fe82afd34b70224011703ee1d220519f365a69f2af96d320100000000feffffff89622be21bb6a22754298de52618808afd36062f4f56f2e9c301c687ea790cd40100000017160014db40ce6a81006888605edb98ad9b6cc9312dd10ffefffffff027a29a53ddcc832485cb3a97e61dbde9f2682abe85ab297ce1673b5493a17b0100000000feffffff58ff4148d8053bdb804f6c4c8ba7c97e7d9464c2843e309cf183481708c89a6b0000000000fefffffff024501cf8cf096c6566fd598c3dada688bc122869079170095438164337fb320200000000feffffff064450c843050000001600147b1ffb49099776c930d2971784d9b826ba34e25deb232b000000000016001485532f824e181cad944add143c131064a522bf0b00062c9c0400000017a914a984c2bc37fa32f1b033fb1a35d57a641c44610f87000c58380900000016001470356654a4a7184a0c1f8e4cca80a5a457d96386ac55aedb1900000017a914a621df798647b6dfd68287ea64226162945af8c387f4c8a2530100000017a91495cf50540051c375c9c49efdae7f0040f9d27e43870247304402207312489cf8d35d1b95b394196f4304c2a2cc8ce88d234f809a20a2294c4fe48c02204f62d683e98208a5c622a42e4952073749a44184ac4f57c3a30bdd09ced5f74a0121025a983e1965364f8ef5cecfb4fa12075c7b17615a834bc33aa697ca7345f311160247304402202b735b3af107b0f754465a05546dc382fffd04647d75cb2dc3627d0ef8d000e902201b3442c4d818f06f2d067cbcf75faff4b824505e57ae3d7f558e2f90baf2e08b0121022b115f95e842ac471111152ce030943f00931bbce9d85d92e4ec39257978694d0247304402202f427ab0afac764db44e7b0607afd6b299e7bca932bfd4d7072199b742bf30bc02207744d3874d1d25b9ba9d65c38071f5f6c1c76d3a367cafd190df4d0f6b9aea2c012103d7dc51178d2308dbdc72777fbe2a93cc8252760352a9873896e4fb8fce5a7bbb02473044022030186cab89074d8e47d1a4d0084ac226da5edf106d8c2d902e4967fcdf37aaf202200b2afaa83c0ecc6aa2261abfb749d86a927260af5fecec44ea43f34efba9eeda012103696469473ffc442bfc8abc76be9baabd3d8a02614ae5a077304a645fe29ed41902473044022030d249715479904962bd88a549c2fac35125c9d987d2df680c9f9dd43a88928502201887174c4f9c56ac8d39b1e37ea0b1a13f87db03d131ac5c1dcf91786dcf4d1d01210278654d3d0370189bf9fc5a048e64477bf909c13b822b73b5ffedfd1d1b75f4480247304402206d482e36e90cad884bab11df23d93c903cec5558c16d78673a13ca28ea8fa5f8022064dde5b2860598810b19b9cad60a0a83b804977f067716451d60f16d6950a49c012103c853237b85d838064000806fb3d3b98c5f783ccdc0ab868ab29e66d76b0633500247304402200fe0882432ab1532eb56ac8d8aa29ccc2d5f81c6f5dfaace7321f61256451c7302200f2a2688699c5cf3df3a623deef897adb50c95d24aeec92f45451feb071e7c570121034527f43f96b2106e4959affdae45238ae0bad655c22f23bcbfaca63b97a7ded602473044022050e0367656fa0e627029798c36c36b326ac44acb3f5a2f3771cc7070ae981f3b02207f5104292d73eb7aa4b319405abe82490a7e750e0de57dcb6696418c4f72c0e9012103038e1b64630df689d42e2f5e4d42932f91039c2bb8fed69239c5f01e519d12930247304402201cbb75ba69ecf7a5336fdd7718151b0e8e3a8e23b6213c62d9c4c29f1261036a02207c94cd28881538836ccf13dfd3944a3f7ab17ca471e1bc1a9caf4f5cfea2526d012102cbb5a5cb68b4344f4431c36859f31bbff16e15484405fd7be043756a047f014b02473044022030cf0ac1c3691495fe47ac0afb54237c756918eeb2505949547879098987a0df02200252de0ed0fc2b2e8691c70594749643cdc475ee5fd4a14f9809c322c89d12b3012103405eea221864e08c685cf3e3c4e1a4ee1dfb901cd0a32c9da98cf338f30c5a710247304402204c17316813894038c73c19ee4ee716c0e85f364db13e0da6d52e8371e77adf9d02205b4c7fc89eca053f0f58f59f583b0355f54f3fab1306f82940241d7114323b31012103f1ef8b5e7d4f3b7dbc9b1d9af49fdf35ac8231d943a78206f6a415e9edadb2e402473044022010705fc044a639ed8906b405ad1fb6ef3cbf2a5f3c24dfd276191d8311fa18fe02207d75d6b17f3177da39e32f74bbc2c18f15577aa0b81e54da6c71ab613d55a0ac01210212b997c496d9fc98b99f663a83f5de74ce0e0acc57acae150eb186799c9debef024730440220036d2480a414ec8ccef528fbfbd40b41d08adc414d945c1e3416a18963a7c14a02201241b72d1f39e849dc294668d554e64df255d39486b1f0378f98cbaa1385a936012102a81c33bf9ebcdd73418325ef0d4be4af5751cb30b18c3d87af407223f7a9bb1402473044022062e8634a2a58032bed074291d989f27b1e1d63628724178916dff204075c35ac022066b030c3a250984ba586c98ef2e83f973d3d31a86a9b181423b78cdefb5b759601210356a825874e7570ed4e93d65539e5cb4e4b15a2e176af37822153a54eaa2e8baa0247304402207b31f7c2abc3f8ebe3b791ba8ee095ab573a6e5b16c958325134693120447a7102201b86576e0a7cbdc83b1217790dab1c331720ed979a96ea558e161b215b9ee8dc012102a9b9ccbf7a550441b7a80ae0ba9efebec1437285364d166764ae93881d0b8c0c02473044022003445ab4e0d16a970cca5e10dd36da2d42b86829d61735f31e88c2fe11f498c7022038afca08eff968d1f4f68ff242a687f0ac45525f9c19e6b2cd0b65b90b7b992e012102da2288d67d65b3e4eab7a4e670aec312942a53e6ad276ff126df7093ed88fdb802463043021f2bbc56098a15c3acf171de420701325e9e48993fda1c15cb091b6bafcc2c6502201ec3106fbfb79fad398703059a94b05fd7608aec29645c84efd2cba53273178b012103b15e49ca66f2b300fbe8dd4c60e0595a0798d21547799634f600302ba63253b90247304402202a2a973ff7d7c61cb021a2f5dbed612660c4204cdfebb50111833378e6f31bbf02206d4462960a72157798409dda51e5a5dfe6171057b23124aa39f7e4e0abf524c90121030c1222667eb9e6e87e756dcf394e72cbd1a8f40b4342c8d1b9585206141513d80247304402203dc0ff3a5b20262522878a029958651955e8fce79e1af61782190e02cf8727aa02206d8a838cbdecbab7af2176f704c279c8d866d713416be5e8175f7599f2d010120121035cbac6fb077a8d8025c69cc03f80f5d1e0f6acec43daf17800bd1f087d84b32d02473044022008ae02509b0235919e2b928c266130d3f7673adbb642c38f1b5bf6860cbf0765022041a62c277b5dbabcf892e4df42a87ffa39b71e06b9a40a20f2649ee82b94310c01210373ff2f3cf64b6c3121d5cc982b38901e47908ab542e6683645eb24a8961ba16c024730440220685ba34ea341998183436e6e01e4384120e8abea8282adc49560ea1c79f5a8b502203164483983bdbdccc435750ce85d1c079e7a41abed245c49589b1d8acc6852f50121024fc4ad2fa9f7d0e1ba8a81f8276fdf60f89a9dab01c3f359ba649694549168e3024730440220527ba25cede8df371e4dc176ed61e005c57146ae2a40039277db8bc4b6f0a58a02207398c0f08553485d54e624745195ac04a5c28f3fb2bace2f690c5141df44574a01210205768efe2c20652702531a6a419d91b8a63cd6042f1d3fe804350fd2136fc2f102473044022068fec0d9f2e8e7145176e0a56709c2ae9aeadf8fd6c4cebdd8175c849b736f78022014a6755e65153f6e44c0372dd5048de3a7f22a98e7fe0f375825e8eaa79b176a012102d9f346afed9a6875dc14704f8da9734951fea5fecb25fe928dae1180dd8c15890247304402204984d800d43fbbb6643d720487a586f889ea06f2bca830c648024930bf540257022014660c055a170ccb3117fd22c11b33ffe2427144d02c410d7eedc383415dc24d0121029a1c786cf5676f8b6d2000115b838d43e3c05956d70599c45cca78e5b8cba8f0024730440220477e1193a6bed3c161f0414a7d168869ff76c4bad7f011bfc519fc76618e345e02200cd48f5dac3a55396ec00388d972a1347d1639e6b44d5acdded0fb9b5bb8a967012102f3c0cb1fb6020428cfad3a5ecb5512f27f4a28884eb79c4a95bc7976c702d6da0247304402207e6638d73d292b473cc2c3c657774fb4f4e011755eec089d0f9ca97dc41a5bb2022041035d0889df525fda9a4ae10a0ac11e1091ea20bb69dada52a2474964dea5e3012102a65a5f360effa7b0f72245eb5695e7d79efda8795c090cacaa2c473c257f38cb0247304402201cde332ce88e6febbd0d1eccda303d4673dabd6170d091b2bd56c0fa00461ea002201eca264bba3bb66239529a840a118d649feb77f6a5801719d1fd84fd8d1b95090121033cd76ba84014dbc63babe3cf32895c54883d744b85cac88718fc8e02bb350a040247304402207d6d8dd66343d2fec07f00e91e3ae8705881493963fede1668b44817523691ca02206b511fdcf68af0151f2eaf8740fc5cbefd60068a9d3eded78416cabb27f66eb8012103074d716c333d731c0f9efdb7dcc4962bdff2d5fe27d694aaf9190029186a5a6f0247304402200cb55443a7d088f596d595f7512c222cbe1cea0eb906a7851edfa1aa912b31cc022005927c3a2b660abf17f33494cda2aa5a29bf08d974a0bd3ea14525e2f4ab2a83012103b94023c6d9c84f556b00d10aad39b90fc154e311e705f540c82a04a361e6852e0247304402201f88d22fec78ee6a23ad98fb17ece25b90c5f471a024c54c039b30d7aff37e2602200b390f0fd86fba654c2f057506ac27dc640c4e95fb88d52a4575cc78c8ef4cdb0121025a983e1965364f8ef5cecfb4fa12075c7b17615a834bc33aa697ca7345f31116000247304402204c8aef02f4964d146332a83091f6850ece9436b2770062a9533ada01bde83cff02203c9385e18b99c3ef9cbaa5b10abc89a875ffc4bc683151f07827a24e0a67a4bb0121033f13569b9d863c28f7341480255f4f2f3b4e072ba4473bd701edcb86d4a227610247304402202938f65932f3f45476bc49e9a4722b4a8b3b43ca33f3c371358f959b67d8c13a022003cfa49a5dfd6d656806dc5527988cefb1626d4f8cd9e03630e2a19eba048fbd01210267e20a987bee4ca796c32934a42f6538e9ced5f6f9c661271b810b0d956adede0247304402207c6231f4ba9563dde7cfb22c257d77a23100ff100697c50d4efa3aa5ba493ad60220400f09218795b55b249694be738a13919ed749d2573afd2cde3f24dab00046ec0121029ca91d8f282517d6be39c38dd26aba23f68462c073350013e4b6885b04113c1f024730440220221b6f999e1b687b81cb37a392ca77105568901cb5896d6ba57e3355cb714e06022058047dd8cc7a123629f265f240c9aaffdc2a219341faf7224db10252d56ed4ab012103650e1db87dfd662204291e4bd10474b9eb8177f2e2a9075e26ccef812b3ea5cc02473044022042120ccf7eb33e87a2554460b20f99b104bf712c37427c0fb3d22ca916e418c00220783046c5f48b05f9585f24b1ba809c14dc6f513db104c95032805650c6a718e5012102bbef1a8f81bdba5526d117e0e9dc2ff2e38f07f4c1b215dfe1b0c53a426164cd02473044022014185907f9ad81b889f8110f28de942ccb94493b37fb77ec0c2fb1b479979d9902202c81b961635d759673b8a1888c18c9be8d5089ab595493982a6aef446475f26e0121026de3db9b4ea9a23fad3f69505a0654b0a734c074e113eb4d1b1ea4adc5877c5a0247304402200ba1845feb0124e2981e867740596ef083134ec7d679dab0f25769a17e770ac202207a6f4d14ac14c842bf09525c578fc91e4a9e508e08b48596cebff4ec9ec85569012103cc50ce2f8595d9b8ba385c99d0484ffa0e7d9e922f18e25fe79a49209966c06502473044022020f0a222840d17f106729ee39b59bdec1fdaefcb8b3d7b4a0f5f9d934eb7d4ac0220238c4f3baf4134994fdf55758c6c1902665e9b82c342ec46d1d607572be6210901210214de39e29efdff0a1a0a5fdfc46aa7bf0f7e5a309233b4e564a2c9687db7e5f5024730440220742ea0b744b6652983466e296b3c9ac9e34945663668877ee194d0d36d1777a602206c9207cab5dd55f0e32ce4f0f8a96311f2c1dc24ed34fafa2feccc49861f0e7b0121025a983e1965364f8ef5cecfb4fa12075c7b17615a834bc33aa697ca7345f311160247304402200f3f67d6ac88d6c5b545be26a79a4bcd10aa5b413fcc72d9222e191bf6e0a84e022065630a77f15a8c7cd0ef88f91c3bebe68b5e99fbd474460231411b857c7743fc01210338498cd0cdc99ef39bac654bf00626b162d8e0b3e54865441a113be47bd0064902473044022035e5aa9b12f4403f21a37c67c91813d5f3affae051ebd54953a2a9ff7f1b66d602206686cb2f1930dab8e70435634fa3ba120954f119a134e7fa481831c4fbc1f05b012103fa9a5e1d38adbc7f09fc45f5f13e8278f482942df879b8d68db051478242286a024730440220376af861eb1bf6b47d5dd8d5a53761052c46c4ad7db365bd5713035a8dd1d31702207eeed98d5f2889e060017727018cde5d0bf6f1666cc6cd3e6645e00efeec80c801210271e59c691b7ee80aaf18b92a6dad3eefb7bc028175aed61de9a70d2ba458fb38c44a020002000000000801f72a7ec4097a264e8b46e3176758706635701a187dc3f033a436a702a7b08ef30000000000ffffffff0110739c17030d0000225820f000c7caa510cc97689a4374b181998a205bfeecc5546de2f8ff73e907eaea7d0000000000018894458d77d1390bcf2a96d9d86e244cdceef168b71ffc31e21faee0860912b34c12840000000000000000000000000000000000000000000000000000000000000000fcde460ea778a115ddfe9c25c37975b118d967cc52ccfd1b1e2a5c3926bf0bf20b3b067dc24e11784e6460cafa78c308e70ba2680123b0fbdf64aad021fc0a3200000000000000000000000000000000000000000000000000000000000000000600000000", + "tx": [ + "980d0ac4f83ce23d4909534d97b9f0b3bbe6718c838073fc81515809dfd5d9ba", + "746ed7265ef42d3b65879f20786a9dc44e3d993d2cf5a980082a524184b5a906", + "2fc5bcd23f4604a2c16c7bb7c83c3af0b20e3fc85853d5b33292927322109373" + ], + "time": 1688783922, + "mediantime": 1688757171, + "nonce": 3595541510, + "bits": "1b0650b1", + "difficulty": 10377.34222817144, + "chainwork": "000000000000000000000000000000000000000000000000054590a7a5ff5a20", + "nTx": 3, + "mweb": { + "hash": "f000c7caa510cc97689a4374b181998a205bfeecc5546de2f8ff73e907eaea7d", + "height": 150213, + "kernel_offset": "0b3b067dc24e11784e6460cafa78c308e70ba2680123b0fbdf64aad021fc0a32", + "stealth_offset": "0000000000000000000000000000000000000000000000000000000000000000", + "num_kernels": 0, + "num_txos": 6, + "kernel_root": "0000000000000000000000000000000000000000000000000000000000000000", + "output_root": "8d77d1390bcf2a96d9d86e244cdceef168b71ffc31e21faee0860912b34c1284", + "leaf_root": "fcde460ea778a115ddfe9c25c37975b118d967cc52ccfd1b1e2a5c3926bf0bf2", + "inputs": [ + ], + "outputs": [ + ], + "kernels": [ + ] + }, + "previousblockhash": "b8eca992e37836e9245ea093cc954b0606697dac67c24dd10d7cd64e18c41dc4", + "nextblockhash": "51d85100c783480db43049f0e99a20551ded418aac9d25163136aab58ba984ed" +} diff --git a/tests/blocks/ferrite_testnet_6282 b/tests/blocks/ferrite_testnet_6282 new file mode 100644 index 0000000..297f6bf --- /dev/null +++ b/tests/blocks/ferrite_testnet_6282 @@ -0,0 +1,48 @@ +{ + "hash": "36e1afc41f495021e59cf341477ac6500ba33242e47a6ab552406b4a5b1d9b1c", + "confirmations": 10, + "strippedsize": 42904, + "size": 43765, + "weight": 172306, + "height": 6282, + "version": 536870912, + "versionHex": "20000000", + "merkleroot": "22cc6b400e1f7794b641ec6de06524f6442df91d9dfd7f43ad5585e22f3dbed6", + "block": "0000002084db6c03fb19761ef7ee9712b29b13744161fc4a9c9855ffd29bed707067d073d6be3d2fe28555ad437ffd9d1df92d44f62465e06dec41b694771f0e406bcc22883d9364ffff0f1f3901000008020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff05028a180101ffffffff020b8b0c54020000001600148d216cb8f2ec113e508bc434aa8c3606a50dc8440000000000000000266a24aa21a9ed938c952cec26b916176e4264a786d0e580ffc78b5daa2a32de64fb520b70355b0120000000000000000000000000000000000000000000000000000000000000000000000000020000000001018ef8a90764df31639b623a11aeb881f2cd232bfa5e0dc64dfceaf6e1ec80a07c0000000000feffffff0277a40b5402000000160014b38daf72fc4c7259755bf2309eb8228fd8ca266b0000000000000000fd823e6a4d7e3e332e313431353932363533353839373933323338343632363433333833323739353032383834313937313639333939333735313035383230393734393434353932333037383136343036323836203230383939383632383033343832353334323131373036373938323134383038363531333238323330363634373039333834343630393535303538323233313732353335393430383132383438312031313734353032383431303237303139333835323131303535353936343436323239343839353439333033383139363434323838313039373536363539333334343631323834373536343832333320373836373833313635323731323031393039313435363438353636393233343630333438363130343534333236363438323133333933363037323630323439313431323733373234353837303036203630363331353538383137343838313532303932303936323832393235343039313731353336343336373839323539303336303031313333303533303534383832303436363532313338343134362039353139343135313136303934333330353732373033363537353935393139353330393231383631313733383139333236313137393331303531313835343830373434363233373939363237343920353637333531383835373532373234383931323237393338313833303131393439313239383333363733333632343430363536363433303836303231333934393436333935323234373337313930203730323137393836303934333730323737303533393231373137363239333137363735323338343637343831383436373636393430353133323030303536383132373134353236333536303832372037383537373133343237353737383936303931373336333731373837323134363834343039303132323439353334333031343635343935383533373130353037393232373936383932353839323320353432303139393536313132313239303231393630383634303334343138313539383133363239373734373731333039393630353138373037323131333439393939393938333732393738303439203935313035393733313733323831363039363331383539353032343435393435353334363930383330323634323532323330383235333334343638353033353236313933313138383137313031302030303331333738333837353238383635383735333332303833383134323036313731373736363931343733303335393832353334393034323837353534363837333131353935363238363338383220333533373837353933373531393537373831383537373830353332313731323236383036363133303031393237383736363131313935393039323136343230313938393338303935323537323031203036353438353836333237383836353933363135333338313832373936383233303330313935323033353330313835323936383939353737333632323539393431333839313234393732313737352032383334373931333135313535373438353732343234353431353036393539353038323935333331313638363137323738353538383930373530393833383137353436333734363439333933313920323535303630343030393237373031363731313339303039383438383234303132383538333631363033353633373037363630313034373130313831393432393535353936313938393436373637203833373434393434383235353337393737343732363834373130343034373533343634363230383034363638343235393036393439313239333331333637373032383938393135323130343735322031363230353639363630323430353830333831353031393335313132353333383234333030333535383736343032343734393634373332363339313431393932373236303432363939323237393620373832333534373831363336303039333431373231363431323139393234353836333135303330323836313832393734353535373036373439383338353035343934353838353836393236393935203639303932373231303739373530393330323935353332313136353334343938373230323735353936303233363438303636353439393131393838313833343739373735333536363336393830372034323635343235323738363235353138313834313735373436373238393039373737373237393338303030383136343730363030313631343532343931393231373332313732313437373233353020313431343431393733353638353438313631333631313537333532353532313333343735373431383439343638343338353233333233393037333934313433333334353437373632343136383632203531383938333536393438353536323039393231393232323138343237323535303235343235363838373637313739303439343630313635333436363830343938383632373233323739313738362030383537383433383338323739363739373636383134353431303039353338383337383633363039353036383030363432323531323532303531313733393239383438393630383431323834383820363236393435363034323431393635323835303232323130363631313836333036373434323738363232303339313934393435303437313233373133373836393630393536333634333731393137203238373436373736343635373537333936323431333839303836353833323634353939353831333339303437383032373539303039393436353736343037383935313236393436383339383335322035393537303938323538323236323035323234383934303737323637313934373832363834383236303134373639393039303236343031333633393434333734353533303530363832303334393620323532343531373439333939363531343331343239383039313930363539323530393337323231363936343631353135373039383538333837343130353937383835393539373732393735343938203933303136313735333932383436383133383236383638333836383934323737343135353939313835353932353234353935333935393433313034393937323532343638303834353938373237332036343436393538343836353338333637333632323236323630393931323436303830353132343338383433393034353132343431333635343937363237383037393737313536393134333539393720373030313239363136303839343431363934383638353535383438343036333533343232303732323235383238343838363438313538343536303238353036303136383432373339343532323637203436373637383839353235323133383532323534393935343636363732373832333938363435363539363131363335343838363233303537373435363439383033353539333633343536383137342033323431313235313530373630363934373934353130393635393630393430323532323838373937313038393331343536363931333638363732323837343839343035363031303135303333303820363137393238363830393230383734373630393137383234393338353839303039373134393039363735393835323631333635353439373831383933313239373834383231363832393938393438203732323635383830343835373536343031343237303437373535353133323337393634313435313532333734363233343336343534323835383434343739353236353836373832313035313134312033353437333537333935323331313334323731363631303231333539363935333632333134343239353234383439333731383731313031343537363534303335393032373939333434303337343220303037333130353738353339303632313938333837343437383038343738343839363833333231343435373133383638373531393433353036343330323138343533313931303438343831303035203337303631343638303637343931393237383139313139373933393935323036313431393636333432383735343434303634333734353132333731383139323137393939383339313031353931392035363138313436373531343236393132333937343839343039303731383634393432333139363135363739343532303830393531343635353032323532333136303338383139333031343230393320373632313337383535393536363338393337373837303833303339303639373932303737333436373232313832353632353939363631353031343231353033303638303338343437373334353439203230323630353431343636353932353230313439373434323835303733323531383636363030323133323433343038383139303731303438363333313733343634393635313435333930353739362032363835363130303535303831303636353837393639393831363335373437333633383430353235373134353931303238393730363431343031313039373132303632383034333930333937353920353135363737313537373030343230333337383639393336303037323330353538373633313736333539343231383733313235313437313230353332393238313931383236313836313235383637203332313537393139383431343834383832393136343437303630393537353237303639353732323039313735363731313637323239313039383136393039313532383031373335303637313237342038353833323232383731383335323039333533393635373235313231303833353739313531333639383832303931343434323130303637353130333334363731313033313431323637313131333620393930383635383531363339383331353031393730313635313531313638353137313433373635373631383335313535363530383834393039393839383539393832333837333435353238333331203633353530373634373931383533353839333232363138353438393633323133323933333038393835373036343230343637353235393037303931353438313431363534393835393436313633372031383032373039383139393433303939323434383839353735373132383238393035393233323333323630393732393937313230383434333335373332363534383933383233393131393332353920373436333636373330353833363034313432383133383833303332303338323439303337353839383532343337343431373032393133323736353631383039333737333434343033303730373436203932313132303139313330323033333033383031393736323131303131303034343932393332313531363038343234343438353936333736363938333839353232383638343738333132333535322036353832313331343439353736383537323632343333343431383933303339363836343236323433343130373733323236393738303238303733313839313534343131303130343436383233323520323731363230313035323635323237323131313636303339363636353537333039323534373131303535373835333736333436363832303635333130393839363532363931383632303536343736203933313235373035383633353636323031383535383130303732393336303635393837363438363131373931303435333334383835303334363131333635373638363735333234393434313636382030333936323635373937383737313835353630383435353239363534313236363534303835333036313433343434333138353836373639373531343536363134303638303037303032333738373720363539313334343031373132373439343730343230353632323330353338393934353631333134303731313237303030343037383534373333323639393339303831343534363634363435383830203739373237303832363638333036333433323835383738353639383330353233353830383933333036353735373430363739353435373136333737353235343230323131343935353736313538312034303032353031323632323835393431333032313634373135353039373932353932333039393037393635343733373631323535313736353637353133353735313738323936363634353437373920313734353031313239393631343839303330343633393934373133323936323130373334303433373531383935373335393631343538393031393338393731333131313739303432393738323835203634373530333230333139383639313531343032383730383038353939303438303130393431323134373232313331373934373634373737323632323431343235343835343534303333323135372031383533303631343232383831333735383530343330363333323137353138323937393836363232333731373231353931363037373136363932353437343837333839383636353439343934353020313134363534303632383433333636333933373930303339373639323635363732313436333835333036373336303936353731323039313830373633383332373136363431363237343838383830203037383639323536303239303232383437323130343033313732313138363038323034313930303034323239363631373131393633373739323133333735373531313439353935303135363630342039363331383632393437323635343733363432353233303831373730333637353135393036373335303233353037323833353430353637303430333836373433353133363232323234373731353820393135303439353330393834343438393333333039363334303837383037363933323539393339373830353431393334313434373337373434313834323633313239383630383039393838383638203734313332363034373231353639353136323339363538363435373330323136333135393831393331393531363733353338313239373431363737323934373836373234323239323436353433362036383030393830363736393238323338323830363839393634303034383234333534303337303134313633313439363538393739343039323433323337383936393037303639373739343232333620323530383232313638383935373338333739383632333030313539333737363437313635313232383933353738363031353838313631373535373832393733353233333434363034323831353132203632373230333733343331343635333139373737373431363033313939303636353534313837363339373932393333343431393532313534313334313839393438353434343733343536373338332031363234393933343139313331383134383039323737373731303338363338373733343331373732303735343536353435333232303737373039323132303139303531363630393632383034393020393236333630313937353938383238313631333332333136363633363532383631393332363638363333363036323733353637363330333534343737363238303335303435303737373233353534203731303538353935343837303237393038313433353632343031343531373138303632343634333632363739343536313237353331383133343037383333303333363235343233323738333934342039373533383234333732303538333533313134373731313939323630363338313333343637373638373936393539373033303938333339313330373731303938373034303835393133333734363420313434323832323737323633343635393437303437343538373834373738373230313932373731353238303733313736373930373730373135373231333434343733303630353730303733333439203234333639333131333833353034393331363331323834303432353132313932353635313739383036393431313335323830313331343730313330343738313634333738383531383532393039322038353435323031313635383339333431393635363231333439313433343135393536323538363538363535373035353236393034393635323039383538303333383530373232343236343832393320393732383538343738333136333035373737373536303638383837363434363234383234363835373932363033393533353237373334383033303438303239303035383736303735383235313034203734373039313634333936313336323637363034343932353632373432303432303833323038353636313139303632353435343333373231333135333539353834353036383737323436303239302031363138373636373935323430363136333432353232353737313935343239313632393931393330363435353337373939313430333733343034333238373532363238383839363339393538373920343735373239313734363432363335373435353235343037393039313435313335373131313336393431303931313933393332353139313037363032303832353230323631383739383533313838203737303538343239373235393136373738313331343936393930303930313932313136393731373337323738343736383437323638363038343930303333373730323432343239313635313330302035303035313638333233333634333530333839353137303239383933393232333334353137323230313338313238303639363530313137383434303837343531393630313231323238353939333720313632333133303137313134343438343634303930333839303634343935343434303036313938363930373534383531363032363332373530353239383334393138373430373836363830383831203833333835313032323833333435303835303438363038323530333933303231333332313937313535313834333036333534353530303736363832383239343933303431333737363535323739332039373531373534363133393533393834363833333933363338333034373436313139393636353338353831353338343230353638353333383632313836373235323333343032383330383731313220333238323738393231323530373731323632393436333232393536333938393839383933353832313136373435363237303130323138333536343632323031333439363731353138383139303937203330333831313938303034393733343037323339363130333638353430363634333139333935303937393031393036393936333935353234353330303534353035383036383535303139353637332030323239323139313339333339313835363830333434393033393832303539353531303032323633353335333631393230343139393437343535333835393338313032333433393535343439353920373738333737393032333734323136313732373131313732333634333433353433393437383232313831383532383632343038353134303036363630343433333235383838353639383637303534203331353437303639363537343734353835353033333233323333343231303733303135343539343035313635353337393036383636323733333337393935383531313536323537383433323239382038323733373233313938393837353731343135393537383131313936333538333330303539343038373330363831323136303238373634393632383637343436303437373436343931353939353020353439373337343235363236393031303439303337373831393836383335393338313436353734313236383034393235363438373938353536313435333732333437383637333330333930343638203833383334333633343635353337393439383634313932373035363338373239333137343837323333323038333736303131323330323939313133363739333836323730383934333837393933362032303136323935313534313333373134323438393238333037323230313236393031343735343636383437363533353736313634373733373934363735323030343930373537313535353237383120393635333632313332333932363430363136303133363335383135353930373432323032303230333138373237373630353237373231393030353536313438343235353531383739323533303334203335313339383434323533323233343135373632333336313036343235303633393034393735303038363536323731303935333539313934363538393735313431333130333438323237363933302036323437343335333633323536393136303738313534373831383131353238343336363739353730363131303836313533333135303434353231323734373339323435343439343534323336383220383836303631333430383431343836333737363730303936313230373135313234393134303433303237323533383630373634383233363334313433333436323335313839373537363634353231203634313337363739363930333134393530313931303835373539383434323339313938363239313634323139333939343930373233363233343634363834343131373339343033323635393138342030343433373830353133333338393435323537343233393935303832393635393132323835303835353538323135373235303331303731323537303132363638333032343032393239353235323220303131383732363736373536323230343135343230353136313834313633343834373536353136393939383131363134313031303032393936303738333836393039323931363033303238383430203032363931303431343037393238383632313530373834323435313637303930383730303036393932383231323036363034313833373138303635333535363732353235333235363735333238362031323931303432343837373631383235383239373635313537393539383437303335363232323632393334383630303334313538373232393830353334393839363530323236323931373438373820383230323733343230393232323234353333393835363236343736363931343930353536323834323530333931323735373731303238343032373939383036363336353832353438383932363438203830323534353636313031373239363730323636343037363535393034323930393934353638313530363532363533303533373138323934313237303333363933313337383531373836303930342030373038363637313134393635353833343334333437363933333835373831373131333836343535383733363738313233303134353837363837313236363033343839313339303935363230303920393339333631303331303239313631363135323838313338343337393039393034323331373437333336333934383034353735393331343933313430353239373633343735373438313139333536203730393131303133373735313732313030383033313535393032343835333039303636393230333736373139323230333332323930393433333436373638353134323231343437373337393339332037353137303334343336363139393130343033333735313131373335343731393138353530343634343930323633363535313238313632323838323434363235373539313633333330333931303720323235333833373432313832313430383833353038363537333931373731353039363832383837343738323635363939353939353734343930363631373538333434313337353232333937303936203833343038303035333535393834393137353431373338313838333939393434363937343836373632363535313635383237363538343833353838343533313432373735363837393030323930392035313730323833353239373136333434353632313239363430343335323331313736303036363531303132343132303036353937353538353132373631373835383338323932303431393734383420343233363038303037313933303435373631383933323334393232393237393635303139383735313837323132373236373530373938313235353437303935383930343535363335373932313232203130333333343636393734393932333536333032353439343738303234393031313431393532313233383238313533303931313430373930373338363032353135323237343239393538313830372032343731363235393136363835343531333333313233393438303439343730373931313931353332363733343330323832343431383630343134323633363339353438303030343438303032363720303439363234383230313739323839363437363639373538333138333237313331343235313730323936393233343838393632373636383434303332333236303932373532343936303335373939203634363932353635303439333638313833363039303033323338303932393334353935383839373036393533363533343934303630333430323136363534343337353538393030343536333238382032323530353435323535363430353634343832343635313531383735343731313936323138343433393635383235333337353433383835363930393431313330333135303935323631373933373820303032393734313230373636353134373933393432353930323938393639353934363939353536353736313231383635363139363733333738363233363235363132353231363332303836323836203932323231303332373438383932313836353433363438303232393637383037303537363536313531343436333230343639323739303638323132303733383833373738313432333335363238322033363038393633323038303638323232343638303132323438323631313737313835383936333831343039313833393033363733363732323230383838333231353133373535363030333732373920383339343030343135323937303032383738333037363637303934343437343536303133343535363431373235343337303930363937393339363132323537313432393839343637313534333537203834363837383836313434343538313233313435393335373139383439323235323834373136303530343932323132343234373031343132313437383035373334353531303530303830313930382036393936303333303237363334373837303831303831373534353031313933303731343132323333393038363633393338333339353239343235373836393035303736343331303036333833353120393833343338393334313539363133313835343334373534363439353536393738313033383239333039373136343635313433383430373030373037333630343131323337333539393834333435203232353136313035303730323730353632333532363630313237363438343833303834303736313138333031333035323739333230353432373436323836353430333630333637343533323836352031303537303635383734383832323536393831353739333637383937363639373432323035373530353936383334343038363937333530323031343130323036373233353835303230303732343520323235363332363531333431303535393234303139303237343231363234383433393134303335393938393533353339343539303934343037303436393132303931343039333837303031323634203536303031363233373432383830323130393237363435373933313036353739323239353532343938383732373538343631303132363438333639393938393232353639353936383831353932302035363030313031363535323536333735363738353636373232373936363139383835373832373934383438383535383334333937353138373434353435353132393635363334343334383033393620363432303535373938323933363830343335323230323737303938343239343233323533333032323537363334313830373033393437363939343135393739313539343533303036393735323134203832393333363635353536363135363738373336343030353336363635363431363534373332313730343339303335323133323935343335323931363934313435393930343136303837353332302031383638333739333730323334383838363839343739313531303731363337383532393032333435323932343430373733363539343935363330353130303734323130383731343236313334393720343539353631353133383439383731333735373034373130313738373935373331303432323936393036363637303231343439383633373436343539353238303832343336393434353738393737203233333030343837363437363532343133333930373539323034333430313936333430333931313437333230323333383037313530393532323230313036383235363334323734373136343630322034333335343430303531353231323636393332343933343139363733393737303431353935363833373533353535313636373330323733393030373439373239373336333534393634353333323820383836393834343036313139363439363136323737333434393531383237333639353538383232303735373335353137363635313538393835353139303938363636353339333534393438313036203838373332303638353939303735343037393233343234303233303039323539303037303137333139363033363232353437353634373839343036343735343833343636343737363034313134362033323333393035363531333433333036383434393533393739303730393033303233343630343631343730393631363936383836383835303134303833343730343035343630373432393538363920393133383239363638323436383138353731303331383837393036353238373033363635303833323433313937343430343737313835353637383933343832333038393433313036383238373032203732323830393733363234383039333939363237303630373437323634353533393932353339393434323830383131333733363934333338383732393430363330373932363135393539393534362032363234363239373037303632353934383435353639303334373131393732393936343039303839343138303539353334333933323531323336323335353038313334393439303034333634323720383532373133383331353931323536383938393239353139363432373238373537333934363931343237323533343336363934313533323336313030343533373330343838313938353531373036203539343132313733353234363235383935343837333031363736303032393838363539323537383636323835363132343936363535323335333338323934323837383534323533343034383330382033333037303136353337323238353633353539313532353334373834343539383138333133343131323930303139393932303539383133353232303531313733333635383536343037383236343820343934323736343431313337363339333836363932343830333131383336343435333639383538393137353434323634373339393838323238343632313834343930303837373736393737363331203237393537323236373236353535363235393632383235343237363533313833303031333430373039323233333433363537373931363031323830393331373934303137313835393835393939332033383439323335343935363430303537303939353538353631313334393830323532343939303636393834323333303137333530333538303434303831313638353532363533313137303939353720303839393432373332383730393235383438373839343433363436303035303431303839323236363931373833353235383730373835393531323938333434313732393533353139353337383835203533343537333734323630383539303239303831373635313535373830333930353934363430383733353036313233323236313132303039333733313038303438353438353236333537323238322035373638323033343136303530343834363632373735303435303033313236323030383030373939383034393235343835333436393431343639373735313634393332373039353034393334363320393338323433323232373138383531353937343035343730323134383238393731313137373739323337363132323537383837333437373138383139363832353436323938313236383638353831203730353037343032373235353032363333323930343439373632373738393434323336323136373431313931383632363934333936353036373135313537373935383637353634383233393933392031373630343236303137363333383730343534393930313736313433363431323034363932313832333730373634383837383334313936383936383631313831353538313538373336303632393320383630333831303137313231353835353237323636383330303832333833343034363536343735383830343035313338303830313633333633383837343231363337313430363433353439353536203138363839363431313232383231343037353333303236353531303034323431303438393637383335323835383832393032343336373039303438383731313831393039303934393435333331342034323138323837363631383130333130303733353437373035343938313539363830373732303039343734363936313334333630393238363134383439343137383530313731383037373933303620383130383534363930303039343435383939353237393432343339383133393231333530353538363432323139363438333439313531323633393031323830333833323030313039373733383638203036363238373739323339373138303134363133343332343435373236343030393733373432353730303733353932313030333135343135303839333637393330303831363939383035333635322030323736303037323737343936373435383430303238333632343035333436303337323633343136353534323539303237363031383334383430333036383131333831383535313035393739373020353636343030373530393432363038373838353733353739363033373332343531343134363738363730333638383039383830363039373136343235383439373539353133383036393330393434203934303135313534323232323139343332393133303231373339313235333833353539313530333130303333333033323531313137343931353639363931373435303237313439343333313531352035383835343033393232313634303937323239313031313239303335353231383135373632383233323833313832333432353438333236313131393132383030393238323532353631393032303520323633303136333931313437373234373333313438353733393130373737353837343432353338373631313734363537383637313136393431343737363432313434313131313236333538333535203338373133363130313130323332363739383737353634313032343638323430333232363438333436343137363633363938303636333738353736383133343932303435333032323430383139372032373835363437313938333936333038373831353433323231313636393132323436343135393131373736373332323533323634333335363836313436313836353435323232363831323638383720323638343435393638343432343136313037383534303136373638313432303830383835303238303035343134333631333134363233303832313032353934313733373536323338393934323037203537313336323735313637343537333138393138393435363238333532353730343431333335343337353835373533343236393836393934373235343730333136353636313339393139393936382032363238323437323730363431333336323232313738393233393033313736303835343238393433373333393335363138383931363531323530343234343034303038393532373139383337383720333836343830353834373236383935343632343338383233343337353137383835323031343339353630303537313034383131393439383834323339303630363133363935373334323331353539203037393637303334363134393134333434373838363336303431303331383233353037333635303237373835393038393735373832373237333133303530343838393339383930303939323339312033353033333733323530383535393832363535383637303839323432363132343239343733363730313933393037373237313330373036383639313730393236343632353438343233323430373420383535303336363038303133363034363638393531313834303039333636383630393534363332353030323134353835323933303935303030303930373135313035383233363236373239333236203435333733383231303439333837323439393636393933333934323436383535313634383332363131333431343631313036383032363734343636333733333433373533343037363432393430322036363832393733383635323230393335373031363236333834363438353238353134393033363239333230313939313939363838323835313731383339353336363931333435323232343434373020383034353932333936363032383137313536353531353635363636313131333539383233313132323530363238393035383534393134353039373135373535333930303234333933313533353139203039303231303731313934353733303032343338383031373636313530333532373038363236303235333738383137393735313934373830363130313337313530303434383939313732313030322032323031333335303133313036303136333931353431353839353738303337313137373932373735323235393738373432383931393137393135353232343137313839353835333631363830353920343734313233343139333339383432303231383734353634393235363434333436323339323533313935333133353130333331313437363339343931313939353037323835383433303635383336203139333533363933323936393932383938333739313439343139333934303630383537323438363339363838333639303332363535363433363432313636343432353736303739313437313038362039393834333135373333373439363438383335323932373639333238323230373632393437323832333831353337343039393631353435353938373938323539383931303933373137313236323120383238333032353834383131323338393031313936383232313432393435373636373538303731383635333830363530363438373032363133333839323832323939343937323537343533303333203238333839363338313834333934343737303737393430323238343335393838333431303033353833383534323338393733353432343339353634373535353638343039353232343834343535342031333932333934313030303136323037363933363336383436373736343133303137383139363539333739393731353537343638353431393436333334383933373438343339313239373432333920313433333635393336303431303033353233343337373730363538383836373738313133393439383631363437383734373134303739333236333835383733383632343733323838393634353634203335393837373436363736333834373934363635303430373431313138323536353833373838373834353438353831343839363239363132373339393834313334343237323630383630363138372032343535343532333630363433313533373130313132373436383039373738373034343634303934373538323830333438373639373538393438333238323431323339323932393630353832393420383631393139363637303931383935383038393833333230313231303331383433303334303132383439353131363230333533343238303134343132373631373238353833303234333535393833203030333230343230323435313230373238373235333535383131393538343031343931383039363932353333393530373537373834303030363734363535323630333134343631363730353038322037363832373732323233353334313931313032363334313633313537313437343036313233383530343235383435393838343139393037363131323837323538303539313133393335363839363020313433313636383238333137363332333536373332353431373037333432303831373333323233303436323938373939323830343930383531343039343739303336383837383638373839343933203035343639353537303330373236313930303935303230373634333334393333353931303630323435343530383634353336323839333534353638363239353835333133313533333731383338362038323635363137383632323733363337313639373537373431383330323339383630303635393134383136313634303439343439363530313137333231333133383935373437303632303838343720343830323336353337313033313135303839383432373939323735343432363835333237373937343331313339353134333537343137323231393735393739393335393638353235323238353734203532363337393632383936313236393135373233353739383636323035373334303833373537363638373338383432363634303539393039393335303530303038313333373534333234353436332035393637353034383434323335323834383734373031343433353435343139353736323538343733353634323136313938313334303733343638353431313137363638383331313836353434383920333737363937393536363531373237393636323332363731343831303333383634333931333735313836353934363733303032343433343530303534343939353339393734323337323332383731203234393438333437303630343430363334373136303633323538333036343938323937393535313031303935343138333632333530333033303934353330393733333538333434363238333934372036333034373735363435303135303038353037353738393439353438393331333933393434383939323136313235353235353937373031343336383538393433353835383737353236333739363220353539373038313637373634333830303132353433363530323337313431323738333436373932363130313939353538353232343731373232303137373732333730303431373830383431393432203339343837323534303638303135353630333539393833393035343839383537323335343637343536343233393035383538353032313637313930333133393532363239343435353433393133312036363331333435333038393339303632303436373834333837373835303534323339333930353234373331333632303132393437363931383734393735313931303131343732333135323839333220363737323533333931383134363630373330303038393032373736383936333131343831303930323230393732343532303735393136373239373030373835303538303731373138363338313035203439363739373331303031363738373038353036393432303730393232333239303830373033383332024730440220096d658625c60ff4b38f6817dc8371b0328050798ecddc03e7171a995b9aa81d022007f503ef372228a36cf50a6d4023009463c1631c578201217ced1f4d555cd6b3012102fd3c94041973fcab36110ba6b3bf6475f5deddbb4cd912a6155de78455de1e730000000002000000000101585ed214f06ac691d7b472f7833c4f58222dbd4c29054f889e4a899bbe9564310000000000feffffff020000000000000000106a0e38313930206368617273206d6178eee87306000000001600149145666024e72386ddf23893260079c4996c09540247304402204632fb527951c3af23e3384c613443de15a4526376e2c9043f6006f4194f17e702201b57cbb3ac96cb1f33d29b54fcacbee6cf5cd82495a7f3bbbceb17045d0f55fc012102c99df952013cf301c253403fbc92707c46234ed1ad02da3329c34b2bec863cba0000000002000000000101f507182c05e33e423f4bd2969042dcd6356c9208416feaf7ed239879701d18bd0000000000feffffff020000000000000000086a06602220696578e5e00b54020000001600141e211fb43002c389af1a737eac720633d67a0c32024730440220701fcf9a22e047bd2baa794172eee21c482965394c2258def6825ad12d4f48660220678287c91ea932de9d3394521c91a80dc04769b3f93f664f7c5378c0259cbd41012103bd019da04d44495a48b7392aa8ba2a6f4fd11f7a2a826ea0043fc0f45eae2e79000000000200000000010151c09ed0d72eec9d015de24c1bc288335e88304f0ca3414128bc4739e9b8ca9d0000000000feffffff02e7bb0b540200000016001418e1067b228568a9b0712a56811417c02c7f13030000000000000000fd12276a4d0e27332e3134313539323635333538393739333233383436323634333338333237393530323838343139373136393339393337353130353832303937343934343539323330373831363430363238362032303839393836323830333438323533343231313730363739383231343830383635313332383233303636343730393338343436303935353035383232333137323533353934303831323834383120313137343530323834313032373031393338353231313035353539363434363232393438393534393330333831393634343238383130393735363635393333343436313238343735363438323333203738363738333136353237313230313930393134353634383536363932333436303334383631303435343332363634383231333339333630373236303234393134313237333732343538373030362036303633313535383831373438383135323039323039363238323932353430393137313533363433363738393235393033363030313133333035333035343838323034363635323133383431343620393531393431353131363039343333303537323730333635373539353931393533303932313836313137333831393332363131373933313035313138353438303734343632333739393632373439203536373335313838353735323732343839313232373933383138333031313934393132393833333637333336323434303635363634333038363032313339343934363339353232343733373139302037303231373938363039343337303237373035333932313731373632393331373637353233383436373438313834363736363934303531333230303035363831323731343532363335363038323720373835373731333432373537373839363039313733363337313738373231343638343430393031323234393533343330313436353439353835333731303530373932323739363839323538393233203534323031393935363131323132393032313936303836343033343431383135393831333632393737343737313330393936303531383730373231313334393939393939383337323937383034392039353130353937333137333238313630393633313835393530323434353934353533343639303833303236343235323233303832353333343436383530333532363139333131383831373130313020303033313337383338373532383836353837353333323038333831343230363137313737363639313437333033353938323533343930343238373535343638373331313539353632383633383832203335333738373539333735313935373738313835373738303533323137313232363830363631333030313932373837363631313139353930393231363432303139383933383039353235373230312030363534383538363332373838363539333631353333383138323739363832333033303139353230333533303138353239363839393537373336323235393934313338393132343937323137373520323833343739313331353135353734383537323432343534313530363935393530383239353333313136383631373237383535383839303735303938333831373534363337343634393339333139203235353036303430303932373730313637313133393030393834383832343031323835383336313630333536333730373636303130343731303138313934323935353539363139383934363736372038333734343934343832353533373937373437323638343731303430343735333436343632303830343636383432353930363934393132393333313336373730323839383931353231303437353220313632303536393636303234303538303338313530313933353131323533333832343330303335353837363430323437343936343733323633393134313939323732363034323639393232373936203738323335343738313633363030393334313732313634313231393932343538363331353033303238363138323937343535353730363734393833383530353439343538383538363932363939352036393039323732313037393735303933303239353533323131363533343439383732303237353539363032333634383036363534393931313938383138333437393737353335363633363938303720343236353432353237383632353531383138343137353734363732383930393737373732373933383030303831363437303630303136313435323439313932313733323137323134373732333530203134313434313937333536383534383136313336313135373335323535323133333437353734313834393436383433383532333332333930373339343134333333343534373736323431363836322035313839383335363934383535363230393932313932323231383432373235353032353432353638383736373137393034393436303136353334363638303439383836323732333237393137383620303835373834333833383237393637393736363831343534313030393533383833373836333630393530363830303634323235313235323035313137333932393834383936303834313238343838203632363934353630343234313936353238353032323231303636313138363330363734343237383632323033393139343934353034373132333731333738363936303935363336343337313931372032383734363737363436353735373339363234313338393038363538333236343539393538313333393034373830323735393030393934363537363430373839353132363934363833393833353220353935373039383235383232363230353232343839343037373236373139343738323638343832363031343736393930393032363430313336333934343337343535333035303638323033343936203235323435313734393339393635313433313432393830393139303635393235303933373232313639363436313531353730393835383338373431303539373838353935393737323937353439382039333031363137353339323834363831333832363836383338363839343237373431353539393138353539323532343539353339353934333130343939373235323436383038343539383732373320363434363935383438363533383336373336323232363236303939313234363038303531323433383834333930343531323434313336353439373632373830373937373135363931343335393937203730303132393631363038393434313639343836383535353834383430363335333432323037323232353832383438383634383135383435363032383530363031363834323733393435323236372034363736373838393532353231333835323235343939353436363637323738323339383634353635393631313633353438383632333035373734353634393830333535393336333435363831373420333234313132353135303736303639343739343531303936353936303934303235323238383739373130383933313435363639313336383637323238373438393430353630313031353033333038203631373932383638303932303837343736303931373832343933383538393030393731343930393637353938353236313336353534393738313839333132393738343832313638323939383934382037323236353838303438353735363430313432373034373735353531333233373936343134353135323337343632333433363435343238353834343437393532363538363738323130353131343120333534373335373339353233313133343237313636313032313335393639353336323331343432393532343834393337313837313130313435373635343033353930323739393334343033373432203030373331303537383533393036323139383338373434373830383437383438393638333332313434353731333836383735313934333530363433303231383435333139313034383438313030352033373036313436383036373439313932373831393131393739333939353230363134313936363334323837353434343036343337343531323337313831393231373939393833393130313539313920353631383134363735313432363931323339373438393430393037313836343934323331393631353637393435323038303935313436353530323235323331363033383831393330313432303933203736323133373835353935363633383933373738373038333033393036393739323037373334363732323138323536323539393636313530313432313530333036383033383434373733343534392032303236303534313436363539323532303134393734343238353037333235313836363630303231333234333430383831393037313034383633333137333436343936353134353339303537393620323638353631303035353038313036363538373936393938313633353734373336333834303532353731343539313032383937303634313430313130393731323036323830343339303339373539203531353637373135373730303432303333373836393933363030373233303535383736333137363335393432313837333132353134373132303533323932383139313832363138363132353836372033323135373931393834313438343838323931363434373036303935373532373036393537323230393137353637313136373232393130393831363930393135323830313733353036373132373420383538333232323837313833353230393335333936353732353132313038333537393135313336393838323039313434343231303036373531303333343637313130333134313236373131313336203939303836353835313633393833313530313937303136353135313136383531373134333736353736313833353135353635303838343930393938393835393938323338373334353532383333312036333535303736343739313835333538393332323631383534383936333231333239333330383938353730363432303436373532353930373039313534383134313635343938353934363136333720313830323730393831393934333039393234343838393537353731323832383930353932333233333236303937323939373132303834343333353733323635343839333832333931313933323539203734363336363733303538333630343134323831333838333033323033383234393033373538393835323433373434313730323931333237363536313830393337373334343430333037303734362039323131323031393133303230333330333830313937363231313031313030343439323933323135313630383432343434383539363337363639383338393532323836383437383331323335353220363538323133313434393537363835373236323433333434313839333033393638363432363234333431303737333232363937383032383037333138393135343431313031303434363832333235203237313632303130353236353232373231313136363033393636363535373330393235343731313035353738353337363334363638323036353331303938393635323639313836323035363437362039333132353730353836333536363230313835353831303037323933363036353938373634383631313739313034353333343838353033343631313336353736383637353332343934343136363820303339363236353739373837373138353536303834353532393635343132363635343038353330363134333434343331383538363736393735313435363631343036383030373030323337383737203635393133343430313731323734393437303432303536323233303533383939343536313331343037313132373030303430373835343733333236393933393038313435343636343634353838302037393732373038323636383330363334333238353837383536393833303532333538303839333330363537353734303637393534353731363337373532353432303231313439353537363135383120343030323530313236323238353934313330323136343731353530393739323539323330393930373936353437333736313235353137363536373531333537353137383239363636343534373739203137343530313132393936313438393033303436333939343731333239363231303733343034333735313839353733353936313435383930313933383937313331313137393034323937383238352036343735303332303331393836393135313430323837303830383539393034383031303934313231343732323133313739343736343737373236323234313432353438353435343033333231353720313835333036313432323838313337353835303433303633333231373531383239373938363632323337313732313539313630373731363639323534373438373338393836363534393439343530203131343635343036323834333336363339333739303033393736393236353637323134363338353330363733363039363537313230393138303736333833323731363634313632373438383838302030373836393235363032393032323834373231303430333137323131383630383230343139303030343232393636313731313936333737393231333337353735313134393539353031353636303420393633313836323934373236353437333634323532333038313737303336373531353930363733353032333530373238333534303536373034303338363734333531333632323232343737313538203931353034393533303938343434383933333330393633343038373830373639333235393933393738303534313933343134343733373734343138343236333132393836303830393938383836382037343133323630343732313536393531363233393635383634353733303231363331353938313933313935313637333533383132393734313637373239343738363732343232393234363534333620363830303938303637363932383233383238303638393936343030343832343335343033373031343136333134393635383937393430393234333233373839363930373036393737393432323336203235303832323136383839353733383337393836323330303135393337373634373136353132323839333537383630313538383136313735353738323937333532333334343630343238313531322036323732303337333433313436353331393737373734313630333139393036363535343138373633393739323933333434313935323135343133343138393934383534343437333435363733383320313632343939333431393133313831343830393237373737313033383633383737333433313737323037353435363534353332323037373730393231323031393035313636303936323830343930203932363336303139373539383832383136313333323331363636333635323836313933323636383633333630363237333536373633303335343437373632383033353034353037373732333535342037313035383539353438373032373930383134333536323430313435313731383036323436343336323637393435363132373533313831333430373833333033333632353432333237383339343420393735333832343337323035383335333131343737313139393236303633383133333436373736383739363935393730333039383333393133303737313039383730343038353931333337343634203134343238323237373236333436353934373034373435383738343737383732303139323737313532383037333137363739303737303731353732313334343437333036303537303037333334392032343336393331313338333530343933313633313238343034323531323139323536353137393830363934313133353238303133313437303133303437383136343337383835313835323930393220383534353230313136353833393334313936353632313334393134333431353935363235383635383635353730353532363930343936353230393835383033333835303732323432363438323933203937323835383437383331363330353737373735363036383838373634343632343832343638353739323630333935333532373733343830333034383032393030353837363037353832353130342037343730393136343339363133363236373630343439323536323734323034323038333230383536363131393036323534353433333732313331353335393538343530363837373234363032393020313631383736363739353234303631363334323532323537373139353432393136323939313933303634353533373739393134303337333430343332383735323632383838393633393935383739203437353732393137343634323633353734353532353430373930393134353133353731313133363934313039313139333933323531393130373630323038323532303236313837393835333138382037373035383432393732353931363737383133313439363939303039303139323131363937313733373237383437363834373236383630383439303033333737303234323432393136353133303020353030353136383332333336343335303338393531373032393839333932323333343531373232303133383132383036393635303131373834343038373435313936303132313232383539393337203136323331333031373131343434383436343039303338393036343439353434343030363139383639303735343835313630323633323735303532393833343931383734303738363638303838312038333338353130323238333334353038353034383630383235303339333032313333323139373135353138343330363335343535303037363638323832393439333034313337373635353237393320393735313735343631333935333938343638333339333633383330343734363131393936363533383538313533383432303536383533333836323138363732353233333430323833303837313132203332383237383932313235303737313236323934363332323935363339383938393839333538323131363734353632373031303231383335363436323230313334393637313531383831393039372033303338313139383030343937333430373233393631303336383534303636343331393339353039373930313930363939363339353532343533303035343530353830363835353031393536373320303232393231393133393333393138353638303334343930333938323035393535313030323236333533353336313932303431393934373435353338353933383130323334333935353434393539203737383337373930323337343231363137323731313137323336343334333534333934373832323138313835323836323430383531343030363636303434333332353838383536393836373035342033313534373036393635373437343538353530333332333233333432313037333031353435393430353136353533373930363836363237333333373939353835313135363235373834333232393820383237333732333139383938373537313431353935373831313139363335383333303035393430383733303638313231363032383736343936323836373434363034373734363439313539393530203534393733373432353632363930313034393033373738313938363833353933383134363537343132363830343932353634383739383535363134353337323334373836373333303339303436382038333833343336333436353533373934393836343139323730353633383732393331373438373233333230383337363031313233303239393131333637393338363237303839343338373939333620323031363239353135343133333731343234383932383330373232303132363930313437353436363834373635333537363136343737333739343637353230303439303735373135353532373831203936353336323133323339323634303631363031333633353831353539303734323230323032303331383732373736303532373732313930303535363134383432353535313837393235333033342033353133393834343235333232333431353736323333363130363432353036333930343937353030383635363237313039353335393139343635383937353134313331303334383232373639333020363234373433353336333235363931363037383135343738313831313532383433363637393537303631313038363135333331353034343532313237343733393234353434393435343233363832203838363036313334303834313438363337373637303039363132303731353132343931343034333032373235333836303736343832333633343134333334363233353138393735373636343532312036343133373637393639303331343935303139313038353735393834343233393139383632393136343231393339393439303732333632333436343638343431313733393430333236353931383420303434333738303531333333383934353235373432333939353038323936353931323238353038353535383231353732353033313037313235373031323636383330323430323932393532353232203031313837323637363735363232303431353432303531363138343136333438343735363531363939393831313631343130313030323939363037383338363930393239313630333032383834302030323639313034313430373932383836323135303738343234353136373039303837303030363939323832313230363630343138333731383036353335353637323532353332353637353332383620313239313034323438373736313832353832393736353135373935393834373033353632323236323933343836303033343135383732323938303533343938393635303232363239313734383738203832303237333432303932323232343533333938353632363437363639313439303535363238343235303339313237353737313032383430323739393830363633363538323534383839323634382038303235343536363130313732393637303236363430373635353930343239303939343536383135303635323635333035333731383239343132373033333639333133373835313738363039303420303730383636373131343936353538333433343334373639333338353738313731313338363435353837333637383132333031343538373638373132363630333438393133393039353632303039203933393336313033313032393136313631353238383133383433373930393930343233313734373333363339343830343537353933313439333134303532393736333437353734383131393335362037303931313031333737353137323130303830333135353930323438353330393036363932303337363731393232303333323239303934333334363736383531343232313434373733373933393320373531373033343433363631393931303430333337353131313733353437313931383535303436343439303236333635353132383136323238383234343632353735393136333333303339313037203232353338333734323138323134303838333530383635373339313737313530393638323838373437383236353639393539393537343439303636313735383334343133373532323339373039362038333430383030353335353938343931373534313733383138383339393934343639373438363736323635353136353832373635383438333538383435333134323737353638373930303239303920353137303238333532393731363334343536323132393634303433353233313137363030363635313031323431323030363539373535383531323736313738353833383239323034313937343834203432333630383030373139333034353736313839333233343932323932373936353031393837353138373231323732363735303739383132353534373039353839303435353633353739323132322031303333333436363937343939323335363330323534393437383032343930313134313935323132333832383135333039313134303739303733383630323531353232373432393935383138303720323437313632353931363638353435313333333132333934383034393437303739313139313533323637333433303238323434313836303431343236333633393534383030303434383030323637203034393632343832303137393238393634373636393735383331383332373133313432353137303239363932333438383936323736363834343033323332363039323735323439363033353739392036343639323536353034393336383138333630393030333233383039323933343539353838393730363935333635333439343036303334303231363635343433373535383930303435363332383820323235303534353235353634303536343438323436353135313837353437313139363231383434333936353832353333373534333838353639303934313133303331353039353236313739333738203030323937343132303736363531343739333934323539303239383936393539343639393535363537363132313836353631393637333337383632333632353631323532313633323038363238362039323232313033323734383839323138363534333634383032323936373830373035373635363135313434363332303436393237393036383231323037333838333737383134323333353632383220333630383936333230383036383232323436383031323234383236313137373138353839363338313430393138333930333637333637323232303838383332313531333735353630303337323739203833393430303431353239373030323837383330373636373039343434373435363031333435353634313732353433373039303639373933393631323235373134323938393436373135343335372038343638373838363134343435383132333134353933353731393834393232353238343731363035303439323231323432343730313431323134373830353733343535313035303038303139303820363939363033333032373633343738373038313038313735343530313139333037313431323233333930383636333933383333393532393432353738363930353037363433313030363338333531203938333433383933343135393631333138353433343735343634393535363937383130333832393330393731363436353134333834303730303730373336303431313233373335393938343334352032323531363130353037303237303536323335323636303132373634383438333038343037363131383330313330353237393332303534323734363238363534303336303336373435333238363520313035373036353837343838323235363938313537393336373839373636393734323230353735303539363833343430383639373335303230313431303230363732333538353032303037323435203232353633323635313334313035353932343031393032373432313632343834333931343033353939383935330247304402201349806f6b39ab92f900a7230e54a27755df6a4bd29d10f1789e232ac888361f02202d45c2a72f995387079dfb75b67a02ac24b1f99455919fd61db2b47f47a11a330121038ff70185498100546a1a7010aa2a113f3710b59059d53b94fb05dc69bb5ab30e00000000020000000001019420b5e258e863590d1e6f21bafbea6958eb5c3d9068186026e2d4b251274e270000000000feffffff020000000000000000fd823e6a4d7e3e332e31343135393236353335383937393332333834363236343333383332373935303238383431393731363933393933373531303538323039373439343435393233303738313634303632383620323038393938363238303334383235333432313137303637393832313438303836353133323832333036363437303933383434363039353530353832323331373235333539343038313238343831203131373435303238343130323730313933383532313130353535393634343632323934383935343933303338313936343432383831303937353636353933333434363132383437353634383233332037383637383331363532373132303139303931343536343835363639323334363033343836313034353433323636343832313333393336303732363032343931343132373337323435383730303620363036333135353838313734383831353230393230393632383239323534303931373135333634333637383932353930333630303131333330353330353438383230343636353231333834313436203935313934313531313630393433333035373237303336353735393539313935333039323138363131373338313933323631313739333130353131383534383037343436323337393936323734392035363733353138383537353237323438393132323739333831383330313139343931323938333336373333363234343036353636343330383630323133393439343633393532323437333731393020373032313739383630393433373032373730353339323137313736323933313736373532333834363734383138343637363639343035313332303030353638313237313435323633353630383237203738353737313334323735373738393630393137333633373137383732313436383434303930313232343935333433303134363534393538353337313035303739323237393638393235383932332035343230313939353631313231323930323139363038363430333434313831353938313336323937373437373133303939363035313837303732313133343939393939393833373239373830343920393531303539373331373332383136303936333138353935303234343539343535333436393038333032363432353232333038323533333434363835303335323631393331313838313731303130203030333133373833383735323838363538373533333230383338313432303631373137373636393134373330333539383235333439303432383735353436383733313135393536323836333838322033353337383735393337353139353737383138353737383035333231373132323638303636313330303139323738373636313131393539303932313634323031393839333830393532353732303120303635343835383633323738383635393336313533333831383237393638323330333031393532303335333031383532393638393935373733363232353939343133383931323439373231373735203238333437393133313531353537343835373234323435343135303639353935303832393533333131363836313732373835353838393037353039383338313735343633373436343933393331392032353530363034303039323737303136373131333930303938343838323430313238353833363136303335363337303736363031303437313031383139343239353535393631393839343637363720383337343439343438323535333739373734373236383437313034303437353334363436323038303436363834323539303639343931323933333133363737303238393839313532313034373532203136323035363936363032343035383033383135303139333531313235333338323433303033353538373634303234373439363437333236333931343139393237323630343236393932323739362037383233353437383136333630303933343137323136343132313939323435383633313530333032383631383239373435353537303637343938333835303534393435383835383639323639393520363930393237323130373937353039333032393535333231313635333434393837323032373535393630323336343830363635343939313139383831383334373937373533353636333639383037203432363534323532373836323535313831383431373537343637323839303937373737323739333830303038313634373036303031363134353234393139323137333231373231343737323335302031343134343139373335363835343831363133363131353733353235353231333334373537343138343934363834333835323333323339303733393431343333333435343737363234313638363220353138393833353639343835353632303939323139323232313834323732353530323534323536383837363731373930343934363031363533343636383034393838363237323332373931373836203038353738343338333832373936373937363638313435343130303935333838333738363336303935303638303036343232353132353230353131373339323938343839363038343132383438382036323639343536303432343139363532383530323232313036363131383633303637343432373836323230333931393439343530343731323337313337383639363039353633363433373139313720323837343637373634363537353733393632343133383930383635383332363435393935383133333930343738303237353930303939343635373634303738393531323639343638333938333532203539353730393832353832323632303532323438393430373732363731393437383236383438323630313437363939303930323634303133363339343433373435353330353036383230333439362032353234353137343933393936353134333134323938303931393036353932353039333732323136393634363135313537303938353833383734313035393738383539353937373239373534393820393330313631373533393238343638313338323638363833383638393432373734313535393931383535393235323435393533393539343331303439393732353234363830383435393837323733203634343639353834383635333833363733363232323632363039393132343630383035313234333838343339303435313234343133363534393736323738303739373731353639313433353939372037303031323936313630383934343136393438363835353538343834303633353334323230373232323538323834383836343831353834353630323835303630313638343237333934353232363720343637363738383935323532313338353232353439393534363636373237383233393836343536353936313136333534383836323330353737343536343938303335353933363334353638313734203332343131323531353037363036393437393435313039363539363039343032353232383837393731303839333134353636393133363836373232383734383934303536303130313530333330382036313739323836383039323038373437363039313738323439333835383930303937313439303936373539383532363133363535343937383138393331323937383438323136383239393839343820373232363538383034383537353634303134323730343737353535313332333739363431343531353233373436323334333634353432383538343434373935323635383637383231303531313431203335343733353733393532333131333432373136363130323133353936393533363233313434323935323438343933373138373131303134353736353430333539303237393933343430333734322030303733313035373835333930363231393833383734343738303834373834383936383333323134343537313338363837353139343335303634333032313834353331393130343834383130303520333730363134363830363734393139323738313931313937393339393532303631343139363633343238373534343430363433373435313233373138313932313739393938333931303135393139203536313831343637353134323639313233393734383934303930373138363439343233313936313536373934353230383039353134363535303232353233313630333838313933303134323039332037363231333738353539353636333839333737383730383330333930363937393230373733343637323231383235363235393936363135303134323135303330363830333834343737333435343920323032363035343134363635393235323031343937343432383530373332353138363636303032313332343334303838313930373130343836333331373334363439363531343533393035373936203236383536313030353530383130363635383739363939383136333537343733363338343035323537313435393130323839373036343134303131303937313230363238303433393033393735392035313536373731353737303034323033333738363939333630303732333035353837363331373633353934323138373331323531343731323035333239323831393138323631383631323538363720333231353739313938343134383438383239313634343730363039353735323730363935373232303931373536373131363732323931303938313639303931353238303137333530363731323734203835383332323238373138333532303933353339363537323531323130383335373931353133363938383230393134343432313030363735313033333436373131303331343132363731313133362039393038363538353136333938333135303139373031363531353131363835313731343337363537363138333531353536353038383439303939383938353939383233383733343535323833333120363335353037363437393138353335383933323236313835343839363332313332393333303839383537303634323034363735323539303730393135343831343136353439383539343631363337203138303237303938313939343330393932343438383935373537313238323839303539323332333332363039373239393731323038343433333537333236353438393338323339313139333235392037343633363637333035383336303431343238313338383330333230333832343930333735383938353234333734343137303239313332373635363138303933373733343434303330373037343620393231313230313931333032303333303338303139373632313130313130303434393239333231353136303834323434343835393633373636393833383935323238363834373833313233353532203635383231333134343935373638353732363234333334343138393330333936383634323632343334313037373332323639373830323830373331383931353434313130313034343638323332352032373136323031303532363532323732313131363630333936363635353733303932353437313130353537383533373633343636383230363533313039383936353236393138363230353634373620393331323537303538363335363632303138353538313030373239333630363539383736343836313137393130343533333438383530333436313133363537363836373533323439343431363638203033393632363537393738373731383535363038343535323936353431323636353430383533303631343334343433313835383637363937353134353636313430363830303730303233373837372036353931333434303137313237343934373034323035363232333035333839393435363133313430373131323730303034303738353437333332363939333930383134353436363436343538383020373937323730383236363833303633343332383538373835363938333035323335383038393333303635373537343036373935343537313633373735323534323032313134393535373631353831203430303235303132363232383539343133303231363437313535303937393235393233303939303739363534373337363132353531373635363735313335373531373832393636363435343737392031373435303131323939363134383930333034363339393437313332393632313037333430343337353138393537333539363134353839303139333839373133313131373930343239373832383520363437353033323033313938363931353134303238373038303835393930343830313039343132313437323231333137393437363437373732363232343134323534383534353430333332313537203138353330363134323238383133373538353034333036333332313735313832393739383636323233373137323135393136303737313636393235343734383733383938363635343934393435302031313436353430363238343333363633393337393030333937363932363536373231343633383533303637333630393635373132303931383037363338333237313636343136323734383838383020303738363932353630323930323238343732313034303331373231313836303832303431393030303432323936363137313139363337373932313333373537353131343935393530313536363034203936333138363239343732363534373336343235323330383137373033363735313539303637333530323335303732383335343035363730343033383637343335313336323232323437373135382039313530343935333039383434343839333333303936333430383738303736393332353939333937383035343139333431343437333737343431383432363331323938363038303939383838363820373431333236303437323135363935313632333936353836343537333032313633313539383139333139353136373335333831323937343136373732393437383637323432323932343635343336203638303039383036373639323832333832383036383939363430303438323433353430333730313431363331343936353839373934303932343332333738393639303730363937373934323233362032353038323231363838393537333833373938363233303031353933373736343731363531323238393335373836303135383831363137353537383239373335323333343436303432383135313220363237323033373334333134363533313937373737343136303331393930363635353431383736333937393239333334343139353231353431333431383939343835343434373334353637333833203136323439393334313931333138313438303932373737373130333836333837373334333137373230373534353635343533323230373737303932313230313930353136363039363238303439302039323633363031393735393838323831363133333233313636363336353238363139333236363836333336303632373335363736333033353434373736323830333530343530373737323335353420373130353835393534383730323739303831343335363234303134353137313830363234363433363236373934353631323735333138313334303738333330333336323534323332373833393434203937353338323433373230353833353331313437373131393932363036333831333334363737363837393639353937303330393833333931333037373130393837303430383539313333373436342031343432383232373732363334363539343730343734353837383437373837323031393237373135323830373331373637393037373037313537323133343434373330363035373030373333343920323433363933313133383335303439333136333132383430343235313231393235363531373938303639343131333532383031333134373031333034373831363433373838353138353239303932203835343532303131363538333933343139363536323133343931343334313539353632353836353836353537303535323639303439363532303938353830333338353037323234323634383239332039373238353834373833313633303537373737353630363838383736343436323438323436383537393236303339353335323737333438303330343830323930303538373630373538323531303420373437303931363433393631333632363736303434393235363237343230343230383332303835363631313930363235343534333337323133313533353935383435303638373732343630323930203136313837363637393532343036313633343235323235373731393534323931363239393139333036343535333737393931343033373334303433323837353236323838383936333939353837392034373537323931373436343236333537343535323534303739303931343531333537313131333639343130393131393339333235313931303736303230383235323032363138373938353331383820373730353834323937323539313637373831333134393639393030393031393231313639373137333732373834373638343732363836303834393030333337373032343234323931363531333030203530303531363833323333363433353033383935313730323938393339323233333435313732323031333831323830363936353031313738343430383734353139363031323132323835393933372031363233313330313731313434343834363430393033383930363434393534343430303631393836393037353438353136303236333237353035323938333439313837343037383636383038383120383333383531303232383333343530383530343836303832353033393330323133333231393731353531383433303633353435353030373636383238323934393330343133373736353532373933203937353137353436313339353339383436383333393336333833303437343631313939363635333835383135333834323035363835333338363231383637323532333334303238333038373131322033323832373839323132353037373132363239343633323239353633393839383938393335383231313637343536323730313032313833353634363232303133343936373135313838313930393720333033383131393830303439373334303732333936313033363835343036363433313933393530393739303139303639393633393535323435333030353435303538303638353530313935363733203032323932313931333933333931383536383033343439303339383230353935353130303232363335333533363139323034313939343734353533383539333831303233343339353534343935392037373833373739303233373432313631373237313131373233363433343335343339343738323231383138353238363234303835313430303636363034343333323538383835363938363730353420333135343730363936353734373435383535303333323332333334323130373330313534353934303531363535333739303638363632373333333739393538353131353632353738343332323938203832373337323331393839383735373134313539353738313131393633353833333030353934303837333036383132313630323837363439363238363734343630343737343634393135393935302035343937333734323536323639303130343930333737383139383638333539333831343635373431323638303439323536343837393835353631343533373233343738363733333033393034363820383338333433363334363535333739343938363431393237303536333837323933313734383732333332303833373630313132333032393931313336373933383632373038393433383739393336203230313632393531353431333337313432343839323833303732323031323639303134373534363638343736353335373631363437373337393436373532303034393037353731353535323738312039363533363231333233393236343036313630313336333538313535393037343232303230323033313837323737363035323737323139303035353631343834323535353138373932353330333420333531333938343432353332323334313537363233333631303634323530363339303439373530303836353632373130393533353931393436353839373531343133313033343832323736393330203632343734333533363332353639313630373831353437383138313135323834333636373935373036313130383631353333313530343435323132373437333932343534343934353432333638322038383630363133343038343134383633373736373030393631323037313531323439313430343330323732353338363037363438323336333431343333343632333531383937353736363435323120363431333736373936393033313439353031393130383537353938343432333931393836323931363432313933393934393037323336323334363436383434313137333934303332363539313834203034343337383035313333333839343532353734323339393530383239363539313232383530383535353832313537323530333130373132353730313236363833303234303239323935323532322030313138373236373637353632323034313534323035313631383431363334383437353635313639393938313136313431303130303239393630373833383639303932393136303330323838343020303236393130343134303739323838363231353037383432343531363730393038373030303639393238323132303636303431383337313830363533353536373235323533323536373533323836203132393130343234383737363138323538323937363531353739353938343730333536323232363239333438363030333431353837323239383035333439383936353032323632393137343837382038323032373334323039323232323435333339383536323634373636393134393035353632383432353033393132373537373130323834303237393938303636333635383235343838393236343820383032353435363631303137323936373032363634303736353539303432393039393435363831353036353236353330353337313832393431323730333336393331333738353137383630393034203037303836363731313439363535383334333433343736393333383537383137313133383634353538373336373831323330313435383736383731323636303334383931333930393536323030392039333933363130333130323931363136313532383831333834333739303939303432333137343733333633393438303435373539333134393331343035323937363334373537343831313933353620373039313130313337373531373231303038303331353539303234383533303930363639323033373637313932323033333232393039343333343637363835313432323134343737333739333933203735313730333434333636313939313034303333373531313137333534373139313835353034363434393032363336353531323831363232383832343436323537353931363333333033393130372032323533383337343231383231343038383335303836353733393137373135303936383238383734373832363536393935393935373434393036363137353833343431333735323233393730393620383334303830303533353539383439313735343137333831383833393939343436393734383637363236353531363538323736353834383335383834353331343237373536383739303032393039203531373032383335323937313633343435363231323936343034333532333131373630303636353130313234313230303635393735353835313237363137383538333832393230343139373438342034323336303830303731393330343537363138393332333439323239323739363530313938373531383732313237323637353037393831323535343730393538393034353536333537393231323220313033333334363639373439393233353633303235343934373830323439303131343139353231323338323831353330393131343037393037333836303235313532323734323939353831383037203234373136323539313636383534353133333331323339343830343934373037393131393135333236373334333032383234343138363034313432363336333935343830303034343830303236372030343936323438323031373932383936343736363937353833313833323731333134323531373032393639323334383839363237363638343430333233323630393237353234393630333537393920363436393235363530343933363831383336303930303332333830393239333435393538383937303639353336353334393430363033343032313636353434333735353839303034353633323838203232353035343532353536343035363434383234363531353138373534373131393632313834343339363538323533333735343338383536393039343131333033313530393532363137393337382030303239373431323037363635313437393339343235393032393839363935393436393935353635373631323138363536313936373333373836323336323536313235323136333230383632383620393232323130333237343838393231383635343336343830323239363738303730353736353631353134343633323034363932373930363832313230373338383337373831343233333536323832203336303839363332303830363832323234363830313232343832363131373731383538393633383134303931383339303336373336373232323038383833323135313337353536303033373237392038333934303034313532393730303238373833303736363730393434343734353630313334353536343137323534333730393036393739333936313232353731343239383934363731353433353720383436383738383631343434353831323331343539333537313938343932323532383437313630353034393232313234323437303134313231343738303537333435353130353030383031393038203639393630333330323736333437383730383130383137353435303131393330373134313232333339303836363339333833333935323934323537383639303530373634333130303633383335312039383334333839333431353936313331383534333437353436343935353639373831303338323933303937313634363531343338343037303037303733363034313132333733353939383433343520323235313631303530373032373035363233353236363031323736343834383330383430373631313833303133303532373933323035343237343632383635343033363033363734353332383635203130353730363538373438383232353639383135373933363738393736363937343232303537353035393638333434303836393733353032303134313032303637323335383530323030373234352032323536333236353133343130353539323430313930323734323136323438343339313430333539393839353335333934353930393434303730343639313230393134303933383730303132363420353630303136323337343238383032313039323736343537393331303635373932323935353234393838373237353834363130313236343833363939393839323235363935393638383135393230203536303031303136353532353633373536373835363637323237393636313938383537383237393438343838353538333433393735313837343435343535313239363536333434333438303339362036343230353537393832393336383034333532323032373730393834323934323332353333303232353736333431383037303339343736393934313539373931353934353330303639373532313420383239333336363535353636313536373837333634303035333636363536343136353437333231373034333930333532313332393534333532393136393431343539393034313630383735333230203138363833373933373032333438383836383934373931353130373136333738353239303233343532393234343037373336353934393536333035313030373432313038373134323631333439372034353935363135313338343938373133373537303437313031373837393537333130343232393639303636363730323134343938363337343634353935323830383234333639343435373839373720323333303034383736343736353234313333393037353932303433343031393633343033393131343733323032333338303731353039353232323031303638323536333432373437313634363032203433333534343030353135323132363639333234393334313936373339373730343135393536383337353335353531363637333032373339303037343937323937333633353439363435333332382038383639383434303631313936343936313632373733343439353138323733363935353838323230373537333535313736363531353839383535313930393836363635333933353439343831303620383837333230363835393930373534303739323334323430323330303932353930303730313733313936303336323235343735363437383934303634373534383334363634373736303431313436203332333339303536353133343333303638343439353339373930373039303330323334363034363134373039363136393638383638383530313430383334373034303534363037343239353836392039313338323936363832343638313835373130333138383739303635323837303336363530383332343331393734343034373731383535363738393334383233303839343331303638323837303220373232383039373336323438303933393936323730363037343732363435353339393235333939343432383038313133373336393433333838373239343036333037393236313539353939353436203236323436323937303730363235393438343535363930333437313139373239393634303930383934313830353935333433393332353132333632333535303831333439343930303433363432372038353237313338333135393132353638393839323935313936343237323837353733393436393134323732353334333636393431353332333631303034353337333034383831393835353137303620353934313231373335323436323538393534383733303136373630303239383836353932353738363632383536313234393636353532333533333832393432383738353432353334303438333038203333303730313635333732323835363335353931353235333437383434353938313833313334313132393030313939393230353938313335323230353131373333363538353634303738323634382034393432373634343131333736333933383636393234383033313138333634343533363938353839313735343432363437333939383832323834363231383434393030383737373639373736333120323739353732323637323635353536323539363238323534323736353331383330303133343037303932323333343336353737393136303132383039333137393430313731383539383539393933203338343932333534393536343030353730393935353835363131333439383032353234393930363639383432333330313733353033353830343430383131363835353236353331313730393935372030383939343237333238373039323538343837383934343336343630303530343130383932323636393137383335323538373037383539353132393833343431373239353335313935333738383520353334353733373432363038353930323930383137363531353537383033393035393436343038373335303631323332323631313230303933373331303830343835343835323633353732323832203537363832303334313630353034383436363237373530343530303331323632303038303037393938303439323534383533343639343134363937373531363439333237303935303439333436332039333832343332323237313838353135393734303534373032313438323839373131313737373932333736313232353738383733343737313838313936383235343632393831323638363835383120373035303734303237323535303236333332393034343937363237373839343432333632313637343131393138363236393433393635303637313531353737393538363735363438323339393339203137363034323630313736333338373034353439393031373631343336343132303436393231383233373037363438383738333431393638393638363131383135353831353837333630363239332038363033383130313731323135383535323732363638333030383233383334303436353634373538383034303531333830383031363333363338383734323136333731343036343335343935353620313836383936343131323238323134303735333330323635353130303432343130343839363738333532383538383239303234333637303930343838373131383139303930393439343533333134203432313832383736363138313033313030373335343737303534393831353936383037373230303934373436393631333433363039323836313438343934313738353031373138303737393330362038313038353436393030303934343538393935323739343234333938313339323133353035353836343232313936343833343931353132363339303132383033383332303031303937373338363820303636323837373932333937313830313436313334333234343537323634303039373337343235373030373335393231303033313534313530383933363739333030383136393938303533363532203032373630303732373734393637343538343030323833363234303533343630333732363334313635353432353930323736303138333438343033303638313133383138353531303539373937302035363634303037353039343236303837383835373335373936303337333234353134313436373836373033363838303938383036303937313634323538343937353935313338303639333039343420393430313531353432323232313934333239313330323137333931323533383335353931353033313030333333303332353131313734393135363936393137343530323731343934333331353135203538383534303339323231363430393732323931303131323930333535323138313537363238323332383331383233343235343833323631313139313238303039323832353235363139303230352032363330313633393131343737323437333331343835373339313037373735383734343235333837363131373436353738363731313639343134373736343231343431313131323633353833353520333837313336313031313032333236373938373735363431303234363832343033323236343833343634313736363336393830363633373835373638313334393230343533303232343038313937203237383536343731393833393633303837383135343332323131363639313232343634313539313137373637333232353332363433333536383631343631383635343532323236383132363838372032363834343539363834343234313631303738353430313637363831343230383038383530323830303534313433363133313436323330383231303235393431373337353632333839393432303720353731333632373531363734353733313839313839343536323833353235373034343133333534333735383537353334323639383639393437323534373033313635363631333939313939393638203236323832343732373036343133333632323231373839323339303331373630383534323839343337333339333536313838393136353132353034323434303430303839353237313938333738372033383634383035383437323638393534363234333838323334333735313738383532303134333935363030353731303438313139343938383432333930363036313336393537333432333135353920303739363730333436313439313433343437383836333630343130333138323335303733363530323737383539303839373537383237323733313330353034383839333938393030393932333931203335303333373332353038353539383236353538363730383932343236313234323934373336373031393339303737323731333037303638363931373039323634363235343834323332343037342038353530333636303830313336303436363839353131383430303933363638363039353436333235303032313435383532393330393530303030393037313531303538323336323637323933323620343533373338323130343933383732343939363639393333393432343638353531363438333236313133343134363131303638303236373434363633373333343337353334303736343239343032203636383239373338363532323039333537303136323633383436343835323835313439303336323933323031393931393936383832383531373138333935333636393133343532323234343437302038303435393233393636303238313731353635353135363536363631313133353938323331313232353036323839303538353439313435303937313537353533393030323433393331353335313920303930323130373131393435373330303234333838303137363631353033353237303836323630323533373838313739373531393437383036313031333731353030343438393931373231303032203232303133333530313331303630313633393135343135383935373830333731313737393237373532323539373837343238393139313739313535323234313731383935383533363136383035392034373431323334313933333938343230323138373435363439323536343433343632333932353331393533313335313033333131343736333934393131393935303732383538343330363538333620313933353336393332393639393238393833373931343934313933393430363038353732343836333936383833363930333236353536343336343231363634343235373630373931343731303836203939383433313537333337343936343838333532393237363933323832323037363239343732383233383135333734303939363135343535393837393832353938393130393337313731323632312038323833303235383438313132333839303131393638323231343239343537363637353830373138363533383036353036343837303236313333383932383232393934393732353734353330333320323833383936333831383433393434373730373739343032323834333539383833343130303335383338353432333839373335343234333935363437353535363834303935323234383434353534203133393233393431303030313632303736393336333638343637373634313330313738313936353933373939373135353734363835343139343633333438393337343834333931323937343233392031343333363539333630343130303335323334333737373036353838383637373831313339343938363136343738373437313430373933323633383538373338363234373332383839363435363420333539383737343636373633383437393436363530343037343131313832353635383337383837383435343835383134383936323936313237333939383431333434323732363038363036313837203234353534353233363036343331353337313031313237343638303937373837303434363430393437353832383033343837363937353839343833323832343132333932393239363035383239342038363139313936363730393138393538303839383333323031323130333138343330333430313238343935313136323033353334323830313434313237363137323835383330323433353539383320303033323034323032343531323037323837323533353538313139353834303134393138303936393235333339353037353737383430303036373436353532363033313434363136373035303832203736383237373232323335333431393131303236333431363331353731343734303631323338353034323538343539383834313939303736313132383732353830353931313339333536383936302031343331363638323833313736333233353637333235343137303733343230383137333332323330343632393837393932383034393038353134303934373930333638383738363837383934393320303534363935353730333037323631393030393530323037363433333439333335393130363032343534353038363435333632383933353435363836323935383533313331353333373138333836203832363536313738363232373336333731363937353737343138333032333938363030363539313438313631363430343934343936353031313733323133313338393537343730363230383834372034383032333635333731303331313530383938343237393932373534343236383533323737393734333131333935313433353734313732323139373539373939333539363835323532323835373420353236333739363238393631323639313537323335373938363632303537333430383337353736363837333838343236363430353939303939333530353030303831333337353433323435343633203539363735303438343432333532383438373437303134343335343534313935373632353834373335363432313631393831333430373334363835343131313736363838333131383635343438392033373736393739353636353137323739363632333236373134383130333338363433393133373531383635393436373330303234343334353030353434393935333939373432333732333238373120323439343833343730363034343036333437313630363332353833303634393832393739353531303130393534313833363233353033303330393435333039373333353833343436323833393437203633303437373536343530313530303835303735373839343935343839333133393339343438393932313631323535323535393737303134333638353839343335383538373735323633373936322035353937303831363737363433383030313235343336353032333731343132373833343637393236313031393935353835323234373137323230313737373233373030343137383038343139343220333934383732353430363830313535363033353939383339303534383938353732333534363734353634323339303538353835303231363731393033313339353236323934343535343339313331203636333133343533303839333930363230343637383433383737383530353432333933393035323437333133363230313239343736393138373439373531393130313134373233313532383933322036373732353333393138313436363037333030303839303237373638393633313134383130393032323039373234353230373539313637323937303037383530353830373137313836333831303520343936373937333130303136373837303835303639343230373039323233323930383037303338333277a40b5402000000160014c4a965d6d2dc8808f4536037fb2c19c34663e27902473044022021e92ac57714b67cecbad34e5fd8ae1e3e1a5d11645190e7555d93fd87237d22022053e5a3c6d76301c27065633e809c3a0d0627688d059f906376a7c4395b1cd1520121037a14211b3f04b6f4dc27e5c038c43f5ccff930e565452f3350b51ba9cb2b34e000000000020000000001010a33c90d829e13e43fc20ac0be48f9ec8b74c9cc0743d9062a1c159c5b0d470e0000000000feffffff02dade0b540200000016001454e9adfc43c19e025d24dc4b60ee15f02a799e0b00000000000000000d6a0b223b206965782068656c7002473044022010f8a2e49534bfd7f9ef4242336fcdfd2fea88f5a0ea045c3423dce9977536490220797767f9212f1f25a31027bcccf5e666fbeae8140ad1f5c0fb65957fd81c96940121032ec34f86ce98e4a0f279a56dc4c4721deb761be383e42627dda0919e4e4589d30000000002000000000801a77ba24f5ebd893e41db97970031bc99a55c2c5751f58b139277e87beba65d460000000000ffffffff016b8de4781401000022582085fcfd4290538943df298bd390ddf2ccb316e230469069bc6ace5644c7b2a5a5000000000001b00ad7723b0b0cee90780044a61bdc328d495869f60c9b1f4884b9480def6144feff00000000000000000000000000000000000000000000000000000000000000007514842e6b7878adbb42971ccad0e5409919c4326e2aabe6172d32eb26441cba6398ebf7a4616b7534657aea9097174bd1a8bb5dabe3821a1c1b54dc2cbb3b0300000000000000000000000000000000000000000000000000000000000000001400000000", + "tx": [ + "13ea17e79efd1f06be63b2ba9bce058802d560bac1c36f4b9bf5846172844139", + "eba64fab98971ff669fef5f5f8eb8ae9eee272dda97d070fddb182e8eaf98216", + "7c5ad533bb60903cd19941fbeafeab47ceb18aed83295d1adf23bbc78fde8e1d", + "814d0b142a0ec481171213d9cca7dee2d6550885f115d4d93400a4c5ea788f54", + "51e87701d89c92731a2099fb5daaf634ca7a6d2e25c132b715ae8851b1695383", + "0a7d4cc53cec2927310eca0438135063302ee11247ec4adc0ce6a81ef0fe2689", + "bd686f0505e7469f36b1da175d5fa25b4f07ffbb99b4f59dd01dab21fc17ddaa", + "8ea4a29d962b81febba840bf9c2b45df50db248a7ee2f9f18f6614ce751b852f" + ], + "time": 1687371144, + "mediantime": 1687368513, + "nonce": 313, + "bits": "1f0fffff", + "difficulty": 9.53660673972713e-07, + "chainwork": "000000000000000000000000000000000000000000000000000000002b037ddc", + "nTx": 8, + "mweb": { + "hash": "85fcfd4290538943df298bd390ddf2ccb316e230469069bc6ace5644c7b2a5a5", + "height": 6282, + "kernel_offset": "6398ebf7a4616b7534657aea9097174bd1a8bb5dabe3821a1c1b54dc2cbb3b03", + "stealth_offset": "0000000000000000000000000000000000000000000000000000000000000000", + "num_kernels": 0, + "num_txos": 20, + "kernel_root": "0000000000000000000000000000000000000000000000000000000000000000", + "output_root": "d7723b0b0cee90780044a61bdc328d495869f60c9b1f4884b9480def6144feff", + "leaf_root": "7514842e6b7878adbb42971ccad0e5409919c4326e2aabe6172d32eb26441cba", + "inputs": [ + ], + "outputs": [ + ], + "kernels": [ + ] + }, + "previousblockhash": "73d0677070ed9bd2ff55989c4afc614174139bb21297eef71e7619fb036cdb84", + "nextblockhash": "94732e0419e605ac48730929a8f6c003be41324650cb42a803731a70029ef463" +} From 4e66804dc0d668cd6bd4602b547e2f5b2e227e97 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 14 Jul 2023 15:16:04 +0000 Subject: [PATCH 135/386] env: add config vars DAEMON_POLL_INTERVAL_* We will use this in electrum regtest tests to save a few seconds in the runtime of each test. --- electrumx/server/block_processor.py | 16 +++++++++++++--- electrumx/server/controller.py | 5 ++++- electrumx/server/env.py | 2 ++ electrumx/server/mempool.py | 2 +- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/electrumx/server/block_processor.py b/electrumx/server/block_processor.py index 376811f..5d821f7 100644 --- a/electrumx/server/block_processor.py +++ b/electrumx/server/block_processor.py @@ -35,7 +35,14 @@ class Prefetcher: '''Prefetches blocks (in the forward direction only).''' - def __init__(self, daemon: 'Daemon', coin: Type['Coin'], blocks_event: asyncio.Event): + def __init__( + self, + daemon: 'Daemon', + coin: Type['Coin'], + blocks_event: asyncio.Event, + *, + polling_delay_secs, + ): self.logger = class_logger(__name__, self.__class__.__name__) self.daemon = daemon self.coin = coin @@ -52,7 +59,7 @@ def __init__(self, daemon: 'Daemon', coin: Type['Coin'], blocks_event: asyncio.E self.min_cache_size = 10 * 1024 * 1024 # This makes the first fetch be 10 blocks self.ave_size = self.min_cache_size // 10 - self.polling_delay = 5 + self.polling_delay = polling_delay_secs async def main_loop(self, bp_height): '''Loop forever polling for more blocks.''' @@ -171,7 +178,10 @@ def __init__(self, env: 'Env', db: DB, daemon: Daemon, notifications: 'Notificat self.coin = env.coin # blocks_event: set when new blocks are put on the queue by the Prefetcher, to be processed self.blocks_event = asyncio.Event() - self.prefetcher = Prefetcher(daemon, env.coin, self.blocks_event) + self.prefetcher = Prefetcher( + daemon, env.coin, self.blocks_event, + polling_delay_secs=env.daemon_poll_interval_blocks_msec/1000, + ) self.logger = class_logger(__name__, self.__class__.__name__) # Meta diff --git a/electrumx/server/controller.py b/electrumx/server/controller.py index bbe3392..abf2fa9 100644 --- a/electrumx/server/controller.py +++ b/electrumx/server/controller.py @@ -111,7 +111,10 @@ def get_db_height(): notifications.raw_transactions = daemon.getrawtransactions notifications.lookup_utxos = db.lookup_utxos MemPoolAPI.register(Notifications) - mempool = MemPool(env.coin, notifications) + mempool = MemPool( + env.coin, notifications, + refresh_secs=env.daemon_poll_interval_mempool_msec/1000, + ) session_mgr = SessionManager(env, db, bp, daemon, mempool, shutdown_event) diff --git a/electrumx/server/env.py b/electrumx/server/env.py index 12735a1..545a550 100644 --- a/electrumx/server/env.py +++ b/electrumx/server/env.py @@ -76,6 +76,8 @@ def __init__(self, coin=None): self.blacklist_url = self.default('BLACKLIST_URL', self.coin.BLACKLIST_URL) self.cache_MB = self.integer('CACHE_MB', 1200) self.reorg_limit = self.integer('REORG_LIMIT', self.coin.REORG_LIMIT) + self.daemon_poll_interval_blocks_msec = self.integer('DAEMON_POLL_INTERVAL_BLOCKS', 5000) + self.daemon_poll_interval_mempool_msec = self.integer('DAEMON_POLL_INTERVAL_MEMPOOL', 5000) # Server limits to help prevent DoS diff --git a/electrumx/server/mempool.py b/electrumx/server/mempool.py index 8385ddf..e2176c6 100644 --- a/electrumx/server/mempool.py +++ b/electrumx/server/mempool.py @@ -107,7 +107,7 @@ class MemPool: hashXs: hashX -> set of all hashes of txs touching the hashX ''' - def __init__(self, coin: Type['Coin'], api: MemPoolAPI, refresh_secs=5.0, log_status_secs=60.0): + def __init__(self, coin: Type['Coin'], api: MemPoolAPI, *, refresh_secs=5.0, log_status_secs=60.0): assert isinstance(api, MemPoolAPI) self.coin = coin self.api = api From bbd985a95db63cada13254bb766f174e9ab674d0 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 14 Jul 2023 15:20:29 +0000 Subject: [PATCH 136/386] follow-up prev: fix pycodestyle --- electrumx/server/mempool.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/electrumx/server/mempool.py b/electrumx/server/mempool.py index e2176c6..ed8565a 100644 --- a/electrumx/server/mempool.py +++ b/electrumx/server/mempool.py @@ -107,7 +107,14 @@ class MemPool: hashXs: hashX -> set of all hashes of txs touching the hashX ''' - def __init__(self, coin: Type['Coin'], api: MemPoolAPI, *, refresh_secs=5.0, log_status_secs=60.0): + def __init__( + self, + coin: Type['Coin'], + api: MemPoolAPI, + *, + refresh_secs=5.0, + log_status_secs=60.0, + ): assert isinstance(api, MemPoolAPI) self.coin = coin self.api = api From ab6ef80132b0882f4c701cd3951f88f313834120 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 20 Oct 2023 15:55:41 +0000 Subject: [PATCH 137/386] setup.py: ~fix "rocksdb" extra by pinning old Cython version see https://github.com/twmht/python-rocksdb/issues/108 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 927f504..9e33a1c 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ def find_version(): extras_require={ 'dev': ['objgraph'], 'rapidjson': ['python-rapidjson>=0.4.1,<2.0'], - 'rocksdb': ['python-rocksdb>=0.6.9'], + 'rocksdb': ['python-rocksdb>=0.6.9', 'Cython<3.0'], 'ujson': ['ujson>=2.0.0,<4.0.0'], 'uvloop': ['uvloop>=0.14'], # For various coins From 8f809f9d676f4c018df9c121fe78ca531adf755a Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 20 Oct 2023 16:29:36 +0000 Subject: [PATCH 138/386] CI: fix unittests task by pinning old Cython for rocksdb, see prev commit --- .cirrus.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index e39fa62..47c02b0 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -22,9 +22,9 @@ unittests_task: - pip install pytest-cov - pip install coveralls - pip install Sphinx - # deps for e-x itself - - pip install -r requirements.txt - - pip install "python-rocksdb>=0.6.9" + # e-x itself + - pip install 'Cython<3.0' # for rocksdb + - pip install ".[rocksdb]" # hashes and other altcoin-specific stuff - pip install tribushashm - pip install blake256 From 9224d1ea6697790b79ec9e4b9be9e2206ce58bf4 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 20 Oct 2023 16:47:16 +0000 Subject: [PATCH 139/386] small style fixes to calm new version of pycodestyle --- electrumx/lib/coins.py | 4 ++-- electrumx/lib/tx.py | 18 ++++++++++++------ electrumx/server/session.py | 2 +- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index ce49f4c..96badd3 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -397,7 +397,7 @@ def interpret_name_prefix(cls, script, possible_ops): # Script structure: https://git.io/fjuRu added, template = cls._add_data_placeholders_to_template(ops[i:], template) offset += added - 1 # subtract the "DATA_PUSH_MULTIPLE" opcode - elif type(op) == str: + elif isinstance(op, str): template.append(-1) named_index[op] = i + offset else: @@ -439,7 +439,7 @@ def _read_data_placeholders_count(cls, opcodes): data_placeholders = 0 for opcode in opcodes: - if type(opcode) == tuple: + if isinstance(opcode, tuple): data_placeholders += 1 else: break diff --git a/electrumx/lib/tx.py b/electrumx/lib/tx.py index a753e70..167aaed 100644 --- a/electrumx/lib/tx.py +++ b/electrumx/lib/tx.py @@ -658,9 +658,12 @@ def _read_tx_parts(self): orig_ser += self.binary[start:self.cursor] vsize = (3 * base_size + self.binary_length) // 4 - return TxTimeSegWit( - version, time, marker, flag, inputs, outputs, witness, locktime),\ - self.TX_HASH_FN(orig_ser), vsize + return ( + TxTimeSegWit( + version, time, marker, flag, inputs, outputs, witness, locktime), + self.TX_HASH_FN(orig_ser), + vsize, + ) def read_tx(self): return self._read_tx_parts()[0] @@ -734,9 +737,12 @@ def _read_tx_parts(self): vsize = (3 * base_size + self.binary_length) // 4 orig_ser += self.binary[start:self.cursor] - return TxTimeSegWit( - version, time, marker, flag, inputs, outputs, witness, locktime),\ - self.TX_HASH_FN(orig_ser), vsize + return ( + TxTimeSegWit( + version, time, marker, flag, inputs, outputs, witness, locktime), + self.TX_HASH_FN(orig_ser), + vsize, + ) def read_tx(self): return self._read_tx_parts()[0] diff --git a/electrumx/server/session.py b/electrumx/server/session.py index b38c369..b7dcd86 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -1860,7 +1860,7 @@ async def name_get_value_proof(self, scripthash, cp_height=0): and height < prev_height - self.coin.NAME_EXPIRATION): break - tx = await(self.transaction_get(txid)) + tx = await self.transaction_get(txid) update['tx'] = tx del update['tx_hash'] From 3b802c7d998d13da490afe233688d5d64a3f8778 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 28 Nov 2023 04:22:11 +0000 Subject: [PATCH 140/386] daemon: rename "WARMING_UP" error code to match naming in bitcoind to match https://github.com/bitcoin/bitcoin/blob/b5a271334ca81a6adcb1c608d85c83621a9eae47/src/rpc/protocol.h#L49 --- electrumx/server/daemon.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/electrumx/server/daemon.py b/electrumx/server/daemon.py index c58e188..97dc193 100644 --- a/electrumx/server/daemon.py +++ b/electrumx/server/daemon.py @@ -44,7 +44,8 @@ class ServiceRefusedError(Exception): class Daemon: '''Handles connections to a daemon at the given URL.''' - WARMING_UP = -28 + RPC_IN_WARMUP = -28 + id_counter = itertools.count() def __init__( @@ -181,7 +182,7 @@ def processor(result): err = result['error'] if not err: return result['result'] - if err.get('code') == self.WARMING_UP: + if err.get('code') == self.RPC_IN_WARMUP: raise WarmingUpError raise DaemonError(err) @@ -198,7 +199,7 @@ async def _send_vector(self, method, params_iterable, replace_errs=False): otherwise an exception is raised.''' def processor(result): errs = [item['error'] for item in result if item['error']] - if any(err.get('code') == self.WARMING_UP for err in errs): + if any(err.get('code') == self.RPC_IN_WARMUP for err in errs): raise WarmingUpError if not errs or replace_errs: return [item['result'] for item in result] From 314893655a7cc0bfcc216b07900fa77b5f66e148 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Tue, 28 Nov 2023 04:25:26 +0000 Subject: [PATCH 141/386] daemon: catch RPC_PARSE_ERROR from bitcoind and simply retry see https://github.com/spesmilo/electrumx/issues/238 So far it is unclear why bitcoind sends RPC_PARSE_ERROR, but instead of doing a full shutdown, let's log the error(+request) and retry. --- electrumx/server/daemon.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/electrumx/server/daemon.py b/electrumx/server/daemon.py index 97dc193..75fe6ff 100644 --- a/electrumx/server/daemon.py +++ b/electrumx/server/daemon.py @@ -41,10 +41,15 @@ class ServiceRefusedError(Exception): some reason.''' +class DaemonParseError(Exception): + '''Internal - when the daemon returns an RPC_PARSE_ERROR.''' + + class Daemon: '''Handles connections to a daemon at the given URL.''' RPC_IN_WARMUP = -28 + RPC_PARSE_ERROR = -32700 id_counter = itertools.count() @@ -172,6 +177,9 @@ def log_error(error): except WarmingUpError: log_error('starting up checking blocks') on_good_message = 'running normally' + except DaemonParseError as e: + log_error(f"received RPC_PARSE_ERROR ({e=!r}). request was: {data=!r}") + on_good_message = 'running normally' await asyncio.sleep(retry) retry = max(min(self.max_retry, retry * 2), self.init_retry) @@ -184,6 +192,8 @@ def processor(result): return result['result'] if err.get('code') == self.RPC_IN_WARMUP: raise WarmingUpError + if err.get('code') == self.RPC_PARSE_ERROR: + raise DaemonParseError(err) raise DaemonError(err) payload = {'method': method, 'id': next(self.id_counter)} @@ -201,6 +211,8 @@ def processor(result): errs = [item['error'] for item in result if item['error']] if any(err.get('code') == self.RPC_IN_WARMUP for err in errs): raise WarmingUpError + if any(err.get('code') == self.RPC_PARSE_ERROR for err in errs): + raise DaemonParseError if not errs or replace_errs: return [item['result'] for item in result] raise DaemonError(errs) From f2a05eeebdad976cc3255633192fd5f58e7de6a3 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 15 Dec 2023 02:10:01 +0300 Subject: [PATCH 142/386] Dash v20 hardfork support (#234) * Support for coinbase tx version 3 * Removed old SubTx tests * Removed SubTx test values * Fixed merkleRootMNList and merkleRootQuorums test values * Renamed bestCLHeight to bestCLHeightDiff in tests --- electrumx/lib/tx_dash.py | 123 ++++---------------------- tests/lib/test_tx_dash.py | 181 ++++++++------------------------------ 2 files changed, 52 insertions(+), 252 deletions(-) diff --git a/electrumx/lib/tx_dash.py b/electrumx/lib/tx_dash.py index be016f2..a852a6b 100644 --- a/electrumx/lib/tx_dash.py +++ b/electrumx/lib/tx_dash.py @@ -250,7 +250,8 @@ def read_tx_extra(cls, deser): class DashCbTx(namedtuple("DashCbTx", "version height merkleRootMNList " - "merkleRootQuorums")): + "merkleRootQuorums bestCLHeightDiff " + "bestCLSignature assetLockedAmount")): '''Class representing DIP4 coinbase special tx''' def serialize(self): assert len(self.merkleRootMNList) == 32 @@ -262,6 +263,10 @@ def serialize(self): if self.version > 1: assert len(self.merkleRootQuorums) == 32 res += self.merkleRootQuorums # merkleRootQuorums + if self.version > 2: + res += pack_varint(self.bestCLHeightDiff) + res += self.bestCLSignature + res += pack_le_int64(self.assetLockedAmount) return res @classmethod @@ -270,107 +275,17 @@ def read_tx_extra(cls, deser): height = deser._read_le_uint32() merkleRootMNList = deser._read_nbytes(32) merkleRootQuorums = b'' + bestCLHeightDiff = 0 + bestCLSignature = b'' + assetLockedAmount = 0 if version > 1: merkleRootQuorums = deser._read_nbytes(32) - return DashCbTx(version, height, merkleRootMNList, merkleRootQuorums) - - -class DashSubTxRegister(namedtuple("DashSubTxRegister", - "version userName pubKey payloadSig")): - '''Class representing DIP5 SubTxRegister''' - def serialize(self): - assert (len(self.pubKey) == 48 - and len(self.payloadSig) == 96) - return ( - pack_le_uint16(self.version) + # version - pack_varbytes(self.userName) + # userName - self.pubKey + # pubKey - self.payloadSig # payloadSig - ) - - @classmethod - def read_tx_extra(cls, deser): - return DashSubTxRegister( - deser._read_le_uint16(), # version - deser._read_varbytes(), # userName - deser._read_nbytes(48), # pubKey - deser._read_nbytes(96) # payloadSig - ) - - -class DashSubTxTopup(namedtuple("DashSubTxTopup", - "version regTxHash")): - '''Class representing DIP5 SubTxTopup''' - def serialize(self): - assert len(self.regTxHash) == 32 - return ( - pack_le_uint16(self.version) + # version - self.regTxHash # regTxHash - ) - - @classmethod - def read_tx_extra(cls, deser): - return DashSubTxTopup( - deser._read_le_uint16(), # version - deser._read_nbytes(32) # regTxHash - ) - - -class DashSubTxResetKey(namedtuple("DashSubTxResetKey", - "version regTxHash hashPrevSubTx " - "creditFee newPubKey payloadSig")): - '''Class representing DIP5 SubTxResetKey''' - def serialize(self): - assert (len(self.regTxHash) == 32 - and len(self.hashPrevSubTx) == 32 - and len(self.newPubKey) == 48 - and len(self.payloadSig) == 96) - return ( - pack_le_uint16(self.version) + # version - self.regTxHash + # regTxHash - self.hashPrevSubTx + # hashPrevSubTx - pack_le_int64(self.creditFee) + # creditFee - self.newPubKey + # newPubKey - self.payloadSig # payloadSig - ) - - @classmethod - def read_tx_extra(cls, deser): - return DashSubTxResetKey( - deser._read_le_uint16(), # version - deser._read_nbytes(32), # regTxHash - deser._read_nbytes(32), # hashPrevSubTx - deser._read_le_int64(), # creditFee - deser._read_nbytes(48), # newPubKey - deser._read_nbytes(96) # payloadSig - ) - - -class DashSubTxCloseAccount(namedtuple("DashSubTxCloseAccount", - "version regTxHash hashPrevSubTx " - "creditFee payloadSig")): - '''Class representing DIP5 SubTxCloseAccount''' - def serialize(self): - assert (len(self.regTxHash) == 32 - and len(self.hashPrevSubTx) == 32 - and len(self.payloadSig) == 96) - return ( - pack_le_uint16(self.version) + # version - self.regTxHash + # regTxHash - self.hashPrevSubTx + # hashPrevSubTx - pack_le_int64(self.creditFee) + # creditFee - self.payloadSig # payloadSig - ) - - @classmethod - def read_tx_extra(cls, deser): - return DashSubTxCloseAccount( - deser._read_le_uint16(), # version - deser._read_nbytes(32), # regTxHash - deser._read_nbytes(32), # hashPrevSubTx - deser._read_le_int64(), # creditFee - deser._read_nbytes(96) # payloadSig - ) + if version > 2: + bestCLHeightDiff = deser._read_varint() + bestCLSignature = deser._read_nbytes(96) + assetLockedAmount = deser._read_le_uint64() + return DashCbTx(version, height, merkleRootMNList, merkleRootQuorums, + bestCLHeightDiff, bestCLSignature, assetLockedAmount) # https://dash-docs.github.io/en/developer-reference#outpoint @@ -399,10 +314,6 @@ class DeserializerDash(Deserializer): PRO_UP_REG_TX = 3 PRO_UP_REV_TX = 4 CB_TX = 5 - SUB_TX_REGISTER = 8 - SUB_TX_TOPUP = 9 - SUB_TX_RESET_KEY = 10 - SUB_TX_CLOSE_ACCOUNT = 11 SPEC_TX_HANDLERS = { PRO_REG_TX: DashProRegTx, @@ -410,10 +321,6 @@ class DeserializerDash(Deserializer): PRO_UP_REG_TX: DashProUpRegTx, PRO_UP_REV_TX: DashProUpRevTx, CB_TX: DashCbTx, - SUB_TX_REGISTER: DashSubTxRegister, - SUB_TX_TOPUP: DashSubTxTopup, - SUB_TX_RESET_KEY: DashSubTxResetKey, - SUB_TX_CLOSE_ACCOUNT: DashSubTxCloseAccount, } def _read_outpoint(self): diff --git a/tests/lib/test_tx_dash.py b/tests/lib/test_tx_dash.py index 18fd60c..a631539 100644 --- a/tests/lib/test_tx_dash.py +++ b/tests/lib/test_tx_dash.py @@ -41,6 +41,17 @@ '88f65889fd3ac0201be87aa227462b5643e8bb2ec1d7a82a76629a6e42fb519188f658' '89fd3ac0201be87aa227462b5643e8bb2ec1d7a82a') +CB_TX_V3 = ( + '0300050001000000000000000000000000000000000000000000000000000000000000' + '0000ffffffff06035cbe0d0101ffffffff0397f4e127000000001976a914c69a0bda7d' + 'aaae481be8def95e5f347a1d00a4b488ac94196f1600000000016a4dd5632500000000' + '1976a914c69a0bda7daaae481be8def95e5f347a1d00a4b488ac00000000af03005cbe' + '0d003c7a25cd3258d4141c1aca784232f28b92f94221c1d6add1c7221ebecffd201297' + '52cf4e10c95caefd2972782eb6ab4bc64170c148c9f32191be3f09d546a5e500b097da' + 'dbd9741dabd85bec96ed8421499ec37aeb0ec48ff25c2a994a47e030ef1c5758bf1918' + 'e4fd04c9f7b149df160800a9fdbf08311b93484e545a876e81e3408a4c8358f11ce2c9' + 'c01206c39122875f9dbfea67e8953da4e63a1cd8551dfc94196f1600000000') + PRO_REG_TX = ( '030001000335f1c2ca44a1eb72e59f589df2852caacba39b7c0a5e61967f6b71d7a763' @@ -145,58 +156,6 @@ '371b9b8d89ba') -SUB_TX_REGISTER = ( - '03000800010931c6b0ad7ce07f3c8aefeeb78e246a4fe6872bbf08ab6e4eb6a7b69acd' - '64a6010000006b483045022100a2feb698c43c752738fabea281b7e9e5a3aa648a4c54' - '1171e06d7c372db92c65022061c1ec3c92f2e76bb7fb1b548d854f19a41e6421267231' - '74150412caf3e98e9601210293360bf2a2e810673412bc6e8e0e358f3fb7bdbe9a667b' - '3d0103f761cc69a211feffffff0189fa433e000000001976a914551ab8ca96a9142217' - '4d22769c3a4f90b2dcd0de88ac00000000960100036162638e7042ec88acefcfe3d578' - '914bb48c6bd71b3459d384e42374e8abfeffffff01570b0000000000001976a91490c5' - 'ce9d8bc992a88ac00000000a40100b67ffbbd095de31ea38446754e8abfeffffff0157' - '0b0000000000001976a91490c5ce9d8bc992a88ac00000000a40100b67ffbbd095de31' - 'ea38446754e8abfeffffff01570b0000000000001976a91490c5ce9d') - - -SUB_TX_TOPUP = ( - '03000900010931c6b0ad7ce07f3c8aefeeb78e246a4fe6872bbf08ab6e4eb6a7b69acd' - '64a6010000006b483045022100a2feb698c43c752738fabea281b7e9e5a3aa648a4c54' - '1171e06d7c372db92c65022061c1ec3c92f2e76bb7fb1b548d854f19a41e6421267231' - '74150412caf3e98e9601210293360bf2a2e810673412bc6e8e0e358f3fb7bdbe9a667b' - '3d0103f761cc69a211feffffff0189fa433e000000001976a914551ab8ca96a9142217' - '4d22769c3a4f90b2dcd0de88ac00000000220100d384e42374e8abfeffffff01570b00' - '0000a40100b67ffbbd095de31ea3844675') - - -SUB_TX_RESET_KEY = ( - '03000a00010931c6b0ad7ce07f3c8aefeeb78e246a4fe6872bbf08ab6e4eb6a7b69acd' - '64a6010000006b483045022100a2feb698c43c752738fabea281b7e9e5a3aa648a4c54' - '1171e06d7c372db92c65022061c1ec3c92f2e76bb7fb1b548d854f19a41e6421267231' - '74150412caf3e98e9601210293360bf2a2e810673412bc6e8e0e358f3fb7bdbe9a667b' - '3d0103f761cc69a211feffffff0189fa433e000000001976a914551ab8ca96a9142217' - '4d22769c3a4f90b2dcd0de88ac00000000da0100d384e42374e8abfeffffff01570b00' - '0000a40100b67ffbbd095de31ea3844675af3e98e9601210293360bf2a2e810673412b' - 'c6e8e0e358f3fb7bdbe9a667b3d0e803000000000000601210293360bf2a2e81067341' - '2bc6e8e0e358f3fb7bdbe9a667b3d0103f761caf3e98e9601210293360bf2a2e810673' - '412bc6e8e0e358f3fb7bdbe9a667b3d0103f761caf3e98e9601210293360bf2a2e8106' - '73412bc6e8e0e358f3fb7bdbe9a667b3d0103f761caf3e98e9601210293360bf2a2e81' - '0673412bc6e8e0e358f3fb7bdbe9a667b3d0103f761cabcdefab') - - -SUB_TX_CLOSE_ACCOUNT = ( - '03000b00010931c6b0ad7ce07f3c8aefeeb78e246a4fe6872bbf08ab6e4eb6a7b69acd' - '64a6010000006b483045022100a2feb698c43c752738fabea281b7e9e5a3aa648a4c54' - '1171e06d7c372db92c65022061c1ec3c92f2e76bb7fb1b548d854f19a41e6421267231' - '74150412caf3e98e9601210293360bf2a2e810673412bc6e8e0e358f3fb7bdbe9a667b' - '3d0103f761cc69a211feffffff0189fa433e000000001976a914551ab8ca96a9142217' - '4d22769c3a4f90b2dcd0de88ac00000000aa0100d384e42374e8abfeffffff01570b00' - '0000a40100b67ffbbd095de31ea3844675af3e98e9601210293360bf2a2e810673412b' - 'c6e8e0e358f3fb7bdbe9a12bc6e8e803000000000000a62bc6e8e0e358f3fb7bdbe9a6' - '67b3d0103f761caf3e98e9601210293360bf2a2e810673412bc6e8e0e358f3fb7bdbe9' - 'a667b3d0103f761caf3e98e9601210293360bf2a2e810673412bc6e8e0e358f3fb7bdb' - 'e9a667b3d0103f761cabcdefab') - - UNKNOWN_SPEC_TX = ( '0300bb00010931c6b0ad7ce07f3c8aefeeb78e246a4fe6872bbf08ab6e4eb6a7b69acd' '64a6010000006b483045022100a2feb698c43c752738fabea281b7e9e5a3aa648a4c54' @@ -266,6 +225,32 @@ def test_dash_tx_cb_tx_v2(): assert ser == test +def test_dash_tx_cb_tx_v3(): + test = bfh(CB_TX_V3) + deser = lib_tx_dash.DeserializerDash(test) + tx = deser.read_tx() + assert tx.version == 3 + assert tx.tx_type == 5 + extra = tx.extra_payload + assert extra.version == 3 + assert extra.height == 900700 + assert len(extra.merkleRootMNList) == 32 + assert extra.merkleRootMNList == bfh( + '3c7a25cd3258d4141c1aca784232f28b92f94221c1d6add1c7221ebecffd2012') + assert len(extra.merkleRootQuorums) == 32 + assert extra.merkleRootQuorums == bfh( + '9752cf4e10c95caefd2972782eb6ab4bc64170c148c9f32191be3f09d546a5e5') + assert extra.bestCLHeightDiff == 0 + assert len(extra.bestCLSignature) == 96 + assert extra.bestCLSignature == bfh( + 'b097dadbd9741dabd85bec96ed8421499ec37aeb0ec48ff25c2a994a47e030ef' + '1c5758bf1918e4fd04c9f7b149df160800a9fdbf08311b93484e545a876e81e3' + '408a4c8358f11ce2c9c01206c39122875f9dbfea67e8953da4e63a1cd8551dfc') + assert extra.assetLockedAmount == 376379796 + ser = tx.serialize() + assert ser == test + + def test_dash_tx_pro_reg_tx(): test = bfh(PRO_REG_TX) deser = lib_tx_dash.DeserializerDash(test) @@ -474,98 +459,6 @@ def test_dash_tx_pro_up_rev_tx(): assert ser == test -def test_dash_tx_sub_tx_register_tx(): - test = bfh(SUB_TX_REGISTER) - deser = lib_tx_dash.DeserializerDash(test) - tx = deser.read_tx() - assert tx.version == 3 - assert tx.tx_type == 8 - extra = tx.extra_payload - assert extra.version == 1 - assert extra.userName == b'abc' - assert len(extra.pubKey) == 48 - assert extra.pubKey == bfh( - '8e7042ec88acefcfe3d578914bb48c6bd71b3459d384e42374e8abfeffff' - 'ff01570b0000000000001976a91490c5ce9d') - assert len(extra.payloadSig) == 96 - assert extra.payloadSig == bfh( - '8bc992a88ac00000000a40100b67ffbbd095de31ea38446754e8abfeffff' - 'ff01570b0000000000001976a91490c5ce9d8bc992a88ac00000000a4010' - '0b67ffbbd095de31ea38446754e8abfeffffff01570b0000000000001976' - 'a91490c5ce9d') - ser = tx.serialize() - assert ser == test - - -def test_dash_tx_sub_tx_topup_tx(): - test = bfh(SUB_TX_TOPUP) - deser = lib_tx_dash.DeserializerDash(test) - tx = deser.read_tx() - assert tx.version == 3 - assert tx.tx_type == 9 - extra = tx.extra_payload - assert extra.version == 1 - assert len(extra.regTxHash) == 32 - assert extra.regTxHash == bfh( - 'd384e42374e8abfeffffff01570b000000a40100b67ffbbd095de31ea3844675') - ser = tx.serialize() - assert ser == test - - -def test_dash_tx_sub_tx_reset_key_tx(): - test = bfh(SUB_TX_RESET_KEY) - deser = lib_tx_dash.DeserializerDash(test) - tx = deser.read_tx() - assert tx.version == 3 - assert tx.tx_type == 10 - extra = tx.extra_payload - assert extra.version == 1 - assert len(extra.regTxHash) == 32 - assert extra.regTxHash == bfh( - 'd384e42374e8abfeffffff01570b000000a40100b67ffbbd095de31ea3844675') - assert len(extra.hashPrevSubTx) == 32 - assert extra.hashPrevSubTx == bfh( - 'af3e98e9601210293360bf2a2e810673412bc6e8e0e358f3fb7bdbe9a667b3d0') - assert extra.creditFee == 1000 - assert len(extra.newPubKey) == 48 - assert extra.newPubKey == bfh( - '601210293360bf2a2e810673412bc6e8e0e358f3fb7bdbe9a667b3d0103f7' - '61caf3e98e9601210293360bf2a2e810673') - assert len(extra.payloadSig) == 96 - assert extra.payloadSig == bfh( - '412bc6e8e0e358f3fb7bdbe9a667b3d0103f761caf3e98e9601210293360b' - 'f2a2e810673412bc6e8e0e358f3fb7bdbe9a667b3d0103f761caf3e98e960' - '1210293360bf2a2e810673412bc6e8e0e358f3fb7bdbe9a667b3d0103f761' - 'cabcdefab') - ser = tx.serialize() - assert ser == test - - -def test_dash_tx_sub_tx_close_account_tx(): - test = bfh(SUB_TX_CLOSE_ACCOUNT) - deser = lib_tx_dash.DeserializerDash(test) - tx = deser.read_tx() - assert tx.version == 3 - assert tx.tx_type == 11 - extra = tx.extra_payload - assert extra.version == 1 - assert len(extra.regTxHash) == 32 - assert extra.regTxHash == bfh( - 'd384e42374e8abfeffffff01570b000000a40100b67ffbbd095de31ea3844675') - assert len(extra.hashPrevSubTx) == 32 - assert extra.hashPrevSubTx == bfh( - 'af3e98e9601210293360bf2a2e810673412bc6e8e0e358f3fb7bdbe9a12bc6e8') - assert extra.creditFee == 1000 - assert len(extra.payloadSig) == 96 - assert extra.payloadSig == bfh( - 'a62bc6e8e0e358f3fb7bdbe9a667b3d0103f761caf3e98e9601210293360b' - 'f2a2e810673412bc6e8e0e358f3fb7bdbe9a667b3d0103f761caf3e98e960' - '1210293360bf2a2e810673412bc6e8e0e358f3fb7bdbe9a667b3d0103f761' - 'cabcdefab') - ser = tx.serialize() - assert ser == test - - def test_dash_tx_unknown_spec_tx(): test = bfh(UNKNOWN_SPEC_TX) deser = lib_tx_dash.DeserializerDash(test) From 97a016c9ca9d49b3e5b22a66fd6caab537879340 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 5 Jan 2024 16:41:06 +0000 Subject: [PATCH 143/386] fix a bug in peer discovery closes https://github.com/spesmilo/electrumx/pull/244 - this also adds some generic protection so that e-x now tolerates peer discovery dying in isolation example stack trace: ``` INFO:Prefetcher:cancelled; prefetcher stopping INFO:BlockProcessor:flushing to DB for a clean shutdown... INFO:Controller:shutting down INFO:Controller:shutdown complete ERROR:electrumx:ElectrumX server terminated abnormally Traceback (most recent call last): File "/home/user/wspace/electrumx/./electrumx_server", line 35, in main asyncio.run(controller.run()) File "/usr/lib/python3.10/asyncio/runners.py", line 44, in run return loop.run_until_complete(main) File "/usr/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete return future.result() File "/home/user/wspace/electrumx/electrumx/lib/server_base.py", line 129, in run await server_task File "/home/user/wspace/electrumx/electrumx/lib/server_base.py", line 102, in serve await self.serve(shutdown_event) File "/home/user/wspace/electrumx/electrumx/server/controller.py", line 134, in serve async with OldTaskGroup() as group: File "/home/user/.local/lib/python3.10/site-packages/aiorpcx/curio.py", line 297, in __aexit__ await self.join() File "/home/user/wspace/electrumx/electrumx/lib/util.py", line 370, in join task.result() File "/home/user/wspace/electrumx/electrumx/server/session.py", line 676, in serve async with self._task_group as group: File "/home/user/.local/lib/python3.10/site-packages/aiorpcx/curio.py", line 297, in __aexit__ await self.join() File "/home/user/wspace/electrumx/electrumx/lib/util.py", line 370, in join task.result() File "/home/user/wspace/electrumx/electrumx/server/peers.py", line 477, in discover_peers async with self.group as group: File "/home/user/.local/lib/python3.10/site-packages/aiorpcx/curio.py", line 297, in __aexit__ await self.join() File "/home/user/wspace/electrumx/electrumx/lib/util.py", line 370, in join task.result() File "/home/user/wspace/electrumx/electrumx/server/peers.py", line 246, in _monitor_peer if await self._should_drop_peer(peer): File "/home/user/wspace/electrumx/electrumx/server/peers.py", line 289, in _should_drop_peer await self._verify_peer(session, peer) File "/home/user/wspace/electrumx/electrumx/server/peers.py", line 387, in _verify_peer async with OldTaskGroup() as g: File "/home/user/.local/lib/python3.10/site-packages/aiorpcx/curio.py", line 297, in __aexit__ await self.join() File "/home/user/wspace/electrumx/electrumx/lib/util.py", line 370, in join task.result() File "/home/user/wspace/electrumx/electrumx/server/peers.py", line 434, in _send_server_features hosts = [host.lower() for host in features.get('hosts', {})] # TypeError: 'int' object is not iterable ``` --- electrumx/server/peers.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/electrumx/server/peers.py b/electrumx/server/peers.py index 5a7f6e1..fd95b37 100644 --- a/electrumx/server/peers.py +++ b/electrumx/server/peers.py @@ -431,7 +431,9 @@ async def _send_server_features(self, session, peer): message = 'server.features' features = await session.send_request(message) assert_good(message, features, dict) - hosts = [host.lower() for host in features.get('hosts', {})] + features_hosts = features.get('hosts', {}) + assert_good(message, features_hosts, dict) + hosts = [host.lower() for host in features_hosts] if self.env.coin.GENESIS_HASH != features.get('genesis_hash'): raise BadPeerError('incorrect genesis hash') if peer.host.lower() in hosts: @@ -474,10 +476,21 @@ async def discover_peers(self): self.logger.info(f'my clearnet self: {self._my_clearnet_peer()}') self.logger.info(f'force use of proxy: {self.env.force_proxy}') self.logger.info(f'beginning peer discovery...') - async with self.group as group: - await group.spawn(self._refresh_blacklist()) - await group.spawn(self._detect_proxy()) - await group.spawn(self._import_peers()) + try: + async with self.group as group: + await group.spawn(self._refresh_blacklist()) + await group.spawn(self._detect_proxy()) + await group.spawn(self._import_peers()) + except Exception: + self.logger.exception( + "PeerManager.group died! Please open a bug report about this, " + "and include at least this traceback and maybe a bit more of " + "the log (if it looks relevant).") + while True: + await sleep(3600) + self.logger.warning( + "Peer discovery is not working as PeerManager.group died. " + "This is not normal. You should inspect the log.") def info(self): '''The number of peers.''' From 77b60f3dbd6234f8bdcb9fcd71376156bb4e333c Mon Sep 17 00:00:00 2001 From: vuittont60 <81072379+vuittont60@users.noreply.github.com> Date: Mon, 8 Jan 2024 22:57:51 +0800 Subject: [PATCH 144/386] Fix some typos (#245) * Fix typos * Fix typos --- contrib/Dockerfile | 2 +- electrumx/lib/tx.py | 2 +- electrumx/server/daemon.py | 2 +- electrumx/server/session.py | 2 +- tests/server/test_notifications.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contrib/Dockerfile b/contrib/Dockerfile index f308495..a7cea0a 100644 --- a/contrib/Dockerfile +++ b/contrib/Dockerfile @@ -1,5 +1,5 @@ # example of Dockerfile that installs spesmilo electrumx 1.16.0 -# ENV variables can be overriden on the `docker run` command +# ENV variables can be overridden on the `docker run` command FROM python:3.9.16-bullseye AS builder diff --git a/electrumx/lib/tx.py b/electrumx/lib/tx.py index 167aaed..95d69b2 100644 --- a/electrumx/lib/tx.py +++ b/electrumx/lib/tx.py @@ -931,7 +931,7 @@ def _is_anon_input(self): self.script[1] == self.OP_ANON_MARKER) def is_generation(self): - # Transactions comming in from stealth addresses are seen by + # Transactions coming in from stealth addresses are seen by # the blockchain as newly minted coins. The reverse, where coins # are sent TO a stealth address, are seen by the blockchain as # a coin burn. diff --git a/electrumx/server/daemon.py b/electrumx/server/daemon.py index 75fe6ff..dde3c37 100644 --- a/electrumx/server/daemon.py +++ b/electrumx/server/daemon.py @@ -134,7 +134,7 @@ async def _send_data(self, data): async def _send(self, payload, processor): '''Send a payload to be converted to JSON. - Handles temporary connection issues. Daemon reponse errors + Handles temporary connection issues. Daemon response errors are raise through DaemonError. ''' def log_error(error): diff --git a/electrumx/server/session.py b/electrumx/server/session.py index b7dcd86..ce782ed 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -500,7 +500,7 @@ async def rpc_daemon_url(self, daemon_url): try: self.daemon.set_url(daemon_url) except Exception as e: - raise RPCError(BAD_REQUEST, f'an error occured: {e!r}') + raise RPCError(BAD_REQUEST, f'an error occurred: {e!r}') return f'now using daemon at {self.daemon.logged_url()}' async def rpc_stop(self): diff --git a/tests/server/test_notifications.py b/tests/server/test_notifications.py index 435a6c3..c8c55b3 100644 --- a/tests/server/test_notifications.py +++ b/tests/server/test_notifications.py @@ -28,7 +28,7 @@ async def notify(height, touched): await n.start(5, notify) # Suppose a gets in block 6 and blocks 7,8 found right after and - # the block processer processes them together. + # the block processor processes them together. await n.on_mempool({'a'}, 5) assert notified == [(5, set()), (5, {'a'})] # Mempool refreshes with daemon on block 6 From 04582cc353b45721e2461176cfce1768003dd5ae Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 1 Feb 2024 16:03:04 +0000 Subject: [PATCH 145/386] dependencies: rm "pylru" dep, instead inline stripped-down "cachetools" closes https://github.com/spesmilo/electrumx/issues/247 --- docs/HOWTO.rst | 2 - electrumx/lib/lrucache.py | 181 ++++++++++++++++++++++++++++++++++++ electrumx/server/session.py | 10 +- requirements.txt | 1 - 4 files changed, 186 insertions(+), 8 deletions(-) create mode 100644 electrumx/lib/lrucache.py diff --git a/docs/HOWTO.rst b/docs/HOWTO.rst index 2681e27..7e52862 100644 --- a/docs/HOWTO.rst +++ b/docs/HOWTO.rst @@ -19,7 +19,6 @@ Python3 ElectrumX uses asyncio. Python version >= 3.8 is **required**. `aiohttp`_ Python library for asynchronous HTTP. Version >= 2.0 required. -`pylru`_ Python LRU cache package. DB Engine A database engine package is required; two are supported (see `Database Engine`_ below). ================ ======================== @@ -443,7 +442,6 @@ You can then set the port as follows and advertise the service externally on the .. _`daemontools`: http://cr.yp.to/daemontools.html .. _`runit`: http://smarden.org/runit/index.html .. _`aiohttp`: https://pypi.python.org/pypi/aiohttp -.. _`pylru`: https://pypi.python.org/pypi/pylru .. _`dash_hash`: https://pypi.python.org/pypi/dash_hash .. _`contrib/raspberrypi3/install_electrumx.sh`: https://github.com/spesmilo/electrumx/blob/master/contrib/raspberrypi3/install_electrumx.sh .. _`contrib/raspberrypi3/run_electrumx.sh`: https://github.com/spesmilo/electrumx/blob/master/contrib/raspberrypi3/run_electrumx.sh diff --git a/electrumx/lib/lrucache.py b/electrumx/lib/lrucache.py new file mode 100644 index 0000000..f6c0956 --- /dev/null +++ b/electrumx/lib/lrucache.py @@ -0,0 +1,181 @@ +# The MIT License (MIT) +# +# Copyright (c) 2014-2022 Thomas Kemmer +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to +# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +# the Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# ----- +# +# This is a stripped down LRU-cache from the "cachetools" library. +# https://github.com/tkem/cachetools/blob/d991ac71b4eb6394be5ec572b835434081393215/src/cachetools/__init__.py + +import collections +import collections.abc + + +class _DefaultSize: + + __slots__ = () + + def __getitem__(self, _): + return 1 + + def __setitem__(self, _, value): + assert value == 1 + + def pop(self, _): + return 1 + + +class Cache(collections.abc.MutableMapping): + """Mutable mapping to serve as a simple cache or cache base class.""" + + __marker = object() + + __size = _DefaultSize() + + def __init__(self, maxsize, getsizeof=None): + if getsizeof: + self.getsizeof = getsizeof + if self.getsizeof is not Cache.getsizeof: + self.__size = dict() + self.__data = dict() + self.__currsize = 0 + self.__maxsize = maxsize + + def __repr__(self): + return "%s(%s, maxsize=%r, currsize=%r)" % ( + self.__class__.__name__, + repr(self.__data), + self.__maxsize, + self.__currsize, + ) + + def __getitem__(self, key): + try: + return self.__data[key] + except KeyError: + return self.__missing__(key) + + def __setitem__(self, key, value): + maxsize = self.__maxsize + size = self.getsizeof(value) + if size > maxsize: + raise ValueError("value too large") + if key not in self.__data or self.__size[key] < size: + while self.__currsize + size > maxsize: + self.popitem() + if key in self.__data: + diffsize = size - self.__size[key] + else: + diffsize = size + self.__data[key] = value + self.__size[key] = size + self.__currsize += diffsize + + def __delitem__(self, key): + size = self.__size.pop(key) + del self.__data[key] + self.__currsize -= size + + def __contains__(self, key): + return key in self.__data + + def __missing__(self, key): + raise KeyError(key) + + def __iter__(self): + return iter(self.__data) + + def __len__(self): + return len(self.__data) + + def get(self, key, default=None): + if key in self: + return self[key] + else: + return default + + def pop(self, key, default=__marker): + if key in self: + value = self[key] + del self[key] + elif default is self.__marker: + raise KeyError(key) + else: + value = default + return value + + def setdefault(self, key, default=None): + if key in self: + value = self[key] + else: + self[key] = value = default + return value + + @property + def maxsize(self): + """The maximum size of the cache.""" + return self.__maxsize + + @property + def currsize(self): + """The current size of the cache.""" + return self.__currsize + + @staticmethod + def getsizeof(value): + """Return the size of a cache element's value.""" + return 1 + + +class LRUCache(Cache): + """Least Recently Used (LRU) cache implementation.""" + + def __init__(self, maxsize, getsizeof=None): + Cache.__init__(self, maxsize, getsizeof) + self.__order = collections.OrderedDict() + + def __getitem__(self, key, cache_getitem=Cache.__getitem__): + value = cache_getitem(self, key) + if key in self: # __missing__ may not store item + self.__update(key) + return value + + def __setitem__(self, key, value, cache_setitem=Cache.__setitem__): + cache_setitem(self, key, value) + self.__update(key) + + def __delitem__(self, key, cache_delitem=Cache.__delitem__): + cache_delitem(self, key) + del self.__order[key] + + def popitem(self): + """Remove and return the `(key, value)` pair least recently used.""" + try: + key = next(iter(self.__order)) + except StopIteration: + raise KeyError("%s is empty" % type(self).__name__) from None + else: + return (key, self.pop(key)) + + def __update(self, key): + try: + self.__order.move_to_end(key) + except KeyError: + self.__order[key] = None diff --git a/electrumx/server/session.py b/electrumx/server/session.py index ce782ed..75f83f3 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -22,7 +22,6 @@ import asyncio import attr -import pylru from aiorpcx import (Event, JSONRPCAutoDetect, JSONRPCConnection, ReplyAndDisconnect, Request, RPCError, RPCSession, handler_invocation, serve_rs, serve_ws, sleep, @@ -30,6 +29,7 @@ import electrumx import electrumx.lib.util as util +from electrumx.lib.lrucache import LRUCache from electrumx.lib.util import OldTaskGroup from electrumx.lib.hash import (HASHX_LEN, Base58Error, hash_to_hex_str, hex_str_to_hash, sha256) @@ -146,17 +146,17 @@ def __init__( self.start_time = time.time() self._method_counts = defaultdict(int) self._reorg_count = 0 - self._history_cache = pylru.lrucache(1000) + self._history_cache = LRUCache(maxsize=1000) self._history_lookups = 0 self._history_hits = 0 - self._tx_hashes_cache = pylru.lrucache(1000) + self._tx_hashes_cache = LRUCache(maxsize=1000) self._tx_hashes_lookups = 0 self._tx_hashes_hits = 0 # Really a MerkleCache cache - self._merkle_cache = pylru.lrucache(1000) + self._merkle_cache = LRUCache(maxsize=1000) self._merkle_lookups = 0 self._merkle_hits = 0 - self.estimatefee_cache = pylru.lrucache(1000) + self.estimatefee_cache = LRUCache(maxsize=1000) self.notified_height = None self.hsub_results = None self._task_group = OldTaskGroup() diff --git a/requirements.txt b/requirements.txt index 958e29a..c221213 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ aiorpcX[ws]>=0.22.0,<0.23 attrs plyvel -pylru aiohttp>=3.3,<4 From ddfdab817bc7d7791a76600707f27960b9c5d36c Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 2 Feb 2024 09:39:56 +0000 Subject: [PATCH 146/386] mempool.py: add some type hints --- electrumx/server/mempool.py | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/electrumx/server/mempool.py b/electrumx/server/mempool.py index ed8565a..f3a49ab 100644 --- a/electrumx/server/mempool.py +++ b/electrumx/server/mempool.py @@ -12,7 +12,7 @@ from abc import ABC, abstractmethod from asyncio import Lock from collections import defaultdict -from typing import Sequence, Tuple, TYPE_CHECKING, Type, Dict +from typing import Sequence, Tuple, TYPE_CHECKING, Type, Dict, Optional, Set import math import attr @@ -31,10 +31,10 @@ class MemPoolTx: prevouts = attr.ib() # type: Sequence[Tuple[bytes, int]] # A pair is a (hashX, value) tuple - in_pairs = attr.ib() - out_pairs = attr.ib() - fee = attr.ib() - size = attr.ib() + in_pairs = attr.ib() # type: Optional[Sequence[Tuple[bytes, int]]] + out_pairs = attr.ib() # type: Sequence[Tuple[bytes, int]] + fee = attr.ib() # type: int + size = attr.ib() # type: int @attr.s(slots=True) @@ -119,7 +119,7 @@ def __init__( self.coin = coin self.api = api self.logger = class_logger(__name__, self.__class__.__name__) - self.txs = {} + self.txs = {} # type: Dict[bytes, MemPoolTx] self.hashXs = defaultdict(set) # None can be a key self.cached_compact_histogram = [] self.refresh_secs = refresh_secs @@ -205,7 +205,7 @@ def _compress_histogram( prev_fee_rate = fee_rate return compact - def _accept_transactions(self, tx_map, utxo_map, touched): + def _accept_transactions(self, tx_map: Dict[bytes, MemPoolTx], utxo_map, touched): '''Accept transactions in tx_map to the mempool if all their inputs can be found in the existing mempool or a utxo_map from the DB. @@ -223,7 +223,7 @@ def _accept_transactions(self, tx_map, utxo_map, touched): try: for prevout in tx.prevouts: utxo = utxo_map.get(prevout) - if not utxo: + if not utxo: # i.e. parent also unconfirmed prev_hash, prev_index = prevout # Raises KeyError if prev_hash is not in txs utxo = txs[prev_hash].out_pairs[prev_index] @@ -274,7 +274,7 @@ async def _refresh_hashes(self, synchronized_event): touched = set() await sleep(self.refresh_secs) - async def _process_mempool(self, all_hashes, touched, mempool_height): + async def _process_mempool(self, all_hashes: Set[bytes], touched, mempool_height): # Re-sync with the new set of hashes txs = self.txs hashXs = self.hashXs @@ -321,16 +321,17 @@ async def _process_mempool(self, all_hashes, touched, mempool_height): return touched - async def _fetch_and_accept(self, hashes, all_hashes, touched): + async def _fetch_and_accept(self, hashes: Sequence[bytes], all_hashes: Set[bytes], touched): '''Fetch a list of mempool transactions.''' hex_hashes_iter = (hash_to_hex_str(hash) for hash in hashes) raw_txs = await self.api.raw_transactions(hex_hashes_iter) - def deserialize_txs(): # This function is pure + def deserialize_txs() -> Dict[bytes, MemPoolTx]: + """This function is pure""" to_hashX = self.coin.hashX_from_script deserializer = self.coin.DESERIALIZER - txs = {} + txs = {} # type: Dict[bytes, MemPoolTx] for hash, raw_tx in zip(hashes, raw_txs): # The daemon may have evicted the tx from its # mempool or it may have gotten in a block @@ -348,12 +349,17 @@ def deserialize_txs(): # This function is pure if not txin.is_generation()) txout_pairs = tuple((to_hashX(txout.pk_script), txout.value) for txout in tx.outputs) - txs[hash] = MemPoolTx(txin_pairs, None, txout_pairs, - 0, tx_size) + txs[hash] = MemPoolTx( + prevouts=txin_pairs, + in_pairs=None, + out_pairs=txout_pairs, + fee=0, + size=tx_size, + ) return txs # Thread this potentially slow operation so as not to block - tx_map = await run_in_thread(deserialize_txs) + tx_map = await run_in_thread(deserialize_txs) # type: Dict[bytes, MemPoolTx] # Determine all prevouts not in the mempool, and fetch the # UTXO information from the database. Failed prevout lookups From df390187eeed7c48fda2c94d5ffb04742c722778 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 10 May 2024 15:33:37 +0000 Subject: [PATCH 147/386] requirements: bump min aiorpcx to 0.23.0 --- electrumx/lib/util.py | 16 ---------------- electrumx/server/controller.py | 4 ++-- requirements.txt | 2 +- 3 files changed, 3 insertions(+), 19 deletions(-) diff --git a/electrumx/lib/util.py b/electrumx/lib/util.py index c92a35e..60a264c 100644 --- a/electrumx/lib/util.py +++ b/electrumx/lib/util.py @@ -381,22 +381,6 @@ async def join(self): self.completed.result() -# We monkey-patch aiorpcx.TaskGroup._add_task. -# This is to plug a memory-leak, see https://github.com/kyuupichan/aiorpcX/issues/46 . -# Note: this breaks the TaskGroup.results and TaskGroup.exceptions APIs -# but we are not using them anyway. -# TODO: this monkey-patch can be removed once we require aiorpcx versions that -# have the upstream fix for #46. -def _patched_TaskGroup_add_task(self: 'aiorpcx.TaskGroup', task): - self._orig_add_task(self, task) - if not hasattr(self, "_retain"): - self.tasks.clear() - - -aiorpcx.TaskGroup._orig_add_task = staticmethod(aiorpcx.TaskGroup._add_task) -aiorpcx.TaskGroup._add_task = _patched_TaskGroup_add_task - - # We monkey-patch aiorpcx TimeoutAfter (used by timeout_after and ignore_after API), # to fix a timing issue present in asyncio as a whole re timing out tasks. # To see the issue we are trying to fix, consider example: diff --git a/electrumx/server/controller.py b/electrumx/server/controller.py index abf2fa9..a8ce113 100644 --- a/electrumx/server/controller.py +++ b/electrumx/server/controller.py @@ -82,8 +82,8 @@ async def serve(self, shutdown_event): '''Start the RPC server and wait for the mempool to synchronize. Then start serving external clients. ''' - if not (0, 22, 0) <= aiorpcx_version < (0, 23): - raise RuntimeError('aiorpcX version 0.22.x is required') + if not (0, 23, 0) <= aiorpcx_version < (0, 24): + raise RuntimeError(f'aiorpcX version {aiorpcx_version!r} does not match required: 0.23.0<=ver<0.24') env = self.env min_str, max_str = env.coin.SESSIONCLS.protocol_min_max_strings() diff --git a/requirements.txt b/requirements.txt index c221213..031bc9c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -aiorpcX[ws]>=0.22.0,<0.23 +aiorpcX[ws]>=0.23.0,<0.24 attrs plyvel aiohttp>=3.3,<4 From c8a10a02d6e21575a2039d020c2af80a00e2e1d0 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 10 May 2024 15:41:53 +0000 Subject: [PATCH 148/386] (trivial) soothe pycodestyle --- electrumx/server/controller.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/electrumx/server/controller.py b/electrumx/server/controller.py index a8ce113..7e62689 100644 --- a/electrumx/server/controller.py +++ b/electrumx/server/controller.py @@ -83,7 +83,8 @@ async def serve(self, shutdown_event): start serving external clients. ''' if not (0, 23, 0) <= aiorpcx_version < (0, 24): - raise RuntimeError(f'aiorpcX version {aiorpcx_version!r} does not match required: 0.23.0<=ver<0.24') + raise RuntimeError( + f'aiorpcX version {aiorpcx_version!r} does not match required: 0.23.0<=ver<0.24') env = self.env min_str, max_str = env.coin.SESSIONCLS.protocol_min_max_strings() From 906c479eef83b64a33d035c9c2382f872f8f41b3 Mon Sep 17 00:00:00 2001 From: s7r Date: Fri, 7 Jun 2024 15:00:47 +0000 Subject: [PATCH 149/386] Add some working peers for coin Namecoin (#258) No peers were still working so updating with fresh ones. For security and anti sybil reasons not all old peers were removed, just some working ones added. --- electrumx/lib/coins.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 96badd3..0499b59 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -1200,14 +1200,19 @@ class Namecoin(NameIndexAuxPoWMixin, Coin): TX_PER_BLOCK = 10 RPC_PORT = 8336 PEERS = [ - '188.167.144.126 s50002', + '198.50.223.25 s50002', '46.229.238.187 s57002', - '82.119.233.36 s50002', + '198.50.223.27 s50002', 'electrum-nmc.le-space.de s50002', - 'ex.lug.gs s446', + 'electrumx2.nmc.dotbit.zone s50002', + 'electrumx4.nmc.dotbit.zone s50002', 'nmc.bitcoins.sk s50002', 'nmc2.bitcoins.sk s57002', 'ulrichard.ch s50006 t50005', + 'h6qrnwxtwiaahsw4qhk2nbutswwb54vnqso6jrxeuk6nwtfbos7jkoqd.onion s50002', + 'mqhok2edqcxdpklwovquuvvl2q5or4g2o777qwrinydgzfxjbixq76yd.onion s50002', + 'wirg2tsto7rme7n26lkd3ivbvxmjyy2pktlozwjuep22jcsfsghfqbqd.onion s50002', + 'quuzpffsddq3aim3gk4oiczzyt5okjjzf4zpe6okzo6qidao6rshynqd.onion s50002', ] BLOCK_PROCESSOR = block_proc.NameIndexBlockProcessor NAME_EXPIRATION = 36_000 From 92589103620abb6e4be7faf441c88012e1f8339c Mon Sep 17 00:00:00 2001 From: WakiyamaP Date: Fri, 6 Sep 2024 21:49:20 +0900 Subject: [PATCH 150/386] Add bitcoin testnet4 (#273) * Add bitcoin testnet4 * Fix pycodestyle test(bitcoin testnet4) --- electrumx/lib/coins.py | 17 ++++++++++++++ tests/blocks/bitcoin_testnet4_40000.json | 29 ++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 tests/blocks/bitcoin_testnet4_40000.json diff --git a/electrumx/lib/coins.py b/electrumx/lib/coins.py index 0499b59..ef25c74 100644 --- a/electrumx/lib/coins.py +++ b/electrumx/lib/coins.py @@ -971,6 +971,23 @@ class BitcoinSegwitSignet(BitcoinSignet): NAME = "BitcoinSegwit" # support legacy name +class BitcoinTestnet4(BitcoinTestnet): + NAME = "Bitcoin" + NET = "testnet4" + GENESIS_HASH = ('00000000da84f2bafbbc53dee25a72ae' + '507ff4914b867c565be350b0da8bf043') + PEERS = [ + 'blackie.c3-soft.com s57010 t57009', + 'testnet4-electrumx.wakiyamap.dev', + ] + TX_COUNT = 1 + TX_COUNT_HEIGHT = 1 + + +class BitcoinSegwitTestnet4(BitcoinTestnet4): + NAME = "BitcoinSegwit" # support legacy name + + class BitcoinNolnet(BitcoinCash): '''Bitcoin Unlimited nolimit testnet.''' NET = "nolnet" diff --git a/tests/blocks/bitcoin_testnet4_40000.json b/tests/blocks/bitcoin_testnet4_40000.json new file mode 100644 index 0000000..11f7baa --- /dev/null +++ b/tests/blocks/bitcoin_testnet4_40000.json @@ -0,0 +1,29 @@ +{ + "hash": "000000000000000c1a1fad82b0e133f4772802b6dff7a95990580ae2e15c634f", + "size": 5826, + "height": 40000, + "merkleroot": "cad0d11c8d5e1304a36a882827e32801142d6a290ce1a2a4e8f0035bfa7f6d45", + "tx": [ + "5c50d460b3b98ea0c70baa0f50d1f0cc6ffa553788b4a7e23918bcdd558828fa", + "fb4e96dad663f44fafbb1277cdfe2d7dbe50e03c99d527f5bff3ac9b4c3570fc", + "d991f3762c955ed327608c7edd1588c741e3369b0fe63d58eae9fd3959eef342", + "e451e0a76f1326d657895a5cfc4fd0be67cc96fdc7bb416b81d55f0b5d210c53", + "8bce727fe08fbb79f02682f71d0b1f33a79039cd7a70623a51945bf2dc86d77c", + "d1f636b56a494ffcb789682ec1db60d5be4100d3e6d760ab6f1a3ca2d7ff2485", + "285f28586b364ccbb35c0e092b6b16615f2cc31f3c1176ec52ed65b19153bf8a", + "659ff9dab924a0e1236e1b465cbd84195564ae297968028037fcab656faefba3", + "b86c427253faf575e49e54643962b0a8e2065289848e8616247a6b198225b5aa", + "4cafc859ac10dd7d297d7498d7635158ad45045312227b593b43b274e33674cc", + "3bce55005ce35f3cf632bbc29a215f7ccf2480194b7fa29c93b2d9693964d3d0", + "ca413bf56518af95872b305fb56502d465bbd605adaeffe086f38c934993dfd9", + "28771e00a44951c2f7d905653bc6afbc6dbefeccbad65c199388e05f54e127dc", + "eb948c9de926abccb9f65bb1e8c98906bd8b0e1103dc39d6a6f4e9b7b74143f0", + "ee75ac577088e34bd552b4aa3e7b4bf1b6528b6b395bcf713f44916aa9a5a2f1", + "3f792690080f2b03d7d5a0ea7e1d7d79d83242024043b685a3ab474f71dd4efd" + ], + "time": 1723908842, + "nonce": 1825621086, + "bits": "1954fa04", + "previousblockhash": "00000000000000124f9e271251c750dbe30d9b3afe9f06fbcfb2fe3baa160203", + "block": "0020b42b030216aa3bfeb2cffb069ffe3a9b0de3db50c75112279e4f1200000000000000456d7ffa5b03f0e8a4a2e10c296a2d140128e32728886aa304135e8d1cd1d0caeac2c06604fa54195ec4d06c10010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff4a03409c000004ccc2c06604389abb271024e1b5660000000000000000000000000a636b706f6f6c22506f72746c616e642e484f444c207c20446574726f6974207c20f09f87baf09f87b8ffffffff02fe01062a010000001976a9142ce72b25fe97b52638c199acfaa5e3891ddfed5b88ac0000000000000000266a24aa21a9ed3e249fa3559a30d20a840b05239f365bb7d93aa6cb5484f169ac8badea0e843501200000000000000000000000000000000000000000000000000000000000000000000000000100000000010143da142315ac08a9f9b04d456d51175c18951a6939c972382ac5e1e6a139038b0000000000ffffffff019c6108000000000016001463841fbc7aeaab5104602a378dd66e72261fef300247304402204974a564116d4537e140859d922ab821aaca036221e2d507ef92b96adaa6cdb6022021ab91e5c86e76c2f7ea4a6b36a3d5a5ab4643da808f1219d7d06903b5827a1601210204cab25185dc08801da7cf5d62e7daa92914d69665a22e03e9ecc30ea02fa7660000000002000000000104e404664445a549e21460911648e6c68b958da123ce455db1b6244bd28886d2320000000017160014ed62876440f9432d9f6bfc48d59cef0a24214c31fdffffff8e32e9d7a682a856bd8e6feaf70458c3031d6a6b64ca6047bd9ad3c066d350990100000017160014c9a8d47f08dfc91a863111af39291486f09b534ffdffffff04b2752cab3d1409ced16e9656985b2b724cb1a6d8c264fd3e0c44072b0902c701000000171600144e140ec7d2f05008e5f785fb9a058f85429f20cffdffffff8a6929fd7d22f5634971620b6d5e2932a7ab60cc70e5f0509d7260b5054c28b90000000017160014a735c6fb24355366304ca88b867bc3a6c8c4852bfdffffff037b710e00000000001600147dd92898c488a0838e5ea32b9fb57d2a49f14b3da0e20e00000000001976a914a0a3a6eab4357f6cade3b5e0031f13564ef9162f88ac80ca07000000000016001463bbdedda0703c995a632a1a4ee92ffe09af4f6e0247304402205028aba5260d08db3444ddeb8f7a56e1b7278c8071ed7e8d37078a9d9ea6033102204c9c15ba2bb2516c8365bda4118ac189f1fda54ed38a4d7002e5d7fa7d74c18b012102ec7430e29962705708224aa88169072fec1094badb1103213939b84ba2ea734a0247304402204f7f475ec40edba8fa12cd30e04752e6c45937a976f93f9076dcf04361513ae702207363038de2d9fc2de7744ee2e1cf7548ca6ce9979c81f6c0ece495ffa2cc646f0121027aa0626d3fff010cbc240a25c5806c717b70523e0601737d4645ce7a4acee88a0247304402201b1784f21a2376b3578f3a193449a86050edd59f1be9aa0da30e9c808844e24602205102ed3ce00cb9fb6d56e642c0ed0c7a460ed6be4592de8bec23793a0d8c72e30121036e4bce262c7b4520b7012d84424324c9b093b17559133cbea66acb798274db850247304402201dca7669b20a6cb677c2f44e9349bd96472a26aa09ada0d3cba68f77ad1bc514022075f309d836af6195c677999659e86d5e80e196c2a7ccda68bc30b1ce940113a60121034d2b3d15f3b7856a7c350748ae48223dd05287c682bce97e6b198ff4ab9b61bf3f9c0000020000000001017a18376793c140241311576124240c41027ffa1d1b1d3e64bbdadf6b4fbe4f910000000000fdffffff02fe42d028170000001600147b458433d0c04323426ef88365bd4cfef141ac7520a10700000000001600147d237cb1fe62153805fa1fca6cbc74765ccaeb5402473044022022784ac1c15c76b320274fcf99cafa55ba1554d0e742f79d5bb7ecd2f3725e9f02205f193c7f36742aa1f23b091d2191208256e33d6084eed65230f5f238432e03370121030db9616d96a7b7a8656191b340f77e905ee2885a09a7a1e80b9c8b64ec746fb30000000002000000000101b2a2b9cbcd6acd886a3adcc7e172d7cdece234a8f4502079ed2d01fa46322f000100000000fdffffff02246d5d13000000001600140633f9fe93dd480f20159602e4e07e1721b7e4c03e6900000000000016001464de410cc361cee4f8b506567df3ca9f30710067024730440220680ae471f2afe3dfd90b276e686e191525f8a36e1e87187d1742c8b5c1b17b020220173a293064b41c70cb58bb1a3715dafbc56fc0a786c15415c9cdda9b237a86fc01210396867c45cc97e85d704a9afdebb388bc6feffa4851dda31168ec2fd7cf5763663f9c00000200000003234711ca14f70dbd873b8606ad0978c82928aa9eaafa6ac5291b6b25789862f9010000006a4730440220028ac25e04a859ba1f7a3ea8f4bdc175adaadd8ce8575d53fa9db37d5ac153e9022046344c62f2e5ddce6ec2f30cbb2ddeb6495621297c645b4948093d6712b586d50121039f4c6ee26308a34cd14a1930fa60a8415378355596c1153295e5275dff66fd90fdffffff0ff3701d18c63d4daea337060f47ade86466e8af46aa918827cc63e3482ae937010000006a473044022025897f1f067f6cbd818fb114a89dd9ce27de00842092f9eaa45374b820f320ee022045670f35a7b15c7e4b519445460ee3cac1ecc93a3bb858a892c9aab46c5ddf7f01210300660d1d4fc56e0b8e203e6cb667f7f78c04fa012cc544686b16a16b9a1c31c8fdffffffa70f23cb877d1eb479a420855fea388e9039a26b5f408d30de59d28bd15e4dcb020000006a47304402206055c9f81ab41b1ed3933422add9949535103e5aba7a8eaaa3618ae85d1a4999022008d3fc627703e6778f6d401b10f1fd71a6f511255cbf5f3a89f9c508ef50f37f0121027aca30f737a95a53f3da28cde58c3517860f8a51f850eb699f7d2bb9039cd46efdffffff0316fc040000000000160014f45ea236de6fd4e2ed8a6e6320cdf2db173c6ee0c1040200000000001976a9147dbe4c26a474503bd6f576c7b36c31d37861df6088ac892d090000000000160014f66ac1d07ccc33d517e26f9dcc00eb6468709105e89b0000020000000001010bcdd5e36694495e4a4d2f746d66388f276919dc84a7bbc612b543f48b36f40c0000000000fdffffff026bbd7f07000000001600147b458433d0c04323426ef88365bd4cfef141ac7520a1070000000000160014989792062dc8cab87dc45f47718950b773ca7d3902473044022050398f98e96da6b4b8cadee89c1eb5145a7db044711b4880e1bf66b2b15e850f0220764043c517a1df9dcdc45fd62c381e94e654eece565cacc53bc5acdd882c511e0121030db9616d96a7b7a8656191b340f77e905ee2885a09a7a1e80b9c8b64ec746fb30000000002000000000103421439343bda7fe764f901845cf7f0c751f186b25e98495c186da39fe751c5f601000000171600141bb112e56f0574f06b72688a12f670a9ffbf42d0fdffffff1dc67c688456f72349792804d2bab77aa60563dfba7405076ec1e63e58f41c620000000017160014a4244c5b94e5e11fd5227377495173f38bc51261fdffffff04b2752cab3d1409ced16e9656985b2b724cb1a6d8c264fd3e0c44072b0902c70000000017160014985c9b1ad953d7bee7533b599dec0d7eb291b9a2fdffffff03b53f00000000000017a9140f1e74ec42ed483d18c463d51bfc5d25b0ffe6b88719f308000000000017a914e7f55f5bd23c4396dcbea8cca6a1819e6d42653087ac2208000000000017a914e1cdf73c9251b789c89022f0760b3a285361d885870247304402200bcf968fca75caaed61f2fd5a3ec607f1c994c0d22cc242921ef532afb0ee2f2022065c95b63b1b357aee5027073bd11d95de5c19f200a87e17165a772e683b4e6c2012103296acc3caefa5e3af62ff8abfd51bf62766941c4b3456098ab20e28f1df1bd5602473044022070836855ba7cf3d66d162c5b78b966003fc17158d5c2ce75faa6f0c709d21da10220492887f041840dc064ccd1a9d9a2f7cfe1e8b7e98bbeaa3aeafd7c3f40642620012102838d41dda4292ee4458969a89dddc6f38f1251ded8c2177a89f61134e0a8552c02473044022065bee42a7eaff5e100ec3d273169b8940cd1f33091caec7dd0cb6c0e2a7560ac02206bc54dae0a001f73001ec5b3c1d6f2483fb6f0214b022399a6994def6635e2030121031aca61b28388afc8446ed57fc5db1cd114f903920d89e94bb818ae12bf4ba1703f9c000002000000000103db5d53cbe8502f4403bd705db7867702dc99d4736deb9b6b5b9507f607cea80c01000000171600146b9bfb89dbdf9c077d7d494a679c90d4a85d4f3cfdffffffe404664445a549e21460911648e6c68b958da123ce455db1b6244bd28886d2320100000017160014c042680868a35165b194816da26217614c93a201fdffffff652213c7259207a84bf193ad0b28f7847054f351ad5ccd49ed025d70105795380200000017160014a546a713910ba0640c7d5c50462288cf41f075a8fdffffff03865e0200000000001976a9147f558c20d7e485a084a80f792dc14453f3d4189088aca0e20e00000000001976a914da9d72caea1f4e1e2c61483dd4e95d1171c922a788ac19f30800000000001976a9144686ab37c08f93f96dc3b822c08da0b2ac4c4c7088ac024730440220151b48153be2c36d6e4492511e8e7d89fce99352b0fd096f4242066b3ff4de3502205c206c90baf6c0a635f0eabc9088e6320e66993780fdeabfecdd14111c6c9c2b012102fbd6c3e6570d6e5f5733108fc21af8a98e5706a282e17fdd49a112d9f587939702473044022037bd3f3308b027630b9b0da62e17966293ab20929aa9ad3f47bdf5f7ec40bbec0220116cf4f8728bb27218f01bafeebde2b8fe7c78a90aeca2af1ad940cc6edf2b1201210223e709ebd92a74215c0307a96e7ad75e86aa98b79d145e92a7759230d067e0d30247304402207fe9f89dd7bafff12b7fe1ca6b6138ad08b8bda2550b9ed9633986ecac19dff3022061c2181f34e7c2b00f95b6ef29d6a977c0123eea800af114488ce8f88b909c7e012103190b45b10e97e021cf37a9c83d6a33b8d6f49a681c801ceeedd3b3aaa59009af3f9c000002000000000101de1b7652d254bf5165dcc51cbf0239bfea2cc9d4a6d76d1c20855dd08d1ac55e0000000000fdffffff02f3cc3f21010000001600147b458433d0c04323426ef88365bd4cfef141ac7520a10700000000001600144db7aca965c9567d5c1f87aca694dd598a09036b0247304402200c60de020be5c1a68e78bb087c3f00fe0da1ad7ea44294726e87e55c5d8cc9cd022023964060b91443948e26d38d310aeda24c7a507a40f4d0bd765094b36a8487c60121030db9616d96a7b7a8656191b340f77e905ee2885a09a7a1e80b9c8b64ec746fb300000000020000000001014cfa93c868e8e02dd18a872e8fe3bf51d28fe6e107ce9bbf80b70b65ac0ff2c60100000000fdffffff0260680000000000001600141dab0d8b807e7130fd61537bbea8ef6272d50341262de60b00000000160014eea78ae6143721c65f3929e71126b93aaed251800247304402201312a9a35a408bbb89313969e03aa47177eb5a384fb10876f6a6ecd93ea2654b02206d1db64958af07ca42e4f197b0789d27fa5070c5d91c85205f3c6516332107680121025de6139cd7311ffe39c90bfa4981d0bd656699d1483337d2bdb33a9f55c9a0253f9c00000200000003181096155d198c9f73050d3f48cffe3bd0c689fbaf5e2c4d17754f5dd3c76bd7000000006a473044022023dab87e2924e966aafef268773c6d805c24aaf3c09a13f4b331f37eaa77544002200f6ed53ff1e4736db9247110323736861b254800683793ad32a473834c4d43bb0121029627f067fa1c2951781cba0b3c4697319b99e2728e7fa448f397fe71eefef6a1fdffffffad94349376a8b89d447b6277243c5fce43a7327acf1b25b90729aac77e139695010000006a4730440220791495bb35e2c07c7e16f35b6ac2e04eb653517fe4d447166c9c26567bfa0f5e02207abd7ccd39b109531e318bcf983b57920bf8f958dc14b57eb643bedbab55d451012103281fb57ad0b79136efcee94c116d988ccafda362bf1c58be2ca0351efd611b71fdffffff1e066a33bf7de35740a9200e76dfb6ea4ece20353f946d55a0057b413d08e45c000000006a47304402204d6c6a8bd99099c1e0bf816cfafc3e565330115fdeed89996145d5544bf7780802206fa823e474e419ba1ced08c470d83abbecafedc74ad9ed567de5e9e4ecd7e2ee0121022028f113d888fe9bd10531f67f245edb9eedb9a0f1f81dd2e8eb1e97c2be2ed9fdffffff0303ed0800000000001976a9140be140309ef888e4c1404fba019068f4f8fee4b888ac892d09000000000017a914da49c0afb58fd3b75daafbe88f2c75e442a1ab2887e74a01000000000017a914553e979b1011d0200c1ac76aa20c53aa2f52b07a873f9c000002000000000101fe630901d98884a24c5355a1cc4504173a8168aee31af49d69d500b7c4a701760000000000fdffffff028b9000000000000016001460662a32e479a04067d5377b2ffc9d09880bb5a041af2213000000001600140a5ac2337ed3971c6e38c1f37537541c4a2d6b860247304402202add082290b0dbf3d579179cf8f3db58e7768503fe9337f6300a2be98b6a4f0d02200e3773b056278092a352e2780458503e022d25972d6a601fe169543e3cd9fa6f012102aa3410167cecc224a443bce8e854646202387b07d495aa42e9611f0bbc0960433e9c00000200000002cf42974c213ef42de09ae2cf85c2e8b96d83ba63cd0aeb549d01ddbe1a4af069020000006a47304402207502dc3e75dfdc44d25fcc3f8208221b5997f7fb309226267290b07b96f3808202207455f0192ff484f37b197483abc0e923642834c2e8e6664f64a1553494fc3441012102eb4eccaf062182f7f10585cbb47d391a341af8f9e9c43c08a607f3f7e60e28a1fdffffff181096155d198c9f73050d3f48cffe3bd0c689fbaf5e2c4d17754f5dd3c76bd7010000006a473044022027f0996a10816e83a54071654b5cdba017a3fc2d1f339fc22f6c9a336bc4850002202d13710bd0791aad8ffe1fbcb40ab070214dd730599b7f538b149554816d9a04012102fe7120ab2cf852dafdd40634d2787a15d9aa418deed0f5fcc0270b88de250913fdffffff033ff2090000000000160014f671dc027d7700b27a4ffc46d271341b6d116bb9a4450500000000001976a9140b0c195da6df222fd3736597b16f6ae995b4b00988ace6860200000000001600148e115a0b49eee020356cea8f0c5173d2b8306f313f9c000002000000000101c1662773863c60aeee798c12208c3900312dc17e2fb99ff0a5d2e7d76e33d79b0000000000fdffffff02c24d1c090000000016001477483494c166dc8f763eeda8a7910471a2942d996880000000000000160014a4751ecb1f1a9372a2cc3127babab06697e2fcd002473044022024f365d6f4fb6d50cf922a3532a5944c65926703337ef262a373d904585f22bd0220078870f318c7153f8fe526d4fd63b4e8d0551923b6d406da1b6948f2dff5573701210263a93472fb83da0ff8496390ed80810cfee3a94404dcc36322f2213ad097c5a53f9c000002000000000101ff653d23f6fa98659dd87c3c12d878aa6525ea876c09992f64487331414ba86d0100000000fdffffff02ec73000000000000160014abb6664c56fbc8a51e80313e4495764e7de647dfb9b4b81100000000160014c5638b4713c9517e3ba511f13324edd7cceeedc302473044022068313c6de4a547ad4b26ccb64bb66899302b5fc596eee1cc6bd328cf45c7536502205569bc98cde3067f5037c8eda811d930356c48361166677b3e5212e6fd1f8903012103425a5d8f185c9117d121eb909e9fbd739fe46d9a593ec2df96a9e11f4e0e27da3f9c0000" +} From f9074fae61d689ff2974bd009d5e42f8b0c8bc34 Mon Sep 17 00:00:00 2001 From: f321x Date: Mon, 20 Jan 2025 12:31:53 +0100 Subject: [PATCH 151/386] increase allowed aiorpcx version to 0.24 --- electrumx/server/controller.py | 4 ++-- requirements.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/electrumx/server/controller.py b/electrumx/server/controller.py index 7e62689..8f7fb56 100644 --- a/electrumx/server/controller.py +++ b/electrumx/server/controller.py @@ -82,9 +82,9 @@ async def serve(self, shutdown_event): '''Start the RPC server and wait for the mempool to synchronize. Then start serving external clients. ''' - if not (0, 23, 0) <= aiorpcx_version < (0, 24): + if not (0, 23, 0) <= aiorpcx_version < (0, 25): raise RuntimeError( - f'aiorpcX version {aiorpcx_version!r} does not match required: 0.23.0<=ver<0.24') + f'aiorpcX version {aiorpcx_version!r} does not match required: 0.23.0<=ver<0.25') env = self.env min_str, max_str = env.coin.SESSIONCLS.protocol_min_max_strings() diff --git a/requirements.txt b/requirements.txt index 031bc9c..d5fa1d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -aiorpcX[ws]>=0.23.0,<0.24 +aiorpcX[ws]>=0.23.0,<0.25 attrs plyvel aiohttp>=3.3,<4 From b293369f76175f846c7ebfedb7fde80a8a439f72 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Wed, 5 Feb 2025 17:10:00 +0000 Subject: [PATCH 152/386] docs: mv protocol docs to separate git repo see https://github.com/spesmilo/electrum-protocol --- docs/protocol-basics.rst | 205 ------ docs/protocol-changes.rst | 177 ----- docs/protocol-ideas.rst | 284 -------- docs/protocol-methods.rst | 1394 ------------------------------------- docs/protocol-removed.rst | 384 ---------- docs/protocol.rst | 11 +- 6 files changed, 2 insertions(+), 2453 deletions(-) delete mode 100644 docs/protocol-basics.rst delete mode 100644 docs/protocol-changes.rst delete mode 100644 docs/protocol-ideas.rst delete mode 100644 docs/protocol-methods.rst delete mode 100644 docs/protocol-removed.rst diff --git a/docs/protocol-basics.rst b/docs/protocol-basics.rst deleted file mode 100644 index 9dbbd54..0000000 --- a/docs/protocol-basics.rst +++ /dev/null @@ -1,205 +0,0 @@ -Protocol Basics -=============== - -Message Stream --------------- - -Clients and servers communicate using **JSON RPC** over an unspecified underlying stream -transport. Examples include TCP, SSL, WS and WSS. - -Two standards `JSON RPC 1.0 -`_ and `JSON RPC 2.0 -`_ are specified; use of version -2.0 is encouraged but not required. Server support of batch requests -is encouraged for version 1.0 but not required. - -.. note:: A client or server should only indicate JSON RPC 2.0 by - setting the `jsonrpc - `_ member of - its messages to ``"2.0"`` if it supports the version 2.0 protocol in - its entirety. ElectrumX does and will expect clients advertizing so - to function correctly. Those that do not will be disconnected and - possibly blacklisted. - -Clients making batch requests should limit their size depending on the -nature of their query, because servers will limit response size as an -anti-DoS mechanism. - -Over TCP and SSL raw sockets each RPC call, and each response, MUST be terminated by a -single newline to delimit messages. Websocket messages are already framed so they MUST -NOT be newline terminated. The JSON specification does not permit control characters -within strings, so no confusion is possible there. However it does permit newlines as -extraneous whitespace between elements; client and server MUST NOT use newlines in such a -way. - -If using JSON RPC 2.0's feature of parameter passing by name, the -names shown in the description of the method or notification in -question MUST be used. - -A server advertising support for a particular protocol version MUST -support each method documented for that protocol version, unless the -method is explicitly marked optional. It may support other methods or -additional parameters with unspecified behaviour. Use of additional -parameters is discouraged as it may conflict with future versions of -the protocol. - - -Notifications -------------- - -Some RPC calls are subscriptions which, after the initial response, -will send a JSON RPC :dfn:`notification` each time the thing -subscribed to changes. The `method` of the notification is the same -as the method of the subscription, and the `params` of the -notification (and their names) are given in the documentation of the -method. - - -Version Negotiation -------------------- - -It is desirable to have a way to enhance and improve the protocol -without forcing servers and clients to upgrade at the same time. - -Protocol versions are denoted by dotted number strings with at least -one dot. Examples: "1.5", "1.4.1", "2.0". In "a.b.c" *a* is the -major version number, *b* the minor version number, and *c* the -revision number. - -A party to a connection will speak all protocol versions in a range, -say from `protocol_min` to `protocol_max`, which may be the same. -When a connection is made, both client and server must initially -assume the protocol to use is their own `protocol_min`. - -The client should send a :func:`server.version` RPC call as early as -possible in order to negotiate the precise protocol version; see its -description for more detail. All responses received in the stream -from and including the server's response to this call will use its -negotiated protocol version. - - -.. _script hashes: - -Script Hashes -------------- - -A :dfn:`script hash` is the hash of the binary bytes of the locking -script (ScriptPubKey), expressed as a hexadecimal string. The hash -function to use is given by the "hash_function" member of -:func:`server.features` (currently :func:`sha256` only). Like for -block and transaction hashes, when converting the big-endian binary -hash to a hexadecimal string the least-significant byte appears first, -and the most-significant byte last. - -For example, the legacy Bitcoin address from the genesis block:: - - 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa - -has P2PKH script:: - - 76a91462e907b15cbf27d5425399ebf6f0fb50ebb88f1888ac - -with SHA256 hash:: - - 6191c3b590bfcfa0475e877c302da1e323497acf3b42c08d8fa28e364edf018b - -which is sent to the server reversed as:: - - 8b01df4e368ea28f8dc0423bcf7a4923e3a12d307c875e47a0cfbf90b5c39161 - -By subscribing to this hash you can find P2PKH payments to that address. - -One public key, the genesis block public key, among the trillions for -that address is:: - - 04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb - 649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f - -which has P2PK script:: - - 4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb - 649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac - -with SHA256 hash:: - - 3318537dfb3135df9f3d950dbdf8a7ae68dd7c7dfef61ed17963ff80f3850474 - -which is sent to the server reversed as:: - - 740485f380ff6379d11ef6fe7d7cdd68aea7f8bd0d953d9fdf3531fb7d531833 - -By subscribing to this hash you can find P2PK payments to the genesis -block public key. - -.. note:: The Genesis block coinbase is uniquely unspendable and - therefore not indexed. It will not show with the above P2PK script - hash subscription. - - -.. _status: - -Status ------- - -To calculate the `status` of a :ref:`script hash