Browse Source

Extend copyright notice; improve comments

master
Neil Booth 8 years ago
parent
commit
d2ebb80fac
  1. 11
      electrumx_rpc.py
  2. 48
      lib/coins.py
  3. 23
      lib/enum.py
  4. 44
      lib/hash.py
  5. 25
      lib/script.py
  6. 15
      lib/tx.py
  7. 10
      lib/util.py
  8. 14
      query.py
  9. 9
      server/block_processor.py
  10. 13
      server/cache.py
  11. 12
      server/controller.py
  12. 8
      server/daemon.py
  13. 9
      server/env.py
  14. 9
      server/protocol.py
  15. 11
      server_main.py

11
electrumx_rpc.py

@ -1,8 +1,15 @@
#!/usr/bin/env python3
# See the file "LICENSE" for information about the copyright
#
# Copyright (c) 2016, Neil Booth
#
# All rights reserved.
#
# See the file "LICENCE" for information about the copyright
# and warranty status of this software.
'''Script to send RPC commands to a running ElectrumX server.'''
import argparse
import asyncio
import json

48
lib/coins.py

@ -1,6 +1,15 @@
# See the file "LICENSE" for information about the copyright
# Copyright (c) 2016, Neil Booth
#
# All rights reserved.
#
# See the file "LICENCE" for information about the copyright
# and warranty status of this software.
'''Module providing coin abstraction.
Anything coin-specific should go in this file and be subclassed where
necessary for appropriate handling.
'''
from decimal import Decimal
import inspect
@ -12,7 +21,7 @@ from lib.tx import Deserializer
class CoinError(Exception):
pass
'''Exception raised for coin-related errors.'''
class Coin(object):
@ -24,17 +33,20 @@ class Coin(object):
VALUE_PER_COIN = 100000000
@staticmethod
def coins():
def coin_classes():
'''Return a list of coin classes in declaration order.'''
is_coin = lambda obj: (inspect.isclass(obj)
and issubclass(obj, Coin)
and obj != Coin)
pairs = inspect.getmembers(sys.modules[__name__], is_coin)
# Returned in the order they appear in this file
return [pair[1] for pair in pairs]
@classmethod
def lookup_coin_class(cls, name, net):
for coin in cls.coins():
'''Return a coin class given name and network.
Raise an exception if unrecognised.'''
for coin in cls.coin_classes():
if (coin.NAME.lower() == name.lower()
and coin.NET.lower() == net.lower()):
return coin
@ -43,13 +55,14 @@ class Coin(object):
@staticmethod
def lookup_xverbytes(verbytes):
'''Return a (is_xpub, coin_class) pair given xpub/xprv verbytes.'''
# Order means BTC testnet will override NMC testnet
for coin in Coin.coins():
for coin in Coin.coin_classes():
if verbytes == coin.XPUB_VERBYTES:
return True, coin
if verbytes == coin.XPRV_VERBYTES:
return False, coin
raise CoinError("version bytes unrecognised")
raise CoinError('version bytes unrecognised')
@classmethod
def address_to_hash168(cls, addr):
@ -129,7 +142,7 @@ class Coin(object):
@classmethod
def prvkey_WIF(privkey_bytes, compressed):
"Return the private key encoded in Wallet Import Format."
'''Return the private key encoded in Wallet Import Format.'''
payload = bytearray([cls.WIF_BYTE]) + privkey_bytes
if compressed:
payload.append(0x01)
@ -137,18 +150,22 @@ class Coin(object):
@classmethod
def header_hashes(cls, header):
'''Given a header return the previous block hash and the current block
hash.'''
'''Given a header return the previous and current block hashes.'''
return header[4:36], double_sha256(header)
@classmethod
def read_block(cls, block):
'''Read a block and return (header, tx_hashes, txs)'''
'''Return a tuple (header, tx_hashes, txs) given a raw block.'''
header, rest = block[:cls.HEADER_LEN], block[cls.HEADER_LEN:]
return (header, ) + Deserializer(rest).read_block()
@classmethod
def decimal_value(cls, value):
'''Return the number of standard coin units as a Decimal given a
quantity of smallest units.
For example 1 BTC is returned for 100 million satoshis.
'''
return Decimal(value) / cls.VALUE_PER_COIN
@ -167,6 +184,7 @@ class Bitcoin(Coin):
TX_COUNT_HEIGHT = 420976
TX_PER_BLOCK = 1600
class BitcoinTestnet(Coin):
NAME = "Bitcoin"
SHORTNAME = "XTN"
@ -177,6 +195,7 @@ class BitcoinTestnet(Coin):
P2SH_VERBYTE = 0xc4
WIF_BYTE = 0xef
# Source: pycoin and others
class Litecoin(Coin):
NAME = "Litecoin"
@ -188,6 +207,7 @@ class Litecoin(Coin):
P2SH_VERBYTE = 0x05
WIF_BYTE = 0xb0
class LitecoinTestnet(Coin):
NAME = "Litecoin"
SHORTNAME = "XLT"
@ -198,6 +218,7 @@ class LitecoinTestnet(Coin):
P2SH_VERBYTE = 0xc4
WIF_BYTE = 0xef
# Source: namecoin.org
class Namecoin(Coin):
NAME = "Namecoin"
@ -209,6 +230,7 @@ class Namecoin(Coin):
P2SH_VERBYTE = 0x0d
WIF_BYTE = 0xe4
class NamecoinTestnet(Coin):
NAME = "Namecoin"
SHORTNAME = "XNM"
@ -219,6 +241,7 @@ class NamecoinTestnet(Coin):
P2SH_VERBYTE = 0xc4
WIF_BYTE = 0xef
# For DOGE there is disagreement across sites like bip32.org and
# pycoin. Taken from bip32.org and bitmerchant on github
class Dogecoin(Coin):
@ -231,6 +254,7 @@ class Dogecoin(Coin):
P2SH_VERBYTE = 0x16
WIF_BYTE = 0x9e
class DogecoinTestnet(Coin):
NAME = "Dogecoin"
SHORTNAME = "XDT"
@ -241,6 +265,7 @@ class DogecoinTestnet(Coin):
P2SH_VERBYTE = 0xc4
WIF_BYTE = 0xf1
# Source: pycoin
class Dash(Coin):
NAME = "Dash"
@ -252,6 +277,7 @@ class Dash(Coin):
P2SH_VERBYTE = 0x10
WIF_BYTE = 0xcc
class DashTestnet(Coin):
NAME = "Dogecoin"
SHORTNAME = "tDASH"

23
lib/enum.py

@ -1,8 +1,17 @@
# enum-like type
# From the Python Cookbook from http://code.activestate.com/recipes/67107/
# Copyright (c) 2016, Neil Booth
#
# All rights reserved.
#
# See the file "LICENCE" for information about the copyright
# and warranty status of this software.
'''An enum-like type with reverse lookup.
class EnumException(Exception):
Source: Python Cookbook, http://code.activestate.com/recipes/67107/
'''
class EnumError(Exception):
pass
@ -20,13 +29,13 @@ class Enumeration:
if isinstance(x, tuple):
x, i = x
if not isinstance(x, str):
raise EnumException("enum name {} not a string".format(x))
raise EnumError("enum name {} not a string".format(x))
if not isinstance(i, int):
raise EnumException("enum value {} not an integer".format(i))
raise EnumError("enum value {} not an integer".format(i))
if x in uniqueNames:
raise EnumException("enum name {} not unique".format(x))
raise EnumError("enum name {} not unique".format(x))
if i in uniqueValues:
raise EnumException("enum value {} not unique".format(x))
raise EnumError("enum value {} not unique".format(x))
uniqueNames.add(x)
uniqueValues.add(i)
lookup[x] = i

44
lib/hash.py

@ -1,6 +1,13 @@
# See the file "LICENSE" for information about the copyright
# Copyright (c) 2016, Neil Booth
#
# All rights reserved.
#
# See the file "LICENCE" for information about the copyright
# and warranty status of this software.
'''Cryptograph hash functions and related classes.'''
import hashlib
import hmac
@ -8,11 +15,13 @@ from lib.util import bytes_to_int, int_to_bytes
def sha256(x):
'''Simple wrapper of hashlib sha256.'''
assert isinstance(x, (bytes, bytearray, memoryview))
return hashlib.sha256(x).digest()
def ripemd160(x):
'''Simple wrapper of hashlib ripemd160.'''
assert isinstance(x, (bytes, bytearray, memoryview))
h = hashlib.new('ripemd160')
h.update(x)
@ -20,36 +29,41 @@ def ripemd160(x):
def double_sha256(x):
'''SHA-256 of SHA-256, as used extensively in bitcoin.'''
return sha256(sha256(x))
def hmac_sha512(key, msg):
'''Use SHA-512 to provide an HMAC.'''
return hmac.new(key, msg, hashlib.sha512).digest()
def hash160(x):
'''RIPEMD-160 of SHA-256.
Used to make bitcoin addresses from pubkeys.'''
return ripemd160(sha256(x))
def hash_to_str(x):
'''Converts a big-endian binary hash to a little-endian hex string, as
shown in block explorers, etc.
'''Convert a big-endian binary hash to displayed hex string.
Display form of a binary hash is reversed and converted to hex.
'''
return bytes(reversed(x)).hex()
def hex_str_to_hash(x):
'''Converts a little-endian hex string as shown to a big-endian binary
hash.'''
'''Convert a displayed hex string to a binary hash.'''
return bytes(reversed(bytes.fromhex(x)))
class InvalidBase58String(Exception):
pass
class InvalidBase58CheckSum(Exception):
pass
class Base58Error(Exception):
'''Exception used for Base58 errors.'''
class Base58(object):
'''Class providing base 58 functionality.'''
chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
assert len(chars) == 58
@ -59,17 +73,17 @@ class Base58(object):
def char_value(c):
val = Base58.cmap.get(c)
if val is None:
raise InvalidBase58String
raise Base58Error('invalid base 58 character "{}"'.format(c))
return val
@staticmethod
def decode(txt):
"""Decodes txt into a big-endian bytearray."""
if not isinstance(txt, str):
raise InvalidBase58String("a string is required")
raise Base58Error('a string is required')
if not txt:
raise InvalidBase58String("string cannot be empty")
raise Base58Error('string cannot be empty')
value = 0
for c in txt:
@ -112,14 +126,14 @@ class Base58(object):
be_bytes = Base58.decode(txt)
result, check = be_bytes[:-4], be_bytes[-4:]
if check != double_sha256(result)[:4]:
raise InvalidBase58CheckSum
raise Base58Error('invalid base 58 checksum for {}'.format(txt))
return result
@staticmethod
def encode_check(payload):
"""Encodes a payload bytearray (which includes the version byte(s))
into a Base58Check string."""
assert isinstance(payload, (bytes, bytearray))
assert isinstance(payload, (bytes, bytearray, memoryview))
be_bytes = payload + double_sha256(payload)[:4]
return Base58.encode(be_bytes)

25
lib/script.py

@ -1,6 +1,13 @@
# See the file "LICENSE" for information about the copyright
# Copyright (c) 2016, Neil Booth
#
# All rights reserved.
#
# See the file "LICENCE" for information about the copyright
# and warranty status of this software.
'''Script-related classes and functions.'''
from binascii import hexlify
import struct
@ -10,7 +17,7 @@ from lib.util import cachedproperty
class ScriptError(Exception):
pass
'''Exception used for script errors.'''
OpCodes = Enumeration("Opcodes", [
@ -52,7 +59,9 @@ assert OpCodes.OP_CHECKMULTISIG == 0xae
class ScriptSig(object):
'''A script from a tx input, typically provides one or more signatures.'''
'''A script from a tx input.
Typically provides one or more signatures.'''
SIG_ADDRESS, SIG_MULTI, SIG_PUBKEY, SIG_UNKNOWN = range(4)
@ -73,8 +82,9 @@ class ScriptSig(object):
@classmethod
def from_script(cls, script, coin):
'''Returns an instance of this class. Uncrecognised scripts return
an object of kind SIG_UNKNOWN.'''
'''Return an instance of this class.
Return an object with kind SIG_UNKNOWN for unrecognised scripts.'''
try:
return cls.parse_script(script, coin)
except ScriptError:
@ -82,8 +92,9 @@ class ScriptSig(object):
@classmethod
def parse_script(cls, script, coin):
'''Returns an instance of this class. Raises on unrecognised
scripts.'''
'''Return an instance of this class.
Raises on unrecognised scripts.'''
ops, datas = Script.get_ops(script)
# Address, PubKey and P2SH redeems only push data

15
lib/tx.py

@ -1,6 +1,13 @@
# See the file "LICENSE" for information about the copyright
# Copyright (c) 2016, Neil Booth
#
# All rights reserved.
#
# See the file "LICENCE" for information about the copyright
# and warranty status of this software.
'''Transaction-related classes and functions.'''
from collections import namedtuple
import struct
@ -9,6 +16,7 @@ from lib.hash import double_sha256, hash_to_str
class Tx(namedtuple("Tx", "version inputs outputs locktime")):
'''Class representing a transaction.'''
@cachedproperty
def is_coinbase(self):
@ -17,6 +25,7 @@ class Tx(namedtuple("Tx", "version inputs outputs locktime")):
# FIXME: add hash as a cached property?
class TxInput(namedtuple("TxInput", "prev_hash prev_idx script sequence")):
'''Class representing a transaction input.'''
ZERO = bytes(32)
MINUS_1 = 4294967295
@ -41,6 +50,7 @@ class TxInput(namedtuple("TxInput", "prev_hash prev_idx script sequence")):
class TxOutput(namedtuple("TxOutput", "value pk_script")):
'''Class representing a transaction output.'''
@cachedproperty
def pay_to(self):
@ -48,9 +58,10 @@ class TxOutput(namedtuple("TxOutput", "value pk_script")):
class Deserializer(object):
'''Deserializes blocks into transactions.'''
def __init__(self, binary):
assert isinstance(binary, (bytes, memoryview))
assert isinstance(binary, bytes)
self.binary = binary
self.cursor = 0

10
lib/util.py

@ -1,6 +1,13 @@
# See the file "LICENSE" for information about the copyright
# Copyright (c) 2016, Neil Booth
#
# All rights reserved.
#
# See the file "LICENCE" for information about the copyright
# and warranty status of this software.
'''Miscellaneous utility classes and functions.'''
import array
import logging
import sys
@ -72,6 +79,7 @@ def deep_getsizeof(obj):
def chunks(items, size):
'''Break up items, an iterable, into chunks of length size.'''
for i in range(0, len(items), size):
yield items[i: i + size]

14
query.py

@ -1,8 +1,18 @@
#!/usr/bin/env python3
# See the file "LICENSE" for information about the copyright
#
# Copyright (c) 2016, Neil Booth
#
# All rights reserved.
#
# See the file "LICENCE" for information about the copyright
# and warranty status of this software.
'''Script to query the database for debugging purposes.
Not currently documented; might become easier to use in future.
'''
import os
import sys

9
server/block_processor.py

@ -1,6 +1,13 @@
# See the file "LICENSE" for information about the copyright
# Copyright (c) 2016, Neil Booth
#
# All rights reserved.
#
# See the file "LICENCE" for information about the copyright
# and warranty status of this software.
'''Block prefetcher and chain processor.'''
import array
import ast
import asyncio

13
server/cache.py

@ -1,6 +1,17 @@
# See the file "LICENSE" for information about the copyright
# Copyright (c) 2016, Neil Booth
#
# All rights reserved.
#
# See the file "LICENCE" for information about the copyright
# and warranty status of this software.
'''UTXO and file cache.
During initial sync these cache data and only flush occasionally.
Once synced flushes are performed after processing each block.
'''
import array
import itertools
import os

12
server/controller.py

@ -1,6 +1,16 @@
# See the file "LICENSE" for information about the copyright
# Copyright (c) 2016, Neil Booth
#
# All rights reserved.
#
# See the file "LICENCE" for information about the copyright
# and warranty status of this software.
'''Server controller.
Coordinates the parts of the server. Serves as a cache for
client-serving data such as histories.
'''
import asyncio
import signal
import traceback

8
server/daemon.py

@ -1,7 +1,11 @@
# See the file "LICENSE" for information about the copyright
# Copyright (c) 2016, Neil Booth
#
# All rights reserved.
#
# See the file "LICENCE" for information about the copyright
# and warranty status of this software.
'''Classes for handling asynchronous connections to a blockchain
'''Class for handling asynchronous connections to a blockchain
daemon.'''
import asyncio

9
server/env.py

@ -1,6 +1,13 @@
# See the file "LICENSE" for information about the copyright
# Copyright (c) 2016, Neil Booth
#
# All rights reserved.
#
# See the file "LICENCE" for information about the copyright
# and warranty status of this software.
'''Class for handling environment configuration and defaults.'''
from os import environ
from lib.coins import Coin

9
server/protocol.py

@ -1,6 +1,13 @@
# See the file "LICENSE" for information about the copyright
# Copyright (c) 2016, Neil Booth
#
# All rights reserved.
#
# See the file "LICENCE" for information about the copyright
# and warranty status of this software.
'''Classes for local RPC server and remote client TCP/SSL servers.'''
import asyncio
import codecs
import json

11
server_main.py

@ -1,8 +1,15 @@
#!/usr/bin/env python3
# See the file "LICENSE" for information about the copyright
#
# Copyright (c) 2016, Neil Booth
#
# All rights reserved.
#
# See the file "LICENCE" for information about the copyright
# and warranty status of this software.
'''Script to kick off the server.'''
import asyncio
import logging
import os

Loading…
Cancel
Save