Extend copyright notice; improve comments
This commit is contained in:
parent
58a5e69fca
commit
d2ebb80fac
@ -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
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
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.
|
||||
|
||||
Source: Python Cookbook, http://code.activestate.com/recipes/67107/
|
||||
'''
|
||||
|
||||
|
||||
class EnumException(Exception):
|
||||
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
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)
|
||||
|
||||
@ -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
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
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
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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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…
Reference in New Issue
Block a user