forked from LBRYCommunity/lbry-sdk
reorganization
This commit is contained in:
parent
c1b96a36a7
commit
5f70ae9923
60 changed files with 320 additions and 243 deletions
15
setup.py
15
setup.py
|
@ -51,6 +51,19 @@ setup(
|
|||
'plyvel',
|
||||
'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]'
|
||||
]
|
||||
}
|
||||
)
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
from orchstr8.testcase import IntegrationTestCase
|
||||
from asyncio import sleep
|
||||
import logging
|
||||
from torba.testcase import IntegrationTestCase
|
||||
|
||||
|
||||
class BlockchainReorganizationTests(IntegrationTestCase):
|
||||
|
||||
VERBOSE = True
|
||||
VERBOSITY = logging.WARN
|
||||
|
||||
async def test_reorg(self):
|
||||
self.assertEqual(self.ledger.headers.height, 200)
|
||||
|
@ -14,11 +14,11 @@ class BlockchainReorganizationTests(IntegrationTestCase):
|
|||
self.assertEqual(self.ledger.headers.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, 2, 1, 10)
|
||||
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)
|
||||
await self._simulate_reorg(height, 4, 4, 3)
|
||||
|
||||
|
@ -32,5 +32,5 @@ class BlockchainReorganizationTests(IntegrationTestCase):
|
|||
await self.blockchain.generate(1)
|
||||
height += 1
|
||||
await self.on_header(height)
|
||||
self.assertEquals(height, self.ledger.headers.height)
|
||||
self.assertEqual(height, self.ledger.headers.height)
|
||||
return height
|
|
@ -1,13 +1,13 @@
|
|||
import logging
|
||||
from asyncio import CancelledError
|
||||
|
||||
from torba.testing import IntegrationTestCase
|
||||
from torba.constants import COIN
|
||||
from torba.testcase import IntegrationTestCase
|
||||
from torba.client.constants import COIN
|
||||
|
||||
|
||||
class ReconnectTests(IntegrationTestCase):
|
||||
|
||||
VERBOSITY = logging.DEBUG
|
||||
VERBOSITY = logging.WARN
|
||||
|
||||
async def test_connection_drop_still_receives_events_after_reconnected(self):
|
||||
address1 = await self.account.receiving.get_or_create_usable_address()
|
|
@ -1,12 +1,12 @@
|
|||
import logging
|
||||
import asyncio
|
||||
from torba.testing import IntegrationTestCase
|
||||
from torba.constants import COIN
|
||||
from torba.testcase import IntegrationTestCase
|
||||
from torba.client.constants import COIN
|
||||
|
||||
|
||||
class BasicTransactionTests(IntegrationTestCase):
|
||||
|
||||
VERBOSITY = logging.WARNING
|
||||
VERBOSITY = logging.WARN
|
||||
|
||||
async def test_sending_and_receiving(self):
|
||||
account1, account2 = self.account, self.wallet.generate_account(self.ledger)
|
BIN
tests/client_tests/unit/bitcoin_headers
Normal file
BIN
tests/client_tests/unit/bitcoin_headers
Normal file
Binary file not shown.
|
@ -1,10 +1,10 @@
|
|||
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.baseaccount import HierarchicalDeterministic, SingleKey
|
||||
from torba.wallet import Wallet
|
||||
from torba.client.baseaccount import HierarchicalDeterministic, SingleKey
|
||||
from torba.client.wallet import Wallet
|
||||
|
||||
|
||||
class TestHierarchicalDeterministicAccount(AsyncioTestCase):
|
|
@ -1,6 +1,6 @@
|
|||
import unittest
|
||||
|
||||
from torba.bcd_data_stream import BCDataStream
|
||||
from torba.client.bcd_data_stream import BCDataStream
|
||||
|
||||
|
||||
class TestBCDataStream(unittest.TestCase):
|
|
@ -1,9 +1,9 @@
|
|||
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 torba.bip32 import PubKey, PrivateKey, from_extended_key_string
|
||||
from client_tests.unit.key_fixtures import expected_ids, expected_privkeys, expected_hardened_privkeys
|
||||
from torba.client.bip32 import PubKey, PrivateKey, from_extended_key_string
|
||||
from torba.coin.bitcoinsegwit import MainNetLedger as ledger_class
|
||||
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
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.coinselection import CoinSelector, MAXIMUM_TRIES
|
||||
from torba.constants import CENT
|
||||
from torba.client.coinselection import CoinSelector, MAXIMUM_TRIES
|
||||
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
|
|
@ -1,13 +1,13 @@
|
|||
import unittest
|
||||
|
||||
from torba.wallet import Wallet
|
||||
from torba.constants import COIN
|
||||
from torba.client.wallet import Wallet
|
||||
from torba.client.constants import COIN
|
||||
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):
|
|
@ -1,5 +1,5 @@
|
|||
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):
|
|
@ -1,7 +1,7 @@
|
|||
import os
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
from torba.testing import AsyncioTestCase
|
||||
from torba.testcase import AsyncioTestCase
|
||||
|
||||
from torba.coin.bitcoinsegwit import MainHeaders
|
||||
|
|
@ -2,10 +2,10 @@ import os
|
|||
from binascii import hexlify
|
||||
|
||||
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 .test_headers import BitcoinHeadersTestCase, block_bytes
|
||||
from client_tests.unit.test_transaction import get_transaction, get_output
|
||||
from client_tests.unit.test_headers import BitcoinHeadersTestCase, block_bytes
|
||||
|
||||
|
||||
class MockNetwork:
|
|
@ -1,7 +1,7 @@
|
|||
import unittest
|
||||
from binascii import hexlify
|
||||
|
||||
from torba.mnemonic import Mnemonic
|
||||
from torba.client.mnemonic import Mnemonic
|
||||
|
||||
|
||||
class TestMnemonic(unittest.TestCase):
|
|
@ -1,10 +1,10 @@
|
|||
import unittest
|
||||
from binascii import hexlify, unhexlify
|
||||
|
||||
from torba.bcd_data_stream import BCDataStream
|
||||
from torba.basescript import Template, ParseError, tokenize, push_data
|
||||
from torba.basescript import PUSH_SINGLE, PUSH_INTEGER, PUSH_MANY, OP_HASH160, OP_EQUAL
|
||||
from torba.basescript import BaseInputScript, BaseOutputScript
|
||||
from torba.client.bcd_data_stream import BCDataStream
|
||||
from torba.client.basescript import Template, ParseError, tokenize, push_data
|
||||
from torba.client.basescript import PUSH_SINGLE, PUSH_INTEGER, PUSH_MANY, OP_HASH160, OP_EQUAL
|
||||
from torba.client.basescript import BaseInputScript, BaseOutputScript
|
||||
|
||||
|
||||
def parse(opcodes, source):
|
|
@ -2,11 +2,11 @@ import unittest
|
|||
from binascii import hexlify, unhexlify
|
||||
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.wallet import Wallet
|
||||
from torba.constants import CENT, COIN
|
||||
from torba.client.wallet import Wallet
|
||||
from torba.client.constants import CENT, COIN
|
||||
|
||||
|
||||
NULL_HASH = b'\x00'*32
|
|
@ -1,6 +1,6 @@
|
|||
import unittest
|
||||
|
||||
from torba.util import ArithUint256
|
||||
from torba.client.util import ArithUint256
|
||||
|
||||
|
||||
class TestArithUint256(unittest.TestCase):
|
|
@ -1,11 +1,11 @@
|
|||
import tempfile
|
||||
|
||||
from torba.testing import AsyncioTestCase
|
||||
from torba.testcase import AsyncioTestCase
|
||||
|
||||
from torba.coin.bitcoinsegwit import MainNetLedger as BTCLedger
|
||||
from torba.coin.bitcoincash import MainNetLedger as BCHLedger
|
||||
from torba.basemanager import BaseWalletManager
|
||||
from torba.wallet import Wallet, WalletStorage
|
||||
from torba.client.basemanager import BaseWalletManager
|
||||
from torba.client.wallet import Wallet, WalletStorage
|
||||
|
||||
|
||||
class TestWalletCreation(AsyncioTestCase):
|
0
torba/client/__init__.py
Normal file
0
torba/client/__init__.py
Normal file
|
@ -2,14 +2,13 @@ import random
|
|||
import typing
|
||||
from typing import Dict, Tuple, Type, Optional, Any
|
||||
|
||||
from torba.mnemonic import Mnemonic
|
||||
from torba.bip32 import PrivateKey, PubKey, from_extended_key_string
|
||||
from torba.hash import aes_encrypt, aes_decrypt
|
||||
from torba.constants import COIN
|
||||
from torba.client.mnemonic import Mnemonic
|
||||
from torba.client.bip32 import PrivateKey, PubKey, from_extended_key_string
|
||||
from torba.client.hash import aes_encrypt, aes_decrypt
|
||||
from torba.client.constants import COIN
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from torba import baseledger
|
||||
from torba import wallet as basewallet
|
||||
from torba.client import baseledger, wallet as basewallet
|
||||
|
||||
|
||||
class AddressManager:
|
|
@ -8,9 +8,9 @@ from typing import Tuple, List, Union, Callable, Any, Awaitable, Iterable
|
|||
|
||||
import sqlite3
|
||||
|
||||
from torba.hash import TXRefImmutable
|
||||
from torba.basetransaction import BaseTransaction
|
||||
from torba.baseaccount import BaseAccount
|
||||
from torba.client.hash import TXRefImmutable
|
||||
from torba.client.basetransaction import BaseTransaction
|
||||
from torba.client.baseaccount import BaseAccount
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
|
@ -5,8 +5,8 @@ from io import BytesIO
|
|||
from typing import Optional, Iterator, Tuple
|
||||
from binascii import hexlify
|
||||
|
||||
from torba.util import ArithUint256
|
||||
from torba.hash import double_sha256
|
||||
from torba.client.util import ArithUint256
|
||||
from torba.client.hash import double_sha256
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
|
@ -8,15 +8,13 @@ from typing import Dict, Type, Iterable
|
|||
from operator import itemgetter
|
||||
from collections import namedtuple
|
||||
|
||||
from torba import baseaccount
|
||||
from torba import basenetwork
|
||||
from torba import basetransaction
|
||||
from torba.basedatabase import BaseDatabase
|
||||
from torba.baseheader import BaseHeaders
|
||||
from torba.coinselection import CoinSelector
|
||||
from torba.constants import COIN, NULL_HASH32
|
||||
from torba.client import baseaccount, basenetwork, basetransaction
|
||||
from torba.client.basedatabase import BaseDatabase
|
||||
from torba.client.baseheader import BaseHeaders
|
||||
from torba.client.coinselection import CoinSelector
|
||||
from torba.client.constants import COIN, NULL_HASH32
|
||||
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__)
|
||||
|
|
@ -2,8 +2,8 @@ import asyncio
|
|||
import logging
|
||||
from typing import Type, MutableSequence, MutableMapping
|
||||
|
||||
from torba.baseledger import BaseLedger, LedgerRegistry
|
||||
from torba.wallet import Wallet, WalletStorage
|
||||
from torba.client.baseledger import BaseLedger, LedgerRegistry
|
||||
from torba.client.wallet import Wallet, WalletStorage
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
|
@ -3,8 +3,8 @@ from binascii import hexlify
|
|||
from collections import namedtuple
|
||||
from typing import List
|
||||
|
||||
from torba.bcd_data_stream import BCDataStream
|
||||
from torba.util import subclass_tuple
|
||||
from torba.client.bcd_data_stream import BCDataStream
|
||||
from torba.client.util import subclass_tuple
|
||||
|
||||
# bitcoin opcodes
|
||||
OP_0 = 0x00
|
|
@ -3,16 +3,15 @@ import typing
|
|||
from typing import List, Iterable, Optional
|
||||
from binascii import hexlify
|
||||
|
||||
from torba.basescript import BaseInputScript, BaseOutputScript
|
||||
from torba.baseaccount import BaseAccount
|
||||
from torba.constants import COIN, NULL_HASH32
|
||||
from torba.bcd_data_stream import BCDataStream
|
||||
from torba.hash import sha256, TXRef, TXRefImmutable
|
||||
from torba.util import ReadOnlyList
|
||||
from torba.client.basescript import BaseInputScript, BaseOutputScript
|
||||
from torba.client.baseaccount import BaseAccount
|
||||
from torba.client.constants import COIN, NULL_HASH32
|
||||
from torba.client.bcd_data_stream import BCDataStream
|
||||
from torba.client.hash import sha256, TXRef, TXRefImmutable
|
||||
from torba.client.util import ReadOnlyList
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from torba import baseledger
|
||||
|
||||
from torba.client import baseledger
|
||||
|
||||
log = logging.getLogger()
|
||||
|
|
@ -9,8 +9,8 @@
|
|||
""" Logic for BIP32 Hierarchical Key Derivation. """
|
||||
from coincurve import PublicKey, PrivateKey as _PrivateKey
|
||||
|
||||
from torba.hash import Base58, hmac_sha512, hash160, double_sha256
|
||||
from torba.util import cachedproperty
|
||||
from torba.client.hash import Base58, hmac_sha512, hash160, double_sha256
|
||||
from torba.client.util import cachedproperty
|
||||
|
||||
|
||||
class DerivationError(Exception):
|
|
@ -3,8 +3,8 @@ import argparse
|
|||
import asyncio
|
||||
import aiohttp
|
||||
|
||||
from torba.testing.node import Conductor, get_ledger_from_environment, get_blockchain_node_from_ledger
|
||||
from torba.testing.service import TestingServiceAPI
|
||||
from torba.orchstr8.node import Conductor, get_ledger_from_environment, get_blockchain_node_from_ledger
|
||||
from torba.orchstr8.service import TestingServiceAPI
|
||||
|
||||
|
||||
def get_argument_parser():
|
|
@ -1,7 +1,7 @@
|
|||
from random import Random
|
||||
from typing import List
|
||||
|
||||
from torba import basetransaction
|
||||
from torba.client import basetransaction
|
||||
|
||||
MAXIMUM_TRIES = 100000
|
||||
|
|
@ -18,8 +18,8 @@ from cryptography.hazmat.primitives.ciphers.algorithms import AES
|
|||
from cryptography.hazmat.primitives.padding import PKCS7
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
from torba.util import bytes_to_int, int_to_bytes
|
||||
from torba.constants import NULL_HASH32
|
||||
from torba.client.util import bytes_to_int, int_to_bytes
|
||||
from torba.client.constants import NULL_HASH32
|
||||
|
||||
|
||||
class TXRef:
|
|
@ -12,8 +12,8 @@ from secrets import randbelow
|
|||
|
||||
import pbkdf2
|
||||
|
||||
from torba.hash import hmac_sha512
|
||||
from torba.words import english
|
||||
from torba.client.hash import hmac_sha512
|
||||
from torba.client.words import english
|
||||
|
||||
# The hash of the mnemonic seed must begin with this
|
||||
SEED_PREFIX = b'01' # Standard wallet
|
|
@ -5,9 +5,7 @@ import typing
|
|||
from typing import Sequence, MutableSequence
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from torba import baseaccount
|
||||
from torba import baseledger
|
||||
from torba import basemanager
|
||||
from torba.client import basemanager, baseaccount, baseledger
|
||||
|
||||
|
||||
class Wallet:
|
0
torba/client/words/__init__.py
Normal file
0
torba/client/words/__init__.py
Normal file
|
@ -7,8 +7,8 @@ __node_url__ = (
|
|||
__spvserver__ = 'torba.server.coins.BitcoinCashRegtest'
|
||||
|
||||
from binascii import unhexlify
|
||||
from torba.baseledger import BaseLedger
|
||||
from torba.basetransaction import BaseTransaction
|
||||
from torba.client.baseledger import BaseLedger
|
||||
from torba.client.basetransaction import BaseTransaction
|
||||
from .bitcoinsegwit import MainHeaders, UnverifiedHeaders
|
||||
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@ __spvserver__ = 'torba.server.coins.BitcoinSegwitRegtest'
|
|||
import struct
|
||||
from typing import Optional
|
||||
from binascii import hexlify, unhexlify
|
||||
from torba.baseledger import BaseLedger
|
||||
from torba.baseheader import BaseHeaders, ArithUint256
|
||||
from torba.client.baseledger import BaseLedger
|
||||
from torba.client.baseheader import BaseHeaders, ArithUint256
|
||||
|
||||
|
||||
class MainHeaders(BaseHeaders):
|
||||
|
|
2
torba/orchstr8/__init__.py
Normal file
2
torba/orchstr8/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
from .node import Conductor
|
||||
from .service import ConductorService
|
89
torba/orchstr8/cli.py
Normal file
89
torba/orchstr8/cli.py
Normal 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()
|
|
@ -14,10 +14,10 @@ import requests
|
|||
|
||||
from torba.server.server import Server
|
||||
from torba.server.env import Env
|
||||
from torba.wallet import Wallet
|
||||
from torba.baseledger import BaseLedger, BlockHeightEvent
|
||||
from torba.basemanager import BaseWalletManager
|
||||
from torba.baseaccount import BaseAccount
|
||||
from torba.client.wallet import Wallet
|
||||
from torba.client.baseledger import BaseLedger, BlockHeightEvent
|
||||
from torba.client.basemanager import BaseWalletManager
|
||||
from torba.client.baseaccount import BaseAccount
|
||||
|
||||
|
||||
def get_manager_from_environment(default_manager=BaseWalletManager):
|
||||
|
@ -170,6 +170,7 @@ class SPVNode:
|
|||
}
|
||||
os.environ.update(conf)
|
||||
self.server = Server(Env(self.coin_class))
|
||||
self.server.bp.prefetcher.polling_delay = 0.5
|
||||
await self.server.start()
|
||||
|
||||
async def stop(self, cleanup=True):
|
|
@ -25,7 +25,7 @@ class WebSocketLogHandler(logging.Handler):
|
|||
self.handleError(record)
|
||||
|
||||
|
||||
class TestingServiceAPI:
|
||||
class ConductorService:
|
||||
|
||||
def __init__(self, stack: Conductor, loop: asyncio.AbstractEventLoop) -> None:
|
||||
self.stack = stack
|
|
@ -45,6 +45,7 @@ from torba.server.hash import HASHX_LEN, hex_str_to_hash
|
|||
from torba.server.script import ScriptPubKey, OpCodes
|
||||
import torba.server.tx as lib_tx
|
||||
import torba.server.block_processor as block_proc
|
||||
from torba.server.db import DB
|
||||
import torba.server.daemon as daemon
|
||||
from torba.server.session import ElectrumX, DashElectrumX
|
||||
|
||||
|
@ -71,6 +72,7 @@ class Coin(object):
|
|||
DESERIALIZER = lib_tx.Deserializer
|
||||
DAEMON = daemon.Daemon
|
||||
BLOCK_PROCESSOR = block_proc.BlockProcessor
|
||||
DB = DB
|
||||
HEADER_VALUES = [
|
||||
'version', 'prev_block_hash', 'merkle_root', 'timestamp', 'bits', 'nonce'
|
||||
]
|
||||
|
|
|
@ -5,33 +5,35 @@
|
|||
# See the file "LICENCE" for information about the copyright
|
||||
# and warranty status of this software.
|
||||
|
||||
'''Class for handling environment configuration and defaults.'''
|
||||
|
||||
|
||||
import re
|
||||
import resource
|
||||
from os import environ
|
||||
from collections import namedtuple
|
||||
from ipaddress import ip_address
|
||||
|
||||
from torba.server.util import class_logger
|
||||
from torba.server.coins import Coin
|
||||
from torba.server.env_base import EnvBase
|
||||
import torba.server.util as lib_util
|
||||
|
||||
|
||||
NetIdentity = namedtuple('NetIdentity', 'host tcp_port ssl_port nick_suffix')
|
||||
|
||||
|
||||
class Env(EnvBase):
|
||||
'''Wraps environment configuration. Optionally, accepts a Coin class
|
||||
as first argument to have ElectrumX serve custom coins not part of
|
||||
the standard distribution.
|
||||
'''
|
||||
class Env:
|
||||
|
||||
# Peer discovery
|
||||
PD_OFF, PD_SELF, PD_ON = range(3)
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
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.db_dir = self.required('DB_DIRECTORY')
|
||||
self.db_engine = self.default('DB_ENGINE', 'leveldb')
|
||||
|
@ -55,8 +57,7 @@ class Env(EnvBase):
|
|||
self.rpc_port = self.integer('RPC_PORT', 8000)
|
||||
self.max_subscriptions = self.integer('MAX_SUBSCRIPTIONS', 10000)
|
||||
self.banner_file = self.default('BANNER_FILE', None)
|
||||
self.tor_banner_file = self.default('TOR_BANNER_FILE',
|
||||
self.banner_file)
|
||||
self.tor_banner_file = self.default('TOR_BANNER_FILE', self.banner_file)
|
||||
self.anon_logs = self.boolean('ANON_LOGS', False)
|
||||
self.log_sessions = self.integer('LOG_SESSIONS', 3600)
|
||||
# Peer discovery
|
||||
|
@ -83,6 +84,78 @@ class Env(EnvBase):
|
|||
for identity in (clearnet_identity, tor_identity)
|
||||
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):
|
||||
'''Return the maximum number of sessions to permit. Normally this
|
||||
is MAX_SESSIONS. However, to prevent open file exhaustion, ajdust
|
||||
|
|
|
@ -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
|
|
@ -1,11 +1,8 @@
|
|||
import signal
|
||||
import time
|
||||
import logging
|
||||
from functools import partial
|
||||
import asyncio
|
||||
|
||||
import torba
|
||||
from torba.server.db import DB
|
||||
from torba.server.mempool import MemPool, MemPoolAPI
|
||||
from torba.server.session import SessionManager
|
||||
|
||||
|
@ -73,21 +70,10 @@ class Server:
|
|||
self.shutdown_event = asyncio.Event()
|
||||
self.cancellable_tasks = []
|
||||
|
||||
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')
|
||||
|
||||
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)
|
||||
self.notifications = notifications = Notifications()
|
||||
self.daemon = daemon = env.coin.DAEMON(env.coin, env.daemon_url)
|
||||
self.db = db = env.coin.DB(env)
|
||||
self.bp = bp = env.coin.BLOCK_PROCESSOR(env, db, daemon, notifications)
|
||||
|
||||
# Set notifications up to implement the MemPoolAPI
|
||||
notifications.height = daemon.height
|
||||
|
@ -96,23 +82,31 @@ class Server:
|
|||
notifications.raw_transactions = daemon.getrawtransactions
|
||||
notifications.lookup_utxos = db.lookup_utxos
|
||||
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
|
||||
)
|
||||
|
||||
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):
|
||||
_flag = asyncio.Event()
|
||||
self.cancellable_tasks.append(asyncio.ensure_future(run(*args, _flag)))
|
||||
return _flag.wait()
|
||||
|
||||
await _start_cancellable(bp.fetch_and_process_blocks)
|
||||
await db.populate_header_merkle_cache()
|
||||
await _start_cancellable(mempool.keep_synchronized)
|
||||
await _start_cancellable(session_mgr.serve, notifications)
|
||||
await _start_cancellable(self.bp.fetch_and_process_blocks)
|
||||
await self.db.populate_header_merkle_cache()
|
||||
await _start_cancellable(self.mempool.keep_synchronized)
|
||||
await _start_cancellable(self.session_mgr.serve, self.notifications)
|
||||
|
||||
def stop(self):
|
||||
for task in reversed(self.cancellable_tasks):
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import sys
|
||||
import logging
|
||||
import unittest
|
||||
from unittest.case import _Outcome
|
||||
from .node import Conductor
|
||||
from torba.orchstr8 import Conductor
|
||||
|
||||
|
||||
try:
|
||||
|
@ -14,6 +15,12 @@ except ImportError:
|
|||
def _cancel_all_tasks(loop):
|
||||
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):
|
||||
# Implementation inspired by discussion:
|
||||
|
@ -119,12 +126,13 @@ class IntegrationTestCase(AsyncioTestCase):
|
|||
|
||||
LEDGER = None
|
||||
MANAGER = None
|
||||
VERBOSITY = logging.WARNING
|
||||
VERBOSITY = logging.WARN
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.conductor = None
|
||||
self.blockchain = None
|
||||
self.wallet_node = None
|
||||
self.manager = None
|
||||
self.ledger = None
|
||||
self.wallet = None
|
||||
|
@ -136,10 +144,11 @@ class IntegrationTestCase(AsyncioTestCase):
|
|||
)
|
||||
await self.conductor.start()
|
||||
self.blockchain = self.conductor.blockchain_node
|
||||
self.manager = self.conductor.wallet_node.manager
|
||||
self.ledger = self.conductor.wallet_node.ledger
|
||||
self.wallet = self.conductor.wallet_node.wallet
|
||||
self.account = self.conductor.wallet_node.wallet.default_account
|
||||
self.wallet_node = self.conductor.wallet_node
|
||||
self.manager = self.wallet_node.manager
|
||||
self.ledger = self.wallet_node.ledger
|
||||
self.wallet = self.wallet_node.wallet
|
||||
self.account = self.wallet_node.wallet.default_account
|
||||
|
||||
async def asyncTearDown(self):
|
||||
await self.conductor.stop()
|
|
@ -1 +0,0 @@
|
|||
from .testcase import IntegrationTestCase, AsyncioTestCase
|
0
torba/ui/__init__.py
Normal file
0
torba/ui/__init__.py
Normal file
6
tox.ini
6
tox.ini
|
@ -14,9 +14,9 @@ changedir = {toxinidir}/tests
|
|||
setenv =
|
||||
integration: TORBA_LEDGER={envname}
|
||||
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: coverage run -p --source={envsitepackagesdir}/torba -m unittest 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_transactions
|
||||
integration: coverage run -p --source={envsitepackagesdir}/torba -m unittest client_tests.integration.test_reconnect
|
||||
# Too slow on Travis
|
||||
# integration: coverage run -p --source={envsitepackagesdir}/torba -m twisted.trial --reactor=asyncio integration.test_blockchain_reorganization
|
||||
|
|
Loading…
Reference in a new issue