reorganization

This commit is contained in:
Lex Berezhny 2018-11-04 01:55:50 -04:00
parent c1b96a36a7
commit 5f70ae9923
60 changed files with 320 additions and 243 deletions

View file

@ -51,6 +51,19 @@ setup(
'plyvel', 'plyvel',
'pylru' 'pylru'
), ),
'ui': (
'pyside2',
)
}, },
entry_points={'console_scripts': ['torba=torba.cli:main']} entry_points={
'console_scripts': [
'torba-client=torba.client.cli:main',
'torba-server=torba.server.cli:main [server]',
'orchstr8=torba.orchstr8.cli:main [server]',
],
'gui_scripts': [
'torba=torba.ui:main [ui]'
'torba-workbench=torba.workbench:main [ui]'
]
}
) )

View file

@ -1,10 +1,10 @@
from orchstr8.testcase import IntegrationTestCase import logging
from asyncio import sleep from torba.testcase import IntegrationTestCase
class BlockchainReorganizationTests(IntegrationTestCase): class BlockchainReorganizationTests(IntegrationTestCase):
VERBOSE = True VERBOSITY = logging.WARN
async def test_reorg(self): async def test_reorg(self):
self.assertEqual(self.ledger.headers.height, 200) self.assertEqual(self.ledger.headers.height, 200)
@ -14,11 +14,11 @@ class BlockchainReorganizationTests(IntegrationTestCase):
self.assertEqual(self.ledger.headers.height, 201) self.assertEqual(self.ledger.headers.height, 201)
height = 201 height = 201
#simple fork (rewind+advance to immediate best) # simple fork (rewind+advance to immediate best)
height = await self._simulate_reorg(height, 1, 1, 2) height = await self._simulate_reorg(height, 1, 1, 2)
height = await self._simulate_reorg(height, 2, 1, 10) height = await self._simulate_reorg(height, 2, 1, 10)
height = await self._simulate_reorg(height, 4, 1, 3) height = await self._simulate_reorg(height, 4, 1, 3)
#lagged fork (rewind+batch catch up with immediate best) # lagged fork (rewind+batch catch up with immediate best)
height = await self._simulate_reorg(height, 4, 2, 3) height = await self._simulate_reorg(height, 4, 2, 3)
await self._simulate_reorg(height, 4, 4, 3) await self._simulate_reorg(height, 4, 4, 3)
@ -32,5 +32,5 @@ class BlockchainReorganizationTests(IntegrationTestCase):
await self.blockchain.generate(1) await self.blockchain.generate(1)
height += 1 height += 1
await self.on_header(height) await self.on_header(height)
self.assertEquals(height, self.ledger.headers.height) self.assertEqual(height, self.ledger.headers.height)
return height return height

View file

@ -1,13 +1,13 @@
import logging import logging
from asyncio import CancelledError from asyncio import CancelledError
from torba.testing import IntegrationTestCase from torba.testcase import IntegrationTestCase
from torba.constants import COIN from torba.client.constants import COIN
class ReconnectTests(IntegrationTestCase): class ReconnectTests(IntegrationTestCase):
VERBOSITY = logging.DEBUG VERBOSITY = logging.WARN
async def test_connection_drop_still_receives_events_after_reconnected(self): async def test_connection_drop_still_receives_events_after_reconnected(self):
address1 = await self.account.receiving.get_or_create_usable_address() address1 = await self.account.receiving.get_or_create_usable_address()

View file

@ -1,12 +1,12 @@
import logging import logging
import asyncio import asyncio
from torba.testing import IntegrationTestCase from torba.testcase import IntegrationTestCase
from torba.constants import COIN from torba.client.constants import COIN
class BasicTransactionTests(IntegrationTestCase): class BasicTransactionTests(IntegrationTestCase):
VERBOSITY = logging.WARNING VERBOSITY = logging.WARN
async def test_sending_and_receiving(self): async def test_sending_and_receiving(self):
account1, account2 = self.account, self.wallet.generate_account(self.ledger) account1, account2 = self.account, self.wallet.generate_account(self.ledger)

Binary file not shown.

View file

@ -1,10 +1,10 @@
from binascii import hexlify from binascii import hexlify
from torba.testing import AsyncioTestCase from torba.testcase import AsyncioTestCase
from torba.coin.bitcoinsegwit import MainNetLedger as ledger_class from torba.coin.bitcoinsegwit import MainNetLedger as ledger_class
from torba.baseaccount import HierarchicalDeterministic, SingleKey from torba.client.baseaccount import HierarchicalDeterministic, SingleKey
from torba.wallet import Wallet from torba.client.wallet import Wallet
class TestHierarchicalDeterministicAccount(AsyncioTestCase): class TestHierarchicalDeterministicAccount(AsyncioTestCase):

View file

@ -1,6 +1,6 @@
import unittest import unittest
from torba.bcd_data_stream import BCDataStream from torba.client.bcd_data_stream import BCDataStream
class TestBCDataStream(unittest.TestCase): class TestBCDataStream(unittest.TestCase):

View file

@ -1,9 +1,9 @@
from binascii import unhexlify, hexlify from binascii import unhexlify, hexlify
from torba.testing import AsyncioTestCase from torba.testcase import AsyncioTestCase
from .key_fixtures import expected_ids, expected_privkeys, expected_hardened_privkeys from client_tests.unit.key_fixtures import expected_ids, expected_privkeys, expected_hardened_privkeys
from torba.bip32 import PubKey, PrivateKey, from_extended_key_string from torba.client.bip32 import PubKey, PrivateKey, from_extended_key_string
from torba.coin.bitcoinsegwit import MainNetLedger as ledger_class from torba.coin.bitcoinsegwit import MainNetLedger as ledger_class

View file

@ -1,12 +1,12 @@
from types import GeneratorType from types import GeneratorType
from torba.testing import AsyncioTestCase from torba.testcase import AsyncioTestCase
from torba.coin.bitcoinsegwit import MainNetLedger as ledger_class from torba.coin.bitcoinsegwit import MainNetLedger as ledger_class
from torba.coinselection import CoinSelector, MAXIMUM_TRIES from torba.client.coinselection import CoinSelector, MAXIMUM_TRIES
from torba.constants import CENT from torba.client.constants import CENT
from .test_transaction import get_output as utxo from client_tests.unit.test_transaction import get_output as utxo
NULL_HASH = b'\x00'*32 NULL_HASH = b'\x00'*32

View file

@ -1,13 +1,13 @@
import unittest import unittest
from torba.wallet import Wallet from torba.client.wallet import Wallet
from torba.constants import COIN from torba.client.constants import COIN
from torba.coin.bitcoinsegwit import MainNetLedger as ledger_class from torba.coin.bitcoinsegwit import MainNetLedger as ledger_class
from torba.basedatabase import query, constraints_to_sql from torba.client.basedatabase import query, constraints_to_sql
from torba.testing import AsyncioTestCase from torba.testcase import AsyncioTestCase
from .test_transaction import get_output, NULL_HASH from client_tests.unit.test_transaction import get_output, NULL_HASH
class TestQueryBuilder(unittest.TestCase): class TestQueryBuilder(unittest.TestCase):

View file

@ -1,5 +1,5 @@
from unittest import TestCase, mock from unittest import TestCase, mock
from torba.hash import aes_decrypt, aes_encrypt from torba.client.hash import aes_decrypt, aes_encrypt
class TestAESEncryptDecrypt(TestCase): class TestAESEncryptDecrypt(TestCase):

View file

@ -1,7 +1,7 @@
import os import os
from urllib.request import Request, urlopen from urllib.request import Request, urlopen
from torba.testing import AsyncioTestCase from torba.testcase import AsyncioTestCase
from torba.coin.bitcoinsegwit import MainHeaders from torba.coin.bitcoinsegwit import MainHeaders

View file

@ -2,10 +2,10 @@ import os
from binascii import hexlify from binascii import hexlify
from torba.coin.bitcoinsegwit import MainNetLedger from torba.coin.bitcoinsegwit import MainNetLedger
from torba.wallet import Wallet from torba.client.wallet import Wallet
from .test_transaction import get_transaction, get_output from client_tests.unit.test_transaction import get_transaction, get_output
from .test_headers import BitcoinHeadersTestCase, block_bytes from client_tests.unit.test_headers import BitcoinHeadersTestCase, block_bytes
class MockNetwork: class MockNetwork:

View file

@ -1,7 +1,7 @@
import unittest import unittest
from binascii import hexlify from binascii import hexlify
from torba.mnemonic import Mnemonic from torba.client.mnemonic import Mnemonic
class TestMnemonic(unittest.TestCase): class TestMnemonic(unittest.TestCase):

View file

@ -1,10 +1,10 @@
import unittest import unittest
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
from torba.bcd_data_stream import BCDataStream from torba.client.bcd_data_stream import BCDataStream
from torba.basescript import Template, ParseError, tokenize, push_data from torba.client.basescript import Template, ParseError, tokenize, push_data
from torba.basescript import PUSH_SINGLE, PUSH_INTEGER, PUSH_MANY, OP_HASH160, OP_EQUAL from torba.client.basescript import PUSH_SINGLE, PUSH_INTEGER, PUSH_MANY, OP_HASH160, OP_EQUAL
from torba.basescript import BaseInputScript, BaseOutputScript from torba.client.basescript import BaseInputScript, BaseOutputScript
def parse(opcodes, source): def parse(opcodes, source):

View file

@ -2,11 +2,11 @@ import unittest
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
from itertools import cycle from itertools import cycle
from torba.testing import AsyncioTestCase from torba.testcase import AsyncioTestCase
from torba.coin.bitcoinsegwit import MainNetLedger as ledger_class from torba.coin.bitcoinsegwit import MainNetLedger as ledger_class
from torba.wallet import Wallet from torba.client.wallet import Wallet
from torba.constants import CENT, COIN from torba.client.constants import CENT, COIN
NULL_HASH = b'\x00'*32 NULL_HASH = b'\x00'*32

View file

@ -1,6 +1,6 @@
import unittest import unittest
from torba.util import ArithUint256 from torba.client.util import ArithUint256
class TestArithUint256(unittest.TestCase): class TestArithUint256(unittest.TestCase):

View file

@ -1,11 +1,11 @@
import tempfile import tempfile
from torba.testing import AsyncioTestCase from torba.testcase import AsyncioTestCase
from torba.coin.bitcoinsegwit import MainNetLedger as BTCLedger from torba.coin.bitcoinsegwit import MainNetLedger as BTCLedger
from torba.coin.bitcoincash import MainNetLedger as BCHLedger from torba.coin.bitcoincash import MainNetLedger as BCHLedger
from torba.basemanager import BaseWalletManager from torba.client.basemanager import BaseWalletManager
from torba.wallet import Wallet, WalletStorage from torba.client.wallet import Wallet, WalletStorage
class TestWalletCreation(AsyncioTestCase): class TestWalletCreation(AsyncioTestCase):

0
torba/client/__init__.py Normal file
View file

View file

@ -2,14 +2,13 @@ import random
import typing import typing
from typing import Dict, Tuple, Type, Optional, Any from typing import Dict, Tuple, Type, Optional, Any
from torba.mnemonic import Mnemonic from torba.client.mnemonic import Mnemonic
from torba.bip32 import PrivateKey, PubKey, from_extended_key_string from torba.client.bip32 import PrivateKey, PubKey, from_extended_key_string
from torba.hash import aes_encrypt, aes_decrypt from torba.client.hash import aes_encrypt, aes_decrypt
from torba.constants import COIN from torba.client.constants import COIN
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from torba import baseledger from torba.client import baseledger, wallet as basewallet
from torba import wallet as basewallet
class AddressManager: class AddressManager:

View file

@ -8,9 +8,9 @@ from typing import Tuple, List, Union, Callable, Any, Awaitable, Iterable
import sqlite3 import sqlite3
from torba.hash import TXRefImmutable from torba.client.hash import TXRefImmutable
from torba.basetransaction import BaseTransaction from torba.client.basetransaction import BaseTransaction
from torba.baseaccount import BaseAccount from torba.client.baseaccount import BaseAccount
log = logging.getLogger(__name__) log = logging.getLogger(__name__)

View file

@ -5,8 +5,8 @@ from io import BytesIO
from typing import Optional, Iterator, Tuple from typing import Optional, Iterator, Tuple
from binascii import hexlify from binascii import hexlify
from torba.util import ArithUint256 from torba.client.util import ArithUint256
from torba.hash import double_sha256 from torba.client.hash import double_sha256
log = logging.getLogger(__name__) log = logging.getLogger(__name__)

View file

@ -8,15 +8,13 @@ from typing import Dict, Type, Iterable
from operator import itemgetter from operator import itemgetter
from collections import namedtuple from collections import namedtuple
from torba import baseaccount from torba.client import baseaccount, basenetwork, basetransaction
from torba import basenetwork from torba.client.basedatabase import BaseDatabase
from torba import basetransaction from torba.client.baseheader import BaseHeaders
from torba.basedatabase import BaseDatabase from torba.client.coinselection import CoinSelector
from torba.baseheader import BaseHeaders from torba.client.constants import COIN, NULL_HASH32
from torba.coinselection import CoinSelector
from torba.constants import COIN, NULL_HASH32
from torba.stream import StreamController from torba.stream import StreamController
from torba.hash import hash160, double_sha256, sha256, Base58 from torba.client.hash import hash160, double_sha256, sha256, Base58
log = logging.getLogger(__name__) log = logging.getLogger(__name__)

View file

@ -2,8 +2,8 @@ import asyncio
import logging import logging
from typing import Type, MutableSequence, MutableMapping from typing import Type, MutableSequence, MutableMapping
from torba.baseledger import BaseLedger, LedgerRegistry from torba.client.baseledger import BaseLedger, LedgerRegistry
from torba.wallet import Wallet, WalletStorage from torba.client.wallet import Wallet, WalletStorage
log = logging.getLogger(__name__) log = logging.getLogger(__name__)

View file

@ -3,8 +3,8 @@ from binascii import hexlify
from collections import namedtuple from collections import namedtuple
from typing import List from typing import List
from torba.bcd_data_stream import BCDataStream from torba.client.bcd_data_stream import BCDataStream
from torba.util import subclass_tuple from torba.client.util import subclass_tuple
# bitcoin opcodes # bitcoin opcodes
OP_0 = 0x00 OP_0 = 0x00

View file

@ -3,16 +3,15 @@ import typing
from typing import List, Iterable, Optional from typing import List, Iterable, Optional
from binascii import hexlify from binascii import hexlify
from torba.basescript import BaseInputScript, BaseOutputScript from torba.client.basescript import BaseInputScript, BaseOutputScript
from torba.baseaccount import BaseAccount from torba.client.baseaccount import BaseAccount
from torba.constants import COIN, NULL_HASH32 from torba.client.constants import COIN, NULL_HASH32
from torba.bcd_data_stream import BCDataStream from torba.client.bcd_data_stream import BCDataStream
from torba.hash import sha256, TXRef, TXRefImmutable from torba.client.hash import sha256, TXRef, TXRefImmutable
from torba.util import ReadOnlyList from torba.client.util import ReadOnlyList
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from torba import baseledger from torba.client import baseledger
log = logging.getLogger() log = logging.getLogger()

View file

@ -9,8 +9,8 @@
""" Logic for BIP32 Hierarchical Key Derivation. """ """ Logic for BIP32 Hierarchical Key Derivation. """
from coincurve import PublicKey, PrivateKey as _PrivateKey from coincurve import PublicKey, PrivateKey as _PrivateKey
from torba.hash import Base58, hmac_sha512, hash160, double_sha256 from torba.client.hash import Base58, hmac_sha512, hash160, double_sha256
from torba.util import cachedproperty from torba.client.util import cachedproperty
class DerivationError(Exception): class DerivationError(Exception):

View file

@ -3,8 +3,8 @@ import argparse
import asyncio import asyncio
import aiohttp import aiohttp
from torba.testing.node import Conductor, get_ledger_from_environment, get_blockchain_node_from_ledger from torba.orchstr8.node import Conductor, get_ledger_from_environment, get_blockchain_node_from_ledger
from torba.testing.service import TestingServiceAPI from torba.orchstr8.service import TestingServiceAPI
def get_argument_parser(): def get_argument_parser():

View file

@ -1,7 +1,7 @@
from random import Random from random import Random
from typing import List from typing import List
from torba import basetransaction from torba.client import basetransaction
MAXIMUM_TRIES = 100000 MAXIMUM_TRIES = 100000

View file

@ -18,8 +18,8 @@ from cryptography.hazmat.primitives.ciphers.algorithms import AES
from cryptography.hazmat.primitives.padding import PKCS7 from cryptography.hazmat.primitives.padding import PKCS7
from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends import default_backend
from torba.util import bytes_to_int, int_to_bytes from torba.client.util import bytes_to_int, int_to_bytes
from torba.constants import NULL_HASH32 from torba.client.constants import NULL_HASH32
class TXRef: class TXRef:

View file

@ -12,8 +12,8 @@ from secrets import randbelow
import pbkdf2 import pbkdf2
from torba.hash import hmac_sha512 from torba.client.hash import hmac_sha512
from torba.words import english from torba.client.words import english
# The hash of the mnemonic seed must begin with this # The hash of the mnemonic seed must begin with this
SEED_PREFIX = b'01' # Standard wallet SEED_PREFIX = b'01' # Standard wallet

View file

@ -5,9 +5,7 @@ import typing
from typing import Sequence, MutableSequence from typing import Sequence, MutableSequence
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from torba import baseaccount from torba.client import basemanager, baseaccount, baseledger
from torba import baseledger
from torba import basemanager
class Wallet: class Wallet:

View file

View file

@ -7,8 +7,8 @@ __node_url__ = (
__spvserver__ = 'torba.server.coins.BitcoinCashRegtest' __spvserver__ = 'torba.server.coins.BitcoinCashRegtest'
from binascii import unhexlify from binascii import unhexlify
from torba.baseledger import BaseLedger from torba.client.baseledger import BaseLedger
from torba.basetransaction import BaseTransaction from torba.client.basetransaction import BaseTransaction
from .bitcoinsegwit import MainHeaders, UnverifiedHeaders from .bitcoinsegwit import MainHeaders, UnverifiedHeaders

View file

@ -9,8 +9,8 @@ __spvserver__ = 'torba.server.coins.BitcoinSegwitRegtest'
import struct import struct
from typing import Optional from typing import Optional
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
from torba.baseledger import BaseLedger from torba.client.baseledger import BaseLedger
from torba.baseheader import BaseHeaders, ArithUint256 from torba.client.baseheader import BaseHeaders, ArithUint256
class MainHeaders(BaseHeaders): class MainHeaders(BaseHeaders):

View file

@ -0,0 +1,2 @@
from .node import Conductor
from .service import ConductorService

89
torba/orchstr8/cli.py Normal file
View file

@ -0,0 +1,89 @@
import logging
import argparse
import asyncio
import aiohttp
from .node import Conductor, get_ledger_from_environment, get_blockchain_node_from_ledger
from .service import ConductorService
def get_argument_parser():
parser = argparse.ArgumentParser(
prog="torba"
)
subparsers = parser.add_subparsers(dest='command', help='sub-command help')
subparsers.add_parser("gui", help="Start Qt GUI.")
subparsers.add_parser("download", help="Download blockchain node binary.")
start = subparsers.add_parser("start", help="Start orchstr8 service.")
start.add_argument("--blockchain", help="Start blockchain node.", action="store_true")
start.add_argument("--spv", help="Start SPV server.", action="store_true")
start.add_argument("--wallet", help="Start wallet daemon.", action="store_true")
generate = subparsers.add_parser("generate", help="Call generate method on running orchstr8 instance.")
generate.add_argument("blocks", type=int, help="Number of blocks to generate")
subparsers.add_parser("transfer", help="Call transfer method on running orchstr8 instance.")
return parser
async def run_remote_command(command, **kwargs):
async with aiohttp.ClientSession() as session:
async with session.post('http://localhost:7954/'+command, data=kwargs) as resp:
print(resp.status)
print(await resp.text())
def main():
parser = get_argument_parser()
args = parser.parse_args()
command = getattr(args, 'command', 'help')
if command == 'gui':
from torba.workbench import main as start_app # pylint: disable=E0611,E0401
return start_app()
loop = asyncio.get_event_loop()
ledger = get_ledger_from_environment()
if command == 'download':
logging.getLogger('blockchain').setLevel(logging.INFO)
get_blockchain_node_from_ledger(ledger).ensure()
elif command == 'generate':
loop.run_until_complete(run_remote_command(
'generate', blocks=args.blocks
))
elif command == 'start':
conductor = Conductor()
if getattr(args, 'blockchain', False):
loop.run_until_complete(conductor.start_blockchain())
if getattr(args, 'spv', False):
loop.run_until_complete(conductor.start_spv())
if getattr(args, 'wallet', False):
loop.run_until_complete(conductor.start_wallet())
service = ConductorService(conductor, loop)
loop.run_until_complete(service.start())
try:
print('========== Orchstr8 API Service Started ========')
loop.run_forever()
except KeyboardInterrupt:
pass
finally:
loop.run_until_complete(service.stop())
loop.run_until_complete(conductor.stop())
loop.close()
else:
parser.print_help()
if __name__ == "__main__":
main()

View file

@ -14,10 +14,10 @@ import requests
from torba.server.server import Server from torba.server.server import Server
from torba.server.env import Env from torba.server.env import Env
from torba.wallet import Wallet from torba.client.wallet import Wallet
from torba.baseledger import BaseLedger, BlockHeightEvent from torba.client.baseledger import BaseLedger, BlockHeightEvent
from torba.basemanager import BaseWalletManager from torba.client.basemanager import BaseWalletManager
from torba.baseaccount import BaseAccount from torba.client.baseaccount import BaseAccount
def get_manager_from_environment(default_manager=BaseWalletManager): def get_manager_from_environment(default_manager=BaseWalletManager):
@ -170,6 +170,7 @@ class SPVNode:
} }
os.environ.update(conf) os.environ.update(conf)
self.server = Server(Env(self.coin_class)) self.server = Server(Env(self.coin_class))
self.server.bp.prefetcher.polling_delay = 0.5
await self.server.start() await self.server.start()
async def stop(self, cleanup=True): async def stop(self, cleanup=True):

View file

@ -25,7 +25,7 @@ class WebSocketLogHandler(logging.Handler):
self.handleError(record) self.handleError(record)
class TestingServiceAPI: class ConductorService:
def __init__(self, stack: Conductor, loop: asyncio.AbstractEventLoop) -> None: def __init__(self, stack: Conductor, loop: asyncio.AbstractEventLoop) -> None:
self.stack = stack self.stack = stack

View file

@ -45,6 +45,7 @@ from torba.server.hash import HASHX_LEN, hex_str_to_hash
from torba.server.script import ScriptPubKey, OpCodes from torba.server.script import ScriptPubKey, OpCodes
import torba.server.tx as lib_tx import torba.server.tx as lib_tx
import torba.server.block_processor as block_proc import torba.server.block_processor as block_proc
from torba.server.db import DB
import torba.server.daemon as daemon import torba.server.daemon as daemon
from torba.server.session import ElectrumX, DashElectrumX from torba.server.session import ElectrumX, DashElectrumX
@ -71,6 +72,7 @@ class Coin(object):
DESERIALIZER = lib_tx.Deserializer DESERIALIZER = lib_tx.Deserializer
DAEMON = daemon.Daemon DAEMON = daemon.Daemon
BLOCK_PROCESSOR = block_proc.BlockProcessor BLOCK_PROCESSOR = block_proc.BlockProcessor
DB = DB
HEADER_VALUES = [ HEADER_VALUES = [
'version', 'prev_block_hash', 'merkle_root', 'timestamp', 'bits', 'nonce' 'version', 'prev_block_hash', 'merkle_root', 'timestamp', 'bits', 'nonce'
] ]

View file

@ -5,33 +5,35 @@
# See the file "LICENCE" for information about the copyright # See the file "LICENCE" for information about the copyright
# and warranty status of this software. # and warranty status of this software.
'''Class for handling environment configuration and defaults.'''
import re import re
import resource import resource
from os import environ
from collections import namedtuple from collections import namedtuple
from ipaddress import ip_address from ipaddress import ip_address
from torba.server.util import class_logger
from torba.server.coins import Coin from torba.server.coins import Coin
from torba.server.env_base import EnvBase
import torba.server.util as lib_util import torba.server.util as lib_util
NetIdentity = namedtuple('NetIdentity', 'host tcp_port ssl_port nick_suffix') NetIdentity = namedtuple('NetIdentity', 'host tcp_port ssl_port nick_suffix')
class Env(EnvBase): class Env:
'''Wraps environment configuration. Optionally, accepts a Coin class
as first argument to have ElectrumX serve custom coins not part of
the standard distribution.
'''
# Peer discovery # Peer discovery
PD_OFF, PD_SELF, PD_ON = range(3) PD_OFF, PD_SELF, PD_ON = range(3)
class Error(Exception):
pass
def __init__(self, coin=None): def __init__(self, coin=None):
super().__init__() self.logger = class_logger(__name__, self.__class__.__name__)
self.allow_root = self.boolean('ALLOW_ROOT', False)
self.host = self.default('HOST', 'localhost')
self.rpc_host = self.default('RPC_HOST', 'localhost')
self.loop_policy = self.event_loop_policy()
self.obsolete(['UTXO_MB', 'HIST_MB', 'NETWORK']) self.obsolete(['UTXO_MB', 'HIST_MB', 'NETWORK'])
self.db_dir = self.required('DB_DIRECTORY') self.db_dir = self.required('DB_DIRECTORY')
self.db_engine = self.default('DB_ENGINE', 'leveldb') self.db_engine = self.default('DB_ENGINE', 'leveldb')
@ -55,8 +57,7 @@ class Env(EnvBase):
self.rpc_port = self.integer('RPC_PORT', 8000) self.rpc_port = self.integer('RPC_PORT', 8000)
self.max_subscriptions = self.integer('MAX_SUBSCRIPTIONS', 10000) self.max_subscriptions = self.integer('MAX_SUBSCRIPTIONS', 10000)
self.banner_file = self.default('BANNER_FILE', None) self.banner_file = self.default('BANNER_FILE', None)
self.tor_banner_file = self.default('TOR_BANNER_FILE', self.tor_banner_file = self.default('TOR_BANNER_FILE', self.banner_file)
self.banner_file)
self.anon_logs = self.boolean('ANON_LOGS', False) self.anon_logs = self.boolean('ANON_LOGS', False)
self.log_sessions = self.integer('LOG_SESSIONS', 3600) self.log_sessions = self.integer('LOG_SESSIONS', 3600)
# Peer discovery # Peer discovery
@ -83,6 +84,78 @@ class Env(EnvBase):
for identity in (clearnet_identity, tor_identity) for identity in (clearnet_identity, tor_identity)
if identity is not None] if identity is not None]
@classmethod
def default(cls, envvar, default):
return environ.get(envvar, default)
@classmethod
def boolean(cls, envvar, default):
default = 'Yes' if default else ''
return bool(cls.default(envvar, default).strip())
@classmethod
def required(cls, envvar):
value = environ.get(envvar)
if value is None:
raise cls.Error('required envvar {} not set'.format(envvar))
return value
@classmethod
def integer(cls, envvar, default):
value = environ.get(envvar)
if value is None:
return default
try:
return int(value)
except Exception:
raise cls.Error('cannot convert envvar {} value {} to an integer'
.format(envvar, value))
@classmethod
def custom(cls, envvar, default, parse):
value = environ.get(envvar)
if value is None:
return default
try:
return parse(value)
except Exception as e:
raise cls.Error('cannot parse envvar {} value {}'
.format(envvar, value)) from e
@classmethod
def obsolete(cls, envvars):
bad = [envvar for envvar in envvars if environ.get(envvar)]
if bad:
raise cls.Error('remove obsolete environment variables {}'
.format(bad))
def event_loop_policy(self):
policy = self.default('EVENT_LOOP_POLICY', None)
if policy is None:
return None
if policy == 'uvloop':
import uvloop
return uvloop.EventLoopPolicy()
raise self.Error('unknown event loop policy "{}"'.format(policy))
def cs_host(self, *, for_rpc):
'''Returns the 'host' argument to pass to asyncio's create_server
call. The result can be a single host name string, a list of
host name strings, or an empty string to bind to all interfaces.
If rpc is True the host to use for the RPC server is returned.
Otherwise the host to use for SSL/TCP servers is returned.
'''
host = self.rpc_host if for_rpc else self.host
result = [part.strip() for part in host.split(',')]
if len(result) == 1:
result = result[0]
# An empty result indicates all interfaces, which we do not
# permitted for an RPC server.
if for_rpc and not result:
result = 'localhost'
return result
def sane_max_sessions(self): def sane_max_sessions(self):
'''Return the maximum number of sessions to permit. Normally this '''Return the maximum number of sessions to permit. Normally this
is MAX_SESSIONS. However, to prevent open file exhaustion, ajdust is MAX_SESSIONS. However, to prevent open file exhaustion, ajdust

View file

@ -1,99 +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 server environment configuration and defaults.'''
from os import environ
from torba.server.util import class_logger
class EnvBase(object):
'''Wraps environment configuration.'''
class Error(Exception):
pass
def __init__(self):
self.logger = class_logger(__name__, self.__class__.__name__)
self.allow_root = self.boolean('ALLOW_ROOT', False)
self.host = self.default('HOST', 'localhost')
self.rpc_host = self.default('RPC_HOST', 'localhost')
self.loop_policy = self.event_loop_policy()
@classmethod
def default(cls, envvar, default):
return environ.get(envvar, default)
@classmethod
def boolean(cls, envvar, default):
default = 'Yes' if default else ''
return bool(cls.default(envvar, default).strip())
@classmethod
def required(cls, envvar):
value = environ.get(envvar)
if value is None:
raise cls.Error('required envvar {} not set'.format(envvar))
return value
@classmethod
def integer(cls, envvar, default):
value = environ.get(envvar)
if value is None:
return default
try:
return int(value)
except Exception:
raise cls.Error('cannot convert envvar {} value {} to an integer'
.format(envvar, value))
@classmethod
def custom(cls, envvar, default, parse):
value = environ.get(envvar)
if value is None:
return default
try:
return parse(value)
except Exception as e:
raise cls.Error('cannot parse envvar {} value {}'
.format(envvar, value)) from e
@classmethod
def obsolete(cls, envvars):
bad = [envvar for envvar in envvars if environ.get(envvar)]
if bad:
raise cls.Error('remove obsolete environment variables {}'
.format(bad))
def event_loop_policy(self):
policy = self.default('EVENT_LOOP_POLICY', None)
if policy is None:
return None
if policy == 'uvloop':
import uvloop
return uvloop.EventLoopPolicy()
raise self.Error('unknown event loop policy "{}"'.format(policy))
def cs_host(self, *, for_rpc):
'''Returns the 'host' argument to pass to asyncio's create_server
call. The result can be a single host name string, a list of
host name strings, or an empty string to bind to all interfaces.
If rpc is True the host to use for the RPC server is returned.
Otherwise the host to use for SSL/TCP servers is returned.
'''
host = self.rpc_host if for_rpc else self.host
result = [part.strip() for part in host.split(',')]
if len(result) == 1:
result = result[0]
# An empty result indicates all interfaces, which we do not
# permitted for an RPC server.
if for_rpc and not result:
result = 'localhost'
return result

View file

@ -1,11 +1,8 @@
import signal import signal
import time
import logging import logging
from functools import partial
import asyncio import asyncio
import torba import torba
from torba.server.db import DB
from torba.server.mempool import MemPool, MemPoolAPI from torba.server.mempool import MemPool, MemPoolAPI
from torba.server.session import SessionManager from torba.server.session import SessionManager
@ -73,21 +70,10 @@ class Server:
self.shutdown_event = asyncio.Event() self.shutdown_event = asyncio.Event()
self.cancellable_tasks = [] self.cancellable_tasks = []
async def start(self): self.notifications = notifications = Notifications()
env = self.env self.daemon = daemon = env.coin.DAEMON(env.coin, env.daemon_url)
min_str, max_str = env.coin.SESSIONCLS.protocol_min_max_strings() self.db = db = env.coin.DB(env)
self.log.info(f'software version: {torba.__version__}') self.bp = bp = env.coin.BLOCK_PROCESSOR(env, db, daemon, notifications)
self.log.info(f'supported protocol versions: {min_str}-{max_str}')
self.log.info(f'event loop policy: {env.loop_policy}')
self.log.info(f'reorg limit is {env.reorg_limit:,d} blocks')
notifications = Notifications()
Daemon = env.coin.DAEMON
BlockProcessor = env.coin.BLOCK_PROCESSOR
daemon = Daemon(env.coin, env.daemon_url)
db = DB(env)
bp = BlockProcessor(env, db, daemon, notifications)
# Set notifications up to implement the MemPoolAPI # Set notifications up to implement the MemPoolAPI
notifications.height = daemon.height notifications.height = daemon.height
@ -96,23 +82,31 @@ class Server:
notifications.raw_transactions = daemon.getrawtransactions notifications.raw_transactions = daemon.getrawtransactions
notifications.lookup_utxos = db.lookup_utxos notifications.lookup_utxos = db.lookup_utxos
MemPoolAPI.register(Notifications) MemPoolAPI.register(Notifications)
mempool = MemPool(env.coin, notifications) self.mempool = mempool = MemPool(env.coin, notifications)
session_mgr = SessionManager( self.session_mgr = SessionManager(
env, db, bp, daemon, mempool, self.shutdown_event env, db, bp, daemon, mempool, self.shutdown_event
) )
await daemon.height() async def start(self):
env = self.env
min_str, max_str = env.coin.SESSIONCLS.protocol_min_max_strings()
self.log.info(f'software version: {torba.__version__}')
self.log.info(f'supported protocol versions: {min_str}-{max_str}')
self.log.info(f'event loop policy: {env.loop_policy}')
self.log.info(f'reorg limit is {env.reorg_limit:,d} blocks')
await self.daemon.height()
def _start_cancellable(run, *args): def _start_cancellable(run, *args):
_flag = asyncio.Event() _flag = asyncio.Event()
self.cancellable_tasks.append(asyncio.ensure_future(run(*args, _flag))) self.cancellable_tasks.append(asyncio.ensure_future(run(*args, _flag)))
return _flag.wait() return _flag.wait()
await _start_cancellable(bp.fetch_and_process_blocks) await _start_cancellable(self.bp.fetch_and_process_blocks)
await db.populate_header_merkle_cache() await self.db.populate_header_merkle_cache()
await _start_cancellable(mempool.keep_synchronized) await _start_cancellable(self.mempool.keep_synchronized)
await _start_cancellable(session_mgr.serve, notifications) await _start_cancellable(self.session_mgr.serve, self.notifications)
def stop(self): def stop(self):
for task in reversed(self.cancellable_tasks): for task in reversed(self.cancellable_tasks):

View file

@ -1,7 +1,8 @@
import sys
import logging import logging
import unittest import unittest
from unittest.case import _Outcome from unittest.case import _Outcome
from .node import Conductor from torba.orchstr8 import Conductor
try: try:
@ -14,6 +15,12 @@ except ImportError:
def _cancel_all_tasks(loop): def _cancel_all_tasks(loop):
pass pass
HANDLER = logging.StreamHandler(sys.stdout)
HANDLER.setFormatter(
logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
)
logging.getLogger().addHandler(HANDLER)
class AsyncioTestCase(unittest.TestCase): class AsyncioTestCase(unittest.TestCase):
# Implementation inspired by discussion: # Implementation inspired by discussion:
@ -119,12 +126,13 @@ class IntegrationTestCase(AsyncioTestCase):
LEDGER = None LEDGER = None
MANAGER = None MANAGER = None
VERBOSITY = logging.WARNING VERBOSITY = logging.WARN
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.conductor = None self.conductor = None
self.blockchain = None self.blockchain = None
self.wallet_node = None
self.manager = None self.manager = None
self.ledger = None self.ledger = None
self.wallet = None self.wallet = None
@ -136,10 +144,11 @@ class IntegrationTestCase(AsyncioTestCase):
) )
await self.conductor.start() await self.conductor.start()
self.blockchain = self.conductor.blockchain_node self.blockchain = self.conductor.blockchain_node
self.manager = self.conductor.wallet_node.manager self.wallet_node = self.conductor.wallet_node
self.ledger = self.conductor.wallet_node.ledger self.manager = self.wallet_node.manager
self.wallet = self.conductor.wallet_node.wallet self.ledger = self.wallet_node.ledger
self.account = self.conductor.wallet_node.wallet.default_account self.wallet = self.wallet_node.wallet
self.account = self.wallet_node.wallet.default_account
async def asyncTearDown(self): async def asyncTearDown(self):
await self.conductor.stop() await self.conductor.stop()

View file

@ -1 +0,0 @@
from .testcase import IntegrationTestCase, AsyncioTestCase

0
torba/ui/__init__.py Normal file
View file

View file

@ -14,9 +14,9 @@ changedir = {toxinidir}/tests
setenv = setenv =
integration: TORBA_LEDGER={envname} integration: TORBA_LEDGER={envname}
commands = commands =
unit: coverage run -p --source={envsitepackagesdir}/torba -m unittest discover -t . unit unit: coverage run -p --source={envsitepackagesdir}/torba -m unittest discover -t . client_tests.unit
integration: torba download integration: torba download
integration: coverage run -p --source={envsitepackagesdir}/torba -m unittest integration.test_transactions integration: coverage run -p --source={envsitepackagesdir}/torba -m unittest client_tests.integration.test_transactions
integration: coverage run -p --source={envsitepackagesdir}/torba -m unittest integration.test_reconnect integration: coverage run -p --source={envsitepackagesdir}/torba -m unittest client_tests.integration.test_reconnect
# Too slow on Travis # Too slow on Travis
# integration: coverage run -p --source={envsitepackagesdir}/torba -m twisted.trial --reactor=asyncio integration.test_blockchain_reorganization # integration: coverage run -p --source={envsitepackagesdir}/torba -m twisted.trial --reactor=asyncio integration.test_blockchain_reorganization