├── .github
└── workflows
│ ├── publish.yml
│ └── python-package.yml
├── .gitignore
├── LICENSE
├── README.md
├── blockchain_parser
├── __init__.py
├── address.py
├── block.py
├── block_header.py
├── blockchain.py
├── index.py
├── input.py
├── output.py
├── script.py
├── tests
│ ├── __init__.py
│ ├── data
│ │ ├── bech32_p2wpkh.txt
│ │ ├── bech32_p2wsh.txt
│ │ ├── bip69_false.txt
│ │ ├── bip69_true.txt
│ │ ├── genesis_block.txt
│ │ ├── invalid_tx.txt
│ │ ├── large_tx.txt
│ │ ├── scripts_invalid.txt
│ │ ├── segwit.txt
│ │ ├── size_non_segwit.txt
│ │ └── size_segwit.txt
│ ├── test_address.py
│ ├── test_block.py
│ ├── test_index.py
│ ├── test_output.py
│ ├── test_script.py
│ ├── test_transaction.py
│ ├── test_utils.py
│ └── utils.py
├── transaction.py
├── undo.py
├── utils.py
└── utils_taproot.py
├── examples
├── non-standard-outputs.py
├── ordered-blocks.py
├── texts-in-coinbases.py
└── undo-blocks.py
├── requirements.txt
├── setup.py
└── tox.ini
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow will upload a Python Package using Twine when a release is created
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
3 |
4 | # This workflow uses actions that are not certified by GitHub.
5 | # They are provided by a third-party and are governed by
6 | # separate terms of service, privacy policy, and support
7 | # documentation.
8 |
9 | name: Upload Python Package
10 |
11 | on:
12 | release:
13 | types: [published]
14 |
15 | permissions:
16 | contents: read
17 |
18 | jobs:
19 | deploy:
20 |
21 | runs-on: ubuntu-latest
22 |
23 | steps:
24 | - uses: actions/checkout@v3
25 | - name: Set up Python
26 | uses: actions/setup-python@v3
27 | with:
28 | python-version: '3.x'
29 | - name: Install dependencies
30 | run: |
31 | python -m pip install --upgrade pip
32 | pip install build
33 | - name: Build package
34 | run: python -m build
35 | - name: Publish package
36 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
37 | with:
38 | user: __token__
39 | password: ${{ secrets.PYPI_API_TOKEN }}
40 |
--------------------------------------------------------------------------------
/.github/workflows/python-package.yml:
--------------------------------------------------------------------------------
1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
3 |
4 | name: Python package
5 |
6 | on:
7 | push:
8 | branches: [ "master" ]
9 | pull_request:
10 | branches: [ "master" ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 | strategy:
17 | fail-fast: false
18 | matrix:
19 | python-version: ["3.9", "3.10", "3.11"]
20 |
21 | steps:
22 | - uses: actions/checkout@v3
23 | - name: Set up Python ${{ matrix.python-version }}
24 | uses: actions/setup-python@v3
25 | with:
26 | python-version: ${{ matrix.python-version }}
27 | - name: Install dependencies
28 | run: |
29 | python -m pip install --upgrade pip
30 | python -m pip install flake8 pytest
31 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
32 | - name: Lint with flake8
33 | run: |
34 | # stop the build if there are Python syntax errors or undefined names
35 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
36 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
37 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
38 | - name: Test with pytest
39 | run: |
40 | pytest
41 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *,cover
46 |
47 | # Translations
48 | *.mo
49 | *.pot
50 |
51 | # Django stuff:
52 | *.log
53 |
54 | # Sphinx documentation
55 | docs/_build/
56 |
57 | # PyBuilder
58 | target/
59 |
60 | # PyCharm
61 | .idea/
62 |
63 | .vscode/
64 | .venv/
65 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | bitcoin-blockchain-parser is free software: you can redistribute it and/or
2 | modify it under the terms of the GNU Lesser General Public License as
3 | published by the Free Software Foundation, either version 3 of the License,
4 | or (at your option) any later version.
5 |
6 | bitcoin-blockchain-parser is distributed in the hope that it will be useful, but
7 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
8 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
9 | below for more details.
10 |
11 |
12 |
13 | GNU LESSER GENERAL PUBLIC LICENSE
14 | Version 3, 29 June 2007
15 |
16 | Copyright (C) 2007 Free Software Foundation, Inc.
17 | Everyone is permitted to copy and distribute verbatim copies
18 | of this license document, but changing it is not allowed.
19 |
20 |
21 | This version of the GNU Lesser General Public License incorporates
22 | the terms and conditions of version 3 of the GNU General Public
23 | License, supplemented by the additional permissions listed below.
24 |
25 | 0. Additional Definitions.
26 |
27 | As used herein, "this License" refers to version 3 of the GNU Lesser
28 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
29 | General Public License.
30 |
31 | "The Library" refers to a covered work governed by this License,
32 | other than an Application or a Combined Work as defined below.
33 |
34 | An "Application" is any work that makes use of an interface provided
35 | by the Library, but which is not otherwise based on the Library.
36 | Defining a subclass of a class defined by the Library is deemed a mode
37 | of using an interface provided by the Library.
38 |
39 | A "Combined Work" is a work produced by combining or linking an
40 | Application with the Library. The particular version of the Library
41 | with which the Combined Work was made is also called the "Linked
42 | Version".
43 |
44 | The "Minimal Corresponding Source" for a Combined Work means the
45 | Corresponding Source for the Combined Work, excluding any source code
46 | for portions of the Combined Work that, considered in isolation, are
47 | based on the Application, and not on the Linked Version.
48 |
49 | The "Corresponding Application Code" for a Combined Work means the
50 | object code and/or source code for the Application, including any data
51 | and utility programs needed for reproducing the Combined Work from the
52 | Application, but excluding the System Libraries of the Combined Work.
53 |
54 | 1. Exception to Section 3 of the GNU GPL.
55 |
56 | You may convey a covered work under sections 3 and 4 of this License
57 | without being bound by section 3 of the GNU GPL.
58 |
59 | 2. Conveying Modified Versions.
60 |
61 | If you modify a copy of the Library, and, in your modifications, a
62 | facility refers to a function or data to be supplied by an Application
63 | that uses the facility (other than as an argument passed when the
64 | facility is invoked), then you may convey a copy of the modified
65 | version:
66 |
67 | a) under this License, provided that you make a good faith effort to
68 | ensure that, in the event an Application does not supply the
69 | function or data, the facility still operates, and performs
70 | whatever part of its purpose remains meaningful, or
71 |
72 | b) under the GNU GPL, with none of the additional permissions of
73 | this License applicable to that copy.
74 |
75 | 3. Object Code Incorporating Material from Library Header Files.
76 |
77 | The object code form of an Application may incorporate material from
78 | a header file that is part of the Library. You may convey such object
79 | code under terms of your choice, provided that, if the incorporated
80 | material is not limited to numerical parameters, data structure
81 | layouts and accessors, or small macros, inline functions and templates
82 | (ten or fewer lines in length), you do both of the following:
83 |
84 | a) Give prominent notice with each copy of the object code that the
85 | Library is used in it and that the Library and its use are
86 | covered by this License.
87 |
88 | b) Accompany the object code with a copy of the GNU GPL and this license
89 | document.
90 |
91 | 4. Combined Works.
92 |
93 | You may convey a Combined Work under terms of your choice that,
94 | taken together, effectively do not restrict modification of the
95 | portions of the Library contained in the Combined Work and reverse
96 | engineering for debugging such modifications, if you also do each of
97 | the following:
98 |
99 | a) Give prominent notice with each copy of the Combined Work that
100 | the Library is used in it and that the Library and its use are
101 | covered by this License.
102 |
103 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
104 | document.
105 |
106 | c) For a Combined Work that displays copyright notices during
107 | execution, include the copyright notice for the Library among
108 | these notices, as well as a reference directing the user to the
109 | copies of the GNU GPL and this license document.
110 |
111 | d) Do one of the following:
112 |
113 | 0) Convey the Minimal Corresponding Source under the terms of this
114 | License, and the Corresponding Application Code in a form
115 | suitable for, and under terms that permit, the user to
116 | recombine or relink the Application with a modified version of
117 | the Linked Version to produce a modified Combined Work, in the
118 | manner specified by section 6 of the GNU GPL for conveying
119 | Corresponding Source.
120 |
121 | 1) Use a suitable shared library mechanism for linking with the
122 | Library. A suitable mechanism is one that (a) uses at run time
123 | a copy of the Library already present on the user's computer
124 | system, and (b) will operate properly with a modified version
125 | of the Library that is interface-compatible with the Linked
126 | Version.
127 |
128 | e) Provide Installation Information, but only if you would otherwise
129 | be required to provide such information under section 6 of the
130 | GNU GPL, and only to the extent that such information is
131 | necessary to install and execute a modified version of the
132 | Combined Work produced by recombining or relinking the
133 | Application with a modified version of the Linked Version. (If
134 | you use option 4d0, the Installation Information must accompany
135 | the Minimal Corresponding Source and Corresponding Application
136 | Code. If you use option 4d1, you must provide the Installation
137 | Information in the manner specified by section 6 of the GNU GPL
138 | for conveying Corresponding Source.)
139 |
140 | 5. Combined Libraries.
141 |
142 | You may place library facilities that are a work based on the
143 | Library side by side in a single library together with other library
144 | facilities that are not Applications and are not covered by this
145 | License, and convey such a combined library under terms of your
146 | choice, if you do both of the following:
147 |
148 | a) Accompany the combined library with a copy of the same work based
149 | on the Library, uncombined with any other library facilities,
150 | conveyed under the terms of this License.
151 |
152 | b) Give prominent notice with the combined library that part of it
153 | is a work based on the Library, and explaining where to find the
154 | accompanying uncombined form of the same work.
155 |
156 | 6. Revised Versions of the GNU Lesser General Public License.
157 |
158 | The Free Software Foundation may publish revised and/or new versions
159 | of the GNU Lesser General Public License from time to time. Such new
160 | versions will be similar in spirit to the present version, but may
161 | differ in detail to address new problems or concerns.
162 |
163 | Each version is given a distinguishing version number. If the
164 | Library as you received it specifies that a certain numbered version
165 | of the GNU Lesser General Public License "or any later version"
166 | applies to it, you have the option of following the terms and
167 | conditions either of that published version or of any later version
168 | published by the Free Software Foundation. If the Library as you
169 | received it does not specify a version number of the GNU Lesser
170 | General Public License, you may choose any version of the GNU Lesser
171 | General Public License ever published by the Free Software Foundation.
172 |
173 | If the Library as you received it specifies that a proxy can decide
174 | whether future versions of the GNU Lesser General Public License shall
175 | apply, that proxy's public statement of acceptance of any version is
176 | permanent authorization for you to choose that version for the
177 | Library.
178 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # bitcoin-blockchain-parser  [](https://coveralls.io/github/alecalve/python-bitcoin-blockchain-parser?branch=master)
2 | This Python 3 library provides a parser for the raw data stored by bitcoind.
3 |
4 | ## Features
5 | - Detects outputs types
6 | - Detects addresses in outputs
7 | - Interprets scripts
8 | - Supports SegWit and Taproot
9 | - Supports ordered block parsing
10 |
11 | ## Installing
12 |
13 | Whether installing using Pip or from source, plyvel requires leveldb development libraries for LevelDB >1.2.X.
14 |
15 | On Linux, install libleveldb-dev
16 |
17 | ```
18 | sudo apt-get install libleveldb-dev
19 | ```
20 |
21 | ### Using pip
22 |
23 | ```
24 | pip install blockchain-parser
25 | ```
26 |
27 | ### Using source
28 |
29 | Requirements : python-bitcoinlib, plyvel, coverage for tests
30 |
31 |
32 | Install dependencies contained in `requirements.txt`:
33 | ```
34 | pip install -r requirements.txt
35 | ```
36 |
37 | Then, just run
38 | ```
39 | python setup.py install
40 | ```
41 |
42 | ## Developing
43 |
44 | First, setup a virtualenv and install dependencies:
45 |
46 | ```
47 | virtualenv -p python3 .venv
48 | source .venv/bin/activate
49 | pip install -r requirements.txt
50 | ```
51 |
52 | Run the test suite by lauching
53 | ```
54 | pytest
55 | ```
56 |
57 | ## Examples
58 |
59 | Below are two basic examples for parsing the blockchain. More examples are available in the examples directory.
60 |
61 | ### Unordered Blocks
62 |
63 | This blockchain parser parses raw blocks saved in Bitcoin Core's `.blk` file format. Bitcoin Core does not guarantee that these blocks are saved in order. If your application does not require that blocks are parsed in order, the `Blockchain.get_unordered_blocks(...)` method can be used:
64 |
65 | ```python
66 | import os
67 | from blockchain_parser.blockchain import Blockchain
68 |
69 | # Instantiate the Blockchain by giving the path to the directory
70 | # containing the .blk files created by bitcoind
71 | blockchain = Blockchain(os.path.expanduser('~/.bitcoin/blocks'))
72 | for block in blockchain.get_unordered_blocks():
73 | for tx in block.transactions:
74 | for no, output in enumerate(tx.outputs):
75 | print("tx=%s outputno=%d type=%s value=%s" % (tx.hash, no, output.type, output.value))
76 | ```
77 |
78 | ### Ordered Blocks
79 |
80 | If maintaining block order is necessary for your application, you should use the `Blockchain.get_ordered_blocks(...)` method. This method uses Bitcoin Core's LevelDB index to locate ordered block data in it's `.blk` files.
81 |
82 | ```python
83 | import os
84 | from blockchain_parser.blockchain import Blockchain
85 |
86 | # To get the blocks ordered by height, you need to provide the path of the
87 | # `index` directory (LevelDB index) being maintained by bitcoind. It contains
88 | # .ldb files and is present inside the `blocks` directory.
89 | blockchain = Blockchain(os.path.expanduser('~/.bitcoin/blocks'))
90 | for block in blockchain.get_ordered_blocks(os.path.expanduser('~/.bitcoin/blocks/index'), end=1000):
91 | print("height=%d block=%s" % (block.height, block.hash))
92 | ```
93 |
94 | Blocks can be iterated in reverse by specifying a start parameter that is greater than the end parameter.
95 |
96 | ```python
97 | for block in blockchain.get_ordered_blocks(os.path.expanduser('~/.bitcoin/blocks/index'), start=510000, end=0):
98 | print("height=%d block=%s" % (block.height, block.hash))
99 | ```
100 |
101 | Building the LevelDB index can take a while which can make iterative development and debugging challenging. For this reason, `Blockchain.get_ordered_blocks(...)` supports caching the LevelDB index database using [pickle](https://docs.python.org/3.6/library/pickle.html). To use a cache simply pass `cache=filename` to the ordered blocks method. If the cached file does not exist it will be created for faster parsing the next time the method is run. If the cached file already exists it will be used instead of re-parsing the LevelDB database.
102 |
103 | ```python
104 | for block in blockchain.get_ordered_blocks(os.path.expanduser('~/.bitcoin/blocks/index'), cache='index-cache.pickle'):
105 | print("height=%d block=%s" % (block.height, block.hash))
106 | ```
107 |
108 | **NOTE**: You must manually/programmatically delete the cache file in order to rebuild the cache. Don't forget to do this each time you would like to re-parse the blockchain with a higher block height than the first time you saved the cache file as the new blocks will not be included in the cache.
109 |
110 |
--------------------------------------------------------------------------------
/blockchain_parser/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2015-2016 The bitcoin-blockchain-parser developers
2 | #
3 | # This file is part of bitcoin-blockchain-parser.
4 | #
5 | # It is subject to the license terms in the LICENSE file found in the top-level
6 | # directory of this distribution.
7 | #
8 | # No part of bitcoin-blockchain-parser, including this file, may be copied,
9 | # modified, propagated, or distributed except according to the terms contained
10 | # in the LICENSE file.
11 |
12 | __version__ = "0.1.6"
13 |
--------------------------------------------------------------------------------
/blockchain_parser/address.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2015-2016 The bitcoin-blockchain-parser developers
2 | #
3 | # This file is part of bitcoin-blockchain-parser.
4 | #
5 | # It is subject to the license terms in the LICENSE file found in the top-level
6 | # directory of this distribution.
7 | #
8 | # No part of bitcoin-blockchain-parser, including this file, may be copied,
9 | # modified, propagated, or distributed except according to the terms contained
10 | # in the LICENSE file.
11 |
12 | from bitcoin import base58
13 | from bitcoin.bech32 import CBech32Data
14 | from .utils import btc_ripemd160, double_sha256
15 | from .utils_taproot import from_taproot
16 | from binascii import b2a_hex
17 |
18 |
19 | class Address(object):
20 | """Represents a bitcoin address"""
21 |
22 | def __init__(self, hash, public_key, address, type, segwit_version):
23 | self._hash = hash
24 | self.public_key = public_key
25 | self._address = address
26 | self.type = type
27 | self._segwit_version = segwit_version
28 |
29 | def __repr__(self):
30 | return "Address(addr=%s)" % self.address
31 |
32 | @classmethod
33 | def from_public_key(cls, public_key):
34 | """Constructs an Address object from a public key"""
35 | return cls(None, public_key, None, "normal", None)
36 |
37 | @classmethod
38 | def from_ripemd160(cls, hash, type="normal"):
39 | """Constructs an Address object from a RIPEMD-160 hash, it may be a
40 | normal address or a P2SH address, the latter is indicated by setting
41 | type to 'p2sh'"""
42 | return cls(hash, None, None, type, None)
43 |
44 | @classmethod
45 | def from_bech32(cls, hash, segwit_version):
46 | """Constructs an Address object from a bech32 hash."""
47 | return cls(hash, None, None, "bech32", segwit_version)
48 |
49 | @classmethod
50 | def from_bech32m(cls, hash, segwit_version):
51 | """Constructs an Address object from a bech32m script."""
52 | return cls(hash, None, None, "bech32m", segwit_version)
53 |
54 | @property
55 | def hash(self):
56 | """Returns the RIPEMD-160 hash corresponding to this address"""
57 | if self.public_key is not None and self._hash is None:
58 | self._hash = btc_ripemd160(self.public_key)
59 | return self._hash
60 |
61 | @property
62 | def address(self):
63 | """Returns the encoded representation of this address.
64 | If Taproot, it's encoded using bech32m,
65 | if SegWit, it's encoded using bech32,
66 | otherwise using base58
67 | """
68 | if self._address is None:
69 | if self.type == "bech32m":
70 | tweaked_pubkey = b2a_hex(self.hash).decode("ascii")
71 | self._address = from_taproot(tweaked_pubkey)
72 | elif self.type != "bech32":
73 | version = b'\x00' if self.type == "normal" else b'\x05'
74 | checksum = double_sha256(version + self.hash)
75 |
76 | self._address = base58.encode(version + self.hash + checksum[:4])
77 | else:
78 | bech_encoded = CBech32Data.from_bytes(self._segwit_version, self._hash)
79 | self._address = str(bech_encoded)
80 | return self._address
81 |
82 | def is_p2sh(self):
83 | return self.type == "p2sh"
84 |
--------------------------------------------------------------------------------
/blockchain_parser/block.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2015-2016 The bitcoin-blockchain-parser developers
2 | #
3 | # This file is part of bitcoin-blockchain-parser.
4 | #
5 | # It is subject to the license terms in the LICENSE file found in the top-level
6 | # directory of this distribution.
7 | #
8 | # No part of bitcoin-blockchain-parser, including this file, may be copied,
9 | # modified, propagated, or distributed except according to the terms contained
10 | # in the LICENSE file.
11 |
12 | from .transaction import Transaction
13 | from .block_header import BlockHeader
14 | from .utils import format_hash, decode_compactsize, double_sha256
15 |
16 |
17 | def get_block_transactions(raw_hex):
18 | """Given the raw hexadecimal representation of a block,
19 | yields the block's transactions
20 | """
21 | # Skipping the header
22 | transaction_data = raw_hex[80:]
23 |
24 | # Decoding the number of transactions, offset is the size of
25 | # the varint (1 to 9 bytes)
26 | n_transactions, offset = decode_compactsize(transaction_data)
27 |
28 | for i in range(n_transactions):
29 | # Try from 1024 (1KiB) -> 1073741824 (1GiB) slice widths
30 | for j in range(0, 20):
31 | try:
32 | offset_e = offset + (1024 * 2 ** j)
33 | transaction = Transaction.from_hex(
34 | transaction_data[offset:offset_e])
35 | yield transaction
36 | break
37 | except:
38 | continue
39 |
40 | # Skipping to the next transaction
41 | offset += transaction.size
42 |
43 |
44 | class Block(object):
45 | """
46 | Represents a Bitcoin block, contains its header and its transactions.
47 | """
48 |
49 | def __init__(self, raw_hex, height=None, blk_file=None):
50 | self.hex = raw_hex
51 | self._hash = None
52 | self._transactions = None
53 | self._header = None
54 | self._n_transactions = None
55 | self.size = len(raw_hex)
56 | self.height = height
57 | self.blk_file = blk_file
58 |
59 | def __repr__(self):
60 | return "Block(%s)" % self.hash
61 |
62 | @classmethod
63 | def from_hex(cls, raw_hex):
64 | """Builds a block object from its bytes representation"""
65 | return cls(raw_hex)
66 |
67 | @property
68 | def hash(self):
69 | """Returns the block's hash (double sha256 of its 80 bytes header"""
70 | if self._hash is None:
71 | self._hash = format_hash(double_sha256(self.hex[:80]))
72 | return self._hash
73 |
74 | @property
75 | def n_transactions(self):
76 | """Return the number of transactions contained in this block,
77 | it is faster to use this than to use len(block.transactions)
78 | as there's no need to parse all transactions to get this information
79 | """
80 | if self._n_transactions is None:
81 | self._n_transactions = decode_compactsize(self.hex[80:])[0]
82 |
83 | return self._n_transactions
84 |
85 | @property
86 | def transactions(self):
87 | """Returns a list of the block's transactions represented
88 | as Transaction objects"""
89 | if self._transactions is None:
90 | self._transactions = list(get_block_transactions(self.hex))
91 |
92 | return self._transactions
93 |
94 | @property
95 | def header(self):
96 | """Returns a BlockHeader object corresponding to this block"""
97 | if self._header is None:
98 | self._header = BlockHeader.from_hex(self.hex[:80])
99 | return self._header
100 |
--------------------------------------------------------------------------------
/blockchain_parser/block_header.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2015-2016 The bitcoin-blockchain-parser developers
2 | #
3 | # This file is part of bitcoin-blockchain-parser.
4 | #
5 | # It is subject to the license terms in the LICENSE file found in the top-level
6 | # directory of this distribution.
7 | #
8 | # No part of bitcoin-blockchain-parser, including this file, may be copied,
9 | # modified, propagated, or distributed except according to the terms contained
10 | # in the LICENSE file.
11 |
12 | from datetime import datetime
13 | from bitcoin.core import CBlockHeader
14 |
15 | from .utils import decode_uint32, format_hash
16 |
17 |
18 | class BlockHeader(object):
19 | """Represents a block header"""
20 |
21 | def __init__(self, raw_hex):
22 | self._version = None
23 | self._previous_block_hash = None
24 | self._merkle_root = None
25 | self._timestamp = None
26 | self._bits = None
27 | self._nonce = None
28 | self._difficulty = None
29 |
30 | self.hex = raw_hex[:80]
31 |
32 | def __repr__(self):
33 | return "BlockHeader(previous_block_hash=%s)" % self.previous_block_hash
34 |
35 | @classmethod
36 | def from_hex(cls, raw_hex):
37 | """Builds a BlockHeader object from its bytes representation"""
38 | return cls(raw_hex)
39 |
40 | @property
41 | def version(self):
42 | """Return the block's version"""
43 | if self._version is None:
44 | self._version = decode_uint32(self.hex[:4])
45 | return self._version
46 |
47 | @property
48 | def previous_block_hash(self):
49 | """Return the hash of the previous block"""
50 | if self._previous_block_hash is None:
51 | self._previous_block_hash = format_hash(self.hex[4:36])
52 | return self._previous_block_hash
53 |
54 | @property
55 | def merkle_root(self):
56 | """Returns the block's merkle root"""
57 | if self._merkle_root is None:
58 | self._merkle_root = format_hash(self.hex[36:68])
59 | return self._merkle_root
60 |
61 | @property
62 | def timestamp(self):
63 | """Returns the timestamp of the block as a UTC datetime object"""
64 | if self._timestamp is None:
65 | self._timestamp = datetime.utcfromtimestamp(
66 | decode_uint32(self.hex[68:72])
67 | )
68 | return self._timestamp
69 |
70 | @property
71 | def bits(self):
72 | """Returns the bits (difficulty target) of the block"""
73 | if self._bits is None:
74 | self._bits = decode_uint32(self.hex[72:76])
75 | return self._bits
76 |
77 | @property
78 | def nonce(self):
79 | """Returns the block's nonce"""
80 | if self._nonce is None:
81 | self._nonce = decode_uint32(self.hex[76:80])
82 | return self._nonce
83 |
84 | @property
85 | def difficulty(self):
86 | """Returns the block's difficulty target as a float"""
87 | if self._difficulty is None:
88 | self._difficulty = CBlockHeader.calc_difficulty(self.bits)
89 |
90 | return self._difficulty
91 |
--------------------------------------------------------------------------------
/blockchain_parser/blockchain.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2015-2016 The bitcoin-blockchain-parser developers
2 | #
3 | # This file is part of bitcoin-blockchain-parser.
4 | #
5 | # It is subject to the license terms in the LICENSE file found in the top-level
6 | # directory of this distribution.
7 | #
8 | # No part of bitcoin-blockchain-parser, including this file, may be copied,
9 | # modified, propagated, or distributed except according to the terms contained
10 | # in the LICENSE file.
11 |
12 | import os
13 | import mmap
14 | import struct
15 | import pickle
16 | import stat
17 | import plyvel
18 |
19 | from blockchain_parser.transaction import Transaction
20 | from blockchain_parser.index import DBTransactionIndex
21 | from blockchain_parser import utils
22 | from binascii import unhexlify
23 | from binascii import hexlify
24 | from .block import Block
25 | from .index import DBBlockIndex
26 | from .utils import format_hash
27 | from .block_header import BlockHeader
28 |
29 |
30 | # Constant separating blocks in the .blk files
31 | BITCOIN_CONSTANT = b"\xf9\xbe\xb4\xd9"
32 |
33 |
34 | def get_files(path):
35 | """
36 | Given the path to the .bitcoin directory, returns the sorted list of .blk
37 | files contained in that directory
38 | """
39 | if not stat.S_ISDIR(os.stat(path)[stat.ST_MODE]):
40 | return [path]
41 | files = os.listdir(path)
42 | files = [f for f in files if f.startswith("blk") and f.endswith(".dat")]
43 | files = map(lambda x: os.path.join(path, x), files)
44 | return sorted(files)
45 |
46 | def get_undo_files(path):
47 | """
48 | Given the path to the .bitcoin directory, returns the sorted list of rev*.dat
49 | files contained in that directory
50 | """
51 | if not stat.S_ISDIR(os.stat(path)[stat.ST_MODE]):
52 | return [path]
53 | files = os.listdir(path)
54 | files = [f for f in files if f.startswith("rev") and f.endswith(".dat")]
55 | files = map(lambda x: os.path.join(path, x), files)
56 | return sorted(files)
57 |
58 |
59 | def get_blocks(blockfile):
60 | """
61 | Given the name of a .dat file, for every block contained in the file,
62 | yields its raw hexadecimal value
63 | """
64 | with open(blockfile, "rb") as f:
65 | if os.name == 'nt':
66 | size = os.path.getsize(f.name)
67 | raw_data = mmap.mmap(f.fileno(), size, access=mmap.ACCESS_READ)
68 | else:
69 | # Unix-only call, will not work on Windows, see python doc.
70 | raw_data = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ)
71 | length = len(raw_data)
72 | offset = 0
73 | block_count = 0
74 | while offset < (length - 4):
75 | if raw_data[offset:offset+4] == BITCOIN_CONSTANT:
76 | offset += 4
77 | size = struct.unpack(" -1:
186 | # if this block is the same height as the last block an orphan
187 | # occurred, now we have to figure out which of the two to keep
188 | if blockIdx.height == last_height:
189 |
190 | # loop through future blocks until we find a chain 6 blocks
191 | # long that includes this block. If we can't find one
192 | # remove this block as it is invalid
193 | if self._index_confirmed(blockIndexes[i:]):
194 |
195 | # if this block is confirmed, the unconfirmed block is
196 | # the previous one. Remove it.
197 | stale_blocks.append(blockIndexes[i - 1].hash)
198 | else:
199 |
200 | # if this block isn't confirmed, remove it.
201 | stale_blocks.append(blockIndexes[i].hash)
202 |
203 | last_height = blockIdx.height
204 |
205 | # filter out stale blocks, so we are left only with block indexes
206 | # that have been confirmed
207 | # (or are new enough that they haven't yet been confirmed)
208 | blockIndexes = list(filter(lambda block: block.hash not in stale_blocks, blockIndexes))
209 |
210 | if end is None:
211 | end = len(blockIndexes)
212 |
213 | if end < start:
214 | blockIndexes = list(reversed(blockIndexes))
215 | start = len(blockIndexes) - start
216 | end = len(blockIndexes) - end
217 |
218 | for blkIdx in blockIndexes[start:end]:
219 | if blkIdx.file == -1 or blkIdx.data_pos == -1:
220 | break
221 | blkFile = os.path.join(self.path, "blk%05d.dat" % blkIdx.file)
222 | yield Block(get_block(blkFile, blkIdx.data_pos), blkIdx.height)
223 |
224 | def get_transaction(self, txid, db):
225 | """Yields the transaction contained in the .blk files as a python
226 | object, similar to
227 | https://developer.bitcoin.org/reference/rpc/getrawtransaction.html
228 | """
229 |
230 | byte_arr = bytearray.fromhex(txid)
231 | byte_arr.reverse()
232 | tx_hash = hexlify(b't').decode('utf-8') + \
233 | hexlify(byte_arr).decode('utf-8')
234 |
235 | tx_hash_fmtd = unhexlify(tx_hash)
236 | raw_hex = db.get(tx_hash_fmtd)
237 |
238 | tx_idx = DBTransactionIndex(utils.format_hash(tx_hash_fmtd), raw_hex)
239 | blk_file = os.path.join(self.path, "blk%05d.dat" % tx_idx.blockfile_no)
240 | raw_hex = get_block(blk_file, tx_idx.file_offset)
241 |
242 | offset = tx_idx.block_offset
243 |
244 | transaction_data = raw_hex[80:]
245 | block_header_data = raw_hex[:80]
246 | # Try from 1024 (1KiB) -> 1073741824 (1GiB) slice widths
247 | for j in range(0, 20):
248 | try:
249 | block_header = BlockHeader.from_hex(block_header_data)
250 | offset_e = offset + (1024 * 2 ** j)
251 | transaction = Transaction.from_hex(
252 | transaction_data[offset:offset_e])
253 | return [block_header, transaction]
254 | except Exception:
255 | continue
256 |
257 | return None
258 |
--------------------------------------------------------------------------------
/blockchain_parser/index.py:
--------------------------------------------------------------------------------
1 | from struct import unpack
2 |
3 | from .utils import format_hash
4 |
5 | BLOCK_HAVE_DATA = 8
6 | BLOCK_HAVE_UNDO = 16
7 |
8 |
9 | def _read_varint(raw_hex):
10 | """
11 | Reads the weird format of VarInt present in src/serialize.h of bitcoin core
12 | and being used for storing data in the leveldb.
13 | This is not the VARINT format described for general bitcoin serialization
14 | use.
15 | """
16 | n = 0
17 | pos = 0
18 | while True:
19 | data = raw_hex[pos]
20 | pos += 1
21 | n = (n << 7) | (data & 0x7f)
22 | if data & 0x80 == 0:
23 | return n, pos
24 | n += 1
25 |
26 |
27 | class DBBlockIndex(object):
28 | def __init__(self, blk_hash, raw_hex):
29 | self.hash = blk_hash
30 | pos = 0
31 | n_version, i = _read_varint(raw_hex[pos:])
32 | pos += i
33 | self.height, i = _read_varint(raw_hex[pos:])
34 | pos += i
35 | self.status, i = _read_varint(raw_hex[pos:])
36 | pos += i
37 | self.n_tx, i = _read_varint(raw_hex[pos:])
38 | pos += i
39 | if self.status & (BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO):
40 | self.file, i = _read_varint(raw_hex[pos:])
41 | pos += i
42 | else:
43 | self.file = -1
44 |
45 | if self.status & BLOCK_HAVE_DATA:
46 | self.data_pos, i = _read_varint(raw_hex[pos:])
47 | pos += i
48 | else:
49 | self.data_pos = -1
50 | if self.status & BLOCK_HAVE_UNDO:
51 | self.undo_pos, i = _read_varint(raw_hex[pos:])
52 | pos += i
53 |
54 | assert (pos + 80 == len(raw_hex))
55 | self.version, p, m, time, bits, self.nonce = unpack(
56 | " 1 and type(self.operations[1]) == bytes:
113 | taproot = from_taproot(b2a_hex(self.operations[1]).decode("ascii"))
114 | return self.operations[0] == 1 \
115 | and isinstance(taproot, str) \
116 | and taproot.startswith("bc1p")
117 |
118 | def is_pubkey(self):
119 | return len(self.operations) == 2 \
120 | and self.operations[-1] == OP_CHECKSIG \
121 | and is_public_key(self.operations[0])
122 |
123 | def is_pubkeyhash(self):
124 | return len(self.hex) == 25 \
125 | and self.operations[0] == OP_DUP \
126 | and self.operations[1] == OP_HASH160 \
127 | and self.operations[-2] == OP_EQUALVERIFY \
128 | and self.operations[-1] == OP_CHECKSIG
129 |
130 | def is_multisig(self):
131 | if len(self.operations) < 4:
132 | return False
133 | m = self.operations[0]
134 |
135 | if not isinstance(m, int):
136 | return False
137 |
138 | for i in range(m):
139 | if not is_public_key(self.operations[1+i]):
140 | return False
141 |
142 | n = self.operations[-2]
143 | if not isinstance(n, int) or n < m \
144 | or self.operations[-1] != OP_CHECKMULTISIG:
145 | return False
146 |
147 | return True
148 |
149 | def is_unknown(self):
150 | return not self.is_pubkeyhash() and not self.is_pubkey() \
151 | and not self.is_p2sh() and not self.is_multisig() \
152 | and not self.is_return() and not self.is_p2wpkh() \
153 | and not self.is_p2wsh() and not self.is_p2tr()
154 |
--------------------------------------------------------------------------------
/blockchain_parser/tests/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2015-2016 The bitcoin-blockchain-parser developers
2 | #
3 | # This file is part of bitcoin-blockchain-parser.
4 | #
5 | # It is subject to the license terms in the LICENSE file found in the top-level
6 | # directory of this distribution.
7 | #
8 | # No part of bitcoin-blockchain-parser, including this file, may be copied,
9 | # modified, propagated, or distributed except according to the terms contained
10 | # in the LICENSE file.
11 |
--------------------------------------------------------------------------------
/blockchain_parser/tests/data/bech32_p2wpkh.txt:
--------------------------------------------------------------------------------
1 | 020000000001016dee1ae581bf13134aa6aea8051b974c064bc2227094dc971561aac44a02ec500000000000ffffffff023cad17000000000017a914682ca28dc5e71f0139ad95a71c639c9e61c9c82e8773a33a0400000000160014a89e7f54db49b31364169f3e8982fcb5d3275e8b024830450221009424d2dcee3e8a00261cd3df8c7a103b14a8f0853075118329a21856f9cef36802207b9a2d51347bd2f73359375260e29fb67c67fa87825a625362a16b4a3c38f2350121030731907d43214f178bd4c9e279ac22043858001e220a935fff94f5fd3e8606a300000000
--------------------------------------------------------------------------------
/blockchain_parser/tests/data/bech32_p2wsh.txt:
--------------------------------------------------------------------------------
1 | 020000000001019425b8169d86af46b96afb6d8e1f40af44fedbb27fab196a88d1ae95b23230fd0200000000000000000260c084000000000017a914a0d015cec889e4cce8b359c6d607346e3985d66587fca67410000000002200206f49e7412b232a3d02c199caff40e9246d935a64799c369db577ecfd0929113804004730440220706ee05f17d6dee8ad42f3bf714a7e4911fed31325bad04284bc4ed7f3261e6b02203d72b089ee893164b203db722517a5bde37c791a3a256976c0379c73ad9f30110147304402207e36fcea59efb1b371dcabbe6766fb6334a498fe4769b263c00c7774df74b7f502200aea2ec5bab47ac16ed67cfc709665cfdcbd4f48d187a6a043267fe96789e1ce0169522102820ade490833f7c3e1abf944caed54b43f88f255f781a393ac4a6eedf04ff4862103c31b3287a9d6820a67c00af26c4955957ddf5fd21312c0ac8f11021b3f6192af2103ae579b44326ac0ba725038a982e3873f338c107b9d169172ca49e5bc7178469153ae00000000
--------------------------------------------------------------------------------
/blockchain_parser/tests/data/bip69_false.txt:
--------------------------------------------------------------------------------
1 | 0100000011aad553bb1650007e9982a8ac79d227cd8c831e1573b11f25573a37664e5f3e64000000006a47304402205438cedd30ee828b0938a863e08d810526123746c1f4abee5b7bc2312373450c02207f26914f4275f8f0040ab3375bacc8c5d610c095db8ed0785de5dc57456591a601210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffc26f3eb7932f7acddc5ddd26602b77e7516079b03090a16e2c2f5485d1fde028000000006b483045022100f81d98c1de9bb61063a5e6671d191b400fda3a07d886e663799760393405439d0220234303c9af4bad3d665f00277fe70cdd26cd56679f114a40d9107249d29c979401210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff456a9e597129f5df2e11b842833fc19a94c563f57449281d3cd01249a830a1f0000000006a47304402202310b00924794ef68a8f09564fd0bb128838c66bc45d1a3f95c5cab52680f166022039fc99138c29f6c434012b14aca651b1c02d97324d6bd9dd0ffced0782c7e3bd01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff571fb3e02278217852dd5d299947e2b7354a639adc32ec1fa7b82cfb5dec530e000000006b483045022100d276251f1f4479d8521269ec8b1b45c6f0e779fcf1658ec627689fa8a55a9ca50220212a1e307e6182479818c543e1b47d62e4fc3ce6cc7fc78183c7071d245839df01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff5d8de50362ff33d3526ac3602e9ee25c1a349def086a7fc1d9941aaeb9e91d38010000006b4830450221008768eeb1240451c127b88d89047dd387d13357ce5496726fc7813edc6acd55ac022015187451c3fb66629af38fdb061dfb39899244b15c45e4a7ccc31064a059730d01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff60ad3408b89ea19caf3abd5e74e7a084344987c64b1563af52242e9d2a8320f3000000006b4830450221009be4261ec050ebf33fa3d47248c7086e4c247cafbb100ea7cee4aa81cd1383f5022008a70d6402b153560096c849d7da6fe61c771a60e41ff457aac30673ceceafee01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffe9b483a8ac4129780c88d1babe41e89dc10a26dedbf14f80a28474e9a11104de010000006b4830450221009bc40eee321b39b5dc26883f79cd1f5a226fc6eed9e79e21d828f4c23190c57e022078182fd6086e265589105023d9efa4cba83f38c674a499481bd54eee196b033f01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffe28db9462d3004e21e765e03a45ecb147f136a20ba8bca78ba60ebfc8e2f8b3b000000006a47304402200fb572b7c6916515452e370c2b6f97fcae54abe0793d804a5a53e419983fae1602205191984b6928bf4a1e25b00e5b5569a0ce1ecb82db2dea75fe4378673b53b9e801210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff7a1ef65ff1b7b7740c662ab6c9735ace4a16279c23a1db5709ed652918ffff54010000006a47304402206bc218a925f7280d615c8ea4f0131a9f26e7fc64cff6eeeb44edb88aba14f1910220779d5d67231bc2d2d93c3c5ab74dcd193dd3d04023e58709ad7ffbf95161be6201210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff850cecf958468ca7ffa6a490afe13b8c271b1326b0ddc1fdfdf9f3c7e365fdba000000006a473044022047df98cc26bd2bfdc5b2b97c27aead78a214810ff023e721339292d5ce50823d02205fe99dc5f667908974dae40cc7a9475af7fa6671ba44f64a00fcd01fa12ab523012102ca46fa75454650afba1784bc7b079d687e808634411e4beff1f70e44596308a1ffffffff8640e312040e476cf6727c60ca3f4a3ad51623500aacdda96e7728dbdd99e8a5000000006a47304402205566aa84d3d84226d5ab93e6f253b57b3ef37eb09bb73441dae35de86271352a02206ee0b7f800f73695a2073a2967c9ad99e19f6ddf18ce877adf822e408ba9291e01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff91c1889c5c24b93b56e643121f7a05a34c10c5495c450504c7b5afcb37e11d7a000000006b483045022100df61d45bbaa4571cdd6c5c822cba458cdc55285cdf7ba9cd5bb9fc18096deb9102201caf8c771204df7fd7c920c4489da7bc3a60e1d23c1a97e237c63afe53250b4a01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff2470947216eb81ea0eeeb4fe19362ec05767db01c3aa3006bb499e8b6d6eaa26010000006a473044022031501a0b2846b8822a32b9947b058d89d32fc758e009fc2130c2e5effc925af70220574ef3c9e350cef726c75114f0701fd8b188c6ec5f84adce0ed5c393828a5ae001210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff0abcd77d65cc14363f8262898335f184d6da5ad060ff9e40bf201741022c2b40010000006b483045022100a6ac110802b699f9a2bff0eea252d32e3d572b19214d49d8bb7405efa2af28f1022033b7563eb595f6d7ed7ec01734e17b505214fe0851352ed9c3c8120d53268e9a01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffa43bebbebf07452a893a95bfea1d5db338d23579be172fe803dce02eeb7c037d010000006b483045022100ebc77ed0f11d15fe630fe533dc350c2ddc1c81cfeb81d5a27d0587163f58a28c02200983b2a32a1014bab633bfc9258083ac282b79566b6b3fa45c1e6758610444f401210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffb102113fa46ce949616d9cda00f6b10231336b3928eaaac6bfe42d1bf3561d6c010000006a473044022010f8731929a55c1c49610722e965635529ed895b2292d781b183d465799906b20220098359adcbc669cd4b294cc129b110fe035d2f76517248f4b7129f3bf793d07f01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffffb861fab2cde188499758346be46b5fbec635addfc4e7b0c8a07c0a908f2b11b4000000006a47304402207328142bb02ef5d6496a210300f4aea71f67683b842fa3df32cae6c88b49a9bb022020f56ddff5042260cfda2c9f39b7dec858cc2f4a76a987cd2dc25945b04e15fe01210391064d5b2d1c70f264969046fcff853a7e2bfde5d121d38dc5ebd7bc37c2b210ffffffff027064d817000000001976a9144a5fba237213a062f6f57978f796390bdcf8d01588ac00902f50090000001976a9145be32612930b8323add2212a4ec03c1562084f8488ac00000000
2 |
--------------------------------------------------------------------------------
/blockchain_parser/tests/data/bip69_true.txt:
--------------------------------------------------------------------------------
1 | 010000000255605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d28350000000049483045022100aa46504baa86df8a33b1192b1b9367b4d729dc41e389f2c04f3e5c7f0559aae702205e82253a54bf5c4f65b7428551554b2045167d6d206dfe6a2e198127d3f7df1501ffffffff55605dc6f5c3dc148b6da58442b0b2cd422be385eab2ebea4119ee9c268d2835010000004847304402202329484c35fa9d6bb32a55a70c0982f606ce0e3634b69006138683bcd12cbb6602200c28feb1e2555c3210f1dddb299738b4ff8bbe9667b68cb8764b5ac17b7adf0001ffffffff0200e1f505000000004341046a0765b5865641ce08dd39690aade26dfbf5511430ca428a3089261361cef170e3929a68aee3d8d4848b0c5111b0a37b82b86ad559fd2a745b44d8e8d9dfdc0cac00180d8f000000004341044a656f065871a353f216ca26cef8dde2f03e8c16202d2e8ad769f02032cb86a5eb5e56842e92e19141d60a01928f8dd2c875a390f67c1f6c94cfc617c0ea45afac00000000
2 |
--------------------------------------------------------------------------------
/blockchain_parser/tests/data/genesis_block.txt:
--------------------------------------------------------------------------------
1 | 0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000
--------------------------------------------------------------------------------
/blockchain_parser/tests/data/invalid_tx.txt:
--------------------------------------------------------------------------------
1 | 010000000b04a4abee8360f7a660bcf1c298496d927b6ad1f25dd12485e6a572e275cbd43f000000008b483045022100c6d59290df934bac08bc4124b06154ee21394d37ec958d145129a885dbd63eab0220036251120e4ceacc8e9ff13585049945c7dbdb85921a6e077a80c7e410931f4a014104f3ce8948c592690cb36a7e6e31534927569f41e45c77fbdb95fdb0e235717a295b2a281c04619139c453dd9139e951086325b4021bd28da6fc3da86943cd04cfffffffff21fd91e6dd64838eadce9bc0d687af571779c3af4b53bada845485466decf005000000008b48304502205d309b632a816f1f2479b8ca284301fce3fc8f979306722bba4cf28c8fa7ed76022100a3313ee59bbf0e61e29302b2ea597a993a73e93be841f2f7dd3f6e9623572793014104ca3b25750307a483c0cff8efb1413c98b4e18765485d94eac147f21f507169997a72aeb50f0b01ed6b468c9554143125f63c0b2b229a597e695718b86d711607ffffffff4dd6c46e3e68fc05243b6f894e2f3e4d2782d1c8a3d17b9a2c5e78912bbaf433010000008b483045022100f3f1d6c0e76a00e89a5278fa14eae8de93454174199f72c98aef54dabb2074d10220172dc0d6958efa8dc18c621f2d56fd0484ecb04e3d65e2284268916c1c324ade014104daeab6af397baf06fcc91b6c04a28ffb2cd80312e954a2a55f7c864094608032ef657e8296dfc2f86ecf28fe32925c0f648d3ef16b96934a34b29dd871ff057affffffff52aabc7fdee0588d63fff3fc00e20b00a87cffa6a37aad7168741b1f0e77bdff000000008b4830450220274a5a9577f0786fb17d373b6d1995a6cff752320bf4e3d5ee41b15fdd86ef2a022100fd1d0c0f43d05e479430c3c9bd5481f5be929a7f2b82d14292ef192653c3b94c014104988828ea7144ff00def9988e88aad98b1f22d01000fc4b52d1003c9b32de0534cff71c0a045098bb28f699bcc9db13533c0998eb2133085d37cac65b5bd74c1fffffffff5aab5581c575a56a803ef5d8698dc34014261d196ada1da3300731380d7e672d000000008b48304502201615a116a2e057f3f8f2a63340d0387b820a6f9d1067758f4f6cdec76c04951a022100a1ada9e03a0d9603743d41347ffd992642544d6dfdc51121b7bf622e12cb1547014104830b3b139b0a8707f348840b0fa358164869fbc0c423a2785cb0a18ff73517f8878cefd643e86e682eb93d07216f76993c9e980592de07c507c3a2659efff6aaffffffff74d7cd370a334aabee3b8328b67a63f1768583405e73ebd291dd86189983a61c000000008a4730440220432ff0f90a3f5372a8dc4b3d2e7165e110efa16b40784ea4d5ab866b736712ed02201a759b630d562f4dbc4a7d2fca406ad54e235564346bc360dec26d6e72bb60fa014104ca0bb72303672885ad953279309694c71574d50d2c6e1f5f1e7ab1b0bf3df67e5439b3f6e2501dda539812e414991ebaa547631374ce2bda9dd7211099886827ffffffff8941eb7fe688ff48357981c4756906e331c07f332429be5b2cfc7a945644f80f000000008a47304402204e074678b525927a8f459d1dc93ad738ce02c0ea77c50a749647032eda662eee02203c1a98e9b116dc2fe9e7325a5696ad8e3ad26d2d10503e6e3209f4f5f40f6e34014104f9baa86b46b04f2a2dc4270903bcca9444026b7f184d879f089462e332f260f6de8e05f08285a4a3c716220d9ec0f7e9fde354c779f32f00dd4d4cd8d4a1ac28ffffffff948be2e03f9537a35319e8cee2b7b9ed2769ca59734e5d4b42951ef7ba3bb4a8000000008b483045022100fcf1deaaa047e8f1b8b8ac783760ea5a0af15adbb9eed1c1748942a57510e27402204c278b12c4991480dc714af66395f5d8f42e411423bf27b024f62267c45a0d37014104cfd128c3f322b59c21d452d5c4c7b4d1a3237a2d31c6f4033b32db78c19cb342ffc151acc272a02a13ba985a523b655689d8118f502b8a7fb8df5fc7bd8458ceffffffffb2cae04cf238b4d47c614da5ce9948f089a02f1ff3e22cda6b9f9196bda87215010000008b483045022017a74373ef19756e275f2d987925a8bd22cfaf1c93eede7db63102dc73b7cfd0022100fd2f040d7d3ccd1a2188923f8c7560253aa2a910d8e9cd06a8a05fc69c12f6ce014104daeab6af397baf06fcc91b6c04a28ffb2cd80312e954a2a55f7c864094608032ef657e8296dfc2f86ecf28fe32925c0f648d3ef16b96934a34b29dd871ff057affffffffbe75c9f1faf79fb885018e3353ada4a7fb5c989bc26f031c5aee69d1eabad957000000008a4730440220360d35b0a98dd086d242f8976aa704b808541ad78ef5cba45e4eb73c1efbaaa7022025a3d981b9e0836a2493698b802bf7f0eae72de0252c7eac059ad14b1b27001b014104d969f0d456196f4c16945bc71fc460615002d1554fc8d774288aa8fc5c9eca1622a3dcb1e6a8314c94844e516c0543316b2e75d465c8fc2f0d0d20334e53f7dbffffffffbcbad95cac8bbe08538f7b12c240e96866358dfa819de6e940bb60ce7fae5c3d000000008b483045022100d916990888d0d26246b8d6cb944b1e8ebd6a4cdb2965ff7e4430883eef02c4e102205693b96aa1d6e9335f3cb2986b7f3a03357959e989570d03a56ccdb18a8d7e6d0141040840a410ed7afdc3e3fe3923ae388413bbd8a9089062403e0adce62212bccfe1f80400530121cfdba5b52f3e16397f1959b14a09089778afd5ff41f8a856980fffffffff02005039278c0400001976a914f1c87a5e8ff7d14e74b858089bf771c94b1b6db488ac00203d88792d00001976a914dcdf2f9892bfa1cb086530354eab3ba078a2f0
--------------------------------------------------------------------------------
/blockchain_parser/tests/data/large_tx.txt:
--------------------------------------------------------------------------------
1 | 010000000b04a4abee8360f7a660bcf1c298496d927b6ad1f25dd12485e6a572e275cbd43f000000008b483045022100c6d59290df934bac08bc4124b06154ee21394d37ec958d145129a885dbd63eab0220036251120e4ceacc8e9ff13585049945c7dbdb85921a6e077a80c7e410931f4a014104f3ce8948c592690cb36a7e6e31534927569f41e45c77fbdb95fdb0e235717a295b2a281c04619139c453dd9139e951086325b4021bd28da6fc3da86943cd04cfffffffff21fd91e6dd64838eadce9bc0d687af571779c3af4b53bada845485466decf005000000008b48304502205d309b632a816f1f2479b8ca284301fce3fc8f979306722bba4cf28c8fa7ed76022100a3313ee59bbf0e61e29302b2ea597a993a73e93be841f2f7dd3f6e9623572793014104ca3b25750307a483c0cff8efb1413c98b4e18765485d94eac147f21f507169997a72aeb50f0b01ed6b468c9554143125f63c0b2b229a597e695718b86d711607ffffffff4dd6c46e3e68fc05243b6f894e2f3e4d2782d1c8a3d17b9a2c5e78912bbaf433010000008b483045022100f3f1d6c0e76a00e89a5278fa14eae8de93454174199f72c98aef54dabb2074d10220172dc0d6958efa8dc18c621f2d56fd0484ecb04e3d65e2284268916c1c324ade014104daeab6af397baf06fcc91b6c04a28ffb2cd80312e954a2a55f7c864094608032ef657e8296dfc2f86ecf28fe32925c0f648d3ef16b96934a34b29dd871ff057affffffff52aabc7fdee0588d63fff3fc00e20b00a87cffa6a37aad7168741b1f0e77bdff000000008b4830450220274a5a9577f0786fb17d373b6d1995a6cff752320bf4e3d5ee41b15fdd86ef2a022100fd1d0c0f43d05e479430c3c9bd5481f5be929a7f2b82d14292ef192653c3b94c014104988828ea7144ff00def9988e88aad98b1f22d01000fc4b52d1003c9b32de0534cff71c0a045098bb28f699bcc9db13533c0998eb2133085d37cac65b5bd74c1fffffffff5aab5581c575a56a803ef5d8698dc34014261d196ada1da3300731380d7e672d000000008b48304502201615a116a2e057f3f8f2a63340d0387b820a6f9d1067758f4f6cdec76c04951a022100a1ada9e03a0d9603743d41347ffd992642544d6dfdc51121b7bf622e12cb1547014104830b3b139b0a8707f348840b0fa358164869fbc0c423a2785cb0a18ff73517f8878cefd643e86e682eb93d07216f76993c9e980592de07c507c3a2659efff6aaffffffff74d7cd370a334aabee3b8328b67a63f1768583405e73ebd291dd86189983a61c000000008a4730440220432ff0f90a3f5372a8dc4b3d2e7165e110efa16b40784ea4d5ab866b736712ed02201a759b630d562f4dbc4a7d2fca406ad54e235564346bc360dec26d6e72bb60fa014104ca0bb72303672885ad953279309694c71574d50d2c6e1f5f1e7ab1b0bf3df67e5439b3f6e2501dda539812e414991ebaa547631374ce2bda9dd7211099886827ffffffff8941eb7fe688ff48357981c4756906e331c07f332429be5b2cfc7a945644f80f000000008a47304402204e074678b525927a8f459d1dc93ad738ce02c0ea77c50a749647032eda662eee02203c1a98e9b116dc2fe9e7325a5696ad8e3ad26d2d10503e6e3209f4f5f40f6e34014104f9baa86b46b04f2a2dc4270903bcca9444026b7f184d879f089462e332f260f6de8e05f08285a4a3c716220d9ec0f7e9fde354c779f32f00dd4d4cd8d4a1ac28ffffffff948be2e03f9537a35319e8cee2b7b9ed2769ca59734e5d4b42951ef7ba3bb4a8000000008b483045022100fcf1deaaa047e8f1b8b8ac783760ea5a0af15adbb9eed1c1748942a57510e27402204c278b12c4991480dc714af66395f5d8f42e411423bf27b024f62267c45a0d37014104cfd128c3f322b59c21d452d5c4c7b4d1a3237a2d31c6f4033b32db78c19cb342ffc151acc272a02a13ba985a523b655689d8118f502b8a7fb8df5fc7bd8458ceffffffffb2cae04cf238b4d47c614da5ce9948f089a02f1ff3e22cda6b9f9196bda87215010000008b483045022017a74373ef19756e275f2d987925a8bd22cfaf1c93eede7db63102dc73b7cfd0022100fd2f040d7d3ccd1a2188923f8c7560253aa2a910d8e9cd06a8a05fc69c12f6ce014104daeab6af397baf06fcc91b6c04a28ffb2cd80312e954a2a55f7c864094608032ef657e8296dfc2f86ecf28fe32925c0f648d3ef16b96934a34b29dd871ff057affffffffbe75c9f1faf79fb885018e3353ada4a7fb5c989bc26f031c5aee69d1eabad957000000008a4730440220360d35b0a98dd086d242f8976aa704b808541ad78ef5cba45e4eb73c1efbaaa7022025a3d981b9e0836a2493698b802bf7f0eae72de0252c7eac059ad14b1b27001b014104d969f0d456196f4c16945bc71fc460615002d1554fc8d774288aa8fc5c9eca1622a3dcb1e6a8314c94844e516c0543316b2e75d465c8fc2f0d0d20334e53f7dbffffffffbcbad95cac8bbe08538f7b12c240e96866358dfa819de6e940bb60ce7fae5c3d000000008b483045022100d916990888d0d26246b8d6cb944b1e8ebd6a4cdb2965ff7e4430883eef02c4e102205693b96aa1d6e9335f3cb2986b7f3a03357959e989570d03a56ccdb18a8d7e6d0141040840a410ed7afdc3e3fe3923ae388413bbd8a9089062403e0adce62212bccfe1f80400530121cfdba5b52f3e16397f1959b14a09089778afd5ff41f8a856980fffffffff02005039278c0400001976a914f1c87a5e8ff7d14e74b858089bf771c94b1b6db488ac00203d88792d00001976a914dcdf2f9892bfa1cb086530354eab3ba078a2f09088ac00000000
--------------------------------------------------------------------------------
/blockchain_parser/tests/data/scripts_invalid.txt:
--------------------------------------------------------------------------------
1 | 01000000019ac03d5ae6a875d970128ef9086cef276a1919684a6988023cc7254691d97e6d010000006b4830450221009d41dc793ba24e65f571473d40b299b6459087cea1509f0d381740b1ac863cb6022039c425906fcaf51b2b84d8092569fb3213de43abaff2180e2a799d4fcb4dd0aa012102d5ede09a8ae667d0f855ef90325e27f6ce35bbe60a1e6e87af7f5b3c652140fdffffffff080100000000000000010101000000000000000202010100000000000000014c0100000000000000034c02010100000000000000014d0100000000000000044dffff010100000000000000014e0100000000000000064effffffff0100000000
--------------------------------------------------------------------------------
/blockchain_parser/tests/data/segwit.txt:
--------------------------------------------------------------------------------
1 | 010000000001019750f68b12fc617dc777d1420e1a34700d763f105303d7199348ff4b315da6b11300000000ffffffff0440164000000000001976a914ad62f43b9eba175ae6df7b3482a4fe6a3dad755588ac00c2eb0b000000001976a91415efba4572d98d9d19be7378e85fe6b2f5a111fb88ac80584f00000000001976a9147cce8012dd98ffa09129016d2fdedf8725b8c0ef88ac60a3a90c00000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d0400483045022100bc2ba8808127f8a74beed6dfa1b9fe54675c55aab85a61d7a74c15b993e67e5f02204dada4e15f0b4e659dae7bf0d0f648010d1f2b665f587a35eb6f22e44194952301483045022100f4c7ec7c2064fe2cc4389733ac0a57d8080a62180a004b02a19b89267113a17f022004ee9fdb081359c549ee42ffb58279363563eaf0191cd8b2b0ceebf62146b50b01695221022b003d276bce58bef509bdcd9cf7e156f0eae18e1175815282e65e7da788bb5b21035c58f2f60ecf38c9c8b9d1316b662627ec672f5fd912b1a2cc28d0b9b00575fd2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae00000000
--------------------------------------------------------------------------------
/blockchain_parser/tests/data/size_non_segwit.txt:
--------------------------------------------------------------------------------
1 | 010000000153dc411869f3b96f3dc48b6ac52438e895d6641142e0d4f6383fabc20e49e761010000006a4730440220177a4f6729b41ee6a06c2e4826b12313d9703e6fd3ee6cca347b4418cfe3a17b0220177ed399130c11f1b37a97f6e7982fc654f70139de0361eddc5d1cbf88d0c6e001210258764172106289665db078db8c6c3510e17e93caa0522d3ffca30a75562699b1ffffffff01ca06fc000000000017a914169e3bb06b5f0355e5085a8a1a5e430a3f6b39258700000000
2 |
--------------------------------------------------------------------------------
/blockchain_parser/tests/data/size_segwit.txt:
--------------------------------------------------------------------------------
1 | 01000000000101621adcd8ea06cc206490ad64f938e30a5fffa01d0d529a29140a858414cb9f7f0100000023220020be38f99e2cb9f29f7a6c34c4e83f35dcc8dae66becd64dcef6d5e5f147301fb7ffffffff02dc2a0400000000001976a9140d599e7368f1f44f5ede79adafa58079e789443f88ac0f9b18000000000017a914cc0a76a97b96703b66b3f0c8adf619e9c3e22fac87040047304402203fbb18de9b0770882f62e2f2d2cb0b6e0256bbee0f95df5083a09f74dbab9f57022053919922e9752c11320d9b76134c659f1588095d42aecda340da5e103399ecaa01483045022100885fc1e985fac55aa3c018d0fb328fc90a2d23bc673bc22155aa62d8fa8963ad02202875de558913de4fc72386a653f1c60ab2ef42be572955675b46467f5c7906b901475221020c13434b1a50bf18863dde6ef0303c7dd4942459512679595b97ecd9b199e0f72102f78ef4eba789c98b5c92e55d139232dbacfa0d5b2dbbd3e727d4b3774dcd79fe52ae00000000
2 |
--------------------------------------------------------------------------------
/blockchain_parser/tests/test_address.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2015-2016 The bitcoin-blockchain-parser developers
2 | #
3 | # This file is part of bitcoin-blockchain-parser.
4 | #
5 | # It is subject to the license terms in the LICENSE file found in the top-level
6 | # directory of this distribution.
7 | #
8 | # No part of bitcoin-blockchain-parser, including this file, may be copied,
9 | # modified, propagated, or distributed except according to the terms contained
10 | # in the LICENSE file.
11 |
12 | import unittest
13 | from binascii import a2b_hex
14 |
15 | from blockchain_parser.address import Address
16 |
17 |
18 | class TestUtils(unittest.TestCase):
19 | def test_from_public_key(self):
20 | public_key = "0450863AD64A87AE8A2FE83C1AF1A8403CB53F53E486D8511DAD8A" \
21 | "04887E5B23522CD470243453A299FA9E77237716103ABC11A1DF38" \
22 | "855ED6F2EE187E9C582BA6"
23 | hex_pk = a2b_hex(public_key)
24 | address = Address.from_public_key(hex_pk)
25 | self.assertEqual(address.address, "16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM")
26 | address_hash = "010966776006953D5567439E5E39F86A0D273BEE"
27 | self.assertEqual(address.hash, a2b_hex(address_hash))
28 |
29 | def test_from_ripemd160(self):
30 | ripemd160 = "010966776006953D5567439E5E39F86A0D273BEE"
31 | address = Address.from_ripemd160(a2b_hex(ripemd160))
32 | self.assertEqual(address.address, "16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM")
33 |
34 | def test_from_bech32(self):
35 | # Example sourced from https://en.bitcoin.it/wiki/Bech32
36 | bech32 = "751e76e8199196d454941c45d1b3a323f1433bd6"
37 | address = Address.from_bech32(a2b_hex(bech32), segwit_version=0)
38 | self.assertEqual(address.address, "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4")
39 |
40 | def test_from_bech32m(self):
41 | # https://blockstream.info/tx/33e794d097969002ee05d336686fc03c9e15a597c1b9827669460fac98799036?expand
42 | # Second output
43 | bech32m = "a37c3903c8d0db6512e2b40b0dffa05e5a3ab73603ce8c9c4b7771e5412328f9"
44 | address = Address.from_bech32m(a2b_hex(bech32m), segwit_version=1)
45 | self.assertEqual(address.address, "bc1p5d7rjq7g6rdk2yhzks9smlaqtedr4dekq08ge8ztwac72sfr9rusxg3297")
46 |
--------------------------------------------------------------------------------
/blockchain_parser/tests/test_block.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2015-2016 The bitcoin-blockchain-parser developers
2 | #
3 | # This file is part of bitcoin-blockchain-parser.
4 | #
5 | # It is subject to the license terms in the LICENSE file found in the top-level
6 | # directory of this distribution.
7 | #
8 | # No part of bitcoin-blockchain-parser, including this file, may be copied,
9 | # modified, propagated, or distributed except according to the terms contained
10 | # in the LICENSE file.
11 |
12 | import unittest
13 | from datetime import datetime
14 |
15 | from .utils import read_test_data
16 | from blockchain_parser.block import Block
17 |
18 |
19 | class TestBlock(unittest.TestCase):
20 | def test_from_hex(self):
21 | block_hex = read_test_data("genesis_block.txt")
22 | block = Block.from_hex(block_hex)
23 | self.assertEqual(1, block.n_transactions)
24 | block_hash = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1" \
25 | "b60a8ce26f"
26 | self.assertEqual(block_hash, block.hash)
27 | self.assertEqual(486604799, block.header.bits)
28 | merkle_root = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127" \
29 | "b7afdeda33b"
30 | self.assertEqual(merkle_root, block.header.merkle_root)
31 | self.assertEqual(2083236893, block.header.nonce)
32 | self.assertEqual(1, block.header.version)
33 | self.assertEqual(1, block.header.difficulty)
34 | self.assertEqual(285, block.size)
35 | self.assertEqual(datetime.utcfromtimestamp(1231006505),
36 | block.header.timestamp)
37 | self.assertEqual("0" * 64, block.header.previous_block_hash)
38 |
39 | for tx in block.transactions:
40 | self.assertEqual(1, tx.version)
41 | tx_hash = "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127" \
42 | "b7afdeda33b"
43 | self.assertEqual(tx_hash, tx.hash)
44 | self.assertEqual(204, tx.size)
45 | self.assertEqual(0, tx.locktime)
46 | self.assertEqual(0xffffffff, tx.inputs[0].transaction_index)
47 | self.assertEqual(0xffffffff, tx.inputs[0].sequence_number)
48 | self.assertTrue("ffff001d" in tx.inputs[0].script.value)
49 | self.assertEqual("0" * 64, tx.inputs[0].transaction_hash)
50 | self.assertEqual(50 * 100000000, tx.outputs[0].value)
51 |
--------------------------------------------------------------------------------
/blockchain_parser/tests/test_index.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from binascii import a2b_hex
3 |
4 | from blockchain_parser.index import DBBlockIndex
5 | from blockchain_parser.index import DBTransactionIndex
6 |
7 |
8 | class TestDBIndex(unittest.TestCase):
9 | def test_from_hex(self):
10 | key_str = "0000000000000000169cdec8dcfa2e408f59e0d50b1a228f65d8f548" \
11 | "0f990000"
12 | value_str = "88927193a7021d8160804aaa89fc0185b6e81e02000000fb759231" \
13 | "e1fa5f80c3508e3a59ebf301930257d04aa4920700000000000000" \
14 | "00c11c6bc67af8264be7979db45043f5f5c1e8d2060082af4ce795" \
15 | "7658a22147e30bf97f54747b1b187d1eac41"
16 |
17 | value_hex = a2b_hex(value_str)
18 | idx = DBBlockIndex(key_str, value_hex)
19 |
20 | self.assertEqual(idx.hash, "0000000000000000169cdec8dcfa2e408f59e0d50"
21 | "b1a228f65d8f5480f990000")
22 | self.assertEqual(idx.height, 332802)
23 | self.assertEqual(idx.status, 29)
24 | self.assertEqual(idx.n_tx, 352)
25 | self.assertEqual(idx.file, 202)
26 | self.assertEqual(idx.data_pos, 90357377)
27 | self.assertEqual(idx.undo_pos, 13497502)
28 | self.assertEqual(idx.version, 2)
29 | self.assertEqual(idx.nonce, 1101799037)
30 | self.assertEqual(idx.prev_hash, "00000000000000000792a44ad057029301f3e"
31 | "b593a8e50c3805ffae1319275fb")
32 | self.assertEqual(idx.merkle_root, "e34721a2587695e74caf820006d2e8c1f5f"
33 | "54350b49d97e74b26f87ac66b1cc1")
34 |
35 |
36 | class TestDBTransactionIndex(unittest.TestCase):
37 | def test_from_hex(self):
38 | key_str = "70ad7da56decc86b8a58ac53dbde792c9e97552cdaafd37312af7c4" \
39 | "d5c7d0cc1"
40 | value_str = "9071938b980ba4bf39"
41 |
42 | value_hex = a2b_hex(value_str)
43 | idx = DBTransactionIndex(key_str, value_hex)
44 |
45 | self.assertEqual(idx.hash, key_str)
46 | self.assertEqual(idx.blockfile_no, 2289)
47 | self.assertEqual(idx.block_offset, 614457)
48 | self.assertEqual(idx.file_offset, 42142859)
49 |
--------------------------------------------------------------------------------
/blockchain_parser/tests/test_output.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2015-2016 The bitcoin-blockchain-parser developers
2 | #
3 | # This file is part of bitcoin-blockchain-parser.
4 | #
5 | # It is subject to the license terms in the LICENSE file found in the top-level
6 | # directory of this distribution.
7 | #
8 | # No part of bitcoin-blockchain-parser, including this file, may be copied,
9 | # modified, propagated, or distributed except according to the terms contained
10 | # in the LICENSE file.
11 |
12 | import unittest
13 | from binascii import a2b_hex
14 |
15 | from blockchain_parser.output import Output
16 |
17 |
18 | class TestOutput(unittest.TestCase):
19 | def test_pubkeyhash_from_hex(self):
20 | raw_output = "01000000000000001976a91432ba382cf668657bae15ee0a97fa87" \
21 | "f12e1bc89f88ac"
22 | output = Output.from_hex(a2b_hex(raw_output))
23 | self.assertTrue(output.is_pubkeyhash())
24 | self.assertEqual("pubkeyhash", output.type)
25 | self.assertEqual(1, output.value)
26 | self.assertEqual(1, len(output.addresses))
27 |
28 | def test_pubkey_from_hex(self):
29 | raw_output = "0100000000000000232102c0993f639534d348e1dca30566491e6c" \
30 | "b11c14afa13ec244c05396a9839aeb17ac"
31 | output = Output.from_hex(a2b_hex(raw_output))
32 | self.assertTrue(output.is_pubkey())
33 | self.assertEqual("pubkey", output.type)
34 | self.assertEqual(1, len(output.addresses))
35 |
36 | def test_p2sh_from_hex(self):
37 | raw_output = "010000000000000017a91471c5c3727fac8dbace94bd38cf8ac16a" \
38 | "034a794787"
39 | output = Output.from_hex(a2b_hex(raw_output))
40 |
41 | self.assertTrue(output.is_p2sh())
42 | self.assertEqual("p2sh", output.type)
43 | self.assertEqual(1, len(output.addresses))
44 |
45 | def test_return_from_hex(self):
46 | raw_output = "01000000000000002a6a2846610000000024958857cc0da391b" \
47 | "7b2bf61bcba59bb9ee438873f902c25da4c079e53d0c55fe991"
48 | output = Output.from_hex(a2b_hex(raw_output))
49 | self.assertTrue(output.is_return())
50 | self.assertEqual("OP_RETURN", output.type)
51 | self.assertEqual(0, len(output.addresses))
52 |
53 | def test_multisig_from_hex(self):
54 | raw_output = "0100000000000000475121025cd452979d4d5e928d47c3581bb287" \
55 | "41b2cf9c54185e7d563a663707b00d956d2102ff99d00aa9d195b9" \
56 | "3732254def8bfe80a786a7973ef8e63afd8d2a65e97b6c3b52ae"
57 | output = Output.from_hex(a2b_hex(raw_output))
58 | self.assertTrue(output.is_multisig())
59 | self.assertEqual("multisig", output.type)
60 | self.assertEqual(2, len(output.addresses))
61 |
62 | def test_unknown_from_hex(self):
63 | raw_output = "01000000000000000151"
64 | output = Output.from_hex(a2b_hex(raw_output))
65 | self.assertEqual("unknown", output.type)
66 | self.assertEqual(0, len(output.addresses))
67 |
--------------------------------------------------------------------------------
/blockchain_parser/tests/test_script.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2015-2016 The bitcoin-blockchain-parser developers
2 | #
3 | # This file is part of bitcoin-blockchain-parser.
4 | #
5 | # It is subject to the license terms in the LICENSE file found in the top-level
6 | # directory of this distribution.
7 | #
8 | # No part of bitcoin-blockchain-parser, including this file, may be copied,
9 | # modified, propagated, or distributed except according to the terms contained
10 | # in the LICENSE file.
11 |
12 | import unittest
13 | from binascii import a2b_hex
14 | from blockchain_parser.script import Script
15 |
16 |
17 | class TestScript(unittest.TestCase):
18 | def test_op_return_script(self):
19 | case1 = "6a"
20 | script = Script.from_hex(a2b_hex(case1))
21 | self.assertEqual("OP_RETURN", script.value)
22 | self.assertFalse(script.is_pubkey())
23 | self.assertFalse(script.is_multisig())
24 | self.assertFalse(script.is_p2sh())
25 | self.assertFalse(script.is_p2wpkh())
26 | self.assertFalse(script.is_p2wsh())
27 | self.assertFalse(script.is_pubkeyhash())
28 | self.assertFalse(script.is_unknown())
29 | self.assertTrue(script.is_return())
30 | self.assertFalse(script.is_p2tr())
31 |
32 | def test_unknown_script(self):
33 | case = "40"
34 | script = Script.from_hex(a2b_hex(case))
35 | self.assertEqual("INVALID_SCRIPT", script.value)
36 | self.assertFalse(script.is_pubkey())
37 | self.assertFalse(script.is_multisig())
38 | self.assertFalse(script.is_p2sh())
39 | self.assertFalse(script.is_p2wpkh())
40 | self.assertFalse(script.is_p2wsh())
41 | self.assertFalse(script.is_pubkeyhash())
42 | self.assertTrue(script.is_unknown())
43 | self.assertFalse(script.is_return())
44 | self.assertFalse(script.is_p2tr())
45 |
46 | case = ""
47 | script = Script.from_hex(a2b_hex(case))
48 | self.assertEqual("", script.value)
49 | self.assertFalse(script.is_pubkey())
50 | self.assertFalse(script.is_multisig())
51 | self.assertFalse(script.is_p2sh())
52 | self.assertFalse(script.is_p2wpkh())
53 | self.assertFalse(script.is_p2wsh())
54 | self.assertFalse(script.is_pubkeyhash())
55 | self.assertTrue(script.is_unknown())
56 | self.assertFalse(script.is_return())
57 | self.assertFalse(script.is_p2tr())
58 |
59 | def test_multisig_script(self):
60 | case = "514104cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4410461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af52ae"
61 | script = Script.from_hex(a2b_hex(case))
62 | self.assertFalse(script.is_pubkey())
63 | self.assertTrue(script.is_multisig())
64 | self.assertFalse(script.is_p2sh())
65 | self.assertFalse(script.is_p2wpkh())
66 | self.assertFalse(script.is_p2wsh())
67 | self.assertFalse(script.is_pubkeyhash())
68 | self.assertFalse(script.is_unknown())
69 | self.assertFalse(script.is_return())
70 | self.assertFalse(script.is_p2tr())
71 |
72 | def test_p2sh_script(self):
73 | case = "a91428ad3e63dcae36e5010527578e2eef0e9eeaf3e487"
74 | script = Script.from_hex(a2b_hex(case))
75 | self.assertFalse(script.is_pubkey())
76 | self.assertFalse(script.is_multisig())
77 | self.assertTrue(script.is_p2sh())
78 | self.assertFalse(script.is_p2wpkh())
79 | self.assertFalse(script.is_p2wsh())
80 | self.assertFalse(script.is_pubkeyhash())
81 | self.assertFalse(script.is_unknown())
82 | self.assertFalse(script.is_return())
83 | self.assertFalse(script.is_p2tr())
84 |
85 | def test_p2wpkh_script(self):
86 | case = "0014c958269b5b6469b6e4b87de1062028ad3bb83cc2"
87 | script = Script.from_hex(a2b_hex(case))
88 | self.assertFalse(script.is_pubkey())
89 | self.assertFalse(script.is_multisig())
90 | self.assertFalse(script.is_p2sh())
91 | self.assertTrue(script.is_p2wpkh())
92 | self.assertFalse(script.is_p2wsh())
93 | self.assertFalse(script.is_pubkeyhash())
94 | self.assertFalse(script.is_unknown())
95 | self.assertFalse(script.is_return())
96 | self.assertFalse(script.is_p2tr())
97 |
98 | def test_p2wsh_script(self):
99 | case = "0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d"
100 | script = Script.from_hex(a2b_hex(case))
101 | self.assertFalse(script.is_p2sh())
102 | self.assertFalse(script.is_multisig())
103 | self.assertFalse(script.is_p2sh())
104 | self.assertFalse(script.is_p2wpkh())
105 | self.assertTrue(script.is_p2wsh())
106 | self.assertFalse(script.is_pubkeyhash())
107 | self.assertFalse(script.is_unknown())
108 | self.assertFalse(script.is_return())
109 | self.assertFalse(script.is_p2tr())
110 |
111 | def test_pubkeyhash_script(self):
112 | case = "76a914e9629ef6f5b82564a9b2ecae6c288c56fb33710888ac"
113 | script = Script.from_hex(a2b_hex(case))
114 | self.assertFalse(script.is_pubkey())
115 | self.assertFalse(script.is_multisig())
116 | self.assertFalse(script.is_p2sh())
117 | self.assertFalse(script.is_p2wpkh())
118 | self.assertFalse(script.is_p2wsh())
119 | self.assertTrue(script.is_pubkeyhash())
120 | self.assertFalse(script.is_unknown())
121 | self.assertFalse(script.is_return())
122 | self.assertFalse(script.is_p2tr())
123 |
124 | def test_pubkey_script(self):
125 | script = Script.from_hex(a2b_hex("4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac"))
126 | self.assertTrue(script.is_pubkey())
127 | self.assertFalse(script.is_multisig())
128 | self.assertFalse(script.is_p2sh())
129 | self.assertFalse(script.is_p2wpkh())
130 | self.assertFalse(script.is_p2wsh())
131 | self.assertFalse(script.is_pubkeyhash())
132 | self.assertFalse(script.is_unknown())
133 | self.assertFalse(script.is_return())
134 | self.assertFalse(script.is_p2tr())
135 |
136 | def test_taproot_script(self):
137 | # https://blockstream.info/tx/33e794d097969002ee05d336686fc03c9e15a597c1b9827669460fac98799036?expand
138 | # Second output
139 | script = Script.from_hex(a2b_hex("5120a37c3903c8d0db6512e2b40b0dffa05e5a3ab73603ce8c9c4b7771e5412328f9"))
140 | self.assertFalse(script.is_pubkey())
141 | self.assertFalse(script.is_multisig())
142 | self.assertFalse(script.is_p2sh())
143 | self.assertFalse(script.is_p2wpkh())
144 | self.assertFalse(script.is_p2wsh())
145 | self.assertFalse(script.is_pubkeyhash())
146 | self.assertFalse(script.is_unknown())
147 | self.assertFalse(script.is_return())
148 | self.assertTrue(script.is_p2tr())
149 |
--------------------------------------------------------------------------------
/blockchain_parser/tests/test_transaction.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2015-2016 The bitcoin-blockchain-parser developers
2 | #
3 | # This file is part of bitcoin-blockchain-parser.
4 | #
5 | # It is subject to the license terms in the LICENSE file found in the top-level
6 | # directory of this distribution.
7 | #
8 | # No part of bitcoin-blockchain-parser, including this file, may be copied,
9 | # modified, propagated, or distributed except according to the terms contained
10 | # in the LICENSE file.
11 |
12 | import os
13 | import unittest
14 | from binascii import a2b_hex, b2a_hex
15 | from blockchain_parser.transaction import Transaction
16 |
17 | from .utils import read_test_data
18 |
19 | class TestTransaction(unittest.TestCase):
20 | def test_rbf(self):
21 | data = a2b_hex("01000000019222bbb054bb9f94571dfe769af5866835f2a97e8839"
22 | "59fa757de4064bed8bca01000000035101b1000000000100000000"
23 | "00000000016a01000000")
24 | tx = Transaction(data)
25 | self.assertTrue(tx.uses_replace_by_fee())
26 |
27 | coinbase = a2b_hex("01000000010000000000000000000000000000000000000000"
28 | "000000000000000000000000ffffffff4203c8e405fabe6d6d"
29 | "98b0e98e3809941f1fd8cafe7c8236e27b8d1a776b1835aa54"
30 | "8bb84fe5b5f3d7010000000000000002650300aaa757eb0000"
31 | "002f736c7573682f0000000001baa98396000000001976a914"
32 | "7c154ed1dc59609e3d26abb2df2ea3d587cd8c4188ac000000"
33 | "00")
34 | tx = Transaction(coinbase)
35 | self.assertTrue(tx.is_coinbase())
36 | self.assertFalse(tx.uses_replace_by_fee())
37 |
38 | def test_bip69(self):
39 | non_compliant = read_test_data("bip69_false.txt")
40 | tx = Transaction(non_compliant)
41 | self.assertFalse(tx.uses_bip69())
42 |
43 | compliant = read_test_data("bip69_true.txt")
44 | tx = Transaction(compliant)
45 | self.assertTrue(tx.uses_bip69())
46 |
47 | def test_bech32_p2wpkh(self):
48 | tx = Transaction(read_test_data("bech32_p2wpkh.txt"))
49 | self.assertEqual(["3BBqfnaPbgi5KWECWdFpvryUfw7QatWy37"], [a.address for a in tx.outputs[0].addresses])
50 | self.assertEqual(["bc1q4z0874xmfxe3xeqknulgnqhukhfjwh5tvjrr2x"], [a.address for a in tx.outputs[1].addresses])
51 |
52 | def test_bech32_p2wsh(self):
53 | tx = Transaction(read_test_data("bech32_p2wsh.txt"))
54 | self.assertEqual(["3GMKKFPNUg13VktgihUD8QfXVQRBdoDNDf"], [a.address for a in tx.outputs[0].addresses])
55 | self.assertEqual(["bc1qday7wsftyv4r6qkpn8907s8fy3kexkny0xwrd8d4wlk06zffzyuqpp629n"], [a.address for a in tx.outputs[1].addresses])
56 |
57 | def test_segwit(self):
58 | tx = Transaction(read_test_data("segwit.txt"))
59 | self.assertTrue(tx.is_segwit)
60 | id = "22116f1d76ab425ddc6d10d184331e70e080dd6275d7aa90237ceb648dc38224"
61 | self.assertTrue(tx.txid == id)
62 | h = "1eac09f372a8c13bb7dea6bd66ee71a6bcc469b57b35c1e394ad7eb7c107c507"
63 | self.assertTrue(tx.hash == h)
64 |
65 | segwit_input = tx.inputs[0]
66 | self.assertTrue(len(segwit_input.witnesses) == 4)
67 | self.assertTrue(len(segwit_input.witnesses[0]) == 0)
68 |
69 | wit_1 = "3045022100bc2ba8808127f8a74beed6dfa1b9fe54675c55aab85a61d7" \
70 | "a74c15b993e67e5f02204dada4e15f0b4e659dae7bf0d0f648010d1f2b" \
71 | "665f587a35eb6f22e44194952301"
72 |
73 | wit_2 = "3045022100f4c7ec7c2064fe2cc4389733ac0a57d8080a62180a004b02" \
74 | "a19b89267113a17f022004ee9fdb081359c549ee42ffb58279363563ea" \
75 | "f0191cd8b2b0ceebf62146b50b01"
76 |
77 | wit_3 = "5221022b003d276bce58bef509bdcd9cf7e156f0eae18e1175815282e6" \
78 | "5e7da788bb5b21035c58f2f60ecf38c9c8b9d1316b662627ec672f5fd9" \
79 | "12b1a2cc28d0b9b00575fd2103c96d495bfdd5ba4145e3e046fee45e84" \
80 | "a8a48ad05bd8dbb395c011a32cf9f88053ae"
81 |
82 | parsed_1 = b2a_hex(segwit_input.witnesses[1]).decode("utf-8")
83 | parsed_2 = b2a_hex(segwit_input.witnesses[2]).decode("utf-8")
84 | parsed_3 = b2a_hex(segwit_input.witnesses[3]).decode("utf-8")
85 |
86 | self.assertEqual(parsed_1, wit_1)
87 | self.assertEqual(parsed_2, wit_2)
88 | self.assertEqual(parsed_3, wit_3)
89 |
90 | def test_vsize(self):
91 | segwit_tx = Transaction(read_test_data("size_segwit.txt"))
92 | non_segwit_tx = Transaction(read_test_data("size_non_segwit.txt"))
93 |
94 | self.assertEqual(non_segwit_tx.vsize, non_segwit_tx.size)
95 | self.assertEqual(non_segwit_tx.vsize, 189)
96 |
97 | self.assertNotEqual(segwit_tx.vsize, segwit_tx.size)
98 | self.assertEqual(segwit_tx.vsize, 208)
99 | self.assertEqual(segwit_tx.size, 373)
100 |
101 | def test_large(self):
102 | data = read_test_data("large_tx.txt")
103 |
104 | tx = Transaction(data)
105 | self.assertTrue(
106 | tx.hash == "29a3efd3ef04f9153d47a990bd7b048a4b2d213daa"
107 | "a5fb8ed670fb85f13bdbcf")
108 | self.assertTrue(tx.size == len(tx.hex))
109 |
110 | def test_incomplete(self):
111 | data = read_test_data("invalid_tx.txt")
112 |
113 | self.assertRaises(Exception, Transaction, data)
114 |
115 | def test_unknown_scripts(self):
116 | data = read_test_data("scripts_invalid.txt")
117 | tx = Transaction(data)
118 |
119 | for output in tx.outputs:
120 | self.assertEqual([], output.addresses)
--------------------------------------------------------------------------------
/blockchain_parser/tests/test_utils.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2015-2016 The bitcoin-blockchain-parser developers
2 | #
3 | # This file is part of bitcoin-blockchain-parser.
4 | #
5 | # It is subject to the license terms in the LICENSE file found in the top-level
6 | # directory of this distribution.
7 | #
8 | # No part of bitcoin-blockchain-parser, including this file, may be copied,
9 | # modified, propagated, or distributed except according to the terms contained
10 | # in the LICENSE file.
11 |
12 | import unittest
13 | from binascii import b2a_hex, a2b_hex
14 |
15 | from blockchain_parser import utils
16 |
17 |
18 | class TestUtils(unittest.TestCase):
19 | def test_btc_ripemd160(self):
20 | data = a2b_hex("02218AD6CDC632E7AE7D04472374311CEBBBBF0AB540D2D08C3400BB844C654231")
21 | ripe = utils.btc_ripemd160(data)
22 | expected = b"5238c71458e464d9ff90299abca4a1d7b9cb76ab"
23 | self.assertEqual(b2a_hex(ripe), expected)
24 |
25 | def test_format_hash(self):
26 | data = a2b_hex("deadbeef")
27 | self.assertEqual(utils.format_hash(data), "efbeadde")
28 |
29 | def test_decode_uint32(self):
30 | uint32_dict = {
31 | "01000000": 1,
32 | "000000ff": 4278190080
33 | }
34 |
35 | for uint32, value in uint32_dict.items():
36 | self.assertEqual(utils.decode_uint32(a2b_hex(uint32)), value)
37 |
38 | def test_decode_uint64(self):
39 | uint64_dict = {
40 | "0100000000000000": 1,
41 | "00000000000000ff": 18374686479671623680
42 | }
43 |
44 | for uint64, value in uint64_dict.items():
45 | self.assertEqual(utils.decode_uint64(a2b_hex(uint64)), value)
46 |
47 | def test_decode_compactsize(self):
48 | case1 = a2b_hex("fa")
49 | self.assertEqual(utils.decode_compactsize(case1), (250, 1))
50 | case2 = a2b_hex("fd0100")
51 | self.assertEqual(utils.decode_compactsize(case2), (1, 3))
52 | case3 = a2b_hex("fe01000000")
53 | self.assertEqual(utils.decode_compactsize(case3), (1, 5))
54 | case4 = a2b_hex("ff0100000000000000")
55 | self.assertEqual(utils.decode_compactsize(case4), (1, 9))
56 |
--------------------------------------------------------------------------------
/blockchain_parser/tests/utils.py:
--------------------------------------------------------------------------------
1 | import os
2 | from binascii import a2b_hex
3 |
4 | dir_path = os.path.dirname(os.path.realpath(__file__))
5 |
6 |
7 | def read_test_data(filename):
8 | with open(os.path.join(dir_path, "data/", filename)) as f:
9 | return a2b_hex(f.read().strip())
--------------------------------------------------------------------------------
/blockchain_parser/transaction.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2015-2016 The bitcoin-blockchain-parser developers
2 | #
3 | # This file is part of bitcoin-blockchain-parser.
4 | #
5 | # It is subject to the license terms in the LICENSE file found in the top-level
6 | # directory of this distribution.
7 | #
8 | # No part of bitcoin-blockchain-parser, including this file, may be copied,
9 | # modified, propagated, or distributed except according to the terms contained
10 | # in the LICENSE file.
11 |
12 | from math import ceil
13 |
14 | from .utils import decode_compactsize, decode_uint32, double_sha256, format_hash
15 | from .input import Input
16 | from .output import Output
17 |
18 |
19 | def bip69_sort(data):
20 | return list(sorted(data, key=lambda t: (t[0], t[1])))
21 |
22 |
23 | class Transaction(object):
24 | """Represents a bitcoin transaction"""
25 |
26 | def __init__(self, raw_hex):
27 | self._hash = None
28 | self._txid = None
29 | self.inputs = None
30 | self.outputs = None
31 | self._version = None
32 | self._locktime = None
33 | self._size = None
34 | self.n_inputs = 0
35 | self.n_outputs = 0
36 | self.is_segwit = False
37 |
38 | offset = 4
39 |
40 | # adds basic support for segwit transactions
41 | # - https://bitcoincore.org/en/segwit_wallet_dev/
42 | # - https://en.bitcoin.it/wiki/Protocol_documentation#BlockTransactions
43 | if b'\x00\x01' == raw_hex[offset:offset + 2]:
44 | self.is_segwit = True
45 | offset += 2
46 |
47 | self.n_inputs, varint_size = decode_compactsize(raw_hex[offset:])
48 | offset += varint_size
49 |
50 | self.inputs = []
51 | for i in range(self.n_inputs):
52 | input = Input.from_hex(raw_hex[offset:])
53 | offset += input.size
54 | self.inputs.append(input)
55 |
56 | self.n_outputs, varint_size = decode_compactsize(raw_hex[offset:])
57 | offset += varint_size
58 |
59 | self.outputs = []
60 | for i in range(self.n_outputs):
61 | output = Output.from_hex(raw_hex[offset:])
62 | offset += output.size
63 | self.outputs.append(output)
64 |
65 | if self.is_segwit:
66 | self._offset_before_tx_witnesses = offset
67 | for inp in self.inputs:
68 | tx_witnesses_n, varint_size = decode_compactsize(raw_hex[offset:])
69 | offset += varint_size
70 | for j in range(tx_witnesses_n):
71 | component_length, varint_size = decode_compactsize(
72 | raw_hex[offset:])
73 | offset += varint_size
74 | witness = raw_hex[offset:offset + component_length]
75 | inp.add_witness(witness)
76 | offset += component_length
77 |
78 | self._size = offset + 4
79 | self.hex = raw_hex[:self._size]
80 |
81 | if self._size != len(self.hex):
82 | raise Exception("Incomplete transaction!")
83 |
84 | def __repr__(self):
85 | return "Transaction(%s)" % self.hash
86 |
87 | @classmethod
88 | def from_hex(cls, hex):
89 | return cls(hex)
90 |
91 | @property
92 | def version(self):
93 | """Returns the transaction's version number"""
94 | if self._version is None:
95 | self._version = decode_uint32(self.hex[:4])
96 | return self._version
97 |
98 | @property
99 | def locktime(self):
100 | """Returns the transaction's locktime as an int"""
101 | if self._locktime is None:
102 | self._locktime = decode_uint32(self.hex[-4:])
103 | return self._locktime
104 |
105 | @property
106 | def hash(self):
107 | """Returns the transaction's id. Equivalent to the hash for non SegWit transactions,
108 | it differs from it for SegWit ones. """
109 | if self._hash is None:
110 | self._hash = format_hash(double_sha256(self.hex))
111 |
112 | return self._hash
113 |
114 | @property
115 | def size(self):
116 | """Returns the transactions size in bytes including the size of the
117 | witness data if there is any."""
118 | return self._size
119 |
120 | @property
121 | def vsize(self):
122 | """Returns the transaction size in virtual bytes."""
123 | if not self.is_segwit:
124 | return self._size
125 | else:
126 | # the witness is the last element in a transaction before the
127 | # 4 byte locktime and self._offset_before_tx_witnesses is the
128 | # position where the witness starts
129 | witness_size = self._size - self._offset_before_tx_witnesses - 4
130 |
131 | # size of the transaction without the segwit marker (2 bytes) and
132 | # the witness
133 | stripped_size = self._size - (2 + witness_size)
134 | weight = stripped_size * 3 + self._size
135 |
136 | # vsize is weight / 4 rounded up
137 | return ceil(weight / 4)
138 |
139 | @property
140 | def txid(self):
141 | """Returns the transaction's id. Equivalent to the hash for non SegWit transactions,
142 | it differs from it for SegWit ones. """
143 | if self._txid is None:
144 | # segwit transactions have two transaction ids/hashes, txid and wtxid
145 | # txid is a hash of all of the legacy transaction fields only
146 | if self.is_segwit:
147 | txid_data = self.hex[:4] + self.hex[
148 | 6:self._offset_before_tx_witnesses] + self.hex[
149 | -4:]
150 | else:
151 | txid_data = self.hex
152 | self._txid = format_hash(double_sha256(txid_data))
153 |
154 | return self._txid
155 |
156 | def is_coinbase(self):
157 | """Returns whether the transaction is a coinbase transaction"""
158 | for input in self.inputs:
159 | if input.transaction_hash == "0" * 64:
160 | return True
161 | return False
162 |
163 | def uses_replace_by_fee(self):
164 | """Returns whether the transaction opted-in for RBF"""
165 | # Coinbase transactions may have a sequence number that signals RBF
166 | # but they cannot use it as it's only enforced for non-coinbase txs
167 | if self.is_coinbase():
168 | return False
169 |
170 | # A transactions opts-in for RBF when having an input
171 | # with a sequence number < MAX_INT - 1
172 | for input in self.inputs:
173 | if input.sequence_number < 4294967294:
174 | return True
175 | return False
176 |
177 | def uses_bip69(self):
178 | """Returns whether the transaction complies to BIP-69,
179 | lexicographical ordering of inputs and outputs"""
180 | # Quick check
181 | if self.n_inputs == 1 and self.n_outputs == 1:
182 | return True
183 |
184 | input_keys = [
185 | (i.transaction_hash, i.transaction_index)
186 | for i in self.inputs
187 | ]
188 |
189 | if bip69_sort(input_keys) != input_keys:
190 | return False
191 |
192 | output_keys = [(o.value, o.script.value) for o in self.outputs]
193 |
194 | return bip69_sort(output_keys) == output_keys
195 |
--------------------------------------------------------------------------------
/blockchain_parser/undo.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2015-2020 The bitcoin-blockchain-parser developers
2 | #
3 | # This file is part of bitcoin-blockchain-parser.
4 | #
5 | # It is subject to the license terms in the LICENSE file found in the top-level
6 | # directory of this distribution.
7 | #
8 | # No part of bitcoin-blockchain-parser, including this file, may be copied,
9 | # modified, propagated, or distributed except according to the terms contained
10 | # in the LICENSE file.
11 |
12 | from .utils import decode_varint, decode_compactsize, decompress_txout_amt
13 |
14 | def decompress_script(raw_hex):
15 | script_type = raw_hex[0]
16 | compressed_script = raw_hex[1:]
17 |
18 | # def decompress_script(compressed_script, script_type):
19 | """ Takes CScript as stored in leveldb and returns it in uncompressed form
20 | (de)compression scheme is defined in bitcoin/src/compressor.cpp
21 | :param compressed_script: raw script bytes hexlified (data in decode_utxo)
22 | :type compressed_script: str
23 | :param script_type: first byte of script data (out_type in decode_utxo)
24 | :type script_type: int
25 | :return: the decompressed CScript
26 | :rtype: str
27 | (this code adapted from https://github.com/sr-gi/bitcoin_tools)
28 | """
29 |
30 | if script_type == 0:
31 | if len(compressed_script) != 20:
32 | raise Exception("Compressed script has wrong size")
33 | script = OutputScript.P2PKH(compressed_script, hash160=True)
34 |
35 | elif script_type == 1:
36 | if len(compressed_script) != 20:
37 | raise Exception("Compressed script has wrong size")
38 | script = OutputScript.P2SH(compressed_script)
39 |
40 | elif script_type in [2, 3]:
41 | if len(compressed_script) != 33:
42 | raise Exception("Compressed script has wrong size")
43 | script = OutputScript.P2PK(compressed_script)
44 |
45 | elif script_type in [4, 5]:
46 | if len(compressed_script) != 33:
47 | raise Exception("Compressed script has wrong size")
48 | prefix = format(script_type - 2, '02')
49 | script = OutputScript.P2PK(get_uncompressed_pk(prefix + compressed_script[2:]))
50 |
51 | else:
52 | assert len(compressed_script) / 2 == script_type - NSPECIALSCRIPTS
53 | script = OutputScript.from_hex(compressed_script)
54 |
55 | return script.content
56 |
57 |
58 | class BlockUndo(object):
59 | """
60 | Represents a block of spent transaction outputs (coins), as encoded
61 | in the undo rev*.dat files
62 | """
63 | def __init__(self, raw_hex):
64 | self._raw_hex = raw_hex
65 | self.spends = []
66 | num_txs, pos = decode_compactsize(raw_hex)
67 | # print("found %d" % num_txs + " transactions")
68 | for i in range(num_txs):
69 | # print("calling SpentOutput with raw_hex %s", raw_hex)
70 | txn = SpentTransaction(raw_hex=raw_hex[pos:])
71 | self.spends.append(txn)
72 | # print("found transaction #%d length %d hex: " % (i, txn.len), raw_hex[pos:pos+txn.len].hex())
73 | pos += txn.len
74 |
75 |
76 | class SpentTransaction(object):
77 | """Represents the script portion of a spent Transaction output"""
78 | def __init__(self, raw_hex=None):
79 | self._raw_hex = raw_hex
80 | self.outputs = []
81 | # print("decoding compactsize for hex: ", raw_hex.hex())
82 | self.output_len, pos = decode_compactsize(raw_hex)
83 | # print("found %d" % self.output_len + " outputs")
84 | for i in range(self.output_len):
85 | output = SpentOutput(raw_hex=raw_hex[pos:])
86 | self.outputs.append(output)
87 | # print("found output #%d length %d hex: " % (i, output.len), raw_hex[pos:pos+output.len].hex())
88 | pos += output.len
89 | self.len = pos
90 |
91 | @classmethod
92 | def from_hex(cls, hex_):
93 | return cls(hex_)
94 |
95 |
96 | class SpentOutput(object):
97 | """Represents a spent Transaction output"""
98 |
99 | def __init__(self, raw_hex=None):
100 | # print("decoding output: ", raw_hex.hex())
101 | self._raw_hex = raw_hex
102 | pos = 0
103 | # self.version = raw_hex[pos]
104 | # pos += 1
105 |
106 | # decode height code
107 | height_code, height_code_len = decode_varint(raw_hex[pos:])
108 | # print("found height code : ", height_code, height_code_len)
109 | if height_code % 2 == 1:
110 | self.is_coinbase = True
111 | height_code -= 1
112 | else:
113 | self.is_coinbase = False
114 | self.height = height_code // 2
115 |
116 | # print("found height: ", self.height)
117 |
118 | # skip byte reserved only for backwards compatibility, should always be 0x00
119 | pos += height_code_len + 1
120 |
121 | # decode compressed txout amount
122 | compressed_amt, compressed_amt_len = decode_varint(raw_hex[pos:])
123 | self.amt = decompress_txout_amt(compressed_amt)
124 | pos += compressed_amt_len
125 |
126 | # get script
127 | script_hex, script_pub_key_compressed_len = SpentScriptPubKey.extract_from_hex(raw_hex[pos:])
128 | self.script_pub_key_compressed = SpentScriptPubKey(script_hex)
129 | self.len = pos + self.script_pub_key_compressed.len
130 |
131 | @classmethod
132 | def from_hex(cls, hex_):
133 | return cls(hex_)
134 |
135 |
136 | @property
137 | def script(self):
138 | if not self.script:
139 | self.script = decompress_script(self.script_pub_key_compressed)
140 | return self.script
141 |
142 |
143 |
144 | class SpentScriptPubKey(object):
145 | """Represents the script portion of a spent Transaction output"""
146 | def __init__(self, raw_hex=None):
147 | self._raw_hex = raw_hex
148 | self.len = len(raw_hex)
149 | # self.script_hex = raw_hex[1:]
150 |
151 | @classmethod
152 | def from_hex(cls, hex_):
153 | return cls(hex_)
154 |
155 | @classmethod
156 | def extract_from_hex(cls, raw_hex):
157 | """
158 | docstring
159 | """
160 | if raw_hex[0] in (0x00, 0x01):
161 | return (raw_hex[:21], 21)
162 | elif raw_hex[0] in (0x02, 0x03):
163 | return (raw_hex[:33], 33)
164 | elif raw_hex[0] in (0x04, 0x05):
165 | # print("found strange script type: ", raw_hex[0])
166 | return (raw_hex[:33], 33)
167 | else:
168 | # print("found strange script type: ", raw_hex[0])
169 | # print("decoding compactsize for raw hex: ", raw_hex.hex())
170 | script_len_code, script_len_code_len = decode_varint(raw_hex)
171 | # print("script_len_code, script_len_code_len: (%s, %s)" % (script_len_code, script_len_code_len))
172 | real_script_len = script_len_code - 6
173 | # print("real_script_len: %d" % real_script_len)
174 | return (raw_hex[:script_len_code_len+real_script_len], real_script_len)
175 |
176 | @property
177 | def script(self):
178 | if not self.script:
179 | self.script = decompress_script(self._raw_hex)
180 | return self.script
181 |
--------------------------------------------------------------------------------
/blockchain_parser/utils.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2015-2016 The bitcoin-blockchain-parser developers
2 | #
3 | # This file is part of bitcoin-blockchain-parser.
4 | #
5 | # It is subject to the license terms in the LICENSE file found in the top-level
6 | # directory of this distribution.
7 | #
8 | # No part of bitcoin-blockchain-parser, including this file, may be copied,
9 | # modified, propagated, or distributed except according to the terms contained
10 | # in the LICENSE file.
11 |
12 | import hashlib
13 | import struct
14 |
15 | from ripemd import ripemd160
16 |
17 | def btc_ripemd160(data):
18 | """Computes ripemd160(sha256(data))"""
19 |
20 | h1 = hashlib.sha256(data).digest()
21 | r160 = ripemd160.new()
22 | r160.update(h1)
23 | return r160.digest()
24 |
25 |
26 | def double_sha256(data):
27 | return hashlib.sha256(hashlib.sha256(data).digest()).digest()
28 |
29 |
30 | def format_hash(hash_):
31 | return hash_[::-1].hex()
32 |
33 |
34 | def decode_uint32(data):
35 | assert(len(data) == 4)
36 | return struct.unpack(" 0)
46 | size = int(data[0])
47 | assert(size <= 255)
48 |
49 | if size < 253:
50 | return size, 1
51 |
52 | if size == 253:
53 | format_ = '> 25
35 | chk = (chk & 0x1ffffff) << 5 ^ value
36 | for i in range(5):
37 | chk ^= generator[i] if ((top >> i) & 1) else 0
38 | return chk
39 |
40 |
41 | def bech32_hrp_expand(hrp):
42 | """Expand the HRP into values for checksum computation."""
43 | return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp]
44 |
45 |
46 | def bech32_verify_checksum(hrp, data):
47 | """Verify a checksum given HRP and converted data characters."""
48 | const = bech32_polymod(bech32_hrp_expand(hrp) + data)
49 | if const == 1:
50 | return Encoding.BECH32
51 | if const == BECH32M_CONST:
52 | return Encoding.BECH32M
53 | return None
54 |
55 |
56 | def bech32_create_checksum(hrp, data, spec):
57 | """Compute the checksum values given HRP and data."""
58 | values = bech32_hrp_expand(hrp) + data
59 | const = BECH32M_CONST if spec == Encoding.BECH32M else 1
60 | polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ const
61 | return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)]
62 |
63 |
64 | def bech32_encode(hrp, data, spec):
65 | """Compute a Bech32 string given HRP and data values."""
66 | combined = data + bech32_create_checksum(hrp, data, spec)
67 | return hrp + '1' + ''.join([CHARSET[d] for d in combined])
68 |
69 |
70 | def bech32_decode(bech):
71 | """Validate a Bech32/Bech32m string, and determine HRP and data."""
72 | if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or
73 | (bech.lower() != bech and bech.upper() != bech)):
74 | return (None, None, None)
75 | bech = bech.lower()
76 | pos = bech.rfind('1')
77 | if pos < 1 or pos + 7 > len(bech) or len(bech) > 90:
78 | return (None, None, None)
79 | if not all(x in CHARSET for x in bech[pos+1:]):
80 | return (None, None, None)
81 | hrp = bech[:pos]
82 | data = [CHARSET.find(x) for x in bech[pos+1:]]
83 | spec = bech32_verify_checksum(hrp, data)
84 | if spec is None:
85 | return (None, None, None)
86 | return (hrp, data[:-6], spec)
87 |
88 |
89 | def convertbits(data, frombits, tobits, pad=True):
90 | """General power-of-2 base conversion."""
91 | acc = 0
92 | bits = 0
93 | ret = []
94 | maxv = (1 << tobits) - 1
95 | max_acc = (1 << (frombits + tobits - 1)) - 1
96 | for value in data:
97 | if value < 0 or (value >> frombits):
98 | return None
99 | acc = ((acc << frombits) | value) & max_acc
100 | bits += frombits
101 | while bits >= tobits:
102 | bits -= tobits
103 | ret.append((acc >> bits) & maxv)
104 | if pad:
105 | if bits:
106 | ret.append((acc << (tobits - bits)) & maxv)
107 | elif bits >= frombits or ((acc << (tobits - bits)) & maxv):
108 | return None
109 | return ret
110 |
111 |
112 | def decode(hrp, addr):
113 | """Decode a segwit address."""
114 | hrpgot, data, spec = bech32_decode(addr)
115 | if hrpgot != hrp:
116 | return (None, None)
117 | decoded = convertbits(data[1:], 5, 8, False)
118 | if decoded is None or len(decoded) < 2 or len(decoded) > 40:
119 | return (None, None)
120 | if data[0] > 16:
121 | return (None, None)
122 | if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32:
123 | return (None, None)
124 | if data[0] == 0 and spec != Encoding.BECH32 \
125 | or data[0] != 0 and spec != Encoding.BECH32M:
126 | return (None, None)
127 | return (data[0], decoded)
128 |
129 |
130 | def encode(witprog):
131 | hrp, witver = "bc", 1
132 | """Encode a segwit address."""
133 | spec = Encoding.BECH32M
134 | ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5), spec)
135 | if decode(hrp, ret) == (None, None):
136 | return None
137 | return ret
138 |
139 |
140 | def from_taproot(tpk):
141 | """Input Tweaked Public Key."""
142 | tpk = [int(tpk[i:i+2], 16) for i in range(0, len(tpk), 2)]
143 | return encode(tpk)
144 |
--------------------------------------------------------------------------------
/examples/non-standard-outputs.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2015-2016 The bitcoin-blockchain-parser developers
2 | #
3 | # This file is part of bitcoin-blockchain-parser.
4 | #
5 | # It is subject to the license terms in the LICENSE file found in the top-level
6 | # directory of this distribution.
7 | #
8 | # No part of bitcoin-blockchain-parser, including this file, may be copied,
9 | # modified, propagated, or distributed except according to the terms contained
10 | # in the LICENSE file.
11 |
12 | import sys
13 | from blockchain_parser.blockchain import Blockchain
14 |
15 |
16 | blockchain = Blockchain(sys.argv[1])
17 | for block in blockchain.get_unordered_blocks():
18 | for transaction in block.transactions:
19 | for output in transaction.outputs:
20 | if output.is_unknown():
21 | print(block.header.timestamp, output.script.value)
22 |
--------------------------------------------------------------------------------
/examples/ordered-blocks.py:
--------------------------------------------------------------------------------
1 | import sys
2 | sys.path.append('..')
3 | from blockchain_parser.blockchain import Blockchain
4 |
5 | # Instantiate the Blockchain by giving the path to the directory
6 | # containing the .blk files created by bitcoind
7 | blockchain = Blockchain(sys.argv[1])
8 |
9 | # To get the blocks ordered by height, you need to provide the path of the
10 | # `index` directory (LevelDB index) being maintained by bitcoind. It contains
11 | # .ldb files and is present inside the `blocks` directory
12 | for block in blockchain.get_ordered_blocks(sys.argv[1] + '/index', end=1000):
13 | print("height=%d block=%s" % (block.height, block.hash))
14 |
--------------------------------------------------------------------------------
/examples/texts-in-coinbases.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2015-2016 The bitcoin-blockchain-parser developers
2 | #
3 | # This file is part of bitcoin-blockchain-parser.
4 | #
5 | # It is subject to the license terms in the LICENSE file found in the top-level
6 | # directory of this distribution.
7 | #
8 | # No part of bitcoin-blockchain-parser, including this file, may be copied,
9 | # modified, propagated, or distributed except according to the terms contained
10 | # in the LICENSE file.
11 |
12 | import sys
13 | from blockchain_parser.blockchain import Blockchain
14 | from blockchain_parser.script import CScriptInvalidError
15 |
16 |
17 | def is_ascii_text(op):
18 | return all(32 <= x <= 127 for x in op)
19 |
20 |
21 | blockchain = Blockchain(sys.argv[1])
22 | for block in blockchain.get_unordered_blocks():
23 | for transaction in block.transactions:
24 | coinbase = transaction.inputs[0]
25 |
26 | # Some coinbase scripts are not valid scripts
27 | try:
28 | script_operations = coinbase.script.operations
29 | except CScriptInvalidError:
30 | break
31 |
32 | # An operation is a CScriptOP or pushed bytes
33 | for operation in script_operations:
34 | if type(operation) == bytes and len(operation) > 3 \
35 | and is_ascii_text(operation):
36 | print(block.header.timestamp, operation.decode("ascii"))
37 | break
38 |
--------------------------------------------------------------------------------
/examples/undo-blocks.py:
--------------------------------------------------------------------------------
1 | import os
2 | from blockchain_parser.blockchain import *
3 | from blockchain_parser.blockchain import Blockchain
4 | from blockchain_parser.utils import *
5 | from blockchain_parser.undo import *
6 |
7 | undo_files = get_undo_files(os.path.expanduser('~/.bitcoin/blocks'))
8 | undo_block_ctr = 0
9 | for i, file_name in enumerate(undo_files):
10 | print("parsing undo file #%d" % i)
11 | for j, block_raw in enumerate(get_blocks(file_name)):
12 | undo_block_ctr += 1
13 | if j % 1000 == 0 or (i == 1 and j > 9000):
14 | print("parsing undo block #%d in file #%d block #%d" % (undo_block_ctr, i, j))
15 | block_undo_current = BlockUndo(block_raw)
16 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | python-bitcoinlib==0.11.0
2 | plyvel==1.5.1
3 | ripemd-hash==1.0.1
4 | pytest==8.1.1
5 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 | from blockchain_parser import __version__
3 |
4 |
5 | setup(
6 | name='blockchain-parser',
7 | version=__version__,
8 | packages=find_packages(),
9 | url='https://github.com/alecalve/python-bitcoin-blockchain-parser',
10 | author='Antoine Le Calvez',
11 | author_email='antoine@p2sh.info',
12 | description='Bitcoin blockchain parser',
13 | long_description=open('README.md').read(),
14 | long_description_content_type='text/markdown',
15 | test_suite='blockchain_parser.tests',
16 | classifiers=[
17 | 'Development Status :: 5 - Production/Stable',
18 | 'Environment :: Console',
19 | 'Intended Audience :: Developers',
20 | 'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)',
21 | 'Topic :: Software Development :: Libraries',
22 | ],
23 | install_requires=[
24 | 'python-bitcoinlib==0.11.0',
25 | 'plyvel==1.5.1',
26 | 'ripemd-hash==1.0.1'
27 | ]
28 | )
29 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | #-*-mode: ini; encoding: utf-8-*-
2 |
3 | [tox] #-------------------------------------------------------------------
4 |
5 | envlist = reset,py27,py33,py34,py35,pypy,pypy3,stats
6 | skip_missing_interpreters = True
7 |
8 | [testenv] #---------------------------------------------------------------
9 |
10 | commands =
11 | coverage run --append --include='blockchain_parser/*' --omit='tests/*' setup.py test -q
12 |
13 | deps =
14 | coverage
15 |
16 | setenv =
17 | PYTHONWARNINGS = all
18 |
19 | [testenv:reset] #---------------------------------------------------------
20 |
21 | commands =
22 | coverage erase
23 |
24 | [testenv:stats] #---------------------------------------------------------
25 |
26 | commands =
27 | coverage report
28 | coverage html
29 |
--------------------------------------------------------------------------------