Latest upstream changes
This commit is contained in:
parent
5fbb1d2dd2
commit
884208d93c
@ -1,19 +1,30 @@
|
|||||||
Metadata-Version: 1.2
|
Metadata-Version: 2.1
|
||||||
Name: electrumX
|
Name: electrumX
|
||||||
Version: 1.8.7
|
Version: 1.13.0
|
||||||
Summary: ElectrumX Server
|
Summary: ElectrumX Server
|
||||||
Home-page: https://github.com/kyuupichan/electrumx
|
Home-page: https://github.com/kyuupichan/electrumx
|
||||||
Author: Neil Booth
|
Author: Neil Booth
|
||||||
Author-email: kyuupichan@gmail.com
|
Author-email: kyuupichan@gmail.com
|
||||||
License: MIT Licence
|
License: MIT Licence
|
||||||
Download-URL: https://github.com/kyuupichan/electrumX/archive/1.8.7.tar.gz
|
Download-URL: https://github.com/kyuupichan/electrumX/archive/1.13.0.tar.gz
|
||||||
Description: Server implementation for the Electrum protocol
|
Description: Server implementation for the Electrum protocol
|
||||||
Platform: UNKNOWN
|
Platform: UNKNOWN
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
Classifier: Development Status :: 5 - Production/Stable
|
||||||
Classifier: Framework :: AsyncIO
|
Classifier: Framework :: AsyncIO
|
||||||
Classifier: License :: OSI Approved :: MIT License
|
Classifier: License :: OSI Approved :: MIT License
|
||||||
Classifier: Operating System :: Unix
|
Classifier: Operating System :: Unix
|
||||||
Classifier: Programming Language :: Python :: 3.6
|
Classifier: Programming Language :: Python :: 3.7
|
||||||
Classifier: Topic :: Database
|
Classifier: Topic :: Database
|
||||||
Classifier: Topic :: Internet
|
Classifier: Topic :: Internet
|
||||||
Requires-Python: >=3.6
|
Requires-Python: >=3.7
|
||||||
|
Provides-Extra: rocksdb
|
||||||
|
Provides-Extra: uvloop
|
||||||
|
Provides-Extra: blake256
|
||||||
|
Provides-Extra: crypto
|
||||||
|
Provides-Extra: groestl
|
||||||
|
Provides-Extra: tribus-hash
|
||||||
|
Provides-Extra: xevan-hash
|
||||||
|
Provides-Extra: x11-hash
|
||||||
|
Provides-Extra: zny-yespower-0-5
|
||||||
|
Provides-Extra: bell-yespower
|
||||||
|
Provides-Extra: cpupower
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
README.rst
|
README.rst
|
||||||
|
electrumx_compact_history
|
||||||
electrumx_rpc
|
electrumx_rpc
|
||||||
electrumx_server
|
electrumx_server
|
||||||
setup.py
|
setup.py
|
||||||
@ -19,6 +20,8 @@ electrumx/lib/script.py
|
|||||||
electrumx/lib/server_base.py
|
electrumx/lib/server_base.py
|
||||||
electrumx/lib/text.py
|
electrumx/lib/text.py
|
||||||
electrumx/lib/tx.py
|
electrumx/lib/tx.py
|
||||||
|
electrumx/lib/tx_axe.py
|
||||||
|
electrumx/lib/tx_dash.py
|
||||||
electrumx/lib/util.py
|
electrumx/lib/util.py
|
||||||
electrumx/server/__init__.py
|
electrumx/server/__init__.py
|
||||||
electrumx/server/block_processor.py
|
electrumx/server/block_processor.py
|
||||||
|
|||||||
@ -1,5 +1,38 @@
|
|||||||
aiorpcX<0.9,>=0.8.1
|
aiorpcX[ws]<0.19,>=0.18.3
|
||||||
attrs
|
attrs
|
||||||
plyvel
|
plyvel
|
||||||
pylru
|
pylru
|
||||||
aiohttp>=2
|
aiohttp>=3.3
|
||||||
|
|
||||||
|
[bell-yespower]
|
||||||
|
bell-yespower
|
||||||
|
|
||||||
|
[blake256]
|
||||||
|
blake256>=0.1.1
|
||||||
|
|
||||||
|
[cpupower]
|
||||||
|
cpupower
|
||||||
|
|
||||||
|
[crypto]
|
||||||
|
pycryptodomex>=3.8.1
|
||||||
|
|
||||||
|
[groestl]
|
||||||
|
groestlcoin-hash>=1.0.1
|
||||||
|
|
||||||
|
[rocksdb]
|
||||||
|
python-rocksdb>=0.6.9
|
||||||
|
|
||||||
|
[tribus-hash]
|
||||||
|
tribus-hash>=1.0.2
|
||||||
|
|
||||||
|
[uvloop]
|
||||||
|
uvloop>=0.12.2
|
||||||
|
|
||||||
|
[x11-hash]
|
||||||
|
x11-hash>=1.4
|
||||||
|
|
||||||
|
[xevan-hash]
|
||||||
|
xevan-hash
|
||||||
|
|
||||||
|
[zny-yespower-0-5]
|
||||||
|
zny-yespower-0-5
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
version = 'ElectrumX 1.13.0'
|
version = 'ElectrumX 1.14.0'
|
||||||
version_short = version.split()[-1]
|
version_short = version.split()[-1]
|
||||||
|
|
||||||
from electrumx.server.controller import Controller
|
from electrumx.server.controller import Controller
|
||||||
|
|||||||
@ -86,6 +86,7 @@ class Coin(object):
|
|||||||
DECODE_CHECK = Base58.decode_check
|
DECODE_CHECK = Base58.decode_check
|
||||||
GENESIS_HASH = ('000000000019d6689c085ae165831e93'
|
GENESIS_HASH = ('000000000019d6689c085ae165831e93'
|
||||||
'4ff763ae46a2a6c172b3f1b60a8ce26f')
|
'4ff763ae46a2a6c172b3f1b60a8ce26f')
|
||||||
|
GENESIS_ACTIVATION = 100_000_000
|
||||||
# Peer discovery
|
# Peer discovery
|
||||||
PEER_DEFAULT_PORTS = {'t': '50001', 's': '50002'}
|
PEER_DEFAULT_PORTS = {'t': '50001', 's': '50002'}
|
||||||
PEERS = []
|
PEERS = []
|
||||||
@ -146,13 +147,7 @@ class Coin(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def hashX_from_script(cls, script):
|
def hashX_from_script(cls, script):
|
||||||
'''Returns a hashX from a script, or None if the script is provably
|
'''Returns a hashX from a script.'''
|
||||||
unspendable so the output can be dropped.
|
|
||||||
'''
|
|
||||||
prefix = script[:2]
|
|
||||||
# Match a prefix of OP_RETURN or (OP_FALSE, OP_RETURN)
|
|
||||||
if prefix == b'\x00\x6a' or (prefix and prefix[0] == 0x6a):
|
|
||||||
return None
|
|
||||||
return sha256(script).digest()[:HASHX_LEN]
|
return sha256(script).digest()[:HASHX_LEN]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -549,6 +544,7 @@ class BitcoinSV(BitcoinMixin, Coin):
|
|||||||
'sv.jochen-hoenicke.de s t',
|
'sv.jochen-hoenicke.de s t',
|
||||||
'sv.satoshi.io s t',
|
'sv.satoshi.io s t',
|
||||||
]
|
]
|
||||||
|
GENESIS_ACTIVATION = 620_538
|
||||||
|
|
||||||
|
|
||||||
class BitcoinCash(BitcoinMixin, Coin):
|
class BitcoinCash(BitcoinMixin, Coin):
|
||||||
@ -777,6 +773,7 @@ class BitcoinSVTestnet(BitcoinTestnetMixin, Coin):
|
|||||||
PEERS = [
|
PEERS = [
|
||||||
'electrontest.cascharia.com t51001 s51002',
|
'electrontest.cascharia.com t51001 s51002',
|
||||||
]
|
]
|
||||||
|
GENESIS_ACTIVATION = 1_344_302
|
||||||
|
|
||||||
|
|
||||||
class BitcoinSVScalingTestnet(BitcoinSVTestnet):
|
class BitcoinSVScalingTestnet(BitcoinSVTestnet):
|
||||||
@ -787,6 +784,7 @@ class BitcoinSVScalingTestnet(BitcoinSVTestnet):
|
|||||||
TX_COUNT = 2015
|
TX_COUNT = 2015
|
||||||
TX_COUNT_HEIGHT = 5711
|
TX_COUNT_HEIGHT = 5711
|
||||||
TX_PER_BLOCK = 5000
|
TX_PER_BLOCK = 5000
|
||||||
|
GENESIS_ACTIVATION = 14_896
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def max_fetch_blocks(cls, height):
|
def max_fetch_blocks(cls, height):
|
||||||
@ -824,6 +822,7 @@ class BitcoinSVRegtest(BitcoinSVTestnet):
|
|||||||
PEERS = []
|
PEERS = []
|
||||||
TX_COUNT = 1
|
TX_COUNT = 1
|
||||||
TX_COUNT_HEIGHT = 1
|
TX_COUNT_HEIGHT = 1
|
||||||
|
GENESIS_ACTIVATION = 10_000
|
||||||
|
|
||||||
|
|
||||||
class BitcoinSegwitTestnet(BitcoinTestnetMixin, Coin):
|
class BitcoinSegwitTestnet(BitcoinTestnetMixin, Coin):
|
||||||
@ -925,16 +924,6 @@ class LitecoinTestnet(Litecoin):
|
|||||||
'electrum.ltc.xurious.com s t',
|
'electrum.ltc.xurious.com s t',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class LitecoinRegtest(LitecoinTestnet):
|
|
||||||
NET = "regtest"
|
|
||||||
GENESIS_HASH = ('530827f38f93b43ed12af0b3ad25a288'
|
|
||||||
'dc02ed74d6d7857862df51fc56c416f9')
|
|
||||||
PEERS = []
|
|
||||||
TX_COUNT = 1
|
|
||||||
TX_COUNT_HEIGHT = 1
|
|
||||||
|
|
||||||
|
|
||||||
class Flo(Coin):
|
class Flo(Coin):
|
||||||
NAME = "FLO"
|
NAME = "FLO"
|
||||||
SHORTNAME = "FLO"
|
SHORTNAME = "FLO"
|
||||||
@ -973,6 +962,15 @@ class FloTestnet(Flo):
|
|||||||
PEERS = []
|
PEERS = []
|
||||||
|
|
||||||
|
|
||||||
|
class LitecoinRegtest(LitecoinTestnet):
|
||||||
|
NET = "regtest"
|
||||||
|
GENESIS_HASH = ('530827f38f93b43ed12af0b3ad25a288'
|
||||||
|
'dc02ed74d6d7857862df51fc56c416f9')
|
||||||
|
PEERS = []
|
||||||
|
TX_COUNT = 1
|
||||||
|
TX_COUNT_HEIGHT = 1
|
||||||
|
|
||||||
|
|
||||||
class BitcoinCashRegtest(BitcoinTestnetMixin, Coin):
|
class BitcoinCashRegtest(BitcoinTestnetMixin, Coin):
|
||||||
NAME = "BitcoinCashABC" # Some releases later remove the ABC suffix
|
NAME = "BitcoinCashABC" # Some releases later remove the ABC suffix
|
||||||
NET = "regtest"
|
NET = "regtest"
|
||||||
@ -3399,3 +3397,30 @@ class Myce(Coin):
|
|||||||
return scrypt.hash(header, header, 1024, 1, 1, 32)
|
return scrypt.hash(header, header, 1024, 1, 1, 32)
|
||||||
else:
|
else:
|
||||||
return double_sha256(header)
|
return double_sha256(header)
|
||||||
|
|
||||||
|
|
||||||
|
class Navcoin(Coin):
|
||||||
|
NAME = "Navcoin"
|
||||||
|
SHORTNAME = "NAV"
|
||||||
|
NET = "mainnet"
|
||||||
|
XPUB_VERBYTES = bytes.fromhex("0488b21e")
|
||||||
|
XPRV_VERBYTES = bytes.fromhex("0488ade4")
|
||||||
|
P2PKH_VERBYTE = bytes.fromhex("35")
|
||||||
|
P2SH_VERBYTES = [bytes.fromhex("55")]
|
||||||
|
WIF_BYTE = bytes.fromhex("96")
|
||||||
|
GENESIS_HASH = ('00006a4e3e18c71c6d48ad6c261e2254'
|
||||||
|
'fa764cf29607a4357c99b712dfbb8e6a')
|
||||||
|
DESERIALIZER = lib_tx.DeserializerTxTimeSegWitNavCoin
|
||||||
|
TX_COUNT = 137641
|
||||||
|
TX_COUNT_HEIGHT = 3649662
|
||||||
|
TX_PER_BLOCK = 2
|
||||||
|
RPC_PORT = 44444
|
||||||
|
REORG_LIMIT = 1000
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def header_hash(cls, header):
|
||||||
|
if int.from_bytes(header[:4], "little") > 6:
|
||||||
|
return double_sha256(header)
|
||||||
|
else:
|
||||||
|
import x13_hash
|
||||||
|
return x13_hash.getPoWHash(header)
|
||||||
|
|||||||
@ -76,6 +76,16 @@ assert OpCodes.OP_CHECKSIG == 0xac
|
|||||||
assert OpCodes.OP_CHECKMULTISIG == 0xae
|
assert OpCodes.OP_CHECKMULTISIG == 0xae
|
||||||
|
|
||||||
|
|
||||||
|
def is_unspendable_legacy(script):
|
||||||
|
# OP_FALSE OP_RETURN or OP_RETURN
|
||||||
|
return script[:2] == b'\x00\x6a' or (script and script[0] == 0x6a)
|
||||||
|
|
||||||
|
|
||||||
|
def is_unspendable_genesis(script):
|
||||||
|
# OP_FALSE OP_RETURN
|
||||||
|
return script[:2] == b'\x00\x6a'
|
||||||
|
|
||||||
|
|
||||||
def _match_ops(ops, pattern):
|
def _match_ops(ops, pattern):
|
||||||
if len(ops) != len(pattern):
|
if len(ops) != len(pattern):
|
||||||
return False
|
return False
|
||||||
|
|||||||
@ -271,7 +271,6 @@ class DeserializerSegWit(Deserializer):
|
|||||||
tx, _tx_hash, vsize = self._read_tx_parts()
|
tx, _tx_hash, vsize = self._read_tx_parts()
|
||||||
return tx, vsize
|
return tx, vsize
|
||||||
|
|
||||||
|
|
||||||
class TxFlo(namedtuple("Tx", "version inputs outputs locktime txcomment")):
|
class TxFlo(namedtuple("Tx", "version inputs outputs locktime txcomment")):
|
||||||
'''Class representing a transaction.'''
|
'''Class representing a transaction.'''
|
||||||
|
|
||||||
@ -346,8 +345,6 @@ class DeserializerFlo(DeserializerSegWit):
|
|||||||
locktime, tx_comment), double_sha256(orig_ser), vsize
|
locktime, tx_comment), double_sha256(orig_ser), vsize
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class DeserializerAuxPow(Deserializer):
|
class DeserializerAuxPow(Deserializer):
|
||||||
VERSION_AUXPOW = (1 << 8)
|
VERSION_AUXPOW = (1 << 8)
|
||||||
|
|
||||||
@ -537,6 +534,82 @@ class DeserializerTxTimeSegWit(DeserializerTxTime):
|
|||||||
return tx, vsize
|
return tx, vsize
|
||||||
|
|
||||||
|
|
||||||
|
class DeserializerTxTimeSegWitNavCoin(DeserializerTxTime):
|
||||||
|
def _read_witness(self, fields):
|
||||||
|
read_witness_field = self._read_witness_field
|
||||||
|
return [read_witness_field() for _ in range(fields)]
|
||||||
|
|
||||||
|
def _read_witness_field(self):
|
||||||
|
read_varbytes = self._read_varbytes
|
||||||
|
return [read_varbytes() for _ in range(self._read_varint())]
|
||||||
|
|
||||||
|
def read_tx_no_segwit(self):
|
||||||
|
version = self._read_le_int32()
|
||||||
|
time = self._read_le_uint32()
|
||||||
|
inputs = self._read_inputs()
|
||||||
|
outputs = self._read_outputs()
|
||||||
|
locktime = self._read_le_uint32()
|
||||||
|
strDZeel = ""
|
||||||
|
if version >= 2:
|
||||||
|
strDZeel = self._read_varbytes()
|
||||||
|
return TxTime(
|
||||||
|
version,
|
||||||
|
time,
|
||||||
|
inputs,
|
||||||
|
outputs,
|
||||||
|
locktime
|
||||||
|
)
|
||||||
|
|
||||||
|
def _read_tx_parts(self):
|
||||||
|
'''Return a (deserialized TX, tx_hash, vsize) tuple.'''
|
||||||
|
start = self.cursor
|
||||||
|
marker = self.binary[self.cursor + 8]
|
||||||
|
if marker:
|
||||||
|
tx = self.read_tx_no_segwit()
|
||||||
|
tx_hash = self.TX_HASH_FN(self.binary[start:self.cursor])
|
||||||
|
return tx, tx_hash, self.binary_length
|
||||||
|
|
||||||
|
version = self._read_le_int32()
|
||||||
|
time = self._read_le_uint32()
|
||||||
|
orig_ser = self.binary[start:self.cursor]
|
||||||
|
|
||||||
|
marker = self._read_byte()
|
||||||
|
flag = self._read_byte()
|
||||||
|
|
||||||
|
start = self.cursor
|
||||||
|
inputs = self._read_inputs()
|
||||||
|
outputs = self._read_outputs()
|
||||||
|
orig_ser += self.binary[start:self.cursor]
|
||||||
|
|
||||||
|
base_size = self.cursor - start
|
||||||
|
witness = self._read_witness(len(inputs))
|
||||||
|
|
||||||
|
start = self.cursor
|
||||||
|
locktime = self._read_le_uint32()
|
||||||
|
strDZeel = ""
|
||||||
|
|
||||||
|
if version >= 2:
|
||||||
|
strDZeel = self._read_varbytes()
|
||||||
|
|
||||||
|
vsize = (3 * base_size + self.binary_length) // 4
|
||||||
|
orig_ser += self.binary[start:self.cursor]
|
||||||
|
|
||||||
|
return TxTimeSegWit(
|
||||||
|
version, time, marker, flag, inputs, outputs, witness, locktime),\
|
||||||
|
self.TX_HASH_FN(orig_ser), vsize
|
||||||
|
|
||||||
|
def read_tx(self):
|
||||||
|
return self._read_tx_parts()[0]
|
||||||
|
|
||||||
|
def read_tx_and_hash(self):
|
||||||
|
tx, tx_hash, vsize = self._read_tx_parts()
|
||||||
|
return tx, tx_hash
|
||||||
|
|
||||||
|
def read_tx_and_vsize(self):
|
||||||
|
tx, tx_hash, vsize = self._read_tx_parts()
|
||||||
|
return tx, vsize
|
||||||
|
|
||||||
|
|
||||||
class TxTrezarcoin(
|
class TxTrezarcoin(
|
||||||
namedtuple("Tx", "version time inputs outputs locktime txcomment")):
|
namedtuple("Tx", "version time inputs outputs locktime txcomment")):
|
||||||
'''Class representing transaction that has a time and txcomment field.'''
|
'''Class representing transaction that has a time and txcomment field.'''
|
||||||
|
|||||||
@ -17,6 +17,7 @@ from aiorpcx import TaskGroup, run_in_thread
|
|||||||
import electrumx
|
import electrumx
|
||||||
from electrumx.server.daemon import DaemonError
|
from electrumx.server.daemon import DaemonError
|
||||||
from electrumx.lib.hash import hash_to_hex_str, HASHX_LEN
|
from electrumx.lib.hash import hash_to_hex_str, HASHX_LEN
|
||||||
|
from electrumx.lib.script import is_unspendable_legacy, is_unspendable_genesis
|
||||||
from electrumx.lib.util import (
|
from electrumx.lib.util import (
|
||||||
chunks, class_logger, pack_le_uint32, pack_le_uint64, unpack_le_uint32
|
chunks, class_logger, pack_le_uint32, pack_le_uint64, unpack_le_uint32
|
||||||
)
|
)
|
||||||
@ -385,10 +386,13 @@ class BlockProcessor(object):
|
|||||||
'''
|
'''
|
||||||
min_height = self.db.min_undo_height(self.daemon.cached_height())
|
min_height = self.db.min_undo_height(self.daemon.cached_height())
|
||||||
height = self.height
|
height = self.height
|
||||||
|
genesis_activation = self.coin.GENESIS_ACTIVATION
|
||||||
|
|
||||||
for block in blocks:
|
for block in blocks:
|
||||||
height += 1
|
height += 1
|
||||||
undo_info = self.advance_txs(block.transactions)
|
is_unspendable = (is_unspendable_genesis if height >= genesis_activation
|
||||||
|
else is_unspendable_legacy)
|
||||||
|
undo_info = self.advance_txs(block.transactions, is_unspendable)
|
||||||
if height >= min_height:
|
if height >= min_height:
|
||||||
self.undo_infos.append((undo_info, height))
|
self.undo_infos.append((undo_info, height))
|
||||||
self.db.write_raw_block(block.raw, height)
|
self.db.write_raw_block(block.raw, height)
|
||||||
@ -398,7 +402,7 @@ class BlockProcessor(object):
|
|||||||
self.headers.extend(headers)
|
self.headers.extend(headers)
|
||||||
self.tip = self.coin.header_hash(headers[-1])
|
self.tip = self.coin.header_hash(headers[-1])
|
||||||
|
|
||||||
def advance_txs(self, txs):
|
def advance_txs(self, txs, is_unspendable):
|
||||||
self.tx_hashes.append(b''.join(tx_hash for tx, tx_hash in txs))
|
self.tx_hashes.append(b''.join(tx_hash for tx, tx_hash in txs))
|
||||||
|
|
||||||
# Use local vars for speed in the loops
|
# Use local vars for speed in the loops
|
||||||
@ -429,12 +433,15 @@ class BlockProcessor(object):
|
|||||||
|
|
||||||
# Add the new UTXOs
|
# Add the new UTXOs
|
||||||
for idx, txout in enumerate(tx.outputs):
|
for idx, txout in enumerate(tx.outputs):
|
||||||
# Get the hashX. Ignore unspendable outputs
|
# Ignore unspendable outputs
|
||||||
|
if is_unspendable(txout.pk_script):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get the hashX
|
||||||
hashX = script_hashX(txout.pk_script)
|
hashX = script_hashX(txout.pk_script)
|
||||||
if hashX:
|
append_hashX(hashX)
|
||||||
append_hashX(hashX)
|
put_utxo(tx_hash + to_le_uint32(idx),
|
||||||
put_utxo(tx_hash + to_le_uint32(idx),
|
hashX + tx_numb + to_le_uint64(txout.value))
|
||||||
hashX + tx_numb + to_le_uint64(txout.value))
|
|
||||||
|
|
||||||
append_hashXs(hashXs)
|
append_hashXs(hashXs)
|
||||||
update_touched(hashXs)
|
update_touched(hashXs)
|
||||||
@ -455,6 +462,7 @@ class BlockProcessor(object):
|
|||||||
'''
|
'''
|
||||||
self.db.assert_flushed(self.flush_data())
|
self.db.assert_flushed(self.flush_data())
|
||||||
assert self.height >= len(raw_blocks)
|
assert self.height >= len(raw_blocks)
|
||||||
|
genesis_activation = self.coin.GENESIS_ACTIVATION
|
||||||
|
|
||||||
coin = self.coin
|
coin = self.coin
|
||||||
for raw_block in raw_blocks:
|
for raw_block in raw_blocks:
|
||||||
@ -467,13 +475,15 @@ class BlockProcessor(object):
|
|||||||
hash_to_hex_str(self.tip),
|
hash_to_hex_str(self.tip),
|
||||||
self.height))
|
self.height))
|
||||||
self.tip = coin.header_prevhash(block.header)
|
self.tip = coin.header_prevhash(block.header)
|
||||||
self.backup_txs(block.transactions)
|
is_unspendable = (is_unspendable_genesis if self.height >= genesis_activation
|
||||||
|
else is_unspendable_legacy)
|
||||||
|
self.backup_txs(block.transactions, is_unspendable)
|
||||||
self.height -= 1
|
self.height -= 1
|
||||||
self.db.tx_counts.pop()
|
self.db.tx_counts.pop()
|
||||||
|
|
||||||
self.logger.info('backed up to height {:,d}'.format(self.height))
|
self.logger.info('backed up to height {:,d}'.format(self.height))
|
||||||
|
|
||||||
def backup_txs(self, txs):
|
def backup_txs(self, txs, is_unspendable):
|
||||||
# Prevout values, in order down the block (coinbase first if present)
|
# Prevout values, in order down the block (coinbase first if present)
|
||||||
# undo_info is in reverse block order
|
# undo_info is in reverse block order
|
||||||
undo_info = self.db.read_undo_info(self.height)
|
undo_info = self.db.read_undo_info(self.height)
|
||||||
@ -493,10 +503,13 @@ class BlockProcessor(object):
|
|||||||
for idx, txout in enumerate(tx.outputs):
|
for idx, txout in enumerate(tx.outputs):
|
||||||
# Spend the TX outputs. Be careful with unspendable
|
# Spend the TX outputs. Be careful with unspendable
|
||||||
# outputs - we didn't save those in the first place.
|
# outputs - we didn't save those in the first place.
|
||||||
|
if is_unspendable(txout.pk_script):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get the hashX
|
||||||
hashX = script_hashX(txout.pk_script)
|
hashX = script_hashX(txout.pk_script)
|
||||||
if hashX:
|
cache_value = spend_utxo(tx_hash, idx)
|
||||||
cache_value = spend_utxo(tx_hash, idx)
|
touched.add(cache_value[:-12])
|
||||||
touched.add(cache_value[:-12])
|
|
||||||
|
|
||||||
# Restore the inputs
|
# Restore the inputs
|
||||||
for txin in reversed(tx.inputs):
|
for txin in reversed(tx.inputs):
|
||||||
@ -590,7 +603,7 @@ class BlockProcessor(object):
|
|||||||
|
|
||||||
if len(candidates) > 1:
|
if len(candidates) > 1:
|
||||||
tx_num, = unpack_le_uint32(tx_num_packed)
|
tx_num, = unpack_le_uint32(tx_num_packed)
|
||||||
hash, height = self.db.fs_tx_hash(tx_num)
|
hash, _height = self.db.fs_tx_hash(tx_num)
|
||||||
if hash != tx_hash:
|
if hash != tx_hash:
|
||||||
assert hash is not None # Should always be found
|
assert hash is not None # Should always be found
|
||||||
continue
|
continue
|
||||||
@ -691,8 +704,8 @@ class DecredBlockProcessor(BlockProcessor):
|
|||||||
|
|
||||||
class NameIndexBlockProcessor(BlockProcessor):
|
class NameIndexBlockProcessor(BlockProcessor):
|
||||||
|
|
||||||
def advance_txs(self, txs):
|
def advance_txs(self, txs, is_unspendable):
|
||||||
result = super().advance_txs(txs)
|
result = super().advance_txs(txs, is_unspendable)
|
||||||
|
|
||||||
tx_num = self.tx_count - len(txs)
|
tx_num = self.tx_count - len(txs)
|
||||||
script_name_hashX = self.coin.name_hashX_from_script
|
script_name_hashX = self.coin.name_hashX_from_script
|
||||||
@ -722,7 +735,7 @@ class NameIndexBlockProcessor(BlockProcessor):
|
|||||||
|
|
||||||
class LTORBlockProcessor(BlockProcessor):
|
class LTORBlockProcessor(BlockProcessor):
|
||||||
|
|
||||||
def advance_txs(self, txs):
|
def advance_txs(self, txs, is_unspendable):
|
||||||
self.tx_hashes.append(b''.join(tx_hash for tx, tx_hash in txs))
|
self.tx_hashes.append(b''.join(tx_hash for tx, tx_hash in txs))
|
||||||
|
|
||||||
# Use local vars for speed in the loops
|
# Use local vars for speed in the loops
|
||||||
@ -744,12 +757,15 @@ class LTORBlockProcessor(BlockProcessor):
|
|||||||
tx_numb = to_le_uint32(tx_num)
|
tx_numb = to_le_uint32(tx_num)
|
||||||
|
|
||||||
for idx, txout in enumerate(tx.outputs):
|
for idx, txout in enumerate(tx.outputs):
|
||||||
# Get the hashX. Ignore unspendable outputs.
|
# Ignore unspendable outputs
|
||||||
|
if is_unspendable(txout.pk_script):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get the hashX
|
||||||
hashX = script_hashX(txout.pk_script)
|
hashX = script_hashX(txout.pk_script)
|
||||||
if hashX:
|
add_hashXs(hashX)
|
||||||
add_hashXs(hashX)
|
put_utxo(tx_hash + to_le_uint32(idx),
|
||||||
put_utxo(tx_hash + to_le_uint32(idx),
|
hashX + tx_numb + to_le_uint64(txout.value))
|
||||||
hashX + tx_numb + to_le_uint64(txout.value))
|
|
||||||
tx_num += 1
|
tx_num += 1
|
||||||
|
|
||||||
# Spend the inputs
|
# Spend the inputs
|
||||||
@ -774,7 +790,7 @@ class LTORBlockProcessor(BlockProcessor):
|
|||||||
|
|
||||||
return undo_info
|
return undo_info
|
||||||
|
|
||||||
def backup_txs(self, txs):
|
def backup_txs(self, txs, is_unspendable):
|
||||||
undo_info = self.db.read_undo_info(self.height)
|
undo_info = self.db.read_undo_info(self.height)
|
||||||
if undo_info is None:
|
if undo_info is None:
|
||||||
raise ChainError('no undo information found for height {:,d}'
|
raise ChainError('no undo information found for height {:,d}'
|
||||||
@ -804,11 +820,14 @@ class LTORBlockProcessor(BlockProcessor):
|
|||||||
# Remove tx outputs made in this block, by spending them.
|
# Remove tx outputs made in this block, by spending them.
|
||||||
for tx, tx_hash in txs:
|
for tx, tx_hash in txs:
|
||||||
for idx, txout in enumerate(tx.outputs):
|
for idx, txout in enumerate(tx.outputs):
|
||||||
hashX = script_hashX(txout.pk_script)
|
# Spend the TX outputs. Be careful with unspendable
|
||||||
if hashX:
|
# outputs - we didn't save those in the first place.
|
||||||
# Be careful with unspendable outputs- we didn't save those
|
if is_unspendable(txout.pk_script):
|
||||||
# in the first place.
|
continue
|
||||||
cache_value = spend_utxo(tx_hash, idx)
|
|
||||||
add_touched(cache_value[:-12])
|
# Get the hashX
|
||||||
|
hashX = script_hashX(txout.script)
|
||||||
|
cache_value = spend_utxo(tx_hash, idx)
|
||||||
|
add_touched(cache_value[:-12])
|
||||||
|
|
||||||
self.tx_count -= len(txs)
|
self.tx_count -= len(txs)
|
||||||
|
|||||||
@ -1,309 +0,0 @@
|
|||||||
# Copyright (c) 2017, Neil Booth
|
|
||||||
#
|
|
||||||
# All rights reserved.
|
|
||||||
#
|
|
||||||
# See the file "LICENCE" for information about the copyright
|
|
||||||
# and warranty status of this software.
|
|
||||||
|
|
||||||
'''Logic for BIP32 Hierarchical Key Derviation.'''
|
|
||||||
|
|
||||||
import struct
|
|
||||||
|
|
||||||
import ecdsa
|
|
||||||
import ecdsa.ellipticcurve as EC
|
|
||||||
import ecdsa.numbertheory as NT
|
|
||||||
|
|
||||||
from electrumx.lib.coins import Coin
|
|
||||||
from electrumx.lib.hash import Base58, hmac_sha512, hash160
|
|
||||||
from electrumx.lib.util import cachedproperty, bytes_to_int, int_to_bytes, \
|
|
||||||
pack_be_uint32, unpack_be_uint32_from
|
|
||||||
|
|
||||||
|
|
||||||
class DerivationError(Exception):
|
|
||||||
'''Raised when an invalid derivation occurs.'''
|
|
||||||
|
|
||||||
|
|
||||||
class _KeyBase(object):
|
|
||||||
'''A BIP32 Key, public or private.'''
|
|
||||||
|
|
||||||
CURVE = ecdsa.SECP256k1
|
|
||||||
|
|
||||||
def __init__(self, chain_code, n, depth, parent):
|
|
||||||
if not isinstance(chain_code, (bytes, bytearray)):
|
|
||||||
raise TypeError('chain code must be raw bytes')
|
|
||||||
if len(chain_code) != 32:
|
|
||||||
raise ValueError('invalid chain code')
|
|
||||||
if not 0 <= n < 1 << 32:
|
|
||||||
raise ValueError('invalid child number')
|
|
||||||
if not 0 <= depth < 256:
|
|
||||||
raise ValueError('invalid depth')
|
|
||||||
if parent is not None:
|
|
||||||
if not isinstance(parent, type(self)):
|
|
||||||
raise TypeError('parent key has bad type')
|
|
||||||
self.chain_code = chain_code
|
|
||||||
self.n = n
|
|
||||||
self.depth = depth
|
|
||||||
self.parent = parent
|
|
||||||
|
|
||||||
def _hmac_sha512(self, msg):
|
|
||||||
'''Use SHA-512 to provide an HMAC, returned as a pair of 32-byte
|
|
||||||
objects.
|
|
||||||
'''
|
|
||||||
hmac = hmac_sha512(self.chain_code, msg)
|
|
||||||
return hmac[:32], hmac[32:]
|
|
||||||
|
|
||||||
def _extended_key(self, ver_bytes, raw_serkey):
|
|
||||||
'''Return the 78-byte extended key given prefix version bytes and
|
|
||||||
serialized key bytes.
|
|
||||||
'''
|
|
||||||
if not isinstance(ver_bytes, (bytes, bytearray)):
|
|
||||||
raise TypeError('ver_bytes must be raw bytes')
|
|
||||||
if len(ver_bytes) != 4:
|
|
||||||
raise ValueError('ver_bytes must have length 4')
|
|
||||||
if not isinstance(raw_serkey, (bytes, bytearray)):
|
|
||||||
raise TypeError('raw_serkey must be raw bytes')
|
|
||||||
if len(raw_serkey) != 33:
|
|
||||||
raise ValueError('raw_serkey must have length 33')
|
|
||||||
|
|
||||||
return (ver_bytes + bytes([self.depth])
|
|
||||||
+ self.parent_fingerprint() + pack_be_uint32(self.n)
|
|
||||||
+ self.chain_code + raw_serkey)
|
|
||||||
|
|
||||||
def fingerprint(self):
|
|
||||||
'''Return the key's fingerprint as 4 bytes.'''
|
|
||||||
return self.identifier()[:4]
|
|
||||||
|
|
||||||
def parent_fingerprint(self):
|
|
||||||
'''Return the parent key's fingerprint as 4 bytes.'''
|
|
||||||
return self.parent.fingerprint() if self.parent else bytes(4)
|
|
||||||
|
|
||||||
def extended_key_string(self, coin):
|
|
||||||
'''Return an extended key as a base58 string.'''
|
|
||||||
return Base58.encode_check(self.extended_key(coin))
|
|
||||||
|
|
||||||
|
|
||||||
class PubKey(_KeyBase):
|
|
||||||
'''A BIP32 public key.'''
|
|
||||||
|
|
||||||
def __init__(self, pubkey, chain_code, n, depth, parent=None):
|
|
||||||
super().__init__(chain_code, n, depth, parent)
|
|
||||||
if isinstance(pubkey, ecdsa.VerifyingKey):
|
|
||||||
self.verifying_key = pubkey
|
|
||||||
else:
|
|
||||||
self.verifying_key = self._verifying_key_from_pubkey(pubkey)
|
|
||||||
self.addresses = {}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _verifying_key_from_pubkey(cls, pubkey):
|
|
||||||
'''Converts a 33-byte compressed pubkey into an ecdsa.VerifyingKey
|
|
||||||
object'''
|
|
||||||
if not isinstance(pubkey, (bytes, bytearray)):
|
|
||||||
raise TypeError('pubkey must be raw bytes')
|
|
||||||
if len(pubkey) != 33:
|
|
||||||
raise ValueError('pubkey must be 33 bytes')
|
|
||||||
if pubkey[0] not in (2, 3):
|
|
||||||
raise ValueError('invalid pubkey prefix byte')
|
|
||||||
curve = cls.CURVE.curve
|
|
||||||
|
|
||||||
is_odd = pubkey[0] == 3
|
|
||||||
x = bytes_to_int(pubkey[1:])
|
|
||||||
|
|
||||||
# p is the finite field order
|
|
||||||
a, b, p = curve.a(), curve.b(), curve.p()
|
|
||||||
y2 = pow(x, 3, p) + b
|
|
||||||
assert a == 0 # Otherwise y2 += a * pow(x, 2, p)
|
|
||||||
y = NT.square_root_mod_prime(y2 % p, p)
|
|
||||||
if bool(y & 1) != is_odd:
|
|
||||||
y = p - y
|
|
||||||
point = EC.Point(curve, x, y)
|
|
||||||
|
|
||||||
return ecdsa.VerifyingKey.from_public_point(point, curve=cls.CURVE)
|
|
||||||
|
|
||||||
@cachedproperty
|
|
||||||
def pubkey_bytes(self):
|
|
||||||
'''Return the compressed public key as 33 bytes.'''
|
|
||||||
point = self.verifying_key.pubkey.point
|
|
||||||
prefix = bytes([2 + (point.y() & 1)])
|
|
||||||
padded_bytes = _exponent_to_bytes(point.x())
|
|
||||||
return prefix + padded_bytes
|
|
||||||
|
|
||||||
def address(self, coin):
|
|
||||||
"The public key as a P2PKH address"
|
|
||||||
address = self.addresses.get(coin)
|
|
||||||
if not address:
|
|
||||||
address = coin.P2PKH_address_from_pubkey(self.pubkey_bytes)
|
|
||||||
self.addresses[coin] = address
|
|
||||||
return address
|
|
||||||
|
|
||||||
def ec_point(self):
|
|
||||||
return self.verifying_key.pubkey.point
|
|
||||||
|
|
||||||
def child(self, n):
|
|
||||||
'''Return the derived child extended pubkey at index N.'''
|
|
||||||
if not 0 <= n < (1 << 31):
|
|
||||||
raise ValueError('invalid BIP32 public key child number')
|
|
||||||
|
|
||||||
msg = self.pubkey_bytes + pack_be_uint32(n)
|
|
||||||
L, R = self._hmac_sha512(msg)
|
|
||||||
|
|
||||||
curve = self.CURVE
|
|
||||||
L = bytes_to_int(L)
|
|
||||||
if L >= curve.order:
|
|
||||||
raise DerivationError
|
|
||||||
|
|
||||||
point = curve.generator * L + self.ec_point()
|
|
||||||
if point == EC.INFINITY:
|
|
||||||
raise DerivationError
|
|
||||||
|
|
||||||
verkey = ecdsa.VerifyingKey.from_public_point(point, curve=curve)
|
|
||||||
|
|
||||||
return PubKey(verkey, R, n, self.depth + 1, self)
|
|
||||||
|
|
||||||
def identifier(self):
|
|
||||||
'''Return the key's identifier as 20 bytes.'''
|
|
||||||
return hash160(self.pubkey_bytes)
|
|
||||||
|
|
||||||
def extended_key(self, coin):
|
|
||||||
'''Return a raw extended public key.'''
|
|
||||||
return self._extended_key(coin.XPUB_VERBYTES, self.pubkey_bytes)
|
|
||||||
|
|
||||||
|
|
||||||
class PrivKey(_KeyBase):
|
|
||||||
'''A BIP32 private key.'''
|
|
||||||
|
|
||||||
HARDENED = 1 << 31
|
|
||||||
|
|
||||||
def __init__(self, privkey, chain_code, n, depth, parent=None):
|
|
||||||
super().__init__(chain_code, n, depth, parent)
|
|
||||||
if isinstance(privkey, ecdsa.SigningKey):
|
|
||||||
self.signing_key = privkey
|
|
||||||
else:
|
|
||||||
self.signing_key = self._signing_key_from_privkey(privkey)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _signing_key_from_privkey(cls, privkey):
|
|
||||||
'''Converts a 32-byte privkey into an ecdsa.SigningKey object.'''
|
|
||||||
exponent = cls._privkey_secret_exponent(privkey)
|
|
||||||
return ecdsa.SigningKey.from_secret_exponent(exponent, curve=cls.CURVE)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _privkey_secret_exponent(cls, privkey):
|
|
||||||
'''Return the private key as a secret exponent if it is a valid private
|
|
||||||
key.'''
|
|
||||||
if not isinstance(privkey, (bytes, bytearray)):
|
|
||||||
raise TypeError('privkey must be raw bytes')
|
|
||||||
if len(privkey) != 32:
|
|
||||||
raise ValueError('privkey must be 32 bytes')
|
|
||||||
exponent = bytes_to_int(privkey)
|
|
||||||
if not 1 <= exponent < cls.CURVE.order:
|
|
||||||
raise ValueError('privkey represents an invalid exponent')
|
|
||||||
|
|
||||||
return exponent
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_seed(cls, seed):
|
|
||||||
# This hard-coded message string seems to be coin-independent...
|
|
||||||
hmac = hmac_sha512(b'Bitcoin seed', seed)
|
|
||||||
privkey, chain_code = hmac[:32], hmac[32:]
|
|
||||||
return cls(privkey, chain_code, 0, 0)
|
|
||||||
|
|
||||||
@cachedproperty
|
|
||||||
def privkey_bytes(self):
|
|
||||||
'''Return the serialized private key (no leading zero byte).'''
|
|
||||||
return _exponent_to_bytes(self.secret_exponent())
|
|
||||||
|
|
||||||
@cachedproperty
|
|
||||||
def public_key(self):
|
|
||||||
'''Return the corresponding extended public key.'''
|
|
||||||
verifying_key = self.signing_key.get_verifying_key()
|
|
||||||
parent_pubkey = self.parent.public_key if self.parent else None
|
|
||||||
return PubKey(verifying_key, self.chain_code, self.n, self.depth,
|
|
||||||
parent_pubkey)
|
|
||||||
|
|
||||||
def ec_point(self):
|
|
||||||
return self.public_key.ec_point()
|
|
||||||
|
|
||||||
def secret_exponent(self):
|
|
||||||
'''Return the private key as a secret exponent.'''
|
|
||||||
return self.signing_key.privkey.secret_multiplier
|
|
||||||
|
|
||||||
def WIF(self, coin):
|
|
||||||
'''Return the private key encoded in Wallet Import Format.'''
|
|
||||||
return coin.privkey_WIF(self.privkey_bytes, compressed=True)
|
|
||||||
|
|
||||||
def address(self, coin):
|
|
||||||
"The public key as a P2PKH address"
|
|
||||||
return self.public_key.address(coin)
|
|
||||||
|
|
||||||
def child(self, n):
|
|
||||||
'''Return the derived child extended privkey at index N.'''
|
|
||||||
if not 0 <= n < (1 << 32):
|
|
||||||
raise ValueError('invalid BIP32 private key child number')
|
|
||||||
|
|
||||||
if n >= self.HARDENED:
|
|
||||||
serkey = b'\0' + self.privkey_bytes
|
|
||||||
else:
|
|
||||||
serkey = self.public_key.pubkey_bytes
|
|
||||||
|
|
||||||
msg = serkey + pack_be_uint32(n)
|
|
||||||
L, R = self._hmac_sha512(msg)
|
|
||||||
|
|
||||||
curve = self.CURVE
|
|
||||||
L = bytes_to_int(L)
|
|
||||||
exponent = (L + bytes_to_int(self.privkey_bytes)) % curve.order
|
|
||||||
if exponent == 0 or L >= curve.order:
|
|
||||||
raise DerivationError
|
|
||||||
|
|
||||||
privkey = _exponent_to_bytes(exponent)
|
|
||||||
|
|
||||||
return PrivKey(privkey, R, n, self.depth + 1, self)
|
|
||||||
|
|
||||||
def identifier(self):
|
|
||||||
'''Return the key's identifier as 20 bytes.'''
|
|
||||||
return self.public_key.identifier()
|
|
||||||
|
|
||||||
def extended_key(self, coin):
|
|
||||||
'''Return a raw extended private key.'''
|
|
||||||
return self._extended_key(coin.XPRV_VERBYTES,
|
|
||||||
b'\0' + self.privkey_bytes)
|
|
||||||
|
|
||||||
|
|
||||||
def _exponent_to_bytes(exponent):
|
|
||||||
'''Convert an exponent to 32 big-endian bytes'''
|
|
||||||
return (bytes(32) + int_to_bytes(exponent))[-32:]
|
|
||||||
|
|
||||||
|
|
||||||
def _from_extended_key(ekey):
|
|
||||||
'''Return a PubKey or PrivKey from an extended key raw bytes.'''
|
|
||||||
if not isinstance(ekey, (bytes, bytearray)):
|
|
||||||
raise TypeError('extended key must be raw bytes')
|
|
||||||
if len(ekey) != 78:
|
|
||||||
raise ValueError('extended key must have length 78')
|
|
||||||
|
|
||||||
is_public, coin = Coin.lookup_xverbytes(ekey[:4])
|
|
||||||
depth = ekey[4]
|
|
||||||
fingerprint = ekey[5:9] # Not used
|
|
||||||
n, = unpack_be_uint32_from(ekey[9:13])
|
|
||||||
chain_code = ekey[13:45]
|
|
||||||
|
|
||||||
if is_public:
|
|
||||||
pubkey = ekey[45:]
|
|
||||||
key = PubKey(pubkey, chain_code, n, depth)
|
|
||||||
else:
|
|
||||||
if ekey[45] is not 0:
|
|
||||||
raise ValueError('invalid extended private key prefix byte')
|
|
||||||
privkey = ekey[46:]
|
|
||||||
key = PrivKey(privkey, chain_code, n, depth)
|
|
||||||
|
|
||||||
return key, coin
|
|
||||||
|
|
||||||
|
|
||||||
def from_extended_key_string(ekey_str):
|
|
||||||
'''Given an extended key string, such as
|
|
||||||
|
|
||||||
xpub6BsnM1W2Y7qLMiuhi7f7dbAwQZ5Cz5gYJCRzTNainXzQXYjFwtuQXHd
|
|
||||||
3qfi3t3KJtHxshXezfjft93w4UE7BGMtKwhqEHae3ZA7d823DVrL
|
|
||||||
|
|
||||||
return a (key, coin) pair. key is either a PubKey or PrivKey.
|
|
||||||
'''
|
|
||||||
return _from_extended_key(Base58.decode_check(ekey_str))
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
# Copyright (c) 2017, 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 electrumx.lib.env_base import EnvBase
|
|
||||||
|
|
||||||
|
|
||||||
class Env(EnvBase):
|
|
||||||
'''Wraps environment configuration.'''
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.db_dir = self.required('DB_DIRECTORY')
|
|
||||||
self.db_engine = self.default('DB_ENGINE', 'leveldb')
|
|
||||||
|
|
||||||
self.ssl_port = self.integer('SSL_PORT', 20214)
|
|
||||||
self.ssl_certfile = self.required('SSL_CERTFILE')
|
|
||||||
self.ssl_keyfile = self.required('SSL_KEYFILE')
|
|
||||||
|
|
||||||
self.rpc_port = self.integer('RPC_PORT', 8001)
|
|
||||||
|
|
||||||
self.tor_proxy_host = self.default('TOR_PROXY_HOST', 'localhost')
|
|
||||||
self.tor_proxy_port = self.integer('TOR_PROXY_PORT', None)
|
|
||||||
|
|
||||||
self.session_timeout = self.integer('SESSION_TIMEOUT', 600)
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env python3.7
|
#!/usr/bin/python3.7
|
||||||
#
|
#
|
||||||
# Copyright (c) 2016-2018, Neil Booth
|
# Copyright (c) 2016-2018, Neil Booth
|
||||||
#
|
#
|
||||||
@ -10,7 +10,7 @@
|
|||||||
'''Script to kick off the server.'''
|
'''Script to kick off the server.'''
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import loggingd
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from electrumx import Controller, Env
|
from electrumx import Controller, Env
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user