Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for altcoins #76

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions blockchain_parser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,28 @@
# modified, propagated, or distributed except according to the terms contained
# in the LICENSE file.


__version__ = "0.1.4"


class BlockchainType():
def __init__(self, symbol, block_delim, p2pkh_addr_version, p2sh_addr_version):
self.symbol = symbol
self.block_delim = block_delim
self.p2pkh_addr_version = p2pkh_addr_version
self.p2sh_addr_version = p2sh_addr_version


BITCOIN = BlockchainType('btc', b'\xf9\xbe\xb4\xd9', b'\x00', b'\x05')

DASH = BlockchainType('dash', b"\xbf\x0c\x6b\xbd", b'\x4c', b'\x10')

DIGIBYTE = BlockchainType('zec', b'\xfa\xc3\xb6\xda', b'\x1e', b'\x05')

DOGECOIN = BlockchainType('doge', b'\xc0\xc0\xc0\xc0', b'\x1e', b'\x16')

LITECOIN = BlockchainType('ltc', b"\xfb\xc0\xb6\xdb", b'\x30', b'\x05')

MONACOIN = BlockchainType('mona', b"\xfb\xc0\xb6\xdb", b'\x32', b'\x05')

ZCASH = BlockchainType('zec', b'\x24\xe9\x27\x64', b'\x1c\xb8', b'\x1c\xbd')
18 changes: 12 additions & 6 deletions blockchain_parser/address.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,35 @@
# in the LICENSE file.

from bitcoin import base58

from .blockchain import BITCOIN
from .utils import btc_ripemd160, double_sha256


class Address(object):
"""Represents a bitcoin address"""

def __init__(self, hash, public_key, address, type):
def __init__(self, hash, public_key, address, type, blockchain_type):
self._hash = hash
self.public_key = public_key
self._address = address
self.type = type
self.blockchain_type = blockchain_type

def __repr__(self):
return "Address(addr=%s)" % self.address

@classmethod
def from_public_key(cls, public_key):
def from_public_key(cls, public_key, blockchain_type):
"""Constructs an Address object from a public key"""
return cls(None, public_key, None, "normal")
return cls(None, public_key, None, "normal", blockchain_type=blockchain_type)

@classmethod
def from_ripemd160(cls, hash, type="normal"):
def from_ripemd160(cls, hash, type="normal", blockchain_type=BITCOIN):
"""Constructs an Address object from a RIPEMD-160 hash, it may be a
normal address or a P2SH address, the latter is indicated by setting
type to 'p2sh'"""
return cls(hash, None, None, type)
return cls(hash, None, None, type, blockchain_type=blockchain_type)

@property
def hash(self):
Expand All @@ -49,7 +52,10 @@ def hash(self):
def address(self):
"""Returns the base58 encoded representation of this address"""
if self._address is None:
version = b'\x00' if self.type == "normal" else b'\x05'
if self.type == 'normal':
version = self.blockchain_type.p2pkh_addr_version
else:
version = self.blockchain_type.p2sh_addr_version
checksum = double_sha256(version + self.hash)

self._address = base58.encode(version + self.hash + checksum[:4])
Expand Down
18 changes: 10 additions & 8 deletions blockchain_parser/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
# modified, propagated, or distributed except according to the terms contained
# in the LICENSE file.

from .transaction import Transaction
from .block_header import BlockHeader
from .utils import format_hash, decode_varint, double_sha256
from .transaction import Transaction
from .utils import decode_varint, double_sha256, format_hash


def get_block_transactions(raw_hex):
def get_block_transactions(raw_hex, blockchain_type):
"""Given the raw hexadecimal representation of a block,
yields the block's transactions
"""
Expand All @@ -31,7 +31,7 @@ def get_block_transactions(raw_hex):
try:
offset_e = offset + (1024 * 2 ** j)
transaction = Transaction.from_hex(
transaction_data[offset:offset_e])
transaction_data[offset:offset_e], blockchain_type)
yield transaction
break
except:
Expand All @@ -46,8 +46,9 @@ class Block(object):
Represents a Bitcoin block, contains its header and its transactions.
"""

def __init__(self, raw_hex, height=None):
def __init__(self, raw_hex, blockchain_type, height=None):
self.hex = raw_hex
self.blockchain_type = blockchain_type
self._hash = None
self._transactions = None
self._header = None
Expand All @@ -59,9 +60,9 @@ def __repr__(self):
return "Block(%s)" % self.hash

@classmethod
def from_hex(cls, raw_hex):
def from_hex(cls, raw_hex, blockchain_type):
"""Builds a block object from its bytes representation"""
return cls(raw_hex)
return cls(raw_hex, blockchain_type)

@property
def hash(self):
Expand All @@ -86,7 +87,8 @@ def transactions(self):
"""Returns a list of the block's transactions represented
as Transaction objects"""
if self._transactions is None:
self._transactions = list(get_block_transactions(self.hex))
self._transactions = list(
get_block_transactions(self.hex, self.blockchain_type))

return self._transactions

Expand Down
25 changes: 12 additions & 13 deletions blockchain_parser/blockchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,20 @@
# No part of bitcoin-blockchain-parser, including this file, may be copied,
# modified, propagated, or distributed except according to the terms contained
# in the LICENSE file.

import os
import mmap
import struct
import os
import pickle
import stat
import struct

import plyvel

from . import BITCOIN
from .block import Block
from .index import DBBlockIndex
from .utils import format_hash


# Constant separating blocks in the .blk files
BITCOIN_CONSTANT = b"\xf9\xbe\xb4\xd9"


def get_files(path):
"""
Given the path to the .bitcoin directory, returns the sorted list of .blk
Expand All @@ -38,7 +35,7 @@ def get_files(path):
return sorted(files)


def get_blocks(blockfile):
def get_blocks(blockfile, delim):
"""
Given the name of a .blk file, for every block contained in the file,
yields its raw hexadecimal value
Expand All @@ -54,7 +51,7 @@ def get_blocks(blockfile):
offset = 0
block_count = 0
while offset < (length - 4):
if raw_data[offset:offset+4] == BITCOIN_CONSTANT:
if raw_data[offset:offset+4] == delim:
offset += 4
size = struct.unpack("<I", raw_data[offset:offset+4])[0]
offset += 4 + size
Expand All @@ -78,8 +75,9 @@ class Blockchain(object):
maintained by bitcoind.
"""

def __init__(self, path):
def __init__(self, path, type=BITCOIN):
self.path = path
self.type = type
self.blockIndexes = None
self.indexPath = None

Expand All @@ -88,8 +86,8 @@ def get_unordered_blocks(self):
without ordering them according to height.
"""
for blk_file in get_files(self.path):
for raw_block in get_blocks(blk_file):
yield Block(raw_block)
for raw_block in get_blocks(blk_file, delim=self.type.block_delim):
yield Block(raw_block, self.type)

def __getBlockIndexes(self, index):
"""There is no method of leveldb to close the db (and release the lock).
Expand Down Expand Up @@ -204,7 +202,8 @@ def get_ordered_blocks(self, index, start=0, end=None, cache=None):
# filter out the orphan blocks, so we are left only with block indexes
# that have been confirmed
# (or are new enough that they haven't yet been confirmed)
blockIndexes = list(filter(lambda block: block.hash not in orphans, blockIndexes))
blockIndexes = list(
filter(lambda block: block.hash not in orphans, blockIndexes))

if end is None:
end = len(blockIndexes)
Expand Down
24 changes: 14 additions & 10 deletions blockchain_parser/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@
# No part of bitcoin-blockchain-parser, including this file, may be copied,
# modified, propagated, or distributed except according to the terms contained
# in the LICENSE file.

from .utils import decode_varint, decode_uint64
from .script import Script
from .address import Address
from .script import Script
from .utils import decode_uint64, decode_varint


class Output(object):
"""Represents a Transaction output"""

def __init__(self, raw_hex):
def __init__(self, raw_hex, blockchain_type):
self.blockchain_type = blockchain_type
self._value = None
self._script = None
self._addresses = None
Expand All @@ -30,8 +30,8 @@ def __init__(self, raw_hex):
self._value_hex = raw_hex[:8]

@classmethod
def from_hex(cls, hex_):
return cls(hex_)
def from_hex(cls, hex_, blockchain_type=None):
return cls(hex_, blockchain_type=blockchain_type)

def __repr__(self):
return "Output(satoshis=%d)" % self.value
Expand All @@ -58,19 +58,23 @@ def addresses(self):
if self._addresses is None:
self._addresses = []
if self.type == "pubkey":
address = Address.from_public_key(self.script.operations[0])
address = Address.from_public_key(
self.script.operations[0], blockchain_type=self.blockchain_type)
self._addresses.append(address)
elif self.type == "pubkeyhash":
address = Address.from_ripemd160(self.script.operations[2])
address = Address.from_ripemd160(
self.script.operations[2], blockchain_type=self.blockchain_type)
self._addresses.append(address)
elif self.type == "p2sh":
address = Address.from_ripemd160(self.script.operations[1],
type="p2sh")
type="p2sh",
blockchain_type=self.blockchain_type)
self._addresses.append(address)
elif self.type == "multisig":
n = self.script.operations[-2]
for operation in self.script.operations[1:1+n]:
self._addresses.append(Address.from_public_key(operation))
self._addresses.append(Address.from_public_key(
operation, blockchain_type=self.blockchain_type))

return self._addresses

Expand Down
21 changes: 11 additions & 10 deletions blockchain_parser/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@
# No part of bitcoin-blockchain-parser, including this file, may be copied,
# modified, propagated, or distributed except according to the terms contained
# in the LICENSE file.

from .utils import decode_varint, decode_uint32, double_sha256, format_hash
from .input import Input
from .output import Output
from .utils import decode_uint32, decode_varint, double_sha256, format_hash


def bip69_sort(data):
Expand All @@ -21,7 +20,7 @@ def bip69_sort(data):
class Transaction(object):
"""Represents a bitcoin transaction"""

def __init__(self, raw_hex):
def __init__(self, raw_hex, blockchain_type):
self._hash = None
self._txid = None
self.inputs = None
Expand All @@ -31,6 +30,7 @@ def __init__(self, raw_hex):
self.n_inputs = 0
self.n_outputs = 0
self.is_segwit = False
self.blockchain_type = blockchain_type

offset = 4

Expand All @@ -55,7 +55,8 @@ def __init__(self, raw_hex):

self.outputs = []
for i in range(self.n_outputs):
output = Output.from_hex(raw_hex[offset:])
output = Output.from_hex(
raw_hex[offset:], blockchain_type=self.blockchain_type)
offset += output.size
self.outputs.append(output)

Expand All @@ -82,8 +83,8 @@ def __repr__(self):
return "Transaction(%s)" % self.hash

@classmethod
def from_hex(cls, hex):
return cls(hex)
def from_hex(cls, hex, blockchain_type=None):
return cls(hex, blockchain_type=blockchain_type)

@property
def version(self):
Expand All @@ -107,8 +108,8 @@ def hash(self):
# txid is a hash of all of the legacy transaction fields only
if self.is_segwit:
txid = self.hex[:4] + self.hex[
6:self._offset_before_tx_witnesses] + self.hex[
-4:]
6:self._offset_before_tx_witnesses] + self.hex[
-4:]
else:
txid = self.hex
self._hash = format_hash(double_sha256(txid))
Expand All @@ -133,8 +134,8 @@ def txid(self):
# txid is a hash of all of the legacy transaction fields only
if self.is_segwit:
txid_data = self.hex[:4] + self.hex[
6:self._offset_before_tx_witnesses] + self.hex[
-4:]
6:self._offset_before_tx_witnesses] + self.hex[
-4:]
else:
txid_data = self.hex
self._txid = format_hash(double_sha256(txid_data))
Expand Down