blockchain-parser-2 0.1.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. blockchain_parser/__init__.py +12 -0
  2. blockchain_parser/address.py +83 -0
  3. blockchain_parser/block.py +104 -0
  4. blockchain_parser/block_header.py +90 -0
  5. blockchain_parser/blockchain.py +302 -0
  6. blockchain_parser/index.py +81 -0
  7. blockchain_parser/input.py +78 -0
  8. blockchain_parser/output.py +145 -0
  9. blockchain_parser/script.py +164 -0
  10. blockchain_parser/tests/__init__.py +10 -0
  11. blockchain_parser/tests/data/bech32_p2wpkh.txt +1 -0
  12. blockchain_parser/tests/data/bech32_p2wsh.txt +1 -0
  13. blockchain_parser/tests/data/bip69_false.txt +1 -0
  14. blockchain_parser/tests/data/bip69_true.txt +1 -0
  15. blockchain_parser/tests/data/genesis_block.txt +1 -0
  16. blockchain_parser/tests/data/invalid_tx.txt +1 -0
  17. blockchain_parser/tests/data/large_tx.txt +1 -0
  18. blockchain_parser/tests/data/scripts_invalid.txt +1 -0
  19. blockchain_parser/tests/data/segwit.txt +1 -0
  20. blockchain_parser/tests/data/size_non_segwit.txt +1 -0
  21. blockchain_parser/tests/data/size_segwit.txt +1 -0
  22. blockchain_parser/tests/test_address.py +45 -0
  23. blockchain_parser/tests/test_block.py +55 -0
  24. blockchain_parser/tests/test_blockchain.py +79 -0
  25. blockchain_parser/tests/test_index.py +48 -0
  26. blockchain_parser/tests/test_output.py +66 -0
  27. blockchain_parser/tests/test_script.py +171 -0
  28. blockchain_parser/tests/test_transaction.py +138 -0
  29. blockchain_parser/tests/test_undo.py +70 -0
  30. blockchain_parser/tests/test_utils.py +55 -0
  31. blockchain_parser/tests/utils.py +9 -0
  32. blockchain_parser/transaction.py +196 -0
  33. blockchain_parser/undo.py +198 -0
  34. blockchain_parser/utils.py +152 -0
  35. blockchain_parser/utils_taproot.py +143 -0
  36. blockchain_parser_2-0.1.6.dist-info/METADATA +143 -0
  37. blockchain_parser_2-0.1.6.dist-info/RECORD +40 -0
  38. blockchain_parser_2-0.1.6.dist-info/WHEEL +5 -0
  39. blockchain_parser_2-0.1.6.dist-info/licenses/LICENSE +177 -0
  40. blockchain_parser_2-0.1.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,12 @@
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"
@@ -0,0 +1,83 @@
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"
@@ -0,0 +1,104 @@
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
+ transaction = None
30
+ # Try from 1024 (1KiB) -> 512MiB slice widths
31
+ for j in range(0, 20):
32
+ try:
33
+ offset_e = offset + (1024 * 2 ** j)
34
+ transaction = Transaction.from_hex(
35
+ transaction_data[offset:offset_e])
36
+ break
37
+ except Exception:
38
+ transaction = None
39
+ if transaction is None:
40
+ raise ValueError(
41
+ "failed to parse transaction %d at offset %d" % (i, offset)
42
+ )
43
+
44
+ yield transaction
45
+ # Skipping to the next transaction
46
+ offset += transaction.size
47
+
48
+
49
+ class Block(object):
50
+ """
51
+ Represents a Bitcoin block, contains its header and its transactions.
52
+ """
53
+
54
+ def __init__(self, raw_hex, height=None, blk_file=None):
55
+ self.hex = raw_hex
56
+ self._hash = None
57
+ self._transactions = None
58
+ self._header = None
59
+ self._n_transactions = None
60
+ self.size = len(raw_hex)
61
+ self.height = height
62
+ self.blk_file = blk_file
63
+
64
+ def __repr__(self):
65
+ return "Block(%s)" % self.hash
66
+
67
+ @classmethod
68
+ def from_hex(cls, raw_hex):
69
+ """Builds a block object from its bytes representation"""
70
+ return cls(raw_hex)
71
+
72
+ @property
73
+ def hash(self):
74
+ """Returns the block's hash (double sha256 of its 80 bytes header"""
75
+ if self._hash is None:
76
+ self._hash = format_hash(double_sha256(self.hex[:80]))
77
+ return self._hash
78
+
79
+ @property
80
+ def n_transactions(self):
81
+ """Return the number of transactions contained in this block,
82
+ it is faster to use this than to use len(block.transactions)
83
+ as there's no need to parse all transactions to get this information
84
+ """
85
+ if self._n_transactions is None:
86
+ self._n_transactions = decode_compactsize(self.hex[80:])[0]
87
+
88
+ return self._n_transactions
89
+
90
+ @property
91
+ def transactions(self):
92
+ """Returns a list of the block's transactions represented
93
+ as Transaction objects"""
94
+ if self._transactions is None:
95
+ self._transactions = list(get_block_transactions(self.hex))
96
+
97
+ return self._transactions
98
+
99
+ @property
100
+ def header(self):
101
+ """Returns a BlockHeader object corresponding to this block"""
102
+ if self._header is None:
103
+ self._header = BlockHeader.from_hex(self.hex[:80])
104
+ return self._header
@@ -0,0 +1,90 @@
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, timezone
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.fromtimestamp(
66
+ decode_uint32(self.hex[68:72]), timezone.utc
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
@@ -0,0 +1,302 @@
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
+
18
+ from blockchain_parser.transaction import Transaction
19
+ from blockchain_parser.index import DBTransactionIndex
20
+ from blockchain_parser import utils
21
+ from binascii import unhexlify
22
+ from binascii import hexlify
23
+ from .block import Block
24
+ from .index import DBBlockIndex
25
+ from .utils import format_hash
26
+ from .block_header import BlockHeader
27
+
28
+
29
+ # Constant separating blocks in the .blk files
30
+ BITCOIN_CONSTANT = b"\xf9\xbe\xb4\xd9"
31
+
32
+
33
+ def get_files(path):
34
+ """
35
+ Given the path to the .bitcoin directory, returns the sorted list of .blk
36
+ files contained in that directory
37
+ """
38
+ if not stat.S_ISDIR(os.stat(path)[stat.ST_MODE]):
39
+ return [path]
40
+ files = os.listdir(path)
41
+ files = [f for f in files if f.startswith("blk") and f.endswith(".dat")]
42
+ files = map(lambda x: os.path.join(path, x), files)
43
+ return sorted(files)
44
+
45
+ def get_undo_files(path):
46
+ """
47
+ Given the path to the .bitcoin directory, returns the sorted list of rev*.dat
48
+ files contained in that directory
49
+ """
50
+ if not stat.S_ISDIR(os.stat(path)[stat.ST_MODE]):
51
+ return [path]
52
+ files = os.listdir(path)
53
+ files = [f for f in files if f.startswith("rev") and f.endswith(".dat")]
54
+ files = map(lambda x: os.path.join(path, x), files)
55
+ return sorted(files)
56
+
57
+
58
+ def get_blocks(blockfile):
59
+ """
60
+ Given the name of a .dat file, for every block contained in the file,
61
+ yields its raw hexadecimal value
62
+ """
63
+ with open(blockfile, "rb") as f:
64
+ if os.name == 'nt':
65
+ size = os.path.getsize(f.name)
66
+ raw_data = mmap.mmap(f.fileno(), size, access=mmap.ACCESS_READ)
67
+ else:
68
+ # Unix-only call, will not work on Windows, see python doc.
69
+ raw_data = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ)
70
+ length = len(raw_data)
71
+ offset = 0
72
+ block_count = 0
73
+ while offset < (length - 4):
74
+ if raw_data[offset:offset+4] == BITCOIN_CONSTANT:
75
+ offset += 4
76
+ size = struct.unpack("<I", raw_data[offset:offset+4])[0]
77
+ offset += 4 + size
78
+ block_count += 1
79
+ yield raw_data[offset-size:offset]
80
+ else:
81
+ offset += 1
82
+ raw_data.close()
83
+
84
+
85
+ def get_block(blockfile, offset):
86
+ """Extracts a single block from the blockfile at the given offset"""
87
+ with open(blockfile, "rb") as f:
88
+ f.seek(offset - 4) # Size is present 4 bytes before the db offset
89
+ size, = struct.unpack("<I", f.read(4))
90
+ return f.read(size)
91
+
92
+
93
+ def _chain_work(bits):
94
+ """Return the proof-of-work contributed by a compact nBits target."""
95
+ if not bits:
96
+ return 0
97
+ exponent = bits >> 24
98
+ mantissa = bits & 0xffffff
99
+ if mantissa == 0:
100
+ return 0
101
+ if exponent <= 3:
102
+ target = mantissa >> (8 * (3 - exponent))
103
+ else:
104
+ target = mantissa << (8 * (exponent - 3))
105
+ if target <= 0:
106
+ return 0
107
+ return (1 << 256) // (target + 1)
108
+
109
+
110
+ def select_main_chain(block_indexes):
111
+ """Return the most-work chain, oldest block first.
112
+
113
+ LevelDB stores block index entries under their hash, so iteration order
114
+ is not block height. Forks are resolved by total chain work, the same
115
+ rule Bitcoin Core uses to choose the active chain.
116
+ """
117
+ if not block_indexes:
118
+ return []
119
+
120
+ by_hash = {}
121
+ for block in block_indexes:
122
+ by_hash[block.hash] = block
123
+
124
+ has_successor = {block.prev_hash for block in block_indexes}
125
+ tips = [block for block in block_indexes if block.hash not in has_successor]
126
+ if not tips:
127
+ tips = list(block_indexes)
128
+
129
+ memo = {}
130
+
131
+ def work_of(start_block):
132
+ stack = [start_block]
133
+ stacked = {start_block.hash}
134
+ while stack:
135
+ current = stack[-1]
136
+ if current.hash in memo:
137
+ stack.pop()
138
+ stacked.discard(current.hash)
139
+ continue
140
+ prev = by_hash.get(current.prev_hash)
141
+ if prev is not None and prev.hash not in memo:
142
+ if prev.hash in stacked:
143
+ prev = None
144
+ else:
145
+ stack.append(prev)
146
+ stacked.add(prev.hash)
147
+ continue
148
+ prev_work = memo[prev.hash] if prev is not None else 0
149
+ memo[current.hash] = prev_work + _chain_work(
150
+ getattr(current, "bits", None)
151
+ )
152
+ stack.pop()
153
+ stacked.discard(current.hash)
154
+ return memo[start_block.hash]
155
+
156
+ for tip in tips:
157
+ work_of(tip)
158
+
159
+ best = max(
160
+ tips,
161
+ key=lambda block: (memo[block.hash], block.height, block.hash),
162
+ )
163
+
164
+ ordered = []
165
+ seen = set()
166
+ current = best
167
+ while current is not None and current.hash not in seen:
168
+ seen.add(current.hash)
169
+ ordered.append(current)
170
+ current = by_hash.get(current.prev_hash)
171
+ ordered.reverse()
172
+ return ordered
173
+
174
+
175
+ def iter_chain_indexes(ordered, start=0, end=None):
176
+ """Select main-chain index entries for a height window.
177
+
178
+ When ``end`` is greater than ``start``, heights ``start`` inclusive
179
+ through ``end`` exclusive are returned. When ``start`` is greater than
180
+ ``end``, heights from ``start`` down to ``end`` are returned and both
181
+ ends are included. Entries without block data are omitted.
182
+ """
183
+ if not ordered:
184
+ return []
185
+
186
+ by_height = {}
187
+ for block in ordered:
188
+ by_height[block.height] = block
189
+
190
+ if end is None:
191
+ end = max(by_height) + 1
192
+
193
+ if end < start:
194
+ heights = range(start, end - 1, -1)
195
+ else:
196
+ heights = range(start, end)
197
+
198
+ selected = []
199
+ for height in heights:
200
+ block = by_height.get(height)
201
+ if block is None:
202
+ continue
203
+ if getattr(block, "file", -1) == -1 or getattr(block, "data_pos", -1) == -1:
204
+ continue
205
+ selected.append(block)
206
+ return selected
207
+
208
+
209
+ def _load_block_indexes(index_path, cache):
210
+ """Load block index entries from a pickle cache or from LevelDB."""
211
+ block_indexes = None
212
+ if cache and os.path.exists(cache):
213
+ with open(cache, 'rb') as f:
214
+ block_indexes = pickle.load(f)
215
+ if not block_indexes or not hasattr(block_indexes[0], "bits"):
216
+ block_indexes = None
217
+
218
+ if block_indexes is None:
219
+ import plyvel
220
+ with plyvel.DB(index_path, compression=None) as db:
221
+ # Block index entries are stored with keys prefixed by 'b'
222
+ with db.iterator(prefix=b'b') as iterator:
223
+ block_indexes = [
224
+ DBBlockIndex(format_hash(k[1:]), v) for k, v in iterator
225
+ ]
226
+ if cache:
227
+ with open(cache, 'wb') as f:
228
+ pickle.dump(block_indexes, f)
229
+ return block_indexes
230
+
231
+
232
+ class Blockchain(object):
233
+ """Represent the blockchain contained in the series of .blk files
234
+ maintained by bitcoind.
235
+ """
236
+
237
+ def __init__(self, path):
238
+ self.path = path
239
+
240
+ def get_unordered_blocks(self):
241
+ """Yields the blocks contained in the .blk files as is,
242
+ without ordering them according to height.
243
+ """
244
+ for blk_file in get_files(self.path):
245
+ for raw_block in get_blocks(blk_file):
246
+ yield Block(raw_block, None, os.path.split(blk_file)[1])
247
+
248
+ def get_ordered_blocks(self, index, start=0, end=None, cache=None):
249
+ """Yield main-chain blocks in height order.
250
+
251
+ ``index`` is the path of Bitcoin Core's LevelDB block index. ``start``
252
+ and ``end`` are block heights. When ``end`` is greater than ``start``,
253
+ heights ``start`` inclusive through ``end`` exclusive are yielded.
254
+ When ``start`` is greater than ``end``, blocks are yielded from
255
+ ``start`` down to ``end`` and both heights are included.
256
+
257
+ ``cache`` stores the parsed index with pickle. Delete that file to
258
+ pick up blocks written after the cache was created. Header-only and
259
+ pruned entries are skipped.
260
+ """
261
+ block_indexes = _load_block_indexes(index, cache)
262
+ ordered = select_main_chain(block_indexes)
263
+ for blk_idx in iter_chain_indexes(ordered, start, end):
264
+ blk_file = os.path.join(self.path, "blk%05d.dat" % blk_idx.file)
265
+ yield Block(get_block(blk_file, blk_idx.data_pos), blk_idx.height)
266
+
267
+ def get_transaction(self, txid, db):
268
+ """Yields the transaction contained in the .blk files as a python
269
+ object, similar to
270
+ https://developer.bitcoin.org/reference/rpc/getrawtransaction.html
271
+ """
272
+
273
+ byte_arr = bytearray.fromhex(txid)
274
+ byte_arr.reverse()
275
+ tx_hash = hexlify(b't').decode('utf-8') + \
276
+ hexlify(byte_arr).decode('utf-8')
277
+
278
+ tx_hash_fmtd = unhexlify(tx_hash)
279
+ raw_hex = db.get(tx_hash_fmtd)
280
+ if raw_hex is None:
281
+ return None
282
+
283
+ tx_idx = DBTransactionIndex(utils.format_hash(tx_hash_fmtd), raw_hex)
284
+ blk_file = os.path.join(self.path, "blk%05d.dat" % tx_idx.blockfile_no)
285
+ raw_hex = get_block(blk_file, tx_idx.file_offset)
286
+
287
+ offset = tx_idx.block_offset
288
+
289
+ transaction_data = raw_hex[80:]
290
+ block_header_data = raw_hex[:80]
291
+ # Try from 1024 (1KiB) -> 1073741824 (1GiB) slice widths
292
+ for j in range(0, 20):
293
+ try:
294
+ block_header = BlockHeader.from_hex(block_header_data)
295
+ offset_e = offset + (1024 * 2 ** j)
296
+ transaction = Transaction.from_hex(
297
+ transaction_data[offset:offset_e])
298
+ return [block_header, transaction]
299
+ except Exception:
300
+ continue
301
+
302
+ return None
@@ -0,0 +1,81 @@
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, self.time, self.bits, self.nonce = unpack(
56
+ "<I32s32sIII",
57
+ raw_hex[-80:]
58
+ )
59
+ self.prev_hash = format_hash(p)
60
+ self.merkle_root = format_hash(m)
61
+
62
+ def __repr__(self):
63
+ return "DBBlockIndex(%s, height=%d, file_no=%d, file_pos=%d)" \
64
+ % (self.hash, self.height, self.file, self.data_pos)
65
+
66
+
67
+ class DBTransactionIndex(object):
68
+ def __init__(self, txn_hash, raw_hex):
69
+ self.hash = txn_hash
70
+ pos = 0
71
+ self.blockfile_no, i = _read_varint(raw_hex[pos:])
72
+ pos += i
73
+ self.file_offset, i = _read_varint(raw_hex[pos:])
74
+ pos += i
75
+ self.block_offset, i = _read_varint(raw_hex[pos:])
76
+
77
+ def __repr__(self):
78
+ return "DBTransactionIndex(%s, blockfile_no=%d, " \
79
+ "file_offset=%d, block_offset=%d)" \
80
+ % (self.hash, self.blockfile_no,
81
+ self.file_offset, self.block_offset)