updated files and scripts post torba merge

This commit is contained in:
Lex Berezhny 2019-12-31 14:52:57 -05:00
parent ea5322af82
commit f0d7ea4cc6
98 changed files with 927 additions and 1520 deletions

View file

@ -31,32 +31,18 @@ test:lint:
- make install tools - make install tools
- make lint - make lint
test:lbry-unit: test:unit:
stage: test stage: test
script: script:
- make install tools - make install tools
- cd lbry && HOME=/tmp coverage run -p --source=lbry -m unittest discover -vv tests.unit - cd lbry && HOME=/tmp coverage run -p --source=lbry -m unittest discover -vv tests.unit
test:lbry-integ: test:integration:
stage: test stage: test
script: script:
- pip install coverage tox-travis - pip install coverage tox-travis
- cd lbry && tox - cd lbry && tox
test:torba-unit:
stage: test
script:
- pip install coverage tox-travis
- cd torba/tests
- tox -e py37-unit
test:torba-integ:
stage: test
script:
- pip install coverage tox-travis
- cd torba/tests
- tox -e py37-integration-torba.coin.bitcoinsegwit
test:json-api: test:json-api:
stage: test stage: test
script: script:

View file

@ -12,7 +12,7 @@ jobs:
script: make lint script: make lint
- stage: test - stage: test
name: "LBRY Unit Tests" name: "Unit Tests"
install: install:
- make install tools - make install tools
script: script:
@ -20,7 +20,7 @@ jobs:
after_success: after_success:
- coverage combine lbry/ - coverage combine lbry/
- name: "LBRY Integration Tests" - name: "Integration Tests"
install: install:
- pip install coverage tox-travis - pip install coverage tox-travis
- sudo mount -o mode=1777,nosuid,nodev -t tmpfs tmpfs /tmp - sudo mount -o mode=1777,nosuid,nodev -t tmpfs tmpfs /tmp
@ -28,19 +28,6 @@ jobs:
after_success: after_success:
- coverage combine lbry - coverage combine lbry
- &torba-tests
name: "Torba Unit Tests"
env: TESTTYPE=unit
install:
- pip install coverage tox-travis
script: cd torba && tox
after_success:
- coverage combine torba/tests
- <<: *torba-tests
name: "Torba Integration Tests"
env: TESTTYPE=integration
- name: "Run Examples" - name: "Run Examples"
install: install:
- make install tools - make install tools

View file

@ -5,7 +5,6 @@ install:
--global-option=fetch \ --global-option=fetch \
--global-option=--version --global-option=3.30.1 --global-option=--all \ --global-option=--version --global-option=3.30.1 --global-option=--all \
--global-option=build --global-option=--enable --global-option=fts5 --global-option=build --global-option=--enable --global-option=fts5
cd torba && pip install -e .
cd lbry && pip install -e . cd lbry && pip install -e .
tools: tools:
@ -13,13 +12,11 @@ tools:
pip install coverage astroid pylint pip install coverage astroid pylint
lint: lint:
cd lbry && pylint lbry cd lbry && pylint --rcfile=setup.cfg lbry
cd torba && pylint --rcfile=setup.cfg torba cd lbry && mypy --ignore-missing-imports lbry
cd torba && mypy --ignore-missing-imports torba
test: test:
cd lbry && tox cd lbry && tox
cd torba && tox
idea: idea:
mkdir -p .idea mkdir -p .idea

View file

@ -1,4 +1,4 @@
__name__ = "lbry" __name__ = "lbry"
__version__ = "0.51.2" __version__ = "0.51.2"
version = tuple(__version__.split('.')) version = tuple(map(int, __version__.split('.')))

View file

@ -9,7 +9,7 @@ from contextlib import contextmanager
from appdirs import user_data_dir, user_config_dir from appdirs import user_data_dir, user_config_dir
from lbry.error import InvalidCurrencyError from lbry.error import InvalidCurrencyError
from lbry.dht import constants from lbry.dht import constants
from torba.client.coinselection import STRATEGIES from lbry.wallet.client.coinselection import STRATEGIES
log = logging.getLogger(__name__) log = logging.getLogger(__name__)

2
lbry/lbry/constants.py Normal file
View file

@ -0,0 +1,2 @@
CENT = 1000000
COIN = 100*CENT

View file

View file

@ -0,0 +1,86 @@
from lbry.crypto.hash import double_sha256
from lbry.crypto.util import bytes_to_int, int_to_bytes
class Base58Error(Exception):
""" Exception used for Base58 errors. """
class Base58:
""" Class providing base 58 functionality. """
chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
assert len(chars) == 58
char_map = {c: n for n, c in enumerate(chars)}
@classmethod
def char_value(cls, c):
val = cls.char_map.get(c)
if val is None:
raise Base58Error(f'invalid base 58 character "{c}"')
return val
@classmethod
def decode(cls, txt):
""" Decodes txt into a big-endian bytearray. """
if isinstance(txt, memoryview):
txt = str(txt)
if isinstance(txt, bytes):
txt = txt.decode()
if not isinstance(txt, str):
raise TypeError('a string is required')
if not txt:
raise Base58Error('string cannot be empty')
value = 0
for c in txt:
value = value * 58 + cls.char_value(c)
result = int_to_bytes(value)
# Prepend leading zero bytes if necessary
count = 0
for c in txt:
if c != '1':
break
count += 1
if count:
result = bytes((0,)) * count + result
return result
@classmethod
def encode(cls, be_bytes):
"""Converts a big-endian bytearray into a base58 string."""
value = bytes_to_int(be_bytes)
txt = ''
while value:
value, mod = divmod(value, 58)
txt += cls.chars[mod]
for byte in be_bytes:
if byte != 0:
break
txt += '1'
return txt[::-1]
@classmethod
def decode_check(cls, txt, hash_fn=double_sha256):
""" Decodes a Base58Check-encoded string to a payload. The version prefixes it. """
be_bytes = cls.decode(txt)
result, check = be_bytes[:-4], be_bytes[-4:]
if check != hash_fn(result)[:4]:
raise Base58Error(f'invalid base 58 checksum for {txt}')
return result
@classmethod
def encode_check(cls, payload, hash_fn=double_sha256):
""" Encodes a payload bytearray (which includes the version byte(s))
into a Base58Check string."""
be_bytes = payload + hash_fn(payload)[:4]
return cls.encode(be_bytes)

69
lbry/lbry/crypto/crypt.py Normal file
View file

@ -0,0 +1,69 @@
import os
import base64
import typing
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt
from cryptography.hazmat.primitives.ciphers import Cipher, modes
from cryptography.hazmat.primitives.ciphers.algorithms import AES
from cryptography.hazmat.primitives.padding import PKCS7
from cryptography.hazmat.backends import default_backend
from lbry.error import InvalidPasswordError
from lbry.crypto.hash import double_sha256
def aes_encrypt(secret: str, value: str, init_vector: bytes = None) -> str:
if init_vector is not None:
assert len(init_vector) == 16
else:
init_vector = os.urandom(16)
key = double_sha256(secret.encode())
encryptor = Cipher(AES(key), modes.CBC(init_vector), default_backend()).encryptor()
padder = PKCS7(AES.block_size).padder()
padded_data = padder.update(value.encode()) + padder.finalize()
encrypted_data = encryptor.update(padded_data) + encryptor.finalize()
return base64.b64encode(init_vector + encrypted_data).decode()
def aes_decrypt(secret: str, value: str) -> typing.Tuple[str, bytes]:
try:
data = base64.b64decode(value.encode())
key = double_sha256(secret.encode())
init_vector, data = data[:16], data[16:]
decryptor = Cipher(AES(key), modes.CBC(init_vector), default_backend()).decryptor()
unpadder = PKCS7(AES.block_size).unpadder()
result = unpadder.update(decryptor.update(data)) + unpadder.finalize()
return result.decode(), init_vector
except ValueError as e:
if e.args[0] == 'Invalid padding bytes.':
raise InvalidPasswordError()
raise
def better_aes_encrypt(secret: str, value: bytes) -> bytes:
init_vector = os.urandom(16)
key = scrypt(secret.encode(), salt=init_vector)
encryptor = Cipher(AES(key), modes.CBC(init_vector), default_backend()).encryptor()
padder = PKCS7(AES.block_size).padder()
padded_data = padder.update(value) + padder.finalize()
encrypted_data = encryptor.update(padded_data) + encryptor.finalize()
return base64.b64encode(b's:8192:16:1:' + init_vector + encrypted_data)
def better_aes_decrypt(secret: str, value: bytes) -> bytes:
try:
data = base64.b64decode(value)
_, scryp_n, scrypt_r, scrypt_p, data = data.split(b':', maxsplit=4)
init_vector, data = data[:16], data[16:]
key = scrypt(secret.encode(), init_vector, int(scryp_n), int(scrypt_r), int(scrypt_p))
decryptor = Cipher(AES(key), modes.CBC(init_vector), default_backend()).decryptor()
unpadder = PKCS7(AES.block_size).unpadder()
return unpadder.update(decryptor.update(data)) + unpadder.finalize()
except ValueError as e:
if e.args[0] == 'Invalid padding bytes.':
raise InvalidPasswordError()
raise
def scrypt(passphrase, salt, scrypt_n=1<<13, scrypt_r=16, scrypt_p=1):
kdf = Scrypt(salt, length=32, n=scrypt_n, r=scrypt_r, p=scrypt_p, backend=default_backend())
return kdf.derive(passphrase)

47
lbry/lbry/crypto/hash.py Normal file
View file

@ -0,0 +1,47 @@
import hashlib
import hmac
from binascii import hexlify, unhexlify
def sha256(x):
""" Simple wrapper of hashlib sha256. """
return hashlib.sha256(x).digest()
def sha512(x):
""" Simple wrapper of hashlib sha512. """
return hashlib.sha512(x).digest()
def ripemd160(x):
""" Simple wrapper of hashlib ripemd160. """
h = hashlib.new('ripemd160')
h.update(x)
return h.digest()
def double_sha256(x):
""" SHA-256 of SHA-256, as used extensively in bitcoin. """
return sha256(sha256(x))
def hmac_sha512(key, msg):
""" Use SHA-512 to provide an HMAC. """
return hmac.new(key, msg, hashlib.sha512).digest()
def hash160(x):
""" RIPEMD-160 of SHA-256.
Used to make bitcoin addresses from pubkeys. """
return ripemd160(sha256(x))
def hash_to_hex_str(x):
""" Convert a big-endian binary hash to displayed hex string.
Display form of a binary hash is reversed and converted to hex. """
return hexlify(reversed(x))
def hex_str_to_hash(x):
""" Convert a displayed hex string to a binary hash. """
return reversed(unhexlify(x))

13
lbry/lbry/crypto/util.py Normal file
View file

@ -0,0 +1,13 @@
from binascii import unhexlify, hexlify
def bytes_to_int(be_bytes):
""" Interprets a big-endian sequence of bytes as an integer. """
return int(hexlify(be_bytes), 16)
def int_to_bytes(value):
""" Converts an integer to a big-endian sequence of bytes. """
length = (value.bit_length() + 7) // 8
s = '%x' % value
return unhexlify(('0' * (len(s) % 2) + s).zfill(length * 2))

View file

@ -17,8 +17,8 @@ from traceback import format_exc
from aiohttp import web from aiohttp import web
from functools import wraps, partial from functools import wraps, partial
from google.protobuf.message import DecodeError from google.protobuf.message import DecodeError
from torba.client.wallet import Wallet, ENCRYPT_ON_DISK from lbry.wallet.client.wallet import Wallet, ENCRYPT_ON_DISK
from torba.client.baseaccount import SingleKey, HierarchicalDeterministic from lbry.wallet.client.baseaccount import SingleKey, HierarchicalDeterministic
from lbry import utils from lbry import utils
from lbry.conf import Config, Setting, NOT_SET from lbry.conf import Config, Setting, NOT_SET

View file

@ -3,9 +3,9 @@ import time
import hashlib import hashlib
import binascii import binascii
from lbry import utils
import ecdsa import ecdsa
from torba.client.hash import sha256 from lbry import utils
from lbry.crypto.hash import sha256
from lbry.wallet.transaction import Output from lbry.wallet.transaction import Output
log = logging.getLogger(__name__) log = logging.getLogger(__name__)

View file

@ -6,8 +6,8 @@ from json import JSONEncoder
from google.protobuf.message import DecodeError from google.protobuf.message import DecodeError
from torba.client.wallet import Wallet from lbry.wallet.client.wallet import Wallet
from torba.client.bip32 import PubKey from lbry.wallet.client.bip32 import PubKey
from lbry.schema.claim import Claim from lbry.schema.claim import Claim
from lbry.wallet.ledger import MainNetLedger, Account from lbry.wallet.ledger import MainNetLedger, Account
from lbry.wallet.transaction import Transaction, Output from lbry.wallet.transaction import Transaction, Output

View file

@ -5,7 +5,7 @@ import typing
import asyncio import asyncio
import binascii import binascii
import time import time
from torba.client.basedatabase import SQLiteMixin from lbry.wallet.client.basedatabase import SQLiteMixin
from lbry.conf import Config from lbry.conf import Config
from lbry.wallet.dewies import dewies_to_lbc, lbc_to_dewies from lbry.wallet.dewies import dewies_to_lbc, lbc_to_dewies
from lbry.wallet.transaction import Transaction from lbry.wallet.transaction import Transaction

View file

@ -3,7 +3,6 @@ import os
import logging.handlers import logging.handlers
from lbry import build_type, __version__ as lbrynet_version from lbry import build_type, __version__ as lbrynet_version
from torba import __version__ as torba_version
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -19,7 +18,7 @@ def get_platform() -> dict:
"os_release": platform.release(), "os_release": platform.release(),
"os_system": os_system, "os_system": os_system,
"lbrynet_version": lbrynet_version, "lbrynet_version": lbrynet_version,
"torba_version": torba_version, "version": lbrynet_version,
"build": build_type.BUILD, # CI server sets this during build step "build": build_type.BUILD, # CI server sets this during build step
} }
if p["os_system"] == "Linux": if p["os_system"] == "Linux":

View file

@ -8,8 +8,8 @@ from decimal import Decimal, ROUND_UP
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
from google.protobuf.json_format import MessageToDict from google.protobuf.json_format import MessageToDict
from torba.client.hash import Base58 from lbry.crypto.base58 import Base58
from torba.client.constants import COIN from lbry.constants import COIN
from lbry.schema.mime_types import guess_media_type from lbry.schema.mime_types import guess_media_type
from lbry.schema.base import Metadata, BaseMessageList from lbry.schema.base import Metadata, BaseMessageList

View file

@ -7,13 +7,10 @@ from time import time
from binascii import unhexlify from binascii import unhexlify
from functools import partial from functools import partial
from torba.testcase import IntegrationTestCase, WalletNode from lbry.wallet.testcase import IntegrationTestCase, WalletNode
import lbry.wallet
from lbry.conf import Config from lbry.conf import Config
from lbry.extras.daemon.Daemon import Daemon, jsonrpc_dumps_pretty from lbry.extras.daemon.Daemon import Daemon, jsonrpc_dumps_pretty
from lbry.wallet import LbryWalletManager
from lbry.wallet.account import Account from lbry.wallet.account import Account
from lbry.wallet.transaction import Transaction from lbry.wallet.transaction import Transaction
from lbry.extras.daemon.Components import Component, WalletComponent from lbry.extras.daemon.Components import Component, WalletComponent
@ -73,8 +70,6 @@ class ExchangeRateManagerComponent(Component):
class CommandTestCase(IntegrationTestCase): class CommandTestCase(IntegrationTestCase):
LEDGER = lbry.wallet
MANAGER = LbryWalletManager
VERBOSITY = logging.WARN VERBOSITY = logging.WARN
blob_lru_cache_size = 0 blob_lru_cache_size = 0

View file

@ -7,7 +7,7 @@ from string import hexdigits
import ecdsa import ecdsa
from lbry.wallet.constants import CLAIM_TYPES, TXO_TYPES from lbry.wallet.constants import CLAIM_TYPES, TXO_TYPES
from torba.client.baseaccount import BaseAccount, HierarchicalDeterministic from lbry.wallet.client.baseaccount import BaseAccount, HierarchicalDeterministic
log = logging.getLogger(__name__) log = logging.getLogger(__name__)

View file

@ -1,6 +1,6 @@
import struct import struct
import binascii import binascii
from torba.client.hash import double_sha256 from lbry.wallet.client.hash import double_sha256
class InvalidProofError(Exception): class InvalidProofError(Exception):

View file

@ -6,14 +6,15 @@ import random
import typing import typing
from typing import Dict, Tuple, Type, Optional, Any, List from typing import Dict, Tuple, Type, Optional, Any, List
from torba.client.mnemonic import Mnemonic from lbry.crypto.hash import sha256
from torba.client.bip32 import PrivateKey, PubKey, from_extended_key_string from lbry.crypto.crypt import aes_encrypt, aes_decrypt
from torba.client.hash import aes_encrypt, aes_decrypt, sha256 from lbry.wallet.client.bip32 import PrivateKey, PubKey, from_extended_key_string
from torba.client.constants import COIN from lbry.wallet.client.mnemonic import Mnemonic
from torba.client.errors import InvalidPasswordError from lbry.wallet.client.constants import COIN
from lbry.error import InvalidPasswordError
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from torba.client import baseledger, wallet as basewallet from lbry.wallet.client import baseledger, wallet as basewallet
class AddressManager: class AddressManager:

View file

@ -7,8 +7,8 @@ from typing import Tuple, List, Union, Callable, Any, Awaitable, Iterable, Dict,
import sqlite3 import sqlite3
from torba.client.basetransaction import BaseTransaction, TXRefImmutable from lbry.wallet.client.basetransaction import BaseTransaction, TXRefImmutable
from torba.client.bip32 import PubKey from lbry.wallet.client.bip32 import PubKey
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
sqlite3.enable_callback_tracebacks(True) sqlite3.enable_callback_tracebacks(True)

View file

@ -7,8 +7,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.client.util import ArithUint256 from lbry.wallet.client.util import ArithUint256
from torba.client.hash import double_sha256 from lbry.crypto.hash import double_sha256
log = logging.getLogger(__name__) log = logging.getLogger(__name__)

View file

@ -12,16 +12,17 @@ from operator import itemgetter
from collections import namedtuple from collections import namedtuple
import pylru import pylru
from torba.client.basetransaction import BaseTransaction from lbry.wallet.client.basetransaction import BaseTransaction
from torba.tasks import TaskGroup from lbry.wallet.tasks import TaskGroup
from torba.client import baseaccount, basenetwork, basetransaction from lbry.wallet.client import baseaccount, basenetwork, basetransaction
from torba.client.basedatabase import BaseDatabase from lbry.wallet.client.basedatabase import BaseDatabase
from torba.client.baseheader import BaseHeaders from lbry.wallet.client.baseheader import BaseHeaders
from torba.client.coinselection import CoinSelector from lbry.wallet.client.coinselection import CoinSelector
from torba.client.constants import COIN, NULL_HASH32 from lbry.wallet.client.constants import COIN, NULL_HASH32
from torba.stream import StreamController from lbry.wallet.stream import StreamController
from torba.client.hash import hash160, double_sha256, sha256, Base58 from lbry.crypto.hash import hash160, double_sha256, sha256
from torba.client.bip32 import PubKey, PrivateKey from lbry.crypto.base58 import Base58
from lbry.wallet.client.bip32 import PubKey, PrivateKey
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, Optional from typing import Type, MutableSequence, MutableMapping, Optional
from torba.client.baseledger import BaseLedger, LedgerRegistry from lbry.wallet.client.baseledger import BaseLedger, LedgerRegistry
from torba.client.wallet import Wallet, WalletStorage from lbry.wallet.client.wallet import Wallet, WalletStorage
log = logging.getLogger(__name__) log = logging.getLogger(__name__)

View file

@ -4,10 +4,9 @@ from operator import itemgetter
from typing import Dict, Optional, Tuple from typing import Dict, Optional, Tuple
from time import perf_counter from time import perf_counter
from torba.rpc import RPCSession as BaseClientSession, Connector, RPCError, ProtocolError import lbry
from lbry.wallet.rpc import RPCSession as BaseClientSession, Connector, RPCError, ProtocolError
from torba import __version__ from lbry.wallet.stream import StreamController
from torba.stream import StreamController
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -115,7 +114,7 @@ class ClientSession(BaseClientSession):
async def ensure_server_version(self, required=None, timeout=3): async def ensure_server_version(self, required=None, timeout=3):
required = required or self.network.PROTOCOL_VERSION required = required or self.network.PROTOCOL_VERSION
return await asyncio.wait_for( return await asyncio.wait_for(
self.send_request('server.version', [__version__, required]), timeout=timeout self.send_request('server.version', [lbry.__version__, required]), timeout=timeout
) )
async def create_connection(self, timeout=6): async def create_connection(self, timeout=6):

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.client.bcd_data_stream import BCDataStream from lbry.wallet.client.bcd_data_stream import BCDataStream
from torba.client.util import subclass_tuple from lbry.wallet.client.util import subclass_tuple
# bitcoin opcodes # bitcoin opcodes
OP_0 = 0x00 OP_0 = 0x00

View file

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

View file

@ -1,16 +1,8 @@
# Copyright (c) 2017, Neil Booth
# Copyright (c) 2018, LBRY Inc.
#
# All rights reserved.
#
# See the file "LICENCE" for information about the copyright
# and warranty status of this software.
""" Logic for BIP32 Hierarchical Key Derivation. """
from coincurve import PublicKey, PrivateKey as _PrivateKey from coincurve import PublicKey, PrivateKey as _PrivateKey
from torba.client.hash import Base58, hmac_sha512, hash160, double_sha256 from lbry.crypto.hash import hmac_sha512, hash160, double_sha256
from torba.client.util import cachedproperty from lbry.crypto.base58 import Base58
from lbry.wallet.client.util import cachedproperty
class DerivationError(Exception): class DerivationError(Exception):

View file

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

View file

@ -1,8 +1,2 @@
class InvalidPasswordError(Exception):
def __init__(self):
super().__init__("Password is invalid.")
class InsufficientFundsError(Exception): class InsufficientFundsError(Exception):
pass pass

View file

@ -1,28 +1,5 @@
# Copyright (c) 2016-2017, Neil Booth
# Copyright (c) 2018, LBRY Inc.
#
# All rights reserved.
#
# See the file "LICENCE" for information about the copyright
# and warranty status of this software.
""" Cryptography hash functions and related classes. """
import os
import base64
import hashlib
import hmac
import typing
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
from cryptography.hazmat.primitives.kdf.scrypt import Scrypt from lbry.wallet.client.constants import NULL_HASH32
from cryptography.hazmat.primitives.ciphers import Cipher, modes
from cryptography.hazmat.primitives.ciphers.algorithms import AES
from cryptography.hazmat.primitives.padding import PKCS7
from cryptography.hazmat.backends import default_backend
from torba.client.util import bytes_to_int, int_to_bytes
from torba.client.constants import NULL_HASH32
from torba.client.errors import InvalidPasswordError
class TXRef: class TXRef:
@ -77,189 +54,3 @@ class TXRefImmutable(TXRef):
@property @property
def height(self): def height(self):
return self._height return self._height
def sha256(x):
""" Simple wrapper of hashlib sha256. """
return hashlib.sha256(x).digest()
def sha512(x):
""" Simple wrapper of hashlib sha512. """
return hashlib.sha512(x).digest()
def ripemd160(x):
""" Simple wrapper of hashlib ripemd160. """
h = hashlib.new('ripemd160')
h.update(x)
return h.digest()
def double_sha256(x):
""" SHA-256 of SHA-256, as used extensively in bitcoin. """
return sha256(sha256(x))
def hmac_sha512(key, msg):
""" Use SHA-512 to provide an HMAC. """
return hmac.new(key, msg, hashlib.sha512).digest()
def hash160(x):
""" RIPEMD-160 of SHA-256.
Used to make bitcoin addresses from pubkeys. """
return ripemd160(sha256(x))
def hash_to_hex_str(x):
""" Convert a big-endian binary hash to displayed hex string.
Display form of a binary hash is reversed and converted to hex. """
return hexlify(reversed(x))
def hex_str_to_hash(x):
""" Convert a displayed hex string to a binary hash. """
return reversed(unhexlify(x))
def aes_encrypt(secret: str, value: str, init_vector: bytes = None) -> str:
if init_vector is not None:
assert len(init_vector) == 16
else:
init_vector = os.urandom(16)
key = double_sha256(secret.encode())
encryptor = Cipher(AES(key), modes.CBC(init_vector), default_backend()).encryptor()
padder = PKCS7(AES.block_size).padder()
padded_data = padder.update(value.encode()) + padder.finalize()
encrypted_data = encryptor.update(padded_data) + encryptor.finalize()
return base64.b64encode(init_vector + encrypted_data).decode()
def aes_decrypt(secret: str, value: str) -> typing.Tuple[str, bytes]:
try:
data = base64.b64decode(value.encode())
key = double_sha256(secret.encode())
init_vector, data = data[:16], data[16:]
decryptor = Cipher(AES(key), modes.CBC(init_vector), default_backend()).decryptor()
unpadder = PKCS7(AES.block_size).unpadder()
result = unpadder.update(decryptor.update(data)) + unpadder.finalize()
return result.decode(), init_vector
except ValueError as e:
if e.args[0] == 'Invalid padding bytes.':
raise InvalidPasswordError()
raise
def better_aes_encrypt(secret: str, value: bytes) -> bytes:
init_vector = os.urandom(16)
key = scrypt(secret.encode(), salt=init_vector)
encryptor = Cipher(AES(key), modes.CBC(init_vector), default_backend()).encryptor()
padder = PKCS7(AES.block_size).padder()
padded_data = padder.update(value) + padder.finalize()
encrypted_data = encryptor.update(padded_data) + encryptor.finalize()
return base64.b64encode(b's:8192:16:1:' + init_vector + encrypted_data)
def better_aes_decrypt(secret: str, value: bytes) -> bytes:
try:
data = base64.b64decode(value)
_, scryp_n, scrypt_r, scrypt_p, data = data.split(b':', maxsplit=4)
init_vector, data = data[:16], data[16:]
key = scrypt(secret.encode(), init_vector, int(scryp_n), int(scrypt_r), int(scrypt_p))
decryptor = Cipher(AES(key), modes.CBC(init_vector), default_backend()).decryptor()
unpadder = PKCS7(AES.block_size).unpadder()
return unpadder.update(decryptor.update(data)) + unpadder.finalize()
except ValueError as e:
if e.args[0] == 'Invalid padding bytes.':
raise InvalidPasswordError()
raise
def scrypt(passphrase, salt, scrypt_n=1<<13, scrypt_r=16, scrypt_p=1):
kdf = Scrypt(salt, length=32, n=scrypt_n, r=scrypt_r, p=scrypt_p, backend=default_backend())
return kdf.derive(passphrase)
class Base58Error(Exception):
""" Exception used for Base58 errors. """
class Base58:
""" Class providing base 58 functionality. """
chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
assert len(chars) == 58
char_map = {c: n for n, c in enumerate(chars)}
@classmethod
def char_value(cls, c):
val = cls.char_map.get(c)
if val is None:
raise Base58Error(f'invalid base 58 character "{c}"')
return val
@classmethod
def decode(cls, txt):
""" Decodes txt into a big-endian bytearray. """
if isinstance(txt, memoryview):
txt = str(txt)
if isinstance(txt, bytes):
txt = txt.decode()
if not isinstance(txt, str):
raise TypeError('a string is required')
if not txt:
raise Base58Error('string cannot be empty')
value = 0
for c in txt:
value = value * 58 + cls.char_value(c)
result = int_to_bytes(value)
# Prepend leading zero bytes if necessary
count = 0
for c in txt:
if c != '1':
break
count += 1
if count:
result = bytes((0,)) * count + result
return result
@classmethod
def encode(cls, be_bytes):
"""Converts a big-endian bytearray into a base58 string."""
value = bytes_to_int(be_bytes)
txt = ''
while value:
value, mod = divmod(value, 58)
txt += cls.chars[mod]
for byte in be_bytes:
if byte != 0:
break
txt += '1'
return txt[::-1]
@classmethod
def decode_check(cls, txt, hash_fn=double_sha256):
""" Decodes a Base58Check-encoded string to a payload. The version prefixes it. """
be_bytes = cls.decode(txt)
result, check = be_bytes[:-4], be_bytes[-4:]
if check != hash_fn(result)[:4]:
raise Base58Error(f'invalid base 58 checksum for {txt}')
return result
@classmethod
def encode_check(cls, payload, hash_fn=double_sha256):
""" Encodes a payload bytearray (which includes the version byte(s))
into a Base58Check string."""
be_bytes = payload + hash_fn(payload)[:4]
return cls.encode(be_bytes)

View file

@ -12,8 +12,8 @@ from secrets import randbelow
import pbkdf2 import pbkdf2
from torba.client.hash import hmac_sha512 from lbry.crypto.hash import hmac_sha512
from torba.client.words import english from lbry.wallet.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

@ -1,7 +1,6 @@
import re import re
from binascii import unhexlify, hexlify
from typing import TypeVar, Sequence, Optional from typing import TypeVar, Sequence, Optional
from torba.client.constants import COIN from lbry.wallet.client.constants import COIN
def coins_to_satoshis(coins): def coins_to_satoshis(coins):
@ -53,18 +52,6 @@ class cachedproperty:
return value return value
def bytes_to_int(be_bytes):
""" Interprets a big-endian sequence of bytes as an integer. """
return int(hexlify(be_bytes), 16)
def int_to_bytes(value):
""" Converts an integer to a big-endian sequence of bytes. """
length = (value.bit_length() + 7) // 8
s = '%x' % value
return unhexlify(('0' * (len(s) % 2) + s).zfill(length * 2))
class ArithUint256: class ArithUint256:
# https://github.com/bitcoin/bitcoin/blob/master/src/arith_uint256.cpp # https://github.com/bitcoin/bitcoin/blob/master/src/arith_uint256.cpp

View file

@ -9,10 +9,10 @@ from typing import List, Sequence, MutableSequence, Optional
from collections import UserDict from collections import UserDict
from hashlib import sha256 from hashlib import sha256
from operator import attrgetter from operator import attrgetter
from torba.client.hash import better_aes_encrypt, better_aes_decrypt from lbry.crypto.crypt import better_aes_encrypt, better_aes_decrypt
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from torba.client import basemanager, baseaccount, baseledger from lbry.wallet.client import basemanager, baseaccount, baseledger
log = logging.getLogger(__name__) log = logging.getLogger(__name__)

View file

@ -1,6 +1,6 @@
from typing import List from typing import List
from torba.client.basedatabase import BaseDatabase from lbry.wallet.client.basedatabase import BaseDatabase
from lbry.wallet.transaction import Output from lbry.wallet.transaction import Output
from lbry.wallet.constants import TXO_TYPES, CLAIM_TYPES from lbry.wallet.constants import TXO_TYPES, CLAIM_TYPES

View file

@ -1,5 +1,5 @@
import textwrap import textwrap
from torba.client.util import coins_to_satoshis, satoshis_to_coins from lbry.wallet.client.util import coins_to_satoshis, satoshis_to_coins
def lbc_to_dewies(lbc: str) -> int: def lbc_to_dewies(lbc: str) -> int:

View file

@ -2,9 +2,9 @@ import struct
from typing import Optional from typing import Optional
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
from torba.client.baseheader import BaseHeaders from lbry.crypto.hash import sha512, double_sha256, ripemd160
from torba.client.util import ArithUint256 from lbry.wallet.client.baseheader import BaseHeaders
from torba.client.hash import sha512, double_sha256, ripemd160 from lbry.wallet.client.util import ArithUint256
class Headers(BaseHeaders): class Headers(BaseHeaders):

View file

@ -6,8 +6,8 @@ from typing import Tuple, List
from datetime import datetime from datetime import datetime
import pylru import pylru
from torba.client.baseledger import BaseLedger, TransactionEvent from lbry.wallet.client.baseledger import BaseLedger, TransactionEvent
from torba.client.baseaccount import SingleKey from lbry.wallet.client.baseaccount import SingleKey
from lbry.schema.result import Outputs from lbry.schema.result import Outputs
from lbry.schema.url import URL from lbry.schema.url import URL
from lbry.wallet.dewies import dewies_to_lbc from lbry.wallet.dewies import dewies_to_lbc

View file

@ -6,9 +6,9 @@ from binascii import unhexlify
from typing import Optional, List from typing import Optional, List
from decimal import Decimal from decimal import Decimal
from torba.client.basemanager import BaseWalletManager from lbry.wallet.client.basemanager import BaseWalletManager
from torba.client.wallet import ENCRYPT_ON_DISK from lbry.wallet.client.wallet import ENCRYPT_ON_DISK
from torba.rpc.jsonrpc import CodeMessageError from lbry.wallet.rpc.jsonrpc import CodeMessageError
from lbry.error import KeyFeeAboveMaxAllowedError from lbry.error import KeyFeeAboveMaxAllowedError
from lbry.wallet.dewies import dewies_to_lbc from lbry.wallet.dewies import dewies_to_lbc

View file

@ -1,8 +1,9 @@
from torba.client.basenetwork import BaseNetwork import lbry
from lbry.wallet.client.basenetwork import BaseNetwork
class Network(BaseNetwork): class Network(BaseNetwork):
PROTOCOL_VERSION = '2.0' PROTOCOL_VERSION = lbry.__version__
def get_claims_by_ids(self, claim_ids): def get_claims_by_ids(self, claim_ids):
return self.rpc('blockchain.claimtrie.getclaimsbyids', claim_ids) return self.rpc('blockchain.claimtrie.getclaimsbyids', claim_ids)

View file

@ -12,12 +12,12 @@ from binascii import hexlify
from typing import Type, Optional from typing import Type, Optional
import urllib.request import urllib.request
from torba.server.server import Server from lbry.wallet.server.server import Server
from torba.server.env import Env from lbry.wallet.server.env import Env
from torba.client.wallet import Wallet from lbry.wallet.client.wallet import Wallet
from torba.client.baseledger import BaseLedger, BlockHeightEvent from lbry.wallet.client.baseledger import BaseLedger, BlockHeightEvent
from torba.client.basemanager import BaseWalletManager from lbry.wallet.client.basemanager import BaseWalletManager
from torba.client.baseaccount import BaseAccount from lbry.wallet.client.baseaccount import BaseAccount
log = logging.getLogger(__name__) log = logging.getLogger(__name__)

View file

@ -3,7 +3,7 @@ import logging
from aiohttp.web import Application, WebSocketResponse, json_response from aiohttp.web import Application, WebSocketResponse, json_response
from aiohttp.http_websocket import WSMsgType, WSCloseCode from aiohttp.http_websocket import WSMsgType, WSCloseCode
from torba.client.util import satoshis_to_coins from lbry.wallet.client.util import satoshis_to_coins
from .node import Conductor, set_logging from .node import Conductor, set_logging

View file

@ -34,7 +34,7 @@ import logging
import time import time
from contextlib import suppress from contextlib import suppress
from torba.tasks import TaskGroup from lbry.wallet.tasks import TaskGroup
from .jsonrpc import Request, JSONRPCConnection, JSONRPCv2, JSONRPC, Batch, Notification from .jsonrpc import Request, JSONRPCConnection, JSONRPCv2, JSONRPC, Batch, Notification
from .jsonrpc import RPCError, ProtocolError from .jsonrpc import RPCError, ProtocolError

View file

@ -1,5 +1,5 @@
from torba.client.basescript import BaseInputScript, BaseOutputScript, Template from lbry.wallet.client.basescript import BaseInputScript, BaseOutputScript, Template
from torba.client.basescript import PUSH_SINGLE, PUSH_INTEGER, OP_DROP, OP_2DROP, PUSH_SUBSCRIPT, OP_VERIFY from lbry.wallet.client.basescript import PUSH_SINGLE, PUSH_INTEGER, OP_DROP, OP_2DROP, PUSH_SUBSCRIPT, OP_VERIFY
class InputScript(BaseInputScript): class InputScript(BaseInputScript):

View file

@ -1,16 +1,14 @@
import time import time
from lbry.schema.claim import Claim
from lbry.wallet.server.db.writer import SQLDB
import asyncio import asyncio
from struct import pack, unpack from struct import pack, unpack
import torba import lbry
from torba.server.daemon import DaemonError from lbry.schema.claim import Claim
from torba.server.hash import hash_to_hex_str, HASHX_LEN from lbry.wallet.server.db.writer import SQLDB
from torba.server.util import chunks, class_logger from lbry.wallet.server.daemon import DaemonError
from torba.server.db import FlushData from lbry.wallet.server.hash import hash_to_hex_str, HASHX_LEN
from lbry.wallet.server.util import chunks, class_logger
from lbry.wallet.server.leveldb import FlushData
class Prefetcher: class Prefetcher:
@ -612,7 +610,7 @@ class BlockProcessor:
self.db.first_sync = False self.db.first_sync = False
await self.flush(True) await self.flush(True)
if first_sync: if first_sync:
self.logger.info(f'{torba.__version__} synced to ' self.logger.info(f'{lbry.__version__} synced to '
f'height {self.height:,d}') f'height {self.height:,d}')
# Reopen for serving # Reopen for serving
await self.db.open_for_serving() await self.db.open_for_serving()
@ -667,46 +665,6 @@ class BlockProcessor:
return False return False
class DecredBlockProcessor(BlockProcessor):
async def calc_reorg_range(self, count):
start, count = await super().calc_reorg_range(count)
if start > 0:
# A reorg in Decred can invalidate the previous block
start -= 1
count += 1
return start, count
class NamecoinBlockProcessor(BlockProcessor):
def advance_txs(self, txs):
result = super().advance_txs(txs)
tx_num = self.tx_count - len(txs)
script_name_hashX = self.coin.name_hashX_from_script
update_touched = self.touched.update
hashXs_by_tx = []
append_hashXs = hashXs_by_tx.append
for tx, tx_hash in txs:
hashXs = []
append_hashX = hashXs.append
# Add the new UTXOs and associate them with the name script
for idx, txout in enumerate(tx.outputs):
# Get the hashX of the name script. Ignore non-name scripts.
hashX = script_name_hashX(txout.pk_script)
if hashX:
append_hashX(hashX)
append_hashXs(hashXs)
update_touched(hashXs)
tx_num += 1
self.db.history.add_unflushed(hashXs_by_tx, self.tx_count - len(txs))
return result
class Timer: class Timer:
def __init__(self, name): def __init__(self, name):

View file

@ -1,35 +1,21 @@
import struct
from hashlib import sha256
from torba.server.script import ScriptPubKey, OpCodes
from torba.server.util import cachedproperty
from torba.server.hash import hash_to_hex_str, HASHX_LEN
from torba.server.tx import DeserializerSegWit
from lbry.wallet.script import OutputScript
from .session import LBRYElectrumX, LBRYSessionManager
from .block_processor import LBRYBlockProcessor
from .daemon import LBCDaemon
from .db.writer import LBRYDB
from collections import namedtuple
import re import re
import struct import struct
from decimal import Decimal from typing import List
from hashlib import sha256 from hashlib import sha256
from functools import partial from decimal import Decimal
import base64 from collections import namedtuple
from typing import Type, List
import torba.server.util as util import lbry.wallet.server.tx as lib_tx
from torba.server.hash import Base58, hash160, double_sha256, hash_to_hex_str from lbry.wallet.script import OutputScript
from torba.server.hash import HASHX_LEN, hex_str_to_hash from lbry.wallet.server.tx import DeserializerSegWit
from torba.server.script import ScriptPubKey, OpCodes from lbry.wallet.server.util import cachedproperty, subclasses
import torba.server.tx as lib_tx from lbry.wallet.server.hash import Base58, hash160, double_sha256, hash_to_hex_str, HASHX_LEN
import torba.server.block_processor as block_proc from lbry.wallet.server.daemon import Daemon, LBCDaemon
from torba.server.db import DB from lbry.wallet.server.script import ScriptPubKey, OpCodes
import torba.server.daemon as daemon from lbry.wallet.server.leveldb import DB
from torba.server.session import ElectrumX, DashElectrumX, SessionManager from lbry.wallet.server.session import LBRYElectrumX, LBRYSessionManager
from lbry.wallet.server.db.writer import LBRYDB
from lbry.wallet.server.block_processor import LBRYBlockProcessor
Block = namedtuple("Block", "raw header transactions") Block = namedtuple("Block", "raw header transactions")
@ -50,11 +36,11 @@ class Coin:
CHUNK_SIZE = 2016 CHUNK_SIZE = 2016
BASIC_HEADER_SIZE = 80 BASIC_HEADER_SIZE = 80
STATIC_BLOCK_HEADERS = True STATIC_BLOCK_HEADERS = True
SESSIONCLS = ElectrumX SESSIONCLS = LBRYElectrumX
DESERIALIZER = lib_tx.Deserializer DESERIALIZER = lib_tx.Deserializer
DAEMON = daemon.Daemon DAEMON = Daemon
BLOCK_PROCESSOR = block_proc.BlockProcessor BLOCK_PROCESSOR = LBRYBlockProcessor
SESSION_MANAGER = SessionManager SESSION_MANAGER = LBRYSessionManager
DB = DB DB = DB
HEADER_VALUES = [ HEADER_VALUES = [
'version', 'prev_block_hash', 'merkle_root', 'timestamp', 'bits', 'nonce' 'version', 'prev_block_hash', 'merkle_root', 'timestamp', 'bits', 'nonce'
@ -75,7 +61,7 @@ class Coin:
Raise an exception if unrecognised.""" Raise an exception if unrecognised."""
req_attrs = ['TX_COUNT', 'TX_COUNT_HEIGHT', 'TX_PER_BLOCK'] req_attrs = ['TX_COUNT', 'TX_COUNT_HEIGHT', 'TX_PER_BLOCK']
for coin in util.subclasses(Coin): for coin in subclasses(Coin):
if (coin.NAME.lower() == name.lower() and if (coin.NAME.lower() == name.lower() and
coin.NET.lower() == net.lower()): coin.NET.lower() == net.lower()):
coin_req_attrs = req_attrs.copy() coin_req_attrs = req_attrs.copy()
@ -125,7 +111,7 @@ class Coin:
def lookup_xverbytes(verbytes): def lookup_xverbytes(verbytes):
"""Return a (is_xpub, coin_class) pair given xpub/xprv verbytes.""" """Return a (is_xpub, coin_class) pair given xpub/xprv verbytes."""
# Order means BTC testnet will override NMC testnet # Order means BTC testnet will override NMC testnet
for coin in util.subclasses(Coin): for coin in subclasses(Coin):
if verbytes == coin.XPUB_VERBYTES: if verbytes == coin.XPUB_VERBYTES:
return True, coin return True, coin
if verbytes == coin.XPRV_VERBYTES: if verbytes == coin.XPRV_VERBYTES:

View file

@ -1,22 +1,14 @@
from functools import wraps
from torba.rpc.jsonrpc import RPCError
import asyncio import asyncio
import itertools import itertools
import json import json
import time import time
from calendar import timegm from functools import wraps
from struct import pack
from time import strptime
import aiohttp import aiohttp
from torba.server.util import hex_to_bytes, class_logger, \ from lbry.wallet.rpc.jsonrpc import RPCError
unpack_le_uint16_from, pack_varint from lbry.wallet.server.util import hex_to_bytes, class_logger
from torba.server.hash import hex_str_to_hash, hash_to_hex_str from lbry.wallet.rpc import JSONRPC
from torba.server.tx import DeserializerDecred
from torba.rpc import JSONRPC
class DaemonError(Exception): class DaemonError(Exception):
@ -280,187 +272,6 @@ class Daemon:
return self._height return self._height
class DashDaemon(Daemon):
async def masternode_broadcast(self, params):
"""Broadcast a transaction to the network."""
return await self._send_single('masternodebroadcast', params)
async def masternode_list(self, params):
"""Return the masternode status."""
return await self._send_single('masternodelist', params)
class FakeEstimateFeeDaemon(Daemon):
"""Daemon that simulates estimatefee and relayfee RPC calls. Coin that
wants to use this daemon must define ESTIMATE_FEE & RELAY_FEE"""
async def estimatefee(self, block_count):
"""Return the fee estimate for the given parameters."""
return self.coin.ESTIMATE_FEE
async def relayfee(self):
"""The minimum fee a low-priority tx must pay in order to be accepted
to the daemon's memory pool."""
return self.coin.RELAY_FEE
class LegacyRPCDaemon(Daemon):
"""Handles connections to a daemon at the given URL.
This class is useful for daemons that don't have the new 'getblock'
RPC call that returns the block in hex, the workaround is to manually
recreate the block bytes. The recreated block bytes may not be the exact
as in the underlying blockchain but it is good enough for our indexing
purposes."""
async def raw_blocks(self, hex_hashes):
"""Return the raw binary blocks with the given hex hashes."""
params_iterable = ((h, ) for h in hex_hashes)
block_info = await self._send_vector('getblock', params_iterable)
blocks = []
for i in block_info:
raw_block = await self.make_raw_block(i)
blocks.append(raw_block)
# Convert hex string to bytes
return blocks
async def make_raw_header(self, b):
pbh = b.get('previousblockhash')
if pbh is None:
pbh = '0' * 64
return b''.join([
pack('<L', b.get('version')),
hex_str_to_hash(pbh),
hex_str_to_hash(b.get('merkleroot')),
pack('<L', self.timestamp_safe(b['time'])),
pack('<L', int(b.get('bits'), 16)),
pack('<L', int(b.get('nonce')))
])
async def make_raw_block(self, b):
"""Construct a raw block"""
header = await self.make_raw_header(b)
transactions = []
if b.get('height') > 0:
transactions = await self.getrawtransactions(b.get('tx'), False)
raw_block = header
num_txs = len(transactions)
if num_txs > 0:
raw_block += pack_varint(num_txs)
raw_block += b''.join(transactions)
else:
raw_block += b'\x00'
return raw_block
def timestamp_safe(self, t):
if isinstance(t, int):
return t
return timegm(strptime(t, "%Y-%m-%d %H:%M:%S %Z"))
class DecredDaemon(Daemon):
async def raw_blocks(self, hex_hashes):
"""Return the raw binary blocks with the given hex hashes."""
params_iterable = ((h, False) for h in hex_hashes)
blocks = await self._send_vector('getblock', params_iterable)
raw_blocks = []
valid_tx_tree = {}
for block in blocks:
# Convert to bytes from hex
raw_block = hex_to_bytes(block)
raw_blocks.append(raw_block)
# Check if previous block is valid
prev = self.prev_hex_hash(raw_block)
votebits = unpack_le_uint16_from(raw_block[100:102])[0]
valid_tx_tree[prev] = self.is_valid_tx_tree(votebits)
processed_raw_blocks = []
for hash, raw_block in zip(hex_hashes, raw_blocks):
if hash in valid_tx_tree:
is_valid = valid_tx_tree[hash]
else:
# Do something complicated to figure out if this block is valid
header = await self._send_single('getblockheader', (hash, ))
if 'nextblockhash' not in header:
raise DaemonError(f'Could not find next block for {hash}')
next_hash = header['nextblockhash']
next_header = await self._send_single('getblockheader',
(next_hash, ))
is_valid = self.is_valid_tx_tree(next_header['votebits'])
if is_valid:
processed_raw_blocks.append(raw_block)
else:
# If this block is invalid remove the normal transactions
self.logger.info(f'block {hash} is invalidated')
processed_raw_blocks.append(self.strip_tx_tree(raw_block))
return processed_raw_blocks
@staticmethod
def prev_hex_hash(raw_block):
return hash_to_hex_str(raw_block[4:36])
@staticmethod
def is_valid_tx_tree(votebits):
# Check if previous block was invalidated.
return bool(votebits & (1 << 0) != 0)
def strip_tx_tree(self, raw_block):
c = self.coin
assert issubclass(c.DESERIALIZER, DeserializerDecred)
d = c.DESERIALIZER(raw_block, start=c.BASIC_HEADER_SIZE)
d.read_tx_tree() # Skip normal transactions
# Create a fake block without any normal transactions
return raw_block[:c.BASIC_HEADER_SIZE] + b'\x00' + raw_block[d.cursor:]
async def height(self):
height = await super().height()
if height > 0:
# Lie about the daemon height as the current tip can be invalidated
height -= 1
self._height = height
return height
async def mempool_hashes(self):
mempool = await super().mempool_hashes()
# Add current tip transactions to the 'fake' mempool.
real_height = await self._send_single('getblockcount')
tip_hash = await self._send_single('getblockhash', (real_height,))
tip = await self.deserialised_block(tip_hash)
# Add normal transactions except coinbase
mempool += tip['tx'][1:]
# Add stake transactions if applicable
mempool += tip.get('stx', [])
return mempool
def client_session(self):
# FIXME allow self signed certificates
connector = aiohttp.TCPConnector(verify_ssl=False)
return aiohttp.ClientSession(connector=connector)
class PreLegacyRPCDaemon(LegacyRPCDaemon):
"""Handles connections to a daemon at the given URL.
This class is useful for daemons that don't have the new 'getblock'
RPC call that returns the block in hex, and need the False parameter
for the getblock"""
async def deserialised_block(self, hex_hash):
"""Return the deserialised block with the given hex hash."""
return await self._send_single('getblock', (hex_hash, False))
def handles_errors(decorated_function): def handles_errors(decorated_function):
@wraps(decorated_function) @wraps(decorated_function)
async def wrapper(*args, **kwargs): async def wrapper(*args, **kwargs):

View file

@ -1,4 +1,4 @@
from torba.client.basedatabase import constraints_to_sql from lbry.wallet.client.basedatabase import constraints_to_sql
CREATE_FULL_TEXT_SEARCH = """ CREATE_FULL_TEXT_SEARCH = """
create virtual table if not exists search using fts5( create virtual table if not exists search using fts5(

View file

@ -10,7 +10,7 @@ from contextvars import ContextVar
from functools import wraps from functools import wraps
from dataclasses import dataclass from dataclasses import dataclass
from torba.client.basedatabase import query, interpolate from lbry.wallet.client.basedatabase import query, interpolate
from lbry.schema.url import URL, normalize_name from lbry.schema.url import URL, normalize_name
from lbry.schema.tags import clean_tags from lbry.schema.tags import clean_tags

View file

@ -5,9 +5,9 @@ from itertools import chain
from decimal import Decimal from decimal import Decimal
from collections import namedtuple from collections import namedtuple
from torba.server.db import DB from lbry.wallet.server.leveldb import DB
from torba.server.util import class_logger from lbry.wallet.server.util import class_logger
from torba.client.basedatabase import query, constraints_to_sql from lbry.wallet.client.basedatabase import query, constraints_to_sql
from lbry.schema.tags import clean_tags from lbry.schema.tags import clean_tags
from lbry.schema.mime_types import guess_stream_type from lbry.schema.mime_types import guess_stream_type

View file

@ -12,9 +12,9 @@ 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 lbry.wallet.server.util import class_logger
from torba.server.coins import Coin from lbry.wallet.server.coin import Coin
import torba.server.util as lib_util import lbry.wallet.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')

View file

@ -29,7 +29,7 @@
import hashlib import hashlib
import hmac import hmac
from torba.server.util import bytes_to_int, int_to_bytes, hex_to_bytes from lbry.wallet.server.util import bytes_to_int, int_to_bytes, hex_to_bytes
_sha256 = hashlib.sha256 _sha256 = hashlib.sha256
_sha512 = hashlib.sha512 _sha512 = hashlib.sha512

View file

@ -15,9 +15,9 @@ import time
from collections import defaultdict from collections import defaultdict
from functools import partial from functools import partial
from torba.server import util from lbry.wallet.server import util
from torba.server.util import pack_be_uint16, unpack_be_uint16_from from lbry.wallet.server.util import pack_be_uint16, unpack_be_uint16_from
from torba.server.hash import hash_to_hex_str, HASHX_LEN from lbry.wallet.server.hash import hash_to_hex_str, HASHX_LEN
class History: class History:

View file

@ -23,12 +23,12 @@ from struct import pack, unpack
import attr import attr
from torba.server import util from lbry.wallet.server import util
from torba.server.hash import hash_to_hex_str, HASHX_LEN from lbry.wallet.server.hash import hash_to_hex_str, HASHX_LEN
from torba.server.merkle import Merkle, MerkleCache from lbry.wallet.server.merkle import Merkle, MerkleCache
from torba.server.util import formatted_time from lbry.wallet.server.util import formatted_time
from torba.server.storage import db_class from lbry.wallet.server.storage import db_class
from torba.server.history import History from lbry.wallet.server.history import History
UTXO = namedtuple("UTXO", "tx_num tx_pos tx_hash height value") UTXO = namedtuple("UTXO", "tx_num tx_pos tx_hash height value")

View file

@ -15,9 +15,9 @@ from collections import defaultdict
import attr import attr
from torba.server.hash import hash_to_hex_str, hex_str_to_hash from lbry.wallet.server.hash import hash_to_hex_str, hex_str_to_hash
from torba.server.util import class_logger, chunks from lbry.wallet.server.util import class_logger, chunks
from torba.server.db import UTXO from lbry.wallet.server.leveldb import UTXO
@attr.s(slots=True) @attr.s(slots=True)

View file

@ -29,7 +29,7 @@
from asyncio import Event from asyncio import Event
from math import ceil, log from math import ceil, log
from torba.server.hash import double_sha256 from lbry.wallet.server.hash import double_sha256
class Merkle: class Merkle:

View file

@ -27,8 +27,8 @@
from ipaddress import ip_address from ipaddress import ip_address
from torba.server import util from lbry.wallet.server import util
from torba.server.util import cachedproperty from lbry.wallet.server.util import cachedproperty
from typing import Dict from typing import Dict

View file

@ -16,13 +16,13 @@ import typing
from asyncio import Event, sleep from asyncio import Event, sleep
from collections import defaultdict, Counter from collections import defaultdict, Counter
from torba.tasks import TaskGroup from lbry.wallet.tasks import TaskGroup
from torba.rpc import ( from lbry.wallet.rpc import (
Connector, RPCSession, SOCKSProxy, Notification, handler_invocation, Connector, RPCSession, SOCKSProxy, Notification, handler_invocation,
SOCKSError, RPCError SOCKSError, RPCError
) )
from torba.server.peer import Peer from lbry.wallet.server.peer import Peer
from torba.server.util import class_logger, protocol_tuple from lbry.wallet.server.util import class_logger, protocol_tuple
PEER_GOOD, PEER_STALE, PEER_NEVER, PEER_BAD = range(4) PEER_GOOD, PEER_STALE, PEER_NEVER, PEER_BAD = range(4)
STALE_SECS = 24 * 3600 STALE_SECS = 24 * 3600

View file

@ -29,7 +29,7 @@
from collections import namedtuple from collections import namedtuple
from torba.server.util import unpack_le_uint16_from, unpack_le_uint32_from, \ from lbry.wallet.server.util import unpack_le_uint16_from, unpack_le_uint32_from, \
pack_le_uint16, pack_le_uint32 pack_le_uint16, pack_le_uint32

View file

@ -3,8 +3,8 @@ import logging
import asyncio import asyncio
from concurrent.futures.thread import ThreadPoolExecutor from concurrent.futures.thread import ThreadPoolExecutor
import torba import lbry
from torba.server.mempool import MemPool, MemPoolAPI from lbry.wallet.server.mempool import MemPool, MemPoolAPI
class Notifications: class Notifications:
@ -91,7 +91,7 @@ class Server:
async def start(self): async def start(self):
env = self.env env = self.env
min_str, max_str = env.coin.SESSIONCLS.protocol_min_max_strings() min_str, max_str = env.coin.SESSIONCLS.protocol_min_max_strings()
self.log.info(f'software version: {torba.__version__}') self.log.info(f'software version: {lbry.__version__}')
self.log.info(f'supported protocol versions: {min_str}-{max_str}') 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'event loop policy: {env.loop_policy}')
self.log.info(f'reorg limit is {env.reorg_limit:,d} blocks') self.log.info(f'reorg limit is {env.reorg_limit:,d} blocks')

View file

@ -1,65 +1,55 @@
import os import os
import ssl
import math import math
import time import time
import json
import zlib
import pylru
import base64 import base64
import codecs
import typing
import asyncio import asyncio
import logging import logging
import itertools
import collections
from asyncio import Event, sleep
from collections import defaultdict
from functools import partial
from binascii import hexlify from binascii import hexlify
from pylru import lrucache from pylru import lrucache
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
from torba.rpc.jsonrpc import RPCError, JSONRPC import lbry
from torba.server.session import ElectrumX, SessionManager
from torba.server import util
from lbry.wallet.server.block_processor import LBRYBlockProcessor from lbry.wallet.server.block_processor import LBRYBlockProcessor
from lbry.wallet.server.db.writer import LBRYDB from lbry.wallet.server.db.writer import LBRYDB
from lbry.wallet.server.db import reader from lbry.wallet.server.db import reader
from lbry.wallet.server.websocket import AdminWebSocket from lbry.wallet.server.websocket import AdminWebSocket
from lbry.wallet.server.metrics import ServerLoadData, APICallMetrics from lbry.wallet.server.metrics import ServerLoadData, APICallMetrics
from lbry import __version__ as sdk_version
import base64 from lbry.wallet.rpc import (
import collections
import asyncio
import codecs
import datetime
import itertools
import json
import os
import zlib
import pylru
import ssl
import time
import typing
from asyncio import Event, sleep
from collections import defaultdict
from functools import partial
import torba
from torba.rpc import (
RPCSession, JSONRPCAutoDetect, JSONRPCConnection, RPCSession, JSONRPCAutoDetect, JSONRPCConnection,
handler_invocation, RPCError, Request handler_invocation, RPCError, Request, JSONRPC
) )
from torba.server import text from lbry.wallet.server import text
from torba.server import util from lbry.wallet.server import util
from torba.server.hash import (sha256, hash_to_hex_str, hex_str_to_hash, from lbry.wallet.server.hash import sha256, hash_to_hex_str, hex_str_to_hash, HASHX_LEN, Base58Error
HASHX_LEN, Base58Error) from lbry.wallet.server.daemon import DaemonError
from torba.server.daemon import DaemonError from lbry.wallet.server.peers import PeerManager
from torba.server.peers import PeerManager
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from torba.server.env import Env from lbry.wallet.server.env import Env
from torba.server.db import DB from lbry.wallet.server.leveldb import DB
from torba.server.block_processor import BlockProcessor from lbry.wallet.server.block_processor import BlockProcessor
from torba.server.mempool import MemPool from lbry.wallet.server.mempool import MemPool
from torba.server.daemon import Daemon from lbry.wallet.server.daemon import Daemon
BAD_REQUEST = 1 BAD_REQUEST = 1
DAEMON_ERROR = 2 DAEMON_ERROR = 2
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def scripthash_to_hashX(scripthash: str) -> bytes: def scripthash_to_hashX(scripthash: str) -> bytes:
try: try:
bin_hash = hex_str_to_hash(scripthash) bin_hash = hex_str_to_hash(scripthash)
@ -149,10 +139,6 @@ class SessionManager:
self.notified_height: typing.Optional[int] = None self.notified_height: typing.Optional[int] = None
# Cache some idea of room to avoid recounting on each subscription # Cache some idea of room to avoid recounting on each subscription
self.subs_room = 0 self.subs_room = 0
# Masternode stuff only for such coins
if issubclass(env.coin.SESSIONCLS, DashElectrumX):
self.mn_cache_height = 0
self.mn_cache = [] # type: ignore
self.session_event = Event() self.session_event = Event()
@ -335,7 +321,7 @@ class SessionManager:
'subs': self._sub_count(), 'subs': self._sub_count(),
'txs_sent': self.txs_sent, 'txs_sent': self.txs_sent,
'uptime': util.formatted_time(time.time() - self.start_time), 'uptime': util.formatted_time(time.time() - self.start_time),
'version': torba.__version__, 'version': lbry.__version__,
} }
def _session_data(self, for_log): def _session_data(self, for_log):
@ -733,11 +719,67 @@ class SessionBase(RPCSession):
return await coro return await coro
class ElectrumX(SessionBase): class LBRYSessionManager(SessionManager):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.query_executor = None
self.websocket = None
self.metrics = ServerLoadData()
self.metrics_loop = None
self.running = False
if self.env.websocket_host is not None and self.env.websocket_port is not None:
self.websocket = AdminWebSocket(self)
self.search_cache = self.bp.search_cache
self.search_cache['search'] = lrucache(10000)
self.search_cache['resolve'] = lrucache(10000)
async def process_metrics(self):
while self.running:
data = self.metrics.to_json_and_reset({
'sessions': self.session_count(),
'height': self.db.db_height,
})
if self.websocket is not None:
self.websocket.send_message(data)
await asyncio.sleep(1)
async def start_other(self):
self.running = True
path = os.path.join(self.env.db_dir, 'claims.db')
args = dict(
initializer=reader.initializer,
initargs=(self.logger, path, self.env.coin.NET, self.env.database_query_timeout,
self.env.track_metrics)
)
if self.env.max_query_workers is not None and self.env.max_query_workers == 0:
self.query_executor = ThreadPoolExecutor(max_workers=1, **args)
else:
self.query_executor = ProcessPoolExecutor(
max_workers=self.env.max_query_workers or max(os.cpu_count(), 4), **args
)
if self.websocket is not None:
await self.websocket.start()
if self.env.track_metrics:
self.metrics_loop = asyncio.create_task(self.process_metrics())
async def stop_other(self):
self.running = False
if self.env.track_metrics:
self.metrics_loop.cancel()
if self.websocket is not None:
await self.websocket.stop()
self.query_executor.shutdown()
class LBRYElectrumX(SessionBase):
"""A TCP server that handles incoming Electrum connections.""" """A TCP server that handles incoming Electrum connections."""
PROTOCOL_MIN = (1, 1) PROTOCOL_MIN = lbry.version
PROTOCOL_MAX = (1, 4) PROTOCOL_MAX = (1, 0)
max_errors = math.inf # don't disconnect people for errors! let them happen...
session_mgr: LBRYSessionManager
version = lbry.__version__
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -748,6 +790,13 @@ class ElectrumX(SessionBase):
self.sv_seen = False self.sv_seen = False
self.mempool_statuses = {} self.mempool_statuses = {}
self.set_request_handlers(self.PROTOCOL_MIN) self.set_request_handlers(self.PROTOCOL_MIN)
# fixme: this is a rebase hack, we need to go through ChainState instead later
self.daemon = self.session_mgr.daemon
self.bp: LBRYBlockProcessor = self.session_mgr.bp
self.db: LBRYDB = self.bp.db
# space separated list of channel URIs used for filtering bad content
filtering_channels = self.env.default('FILTERING_CHANNELS_IDS', '')
self.filtering_channels_ids = list(filter(None, filtering_channels.split(' ')))
@classmethod @classmethod
def protocol_min_max_strings(cls): def protocol_min_max_strings(cls):
@ -825,6 +874,169 @@ class ElectrumX(SessionBase):
es = '' if len(changed) == 1 else 'es' es = '' if len(changed) == 1 else 'es'
self.logger.info(f'notified of {len(changed):,d} address{es}') self.logger.info(f'notified of {len(changed):,d} address{es}')
def get_metrics_or_placeholder_for_api(self, query_name):
""" Do not hold on to a reference to the metrics
returned by this method past an `await` or
you may be working with a stale metrics object.
"""
if self.env.track_metrics:
return self.session_mgr.metrics.for_api(query_name)
else:
return APICallMetrics(query_name)
async def run_in_executor(self, query_name, func, kwargs):
start = time.perf_counter()
try:
result = await asyncio.get_running_loop().run_in_executor(
self.session_mgr.query_executor, func, kwargs
)
except asyncio.CancelledError:
raise
except reader.SQLiteInterruptedError as error:
metrics = self.get_metrics_or_placeholder_for_api(query_name)
metrics.query_interrupt(start, error.metrics)
raise RPCError(JSONRPC.QUERY_TIMEOUT, 'sqlite query timed out')
except reader.SQLiteOperationalError as error:
metrics = self.get_metrics_or_placeholder_for_api(query_name)
metrics.query_error(start, error.metrics)
raise RPCError(JSONRPC.INTERNAL_ERROR, 'query failed to execute')
except Exception:
log.exception("dear devs, please handle this exception better")
metrics = self.get_metrics_or_placeholder_for_api(query_name)
metrics.query_error(start, {})
raise RPCError(JSONRPC.INTERNAL_ERROR, 'unknown server error')
if self.env.track_metrics:
metrics = self.get_metrics_or_placeholder_for_api(query_name)
(result, metrics_data) = result
metrics.query_response(start, metrics_data)
return base64.b64encode(result).decode()
async def run_and_cache_query(self, query_name, function, kwargs):
metrics = self.get_metrics_or_placeholder_for_api(query_name)
metrics.start()
cache = self.session_mgr.search_cache[query_name]
cache_key = str(kwargs)
cache_item = cache.get(cache_key)
if cache_item is None:
cache_item = cache[cache_key] = ResultCacheItem()
elif cache_item.result is not None:
metrics.cache_response()
return cache_item.result
async with cache_item.lock:
if cache_item.result is None:
cache_item.result = await self.run_in_executor(
query_name, function, kwargs
)
else:
metrics = self.get_metrics_or_placeholder_for_api(query_name)
metrics.cache_response()
return cache_item.result
async def claimtrie_search(self, **kwargs):
if kwargs:
kwargs.setdefault('blocklist_channel_ids', []).extend(self.filtering_channels_ids)
return await self.run_and_cache_query('search', reader.search_to_bytes, kwargs)
async def claimtrie_resolve(self, *urls):
if urls:
return await self.run_and_cache_query('resolve', reader.resolve_to_bytes, urls)
async def get_server_height(self):
return self.bp.height
async def transaction_get_height(self, tx_hash):
self.assert_tx_hash(tx_hash)
transaction_info = await self.daemon.getrawtransaction(tx_hash, True)
if transaction_info and 'hex' in transaction_info and 'confirmations' in transaction_info:
# an unconfirmed transaction from lbrycrdd will not have a 'confirmations' field
return (self.db.db_height - transaction_info['confirmations']) + 1
elif transaction_info and 'hex' in transaction_info:
return -1
return None
async def claimtrie_getclaimsbyids(self, *claim_ids):
claims = await self.batched_formatted_claims_from_daemon(claim_ids)
return dict(zip(claim_ids, claims))
async def batched_formatted_claims_from_daemon(self, claim_ids):
claims = await self.daemon.getclaimsbyids(claim_ids)
result = []
for claim in claims:
if claim and claim.get('value'):
result.append(self.format_claim_from_daemon(claim))
return result
def format_claim_from_daemon(self, claim, name=None):
"""Changes the returned claim data to the format expected by lbry and adds missing fields."""
if not claim:
return {}
# this ISO-8859 nonsense stems from a nasty form of encoding extended characters in lbrycrd
# it will be fixed after the lbrycrd upstream merge to v17 is done
# it originated as a fear of terminals not supporting unicode. alas, they all do
if 'name' in claim:
name = claim['name'].encode('ISO-8859-1').decode()
info = self.db.sql.get_claims(claim_id=claim['claimId'])
if not info:
# raise RPCError("Lbrycrd has {} but not lbryumx, please submit a bug report.".format(claim_id))
return {}
address = info.address.decode()
# fixme: temporary
#supports = self.format_supports_from_daemon(claim.get('supports', []))
supports = []
amount = get_from_possible_keys(claim, 'amount', 'nAmount')
height = get_from_possible_keys(claim, 'height', 'nHeight')
effective_amount = get_from_possible_keys(claim, 'effective amount', 'nEffectiveAmount')
valid_at_height = get_from_possible_keys(claim, 'valid at height', 'nValidAtHeight')
result = {
"name": name,
"claim_id": claim['claimId'],
"txid": claim['txid'],
"nout": claim['n'],
"amount": amount,
"depth": self.db.db_height - height + 1,
"height": height,
"value": hexlify(claim['value'].encode('ISO-8859-1')).decode(),
"address": address, # from index
"supports": supports,
"effective_amount": effective_amount,
"valid_at_height": valid_at_height
}
if 'claim_sequence' in claim:
# TODO: ensure that lbrycrd #209 fills in this value
result['claim_sequence'] = claim['claim_sequence']
else:
result['claim_sequence'] = -1
if 'normalized_name' in claim:
result['normalized_name'] = claim['normalized_name'].encode('ISO-8859-1').decode()
return result
def assert_tx_hash(self, value):
'''Raise an RPCError if the value is not a valid transaction
hash.'''
try:
if len(util.hex_to_bytes(value)) == 32:
return
except Exception:
pass
raise RPCError(1, f'{value} should be a transaction hash')
def assert_claim_id(self, value):
'''Raise an RPCError if the value is not a valid claim id
hash.'''
try:
if len(util.hex_to_bytes(value)) == 20:
return
except Exception:
pass
raise RPCError(1, f'{value} should be a claim id hash')
async def subscribe_headers_result(self): async def subscribe_headers_result(self):
"""The result of a header subscription or notification.""" """The result of a header subscription or notification."""
return self.session_mgr.hsub_results[self.subscribe_headers_raw] return self.session_mgr.hsub_results[self.subscribe_headers_raw]
@ -1264,7 +1476,6 @@ class ElectrumX(SessionBase):
def set_request_handlers(self, ptuple): def set_request_handlers(self, ptuple):
self.protocol_tuple = ptuple self.protocol_tuple = ptuple
handlers = { handlers = {
'blockchain.block.get_chunk': self.block_get_chunk, 'blockchain.block.get_chunk': self.block_get_chunk,
'blockchain.block.get_header': self.block_get_header, 'blockchain.block.get_header': self.block_get_header,
@ -1284,32 +1495,14 @@ class ElectrumX(SessionBase):
'server.features': self.server_features_async, 'server.features': self.server_features_async,
'server.peers.subscribe': self.peers_subscribe, 'server.peers.subscribe': self.peers_subscribe,
'server.version': self.server_version, 'server.version': self.server_version,
} 'blockchain.transaction.get_height': self.transaction_get_height,
'blockchain.claimtrie.search': self.claimtrie_search,
if ptuple >= (1, 2): 'blockchain.claimtrie.resolve': self.claimtrie_resolve,
# New handler as of 1.2 'blockchain.claimtrie.getclaimsbyids': self.claimtrie_getclaimsbyids,
handlers.update({ 'blockchain.block.get_server_height': self.get_server_height,
'mempool.get_fee_histogram': 'mempool.get_fee_histogram': self.mempool.compact_fee_histogram,
self.mempool.compact_fee_histogram,
'blockchain.block.headers': self.block_headers, 'blockchain.block.headers': self.block_headers,
'server.ping': self.ping, 'server.ping': self.ping,
})
if ptuple >= (1, 4):
handlers.update({
'blockchain.block.header': self.block_header,
'blockchain.block.headers': self.block_headers,
'blockchain.headers.subscribe': self.headers_subscribe,
'blockchain.transaction.id_from_pos':
self.transaction_id_from_pos,
})
elif ptuple >= (1, 3):
handlers.update({
'blockchain.block.header': self.block_header_13,
'blockchain.headers.subscribe': self.headers_subscribe_True,
})
else:
handlers.update({
'blockchain.headers.subscribe': self.headers_subscribe_False, 'blockchain.headers.subscribe': self.headers_subscribe_False,
'blockchain.address.get_balance': self.address_get_balance, 'blockchain.address.get_balance': self.address_get_balance,
'blockchain.address.get_history': self.address_get_history, 'blockchain.address.get_history': self.address_get_history,
@ -1317,8 +1510,7 @@ class ElectrumX(SessionBase):
'blockchain.address.listunspent': self.address_listunspent, 'blockchain.address.listunspent': self.address_listunspent,
'blockchain.address.subscribe': self.address_subscribe, 'blockchain.address.subscribe': self.address_subscribe,
'blockchain.address.unsubscribe': self.address_unsubscribe, 'blockchain.address.unsubscribe': self.address_unsubscribe,
}) }
self.request_handlers = handlers self.request_handlers = handlers
@ -1334,153 +1526,6 @@ class LocalRPC(SessionBase):
return 'RPC' return 'RPC'
class DashElectrumX(ElectrumX):
"""A TCP server that handles incoming Electrum Dash connections."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.mns = set()
def set_request_handlers(self, ptuple):
super().set_request_handlers(ptuple)
self.request_handlers.update({
'masternode.announce.broadcast':
self.masternode_announce_broadcast,
'masternode.subscribe': self.masternode_subscribe,
'masternode.list': self.masternode_list
})
async def notify(self, touched, height_changed):
"""Notify the client about changes in masternode list."""
await super().notify(touched, height_changed)
for mn in self.mns:
status = await self.daemon_request('masternode_list',
['status', mn])
await self.send_notification('masternode.subscribe',
[mn, status.get(mn)])
# Masternode command handlers
async def masternode_announce_broadcast(self, signmnb):
"""Pass through the masternode announce message to be broadcast
by the daemon.
signmnb: signed masternode broadcast message."""
try:
return await self.daemon_request('masternode_broadcast',
['relay', signmnb])
except DaemonError as e:
error, = e.args
message = error['message']
self.logger.info(f'masternode_broadcast: {message}')
raise RPCError(BAD_REQUEST, 'the masternode broadcast was '
f'rejected.\n\n{message}\n[{signmnb}]')
async def masternode_subscribe(self, collateral):
"""Returns the status of masternode.
collateral: masternode collateral.
"""
result = await self.daemon_request('masternode_list',
['status', collateral])
if result is not None:
self.mns.add(collateral)
return result.get(collateral)
return None
async def masternode_list(self, payees):
"""
Returns the list of masternodes.
payees: a list of masternode payee addresses.
"""
if not isinstance(payees, list):
raise RPCError(BAD_REQUEST, 'expected a list of payees')
def get_masternode_payment_queue(mns):
"""Returns the calculated position in the payment queue for all the
valid masterernodes in the given mns list.
mns: a list of masternodes information.
"""
now = int(datetime.datetime.utcnow().strftime("%s"))
mn_queue = []
# Only ENABLED masternodes are considered for the list.
for line in mns:
mnstat = mns[line].split()
if mnstat[0] == 'ENABLED':
# if last paid time == 0
if int(mnstat[5]) == 0:
# use active seconds
mnstat.append(int(mnstat[4]))
else:
# now minus last paid
delta = now - int(mnstat[5])
# if > active seconds, use active seconds
if delta >= int(mnstat[4]):
mnstat.append(int(mnstat[4]))
# use active seconds
else:
mnstat.append(delta)
mn_queue.append(mnstat)
mn_queue = sorted(mn_queue, key=lambda x: x[8], reverse=True)
return mn_queue
def get_payment_position(payment_queue, address):
"""
Returns the position of the payment list for the given address.
payment_queue: position in the payment queue for the masternode.
address: masternode payee address.
"""
position = -1
for pos, mn in enumerate(payment_queue, start=1):
if mn[2] == address:
position = pos
break
return position
# Accordingly with the masternode payment queue, a custom list
# with the masternode information including the payment
# position is returned.
cache = self.session_mgr.mn_cache
if not cache or self.session_mgr.mn_cache_height != self.db.db_height:
full_mn_list = await self.daemon_request('masternode_list',
['full'])
mn_payment_queue = get_masternode_payment_queue(full_mn_list)
mn_payment_count = len(mn_payment_queue)
mn_list = []
for key, value in full_mn_list.items():
mn_data = value.split()
mn_info = {}
mn_info['vin'] = key
mn_info['status'] = mn_data[0]
mn_info['protocol'] = mn_data[1]
mn_info['payee'] = mn_data[2]
mn_info['lastseen'] = mn_data[3]
mn_info['activeseconds'] = mn_data[4]
mn_info['lastpaidtime'] = mn_data[5]
mn_info['lastpaidblock'] = mn_data[6]
mn_info['ip'] = mn_data[7]
mn_info['paymentposition'] = get_payment_position(
mn_payment_queue, mn_info['payee'])
mn_info['inselection'] = (
mn_info['paymentposition'] < mn_payment_count // 10)
balance = await self.address_get_balance(mn_info['payee'])
mn_info['balance'] = (sum(balance.values())
/ self.coin.VALUE_PER_COIN)
mn_list.append(mn_info)
cache.clear()
cache.extend(mn_list)
self.session_mgr.mn_cache_height = self.db.db_height
# If payees is an empty list the whole masternode list is returned
if payees:
return [mn for mn in cache if mn['payee'] in payees]
else:
return cache
class ResultCacheItem: class ResultCacheItem:
__slots__ = '_result', 'lock', 'has_result' __slots__ = '_result', 'lock', 'has_result'
@ -1500,251 +1545,6 @@ class ResultCacheItem:
self.has_result.set() self.has_result.set()
class LBRYSessionManager(SessionManager):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.query_executor = None
self.websocket = None
self.metrics = ServerLoadData()
self.metrics_loop = None
self.running = False
if self.env.websocket_host is not None and self.env.websocket_port is not None:
self.websocket = AdminWebSocket(self)
self.search_cache = self.bp.search_cache
self.search_cache['search'] = lrucache(10000)
self.search_cache['resolve'] = lrucache(10000)
async def process_metrics(self):
while self.running:
data = self.metrics.to_json_and_reset({
'sessions': self.session_count(),
'height': self.db.db_height,
})
if self.websocket is not None:
self.websocket.send_message(data)
await asyncio.sleep(1)
async def start_other(self):
self.running = True
path = os.path.join(self.env.db_dir, 'claims.db')
args = dict(
initializer=reader.initializer,
initargs=(self.logger, path, self.env.coin.NET, self.env.database_query_timeout,
self.env.track_metrics)
)
if self.env.max_query_workers is not None and self.env.max_query_workers == 0:
self.query_executor = ThreadPoolExecutor(max_workers=1, **args)
else:
self.query_executor = ProcessPoolExecutor(
max_workers=self.env.max_query_workers or max(os.cpu_count(), 4), **args
)
if self.websocket is not None:
await self.websocket.start()
if self.env.track_metrics:
self.metrics_loop = asyncio.create_task(self.process_metrics())
async def stop_other(self):
self.running = False
if self.env.track_metrics:
self.metrics_loop.cancel()
if self.websocket is not None:
await self.websocket.stop()
self.query_executor.shutdown()
class LBRYElectrumX(ElectrumX):
PROTOCOL_MIN = (2, 0) # v0.48 is backwards incompatible, forking a new protocol
PROTOCOL_MAX = (2, 0)
max_errors = math.inf # don't disconnect people for errors! let them happen...
session_mgr: LBRYSessionManager
version = sdk_version
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# fixme: this is a rebase hack, we need to go through ChainState instead later
self.daemon = self.session_mgr.daemon
self.bp: LBRYBlockProcessor = self.session_mgr.bp
self.db: LBRYDB = self.bp.db
# space separated list of channel URIs used for filtering bad content
filtering_channels = self.env.default('FILTERING_CHANNELS_IDS', '')
self.filtering_channels_ids = list(filter(None, filtering_channels.split(' ')))
def set_request_handlers(self, ptuple):
super().set_request_handlers((1, 2) if ptuple == (2, 0) else ptuple) # our "2.0" is electrumx 1.2
handlers = {
'blockchain.transaction.get_height': self.transaction_get_height,
'blockchain.claimtrie.search': self.claimtrie_search,
'blockchain.claimtrie.resolve': self.claimtrie_resolve,
'blockchain.claimtrie.getclaimsbyids': self.claimtrie_getclaimsbyids,
'blockchain.block.get_server_height': self.get_server_height,
}
self.request_handlers.update(handlers)
def get_metrics_or_placeholder_for_api(self, query_name):
""" Do not hold on to a reference to the metrics
returned by this method past an `await` or
you may be working with a stale metrics object.
"""
if self.env.track_metrics:
return self.session_mgr.metrics.for_api(query_name)
else:
return APICallMetrics(query_name)
async def run_in_executor(self, query_name, func, kwargs):
start = time.perf_counter()
try:
result = await asyncio.get_running_loop().run_in_executor(
self.session_mgr.query_executor, func, kwargs
)
except asyncio.CancelledError:
raise
except reader.SQLiteInterruptedError as error:
metrics = self.get_metrics_or_placeholder_for_api(query_name)
metrics.query_interrupt(start, error.metrics)
raise RPCError(JSONRPC.QUERY_TIMEOUT, 'sqlite query timed out')
except reader.SQLiteOperationalError as error:
metrics = self.get_metrics_or_placeholder_for_api(query_name)
metrics.query_error(start, error.metrics)
raise RPCError(JSONRPC.INTERNAL_ERROR, 'query failed to execute')
except Exception:
log.exception("dear devs, please handle this exception better")
metrics = self.get_metrics_or_placeholder_for_api(query_name)
metrics.query_error(start, {})
raise RPCError(JSONRPC.INTERNAL_ERROR, 'unknown server error')
if self.env.track_metrics:
metrics = self.get_metrics_or_placeholder_for_api(query_name)
(result, metrics_data) = result
metrics.query_response(start, metrics_data)
return base64.b64encode(result).decode()
async def run_and_cache_query(self, query_name, function, kwargs):
metrics = self.get_metrics_or_placeholder_for_api(query_name)
metrics.start()
cache = self.session_mgr.search_cache[query_name]
cache_key = str(kwargs)
cache_item = cache.get(cache_key)
if cache_item is None:
cache_item = cache[cache_key] = ResultCacheItem()
elif cache_item.result is not None:
metrics.cache_response()
return cache_item.result
async with cache_item.lock:
if cache_item.result is None:
cache_item.result = await self.run_in_executor(
query_name, function, kwargs
)
else:
metrics = self.get_metrics_or_placeholder_for_api(query_name)
metrics.cache_response()
return cache_item.result
async def claimtrie_search(self, **kwargs):
if kwargs:
kwargs.setdefault('blocklist_channel_ids', []).extend(self.filtering_channels_ids)
return await self.run_and_cache_query('search', reader.search_to_bytes, kwargs)
async def claimtrie_resolve(self, *urls):
if urls:
return await self.run_and_cache_query('resolve', reader.resolve_to_bytes, urls)
async def get_server_height(self):
return self.bp.height
async def transaction_get_height(self, tx_hash):
self.assert_tx_hash(tx_hash)
transaction_info = await self.daemon.getrawtransaction(tx_hash, True)
if transaction_info and 'hex' in transaction_info and 'confirmations' in transaction_info:
# an unconfirmed transaction from lbrycrdd will not have a 'confirmations' field
return (self.db.db_height - transaction_info['confirmations']) + 1
elif transaction_info and 'hex' in transaction_info:
return -1
return None
async def claimtrie_getclaimsbyids(self, *claim_ids):
claims = await self.batched_formatted_claims_from_daemon(claim_ids)
return dict(zip(claim_ids, claims))
async def batched_formatted_claims_from_daemon(self, claim_ids):
claims = await self.daemon.getclaimsbyids(claim_ids)
result = []
for claim in claims:
if claim and claim.get('value'):
result.append(self.format_claim_from_daemon(claim))
return result
def format_claim_from_daemon(self, claim, name=None):
"""Changes the returned claim data to the format expected by lbry and adds missing fields."""
if not claim:
return {}
# this ISO-8859 nonsense stems from a nasty form of encoding extended characters in lbrycrd
# it will be fixed after the lbrycrd upstream merge to v17 is done
# it originated as a fear of terminals not supporting unicode. alas, they all do
if 'name' in claim:
name = claim['name'].encode('ISO-8859-1').decode()
info = self.db.sql.get_claims(claim_id=claim['claimId'])
if not info:
# raise RPCError("Lbrycrd has {} but not lbryumx, please submit a bug report.".format(claim_id))
return {}
address = info.address.decode()
# fixme: temporary
#supports = self.format_supports_from_daemon(claim.get('supports', []))
supports = []
amount = get_from_possible_keys(claim, 'amount', 'nAmount')
height = get_from_possible_keys(claim, 'height', 'nHeight')
effective_amount = get_from_possible_keys(claim, 'effective amount', 'nEffectiveAmount')
valid_at_height = get_from_possible_keys(claim, 'valid at height', 'nValidAtHeight')
result = {
"name": name,
"claim_id": claim['claimId'],
"txid": claim['txid'],
"nout": claim['n'],
"amount": amount,
"depth": self.db.db_height - height + 1,
"height": height,
"value": hexlify(claim['value'].encode('ISO-8859-1')).decode(),
"address": address, # from index
"supports": supports,
"effective_amount": effective_amount,
"valid_at_height": valid_at_height
}
if 'claim_sequence' in claim:
# TODO: ensure that lbrycrd #209 fills in this value
result['claim_sequence'] = claim['claim_sequence']
else:
result['claim_sequence'] = -1
if 'normalized_name' in claim:
result['normalized_name'] = claim['normalized_name'].encode('ISO-8859-1').decode()
return result
def assert_tx_hash(self, value):
'''Raise an RPCError if the value is not a valid transaction
hash.'''
try:
if len(util.hex_to_bytes(value)) == 32:
return
except Exception:
pass
raise RPCError(1, f'{value} should be a transaction hash')
def assert_claim_id(self, value):
'''Raise an RPCError if the value is not a valid claim id
hash.'''
try:
if len(util.hex_to_bytes(value)) == 20:
return
except Exception:
pass
raise RPCError(1, f'{value} should be a claim id hash')
def get_from_possible_keys(dictionary, *keys): def get_from_possible_keys(dictionary, *keys):
for key in keys: for key in keys:
if key in dictionary: if key in dictionary:

View file

@ -10,7 +10,7 @@
import os import os
from functools import partial from functools import partial
from torba.server import util from lbry.wallet.server import util
def db_class(db_dir, name): def db_class(db_dir, name):

View file

@ -1,6 +1,6 @@
import time import time
from torba.server import util from lbry.wallet.server import util
def sessions_lines(data): def sessions_lines(data):

View file

@ -29,9 +29,9 @@
from collections import namedtuple from collections import namedtuple
from torba.server.hash import sha256, double_sha256, hash_to_hex_str from lbry.wallet.server.hash import sha256, double_sha256, hash_to_hex_str
from torba.server.script import OpCodes from lbry.wallet.server.script import OpCodes
from torba.server.util import ( from lbry.wallet.server.util import (
unpack_le_int32_from, unpack_le_int64_from, unpack_le_uint16_from, unpack_le_int32_from, unpack_le_int64_from, unpack_le_uint16_from,
unpack_le_uint32_from, unpack_le_uint64_from, pack_le_int32, pack_varint, unpack_le_uint32_from, unpack_le_uint64_from, pack_le_int32, pack_varint,
pack_le_uint32, pack_le_int64, pack_varbytes, pack_le_uint32, pack_le_int64, pack_varbytes,

View file

@ -6,13 +6,16 @@ from asyncio.runners import _cancel_all_tasks # type: ignore
import unittest import unittest
from unittest.case import _Outcome from unittest.case import _Outcome
from typing import Optional from typing import Optional
from torba.orchstr8 import Conductor
from torba.orchstr8.node import BlockchainNode, WalletNode import lbry.wallet
from torba.client.baseledger import BaseLedger from lbry.wallet.orchstr8 import Conductor
from torba.client.baseaccount import BaseAccount from lbry.wallet.orchstr8.node import BlockchainNode, WalletNode
from torba.client.basemanager import BaseWalletManager from lbry.wallet.client.baseledger import BaseLedger
from torba.client.wallet import Wallet from lbry.wallet.client.baseaccount import BaseAccount
from torba.client.util import satoshis_to_coins from lbry.wallet.client.basemanager import BaseWalletManager
from lbry.wallet.client.wallet import Wallet
from lbry.wallet.client.util import satoshis_to_coins
from lbry.wallet import LbryWalletManager
class ColorHandler(logging.StreamHandler): class ColorHandler(logging.StreamHandler):
@ -187,8 +190,8 @@ class AdvanceTimeTestCase(AsyncioTestCase):
class IntegrationTestCase(AsyncioTestCase): class IntegrationTestCase(AsyncioTestCase):
SEED = None SEED = None
LEDGER = None LEDGER = lbry.wallet
MANAGER = None MANAGER = LbryWalletManager
ENABLE_SEGWIT = False ENABLE_SEGWIT = False
VERBOSITY = logging.WARN VERBOSITY = logging.WARN

View file

@ -11,8 +11,9 @@ from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import Prehashed from cryptography.hazmat.primitives.asymmetric.utils import Prehashed
from cryptography.exceptions import InvalidSignature from cryptography.exceptions import InvalidSignature
from torba.client.basetransaction import BaseTransaction, BaseInput, BaseOutput, ReadOnlyList from lbry.crypto.base58 import Base58
from torba.client.hash import hash160, sha256, Base58 from lbry.crypto.hash import hash160, sha256
from lbry.wallet.client.basetransaction import BaseTransaction, BaseInput, BaseOutput, ReadOnlyList
from lbry.schema.claim import Claim from lbry.schema.claim import Claim
from lbry.schema.purchase import Purchase from lbry.schema.purchase import Purchase
from lbry.schema.url import normalize_name from lbry.schema.url import normalize_name

View file

@ -1,9 +1,5 @@
import asyncio
import sqlite3
from lbry.testcase import CommandTestCase from lbry.testcase import CommandTestCase
from torba.client.basedatabase import SQLiteMixin
from lbry.wallet.dewies import dewies_to_lbc from lbry.wallet.dewies import dewies_to_lbc
from lbry.wallet.account import Account
def extract(d, keys): def extract(d, keys):

View file

@ -1,5 +1,5 @@
import logging import logging
from torba.testcase import IntegrationTestCase from lbry.wallet.testcase import IntegrationTestCase
class BlockchainReorganizationTests(IntegrationTestCase): class BlockchainReorganizationTests(IntegrationTestCase):

View file

@ -5,7 +5,7 @@ from binascii import unhexlify
from urllib.request import urlopen from urllib.request import urlopen
from torba.client.errors import InsufficientFundsError from lbry.wallet.client.errors import InsufficientFundsError
from lbry.extras.daemon.Daemon import DEFAULT_PAGE_SIZE from lbry.extras.daemon.Daemon import DEFAULT_PAGE_SIZE
from lbry.testcase import CommandTestCase from lbry.testcase import CommandTestCase

View file

@ -1,6 +1,6 @@
import contextlib import contextlib
from io import StringIO from io import StringIO
from torba.testcase import AsyncioTestCase from lbry.wallet.testcase import AsyncioTestCase
from lbry.conf import Config from lbry.conf import Config
from lbry.extras import cli from lbry.extras import cli

View file

@ -7,7 +7,7 @@ from lbry.dht import constants
from lbry.dht.node import Node from lbry.dht.node import Node
from lbry.dht import peer as dht_peer from lbry.dht import peer as dht_peer
from lbry.dht.peer import PeerManager, make_kademlia_peer from lbry.dht.peer import PeerManager, make_kademlia_peer
from torba.testcase import AsyncioTestCase from lbry.wallet.testcase import AsyncioTestCase
class DHTIntegrationTest(AsyncioTestCase): class DHTIntegrationTest(AsyncioTestCase):

View file

@ -1,5 +1,5 @@
from decimal import Decimal from decimal import Decimal
from torba.testcase import AsyncioTestCase from lbry.wallet.testcase import AsyncioTestCase
from lbry.extras.daemon.exchange_rate_manager import ExchangeRate, ExchangeRateManager from lbry.extras.daemon.exchange_rate_manager import ExchangeRate, ExchangeRateManager

View file

@ -3,7 +3,6 @@ import os
from binascii import hexlify from binascii import hexlify
from lbry.testcase import CommandTestCase from lbry.testcase import CommandTestCase
from lbry.blob_exchange.downloader import BlobDownloader
class FileCommands(CommandTestCase): class FileCommands(CommandTestCase):

View file

@ -1,6 +1,6 @@
import asyncio import asyncio
from torba.testcase import IntegrationTestCase from lbry.wallet.testcase import IntegrationTestCase
import lbry.wallet import lbry.wallet
from lbry.schema.claim import Claim from lbry.schema.claim import Claim

View file

@ -1,12 +1,14 @@
import logging import logging
import os import os
import asyncio import asyncio
import lbry
from unittest.mock import Mock from unittest.mock import Mock
from torba.client.basenetwork import BaseNetwork from lbry.wallet.client.basenetwork import BaseNetwork
from torba.orchstr8.node import SPVNode from lbry.wallet.orchstr8.node import SPVNode
from torba.rpc import RPCSession from lbry.wallet.rpc import RPCSession
from torba.testcase import IntegrationTestCase, AsyncioTestCase from lbry.wallet.testcase import IntegrationTestCase, AsyncioTestCase
class NetworkTests(IntegrationTestCase): class NetworkTests(IntegrationTestCase):
@ -22,13 +24,13 @@ class NetworkTests(IntegrationTestCase):
'genesis_hash': self.conductor.spv_node.coin_class.GENESIS_HASH, 'genesis_hash': self.conductor.spv_node.coin_class.GENESIS_HASH,
'hash_function': 'sha256', 'hash_function': 'sha256',
'hosts': {}, 'hosts': {},
'protocol_max': '1.4', 'protocol_max': '1.0',
'protocol_min': '1.1', 'protocol_min': lbry.__version__,
'pruning': None, 'pruning': None,
'description': '', 'description': '',
'payment_address': '', 'payment_address': '',
'daily_fee': 0, 'daily_fee': 0,
'server_version': '0.5.7'}, await self.ledger.network.get_server_features()) 'server_version': lbry.__version__}, await self.ledger.network.get_server_features())
await self.conductor.spv_node.stop() await self.conductor.spv_node.stop()
address = (await self.account.get_addresses(limit=1))[0] address = (await self.account.get_addresses(limit=1))[0]
os.environ.update({ os.environ.update({
@ -41,13 +43,13 @@ class NetworkTests(IntegrationTestCase):
'genesis_hash': self.conductor.spv_node.coin_class.GENESIS_HASH, 'genesis_hash': self.conductor.spv_node.coin_class.GENESIS_HASH,
'hash_function': 'sha256', 'hash_function': 'sha256',
'hosts': {}, 'hosts': {},
'protocol_max': '1.4', 'protocol_max': '1.0',
'protocol_min': '1.1', 'protocol_min': lbry.__version__,
'pruning': None, 'pruning': None,
'description': 'Fastest server in the west.', 'description': 'Fastest server in the west.',
'payment_address': address, 'payment_address': address,
'daily_fee': 42, 'daily_fee': 42,
'server_version': '0.5.7'}, await self.ledger.network.get_server_features()) 'server_version': lbry.__version__}, await self.ledger.network.get_server_features())
class ReconnectTests(IntegrationTestCase): class ReconnectTests(IntegrationTestCase):

View file

@ -1,12 +1,11 @@
import asyncio import asyncio
import json import json
import hashlib import hashlib
from unittest import skip
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
from lbry.testcase import CommandTestCase from lbry.testcase import CommandTestCase
from lbry.wallet.transaction import Transaction, Output from lbry.wallet.transaction import Transaction, Output
from lbry.schema.compat import OldClaimMessage from lbry.schema.compat import OldClaimMessage
from torba.client.hash import sha256, Base58 from lbry.wallet.client.hash import sha256, Base58
class BaseResolveTestCase(CommandTestCase): class BaseResolveTestCase(CommandTestCase):

View file

@ -1,7 +1,7 @@
import asyncio import asyncio
import logging import logging
from torba.testcase import IntegrationTestCase, WalletNode from lbry.testcase import IntegrationTestCase, WalletNode
from torba.client.constants import CENT from lbry.constants import CENT
class SyncTests(IntegrationTestCase): class SyncTests(IntegrationTestCase):

View file

@ -2,16 +2,14 @@ import logging
import asyncio import asyncio
import random import random
from itertools import chain from itertools import chain
from random import shuffle
from torba.testcase import IntegrationTestCase from lbry.wallet.transaction import Transaction, Output, Input
from torba.client.util import satoshis_to_coins, coins_to_satoshis from lbry.wallet.testcase import IntegrationTestCase
from lbry.wallet.client.util import satoshis_to_coins, coins_to_satoshis
class BasicTransactionTests(IntegrationTestCase): class BasicTransactionTests(IntegrationTestCase):
VERBOSITY = logging.WARN
async def test_variety_of_transactions_and_longish_history(self): async def test_variety_of_transactions_and_longish_history(self):
await self.blockchain.generate(300) await self.blockchain.generate(300)
await self.assertBalance(self.account, '0.0') await self.assertBalance(self.account, '0.0')
@ -41,9 +39,9 @@ class BasicTransactionTests(IntegrationTestCase):
# spend from each of the first 10 addresses to the subsequent 10 addresses # spend from each of the first 10 addresses to the subsequent 10 addresses
txs = [] txs = []
for address in addresses[10:20]: for address in addresses[10:20]:
txs.append(await self.ledger.transaction_class.create( txs.append(await Transaction.create(
[], [],
[self.ledger.transaction_class.output_class.pay_pubkey_hash( [Output.pay_pubkey_hash(
coins_to_satoshis('1.0'), self.ledger.address_to_hash160(address) coins_to_satoshis('1.0'), self.ledger.address_to_hash160(address)
)], )],
[self.account], self.account [self.account], self.account
@ -66,9 +64,9 @@ class BasicTransactionTests(IntegrationTestCase):
self.assertEqual(30, await self.account.get_utxo_count()) self.assertEqual(30, await self.account.get_utxo_count())
# spend all 30 UTXOs into a a 199 coin UTXO and change # spend all 30 UTXOs into a a 199 coin UTXO and change
tx = await self.ledger.transaction_class.create( tx = await Transaction.create(
[], [],
[self.ledger.transaction_class.output_class.pay_pubkey_hash( [Output.pay_pubkey_hash(
coins_to_satoshis('199.0'), self.ledger.address_to_hash160(addresses[-1]) coins_to_satoshis('199.0'), self.ledger.address_to_hash160(addresses[-1])
)], )],
[self.account], self.account [self.account], self.account
@ -99,9 +97,9 @@ class BasicTransactionTests(IntegrationTestCase):
await self.assertBalance(account2, '0.0') await self.assertBalance(account2, '0.0')
address2 = await account2.receiving.get_or_create_usable_address() address2 = await account2.receiving.get_or_create_usable_address()
tx = await self.ledger.transaction_class.create( tx = await Transaction.create(
[], [],
[self.ledger.transaction_class.output_class.pay_pubkey_hash( [Output.pay_pubkey_hash(
coins_to_satoshis('2.0'), self.ledger.address_to_hash160(address2) coins_to_satoshis('2.0'), self.ledger.address_to_hash160(address2)
)], )],
[account1], account1 [account1], account1
@ -115,8 +113,8 @@ class BasicTransactionTests(IntegrationTestCase):
await self.assertBalance(account2, '2.0') await self.assertBalance(account2, '2.0')
utxos = await self.account.get_utxos() utxos = await self.account.get_utxos()
tx = await self.ledger.transaction_class.create( tx = await Transaction.create(
[self.ledger.transaction_class.input_class.spend(utxos[0])], [Input.spend(utxos[0])],
[], [],
[account1], account1 [account1], account1
) )
@ -159,8 +157,8 @@ class BasicTransactionTests(IntegrationTestCase):
utxos = await self.account.get_utxos() utxos = await self.account.get_utxos()
txs = [] txs = []
for utxo in utxos: for utxo in utxos:
tx = await self.ledger.transaction_class.create( tx = await Transaction.create(
[self.ledger.transaction_class.input_class.spend(utxo)], [Input.spend(utxo)],
[], [],
[self.account], self.account [self.account], self.account
) )

View file

@ -1,8 +1,8 @@
import asyncio import asyncio
import json import json
from torba.client.wallet import ENCRYPT_ON_DISK from lbry.wallet.client.wallet import ENCRYPT_ON_DISK
from torba.client.errors import InvalidPasswordError from lbry.error import InvalidPasswordError
from lbry.testcase import CommandTestCase from lbry.testcase import CommandTestCase
from lbry.wallet.dewies import dict_values_to_lbc from lbry.wallet.dewies import dict_values_to_lbc

View file

@ -1,9 +1,9 @@
import asyncio import asyncio
import lbry
import lbry.wallet import lbry.wallet
from lbry import __version__ as sdk_version from lbry.wallet.client.basenetwork import ClientSession
from torba.client.basenetwork import ClientSession from lbry.testcase import IntegrationTestCase
from torba.testcase import IntegrationTestCase
class TestSessions(IntegrationTestCase): class TestSessions(IntegrationTestCase):
@ -33,7 +33,7 @@ class TestSessions(IntegrationTestCase):
async def test_proper_version(self): async def test_proper_version(self):
info = await self.ledger.network.get_server_features() info = await self.ledger.network.get_server_features()
self.assertEqual(sdk_version, info['server_version']) self.assertEqual(lbry.__version__, info['server_version'])
async def test_client_errors(self): async def test_client_errors(self):
# Goal is ensuring thsoe are raised and not trapped accidentally # Goal is ensuring thsoe are raised and not trapped accidentally

View file

@ -3,7 +3,7 @@ import ecdsa
import hashlib import hashlib
import logging import logging
from binascii import hexlify from binascii import hexlify
from torba.client.constants import COIN, NULL_HASH32 from lbry.wallet.client.constants import COIN, NULL_HASH32
from lbry.schema.claim import Claim from lbry.schema.claim import Claim
from lbry.wallet.server.db import reader, writer from lbry.wallet.server.db import reader, writer

View file

@ -1,9 +1,10 @@
from torba.testcase import AsyncioTestCase from binascii import hexlify
from torba.client.wallet import Wallet from lbry.wallet.testcase import AsyncioTestCase
from lbry.wallet.client.wallet import Wallet
from lbry.wallet.ledger import MainNetLedger, WalletDatabase from lbry.wallet.ledger import MainNetLedger, WalletDatabase
from lbry.wallet.header import Headers from lbry.wallet.header import Headers
from lbry.wallet.account import Account from lbry.wallet.account import Account
from lbry.wallet.client.baseaccount import SingleKey, HierarchicalDeterministic
class TestAccount(AsyncioTestCase): class TestAccount(AsyncioTestCase):
@ -41,13 +42,14 @@ class TestAccount(AsyncioTestCase):
self.assertEqual(len(addresses), 6) self.assertEqual(len(addresses), 6)
async def test_generate_keys_over_batch_threshold_saves_it_properly(self): async def test_generate_keys_over_batch_threshold_saves_it_properly(self):
async with self.account.receiving.address_generator_lock: account = Account.generate(self.ledger, Wallet(), 'lbryum')
await self.account.receiving._generate_keys(0, 200) async with account.receiving.address_generator_lock:
records = await self.account.receiving.get_address_records() await account.receiving._generate_keys(0, 200)
records = await account.receiving.get_address_records()
self.assertEqual(len(records), 201) self.assertEqual(len(records), 201)
async def test_ensure_address_gap(self): async def test_ensure_address_gap(self):
account = self.account account = Account.generate(self.ledger, Wallet(), 'lbryum')
self.assertIsInstance(account.receiving, HierarchicalDeterministic) self.assertIsInstance(account.receiving, HierarchicalDeterministic)
@ -86,7 +88,7 @@ class TestAccount(AsyncioTestCase):
self.assertEqual(len(new_keys), 20) self.assertEqual(len(new_keys), 20)
async def test_get_or_create_usable_address(self): async def test_get_or_create_usable_address(self):
account = self.account account = Account.generate(self.ledger, Wallet(), 'lbryum')
keys = await account.receiving.get_addresses() keys = await account.receiving.get_addresses()
self.assertEqual(len(keys), 0) self.assertEqual(len(keys), 0)
@ -158,9 +160,9 @@ class TestAccount(AsyncioTestCase):
await account.ensure_address_gap() await account.ensure_address_gap()
addresses = await account.receiving.get_addresses() addresses = await account.receiving.get_addresses()
self.assertEqual(len(addresses), 5) self.assertEqual(len(addresses), 17)
addresses = await account.change.get_addresses() addresses = await account.change.get_addresses()
self.assertEqual(len(addresses), 5) self.assertEqual(len(addresses), 10)
account_data['ledger'] = 'lbc_mainnet' account_data['ledger'] = 'lbc_mainnet'
self.assertDictEqual(account_data, account.to_dict()) self.assertDictEqual(account_data, account.to_dict())
@ -202,7 +204,7 @@ class TestAccount(AsyncioTestCase):
'change': {'gap': 5, 'maximum_uses_per_address': 2} 'change': {'gap': 5, 'maximum_uses_per_address': 2}
} }
} }
account = self.ledger.account_class.from_dict(self.ledger, Wallet(), account_data) account = Account.from_dict(self.ledger, Wallet(), account_data)
self.assertEqual(account.name, 'My Account') self.assertEqual(account.name, 'My Account')
self.assertEqual(account.modified_on, 123.456) self.assertEqual(account.modified_on, 123.456)
@ -234,13 +236,12 @@ class TestAccount(AsyncioTestCase):
class TestSingleKeyAccount(AsyncioTestCase): class TestSingleKeyAccount(AsyncioTestCase):
async def asyncSetUp(self): async def asyncSetUp(self):
self.ledger = ledger_class({ self.ledger = MainNetLedger({
'db': ledger_class.database_class(':memory:'), 'db': WalletDatabase(':memory:'),
'headers': ledger_class.headers_class(':memory:'), 'headers': Headers(':memory:')
}) })
await self.ledger.db.open() await self.ledger.db.open()
self.account = self.ledger.account_class.generate( self.account = Account.generate(self.ledger, Wallet(), "torba", {'name': 'single-address'})
self.ledger, Wallet(), "torba", {'name': 'single-address'})
async def asyncTearDown(self): async def asyncTearDown(self):
await self.ledger.db.close() await self.ledger.db.close()
@ -336,13 +337,13 @@ class TestSingleKeyAccount(AsyncioTestCase):
) )
self.assertEqual( self.assertEqual(
account.private_key.extended_key_string(), account.private_key.extended_key_string(),
'xprv9s21ZrQH143K3TsAz5efNV8K93g3Ms3FXcjaWB9fVUsMwAoE3ZT4vYymkp' 'xprv9s21ZrQH143K42ovpZygnjfHdAqSd9jo7zceDfPRogM7bkkoNVv7'
'5BxKKfnpz8J6sHDFriX1SnpvjNkzcks8XBnxjGLS83BTyfpna', 'DRNLEoB8HoirMgH969NrgL8jNzLEegqFzPRWM37GXd4uE8uuRkx4LAe',
) )
self.assertEqual( self.assertEqual(
account.public_key.extended_key_string(), account.public_key.extended_key_string(),
'xpub661MyMwAqRbcFwwe67Bfjd53h5WXmKm6tqfBJZZH3pQLoy8Nb6mKUMJFc7' 'xpub661MyMwAqRbcGWtPvbWh9sc2BCfw2cTeVDYF23o3N1t6UZ5wv3EM'
'UbpVNzmwFPN2evn3YHnig1pkKVYcvCV8owTd2yAcEkJfCX53g', 'mDgp66FxHuDtWdft3B5eL5xQtyzAtkdmhhC95gjRjLzSTdkho95asu9',
) )
address = await account.receiving.ensure_address_gap() address = await account.receiving.ensure_address_gap()
self.assertEqual(address[0], account.public_key.address) self.assertEqual(address[0], account.public_key.address)
@ -352,8 +353,8 @@ class TestSingleKeyAccount(AsyncioTestCase):
) )
self.assertEqual( self.assertEqual(
private_key.extended_key_string(), private_key.extended_key_string(),
'xprv9s21ZrQH143K3TsAz5efNV8K93g3Ms3FXcjaWB9fVUsMwAoE3ZT4vYymkp' 'xprv9s21ZrQH143K42ovpZygnjfHdAqSd9jo7zceDfPRogM7bkkoNVv7'
'5BxKKfnpz8J6sHDFriX1SnpvjNkzcks8XBnxjGLS83BTyfpna', 'DRNLEoB8HoirMgH969NrgL8jNzLEegqFzPRWM37GXd4uE8uuRkx4LAe',
) )
invalid_key = await self.ledger.get_private_key_for_address( invalid_key = await self.ledger.get_private_key_for_address(
@ -363,7 +364,7 @@ class TestSingleKeyAccount(AsyncioTestCase):
self.assertEqual( self.assertEqual(
hexlify(private_key.wif()), hexlify(private_key.wif()),
b'1c92caa0ef99bfd5e2ceb73b66da8cd726a9370be8c368d448a322f3c5b23aaab901' b'1cef6c80310b1bcbcfa3176ea809ac840f48cda634c475d402e6bd68d5bb3827d601'
) )
async def test_load_and_save_account(self): async def test_load_and_save_account(self):
@ -374,16 +375,15 @@ class TestSingleKeyAccount(AsyncioTestCase):
"carbon smart garage balance margin twelve chest sword toast envelope bottom stomac" "carbon smart garage balance margin twelve chest sword toast envelope bottom stomac"
"h absent", "h absent",
'encrypted': False, 'encrypted': False,
'private_key': 'private_key': 'xprv9s21ZrQH143K42ovpZygnjfHdAqSd9jo7zceDfPRogM7bkkoNVv7'
'xprv9s21ZrQH143K3TsAz5efNV8K93g3Ms3FXcjaWB9fVUsMwAoE3ZT4vYymkp' 'DRNLEoB8HoirMgH969NrgL8jNzLEegqFzPRWM37GXd4uE8uuRkx4LAe',
'5BxKKfnpz8J6sHDFriX1SnpvjNkzcks8XBnxjGLS83BTyfpna', 'public_key': 'xpub661MyMwAqRbcGWtPvbWh9sc2BCfw2cTeVDYF23o3N1t6UZ5wv3EM'
'public_key': 'mDgp66FxHuDtWdft3B5eL5xQtyzAtkdmhhC95gjRjLzSTdkho95asu9',
'xpub661MyMwAqRbcFwwe67Bfjd53h5WXmKm6tqfBJZZH3pQLoy8Nb6mKUMJFc7' 'address_generator': {'name': 'single-address'},
'UbpVNzmwFPN2evn3YHnig1pkKVYcvCV8owTd2yAcEkJfCX53g', 'certificates': {}
'address_generator': {'name': 'single-address'}
} }
account = self.ledger.account_class.from_dict(self.ledger, Wallet(), account_data) account = Account.from_dict(self.ledger, Wallet(), account_data)
await account.ensure_address_gap() await account.ensure_address_gap()
@ -393,7 +393,7 @@ class TestSingleKeyAccount(AsyncioTestCase):
self.assertEqual(len(addresses), 1) self.assertEqual(len(addresses), 1)
self.maxDiff = None self.maxDiff = None
account_data['ledger'] = 'btc_mainnet' account_data['ledger'] = 'lbc_mainnet'
self.assertDictEqual(account_data, account.to_dict()) self.assertDictEqual(account_data, account.to_dict())
@ -407,8 +407,8 @@ class AccountEncryptionTests(AsyncioTestCase):
"h absent", "h absent",
'encrypted': False, 'encrypted': False,
'private_key': 'private_key':
'xprv9s21ZrQH143K3TsAz5efNV8K93g3Ms3FXcjaWB9fVUsMwAoE3ZT4vYymkp' 'xprv9s21ZrQH143K42ovpZygnjfHdAqSd9jo7zceDfPRogM7bkkoNVv7DRNLEo'
'5BxKKfnpz8J6sHDFriX1SnpvjNkzcks8XBnxjGLS83BTyfpna', 'B8HoirMgH969NrgL8jNzLEegqFzPRWM37GXd4uE8uuRkx4LAe',
'public_key': 'public_key':
'xpub661MyMwAqRbcFwwe67Bfjd53h5WXmKm6tqfBJZZH3pQLoy8Nb6mKUMJFc7' 'xpub661MyMwAqRbcFwwe67Bfjd53h5WXmKm6tqfBJZZH3pQLoy8Nb6mKUMJFc7'
'UbpVNzmwFPN2evn3YHnig1pkKVYcvCV8owTd2yAcEkJfCX53g', 'UbpVNzmwFPN2evn3YHnig1pkKVYcvCV8owTd2yAcEkJfCX53g',
@ -422,9 +422,9 @@ class AccountEncryptionTests(AsyncioTestCase):
"bIkZ//trah9AIkmrc/ZvNkC0Q==", "bIkZ//trah9AIkmrc/ZvNkC0Q==",
'encrypted': True, 'encrypted': True,
'private_key': 'private_key':
'MDAwMDAwMDAwMDAwMDAwMLkWikOLScA/ZxlFSGU7dl//7Q/1gS9h7vqQyrd8DX+' 'MDAwMDAwMDAwMDAwMDAwMLkWikOLScA/ZxlFSGU7dl8pqVjgdpu1S3MWQF3IJ5H'
'jwcp7SwlJ1mkMwuraUaWLq9/LxiaGmqJBUZ50p77YVZbDycaCN1unBr1/i1q6RP' 'OXPAQcgnhHldVq98uP7Q8JqSWOv1p4gpxGSYnA4w5Gbuh0aUD4hmV70m7nVTj7T'
'Ob2MNCaG8nyjxZhQai+V/2JmJ+UnFMp3nHany7F8/Hr0g=', '15+Pu30DCspndru59pee/S+mShoK68q7t7r32leaVIfzw=',
'public_key': 'public_key':
'xpub661MyMwAqRbcFwwe67Bfjd53h5WXmKm6tqfBJZZH3pQLoy8Nb6mKUMJFc7' 'xpub661MyMwAqRbcFwwe67Bfjd53h5WXmKm6tqfBJZZH3pQLoy8Nb6mKUMJFc7'
'UbpVNzmwFPN2evn3YHnig1pkKVYcvCV8owTd2yAcEkJfCX53g', 'UbpVNzmwFPN2evn3YHnig1pkKVYcvCV8owTd2yAcEkJfCX53g',
@ -432,13 +432,13 @@ class AccountEncryptionTests(AsyncioTestCase):
} }
async def asyncSetUp(self): async def asyncSetUp(self):
self.ledger = ledger_class({ self.ledger = MainNetLedger({
'db': ledger_class.database_class(':memory:'), 'db': WalletDatabase(':memory:'),
'headers': ledger_class.headers_class(':memory:'), 'headers': Headers(':memory:')
}) })
def test_encrypt_wallet(self): def test_encrypt_wallet(self):
account = self.ledger.account_class.from_dict(self.ledger, Wallet(), self.unencrypted_account) account = Account.from_dict(self.ledger, Wallet(), self.unencrypted_account)
account.init_vectors = { account.init_vectors = {
'seed': self.init_vector, 'seed': self.init_vector,
'private_key': self.init_vector 'private_key': self.init_vector
@ -468,7 +468,7 @@ class AccountEncryptionTests(AsyncioTestCase):
self.assertFalse(account.encrypted) self.assertFalse(account.encrypted)
def test_decrypt_wallet(self): def test_decrypt_wallet(self):
account = self.ledger.account_class.from_dict(self.ledger, Wallet(), self.encrypted_account) account = Account.from_dict(self.ledger, Wallet(), self.encrypted_account)
self.assertTrue(account.encrypted) self.assertTrue(account.encrypted)
account.decrypt(self.password) account.decrypt(self.password)

View file

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

View file

@ -1,10 +1,10 @@
from binascii import unhexlify, hexlify from binascii import unhexlify, hexlify
from torba.testcase import AsyncioTestCase from lbry.wallet.testcase import AsyncioTestCase
from client_tests.unit.key_fixtures import expected_ids, expected_privkeys, expected_hardened_privkeys from tests.unit.wallet.key_fixtures import expected_ids, expected_privkeys, expected_hardened_privkeys
from torba.client.bip32 import PubKey, PrivateKey, from_extended_key_string from lbry.wallet.client.bip32 import PubKey, PrivateKey, from_extended_key_string
from torba.coin.bitcoinsegwit import MainNetLedger as ledger_class from lbry.wallet import MainNetLedger as ledger_class
class BIP32Tests(AsyncioTestCase): class BIP32Tests(AsyncioTestCase):
@ -60,7 +60,7 @@ class BIP32Tests(AsyncioTestCase):
self.assertEqual( self.assertEqual(
ec_point[1], 86198965946979720220333266272536217633917099472454294641561154971209433250106 ec_point[1], 86198965946979720220333266272536217633917099472454294641561154971209433250106
) )
self.assertEqual(private_key.address(), '1GVM5dEhThbiyCZ9gqBZBv6p9whga7MTXo' ) self.assertEqual('bUDcmraBp2zCV3QWmVVeQaEgepbs1b2gC9', private_key.address())
with self.assertRaisesRegex(ValueError, 'invalid BIP32 private key child number'): with self.assertRaisesRegex(ValueError, 'invalid BIP32 private key child number'):
private_key.child(-1) private_key.child(-1)
self.assertIsInstance(private_key.child(PrivateKey.HARDENED), PrivateKey) self.assertIsInstance(private_key.child(PrivateKey.HARDENED), PrivateKey)

View file

@ -2,7 +2,7 @@ import unittest
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
from lbry.wallet.claim_proofs import get_hash_for_outpoint, verify_proof from lbry.wallet.claim_proofs import get_hash_for_outpoint, verify_proof
from torba.client.hash import double_sha256 from lbry.wallet.client.hash import double_sha256
class ClaimProofsTestCase(unittest.TestCase): class ClaimProofsTestCase(unittest.TestCase):

View file

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

View file

@ -6,15 +6,15 @@ import tempfile
import asyncio import asyncio
from concurrent.futures.thread import ThreadPoolExecutor from concurrent.futures.thread import ThreadPoolExecutor
from torba.client.wallet import Wallet from lbry.wallet import MainNetLedger
from torba.client.constants import COIN from lbry.wallet.transaction import Transaction
from torba.coin.bitcoinsegwit import MainNetLedger as ledger_class from lbry.wallet.client.wallet import Wallet
from torba.client.basedatabase import query, interpolate, constraints_to_sql, AIOSQLite from lbry.wallet.client.constants import COIN
from torba.client.hash import sha256 from lbry.wallet.client.basedatabase import query, interpolate, constraints_to_sql, AIOSQLite
from lbry.wallet.client.hash import sha256
from lbry.wallet.testcase import AsyncioTestCase
from torba.testcase import AsyncioTestCase from tests.unit.wallet.test_transaction import get_output, NULL_HASH
from client_tests.unit.test_transaction import get_output, NULL_HASH
class TestAIOSQLite(AsyncioTestCase): class TestAIOSQLite(AsyncioTestCase):
@ -195,9 +195,9 @@ class TestQueryBuilder(unittest.TestCase):
class TestQueries(AsyncioTestCase): class TestQueries(AsyncioTestCase):
async def asyncSetUp(self): async def asyncSetUp(self):
self.ledger = ledger_class({ self.ledger = MainNetLedger({
'db': ledger_class.database_class(':memory:'), 'db': MainNetLedger.database_class(':memory:'),
'headers': ledger_class.headers_class(':memory:'), 'headers': MainNetLedger.headers_class(':memory:')
}) })
self.wallet = Wallet() self.wallet = Wallet()
await self.ledger.db.open() await self.ledger.db.open()
@ -212,8 +212,8 @@ class TestQueries(AsyncioTestCase):
async def create_tx_from_nothing(self, my_account, height): async def create_tx_from_nothing(self, my_account, height):
to_address = await my_account.receiving.get_or_create_usable_address() to_address = await my_account.receiving.get_or_create_usable_address()
to_hash = ledger_class.address_to_hash160(to_address) to_hash = MainNetLedger.address_to_hash160(to_address)
tx = ledger_class.transaction_class(height=height, is_verified=True) \ tx = Transaction(height=height, is_verified=True) \
.add_inputs([self.txi(self.txo(1, sha256(str(height).encode())))]) \ .add_inputs([self.txi(self.txo(1, sha256(str(height).encode())))]) \
.add_outputs([self.txo(1, to_hash)]) .add_outputs([self.txo(1, to_hash)])
await self.ledger.db.insert_transaction(tx) await self.ledger.db.insert_transaction(tx)
@ -224,8 +224,8 @@ class TestQueries(AsyncioTestCase):
from_hash = txo.script.values['pubkey_hash'] from_hash = txo.script.values['pubkey_hash']
from_address = self.ledger.hash160_to_address(from_hash) from_address = self.ledger.hash160_to_address(from_hash)
to_address = await to_account.receiving.get_or_create_usable_address() to_address = await to_account.receiving.get_or_create_usable_address()
to_hash = ledger_class.address_to_hash160(to_address) to_hash = MainNetLedger.address_to_hash160(to_address)
tx = ledger_class.transaction_class(height=height, is_verified=True) \ tx = Transaction(height=height, is_verified=True) \
.add_inputs([self.txi(txo)]) \ .add_inputs([self.txi(txo)]) \
.add_outputs([self.txo(1, to_hash)]) .add_outputs([self.txo(1, to_hash)])
await self.ledger.db.insert_transaction(tx) await self.ledger.db.insert_transaction(tx)
@ -237,7 +237,7 @@ class TestQueries(AsyncioTestCase):
from_hash = txo.script.values['pubkey_hash'] from_hash = txo.script.values['pubkey_hash']
from_address = self.ledger.hash160_to_address(from_hash) from_address = self.ledger.hash160_to_address(from_hash)
to_hash = NULL_HASH to_hash = NULL_HASH
tx = ledger_class.transaction_class(height=height, is_verified=True) \ tx = Transaction(height=height, is_verified=True) \
.add_inputs([self.txi(txo)]) \ .add_inputs([self.txi(txo)]) \
.add_outputs([self.txo(1, to_hash)]) .add_outputs([self.txo(1, to_hash)])
await self.ledger.db.insert_transaction(tx) await self.ledger.db.insert_transaction(tx)
@ -248,7 +248,7 @@ class TestQueries(AsyncioTestCase):
return get_output(int(amount*COIN), address) return get_output(int(amount*COIN), address)
def txi(self, txo): def txi(self, txo):
return ledger_class.transaction_class.input_class.spend(txo) return Transaction.input_class.spend(txo)
async def test_large_tx_doesnt_hit_variable_limits(self): async def test_large_tx_doesnt_hit_variable_limits(self):
# SQLite is usually compiled with 999 variables limit: https://www.sqlite.org/limits.html # SQLite is usually compiled with 999 variables limit: https://www.sqlite.org/limits.html
@ -408,9 +408,9 @@ class TestUpgrade(AsyncioTestCase):
return [col[0] for col in conn.execute(sql).fetchall()] return [col[0] for col in conn.execute(sql).fetchall()]
async def test_reset_on_version_change(self): async def test_reset_on_version_change(self):
self.ledger = ledger_class({ self.ledger = MainNetLedger({
'db': ledger_class.database_class(self.path), 'db': MainNetLedger.database_class(self.path),
'headers': ledger_class.headers_class(':memory:'), 'headers': MainNetLedger.headers_class(':memory:')
}) })
# initial open, pre-version enabled db # initial open, pre-version enabled db

View file

@ -1,6 +1,6 @@
from unittest import TestCase, mock from unittest import TestCase, mock
from torba.client.hash import aes_decrypt, aes_encrypt, better_aes_decrypt, better_aes_encrypt from lbry.wallet.client.hash import aes_decrypt, aes_encrypt, better_aes_decrypt, better_aes_encrypt
from torba.client.errors import InvalidPasswordError from lbry.wallet.client.errors import InvalidPasswordError
class TestAESEncryptDecrypt(TestCase): class TestAESEncryptDecrypt(TestCase):

View file

@ -1,169 +1,16 @@
import asyncio
import os import os
import asyncio
import tempfile import tempfile
from binascii import hexlify from binascii import hexlify, unhexlify
from torba.client.hash import sha256
from torba.testcase import AsyncioTestCase
from torba.coin.bitcoinsegwit import MainHeaders
from binascii import unhexlify
from torba.testcase import AsyncioTestCase
from torba.client.util import ArithUint256
from lbry.wallet.client.hash import sha256
from lbry.wallet.client.util import ArithUint256
from lbry.wallet.testcase import AsyncioTestCase
from lbry.wallet.ledger import Headers from lbry.wallet.ledger import Headers
def block_bytes(blocks): def block_bytes(blocks):
return blocks * MainHeaders.header_size return blocks * Headers.header_size
class BitcoinHeadersTestCase(AsyncioTestCase):
HEADER_FILE = 'bitcoin_headers'
RETARGET_BLOCK = 32256 # difficulty: 1 -> 1.18
def setUp(self):
self.maxDiff = None
self.header_file_name = os.path.join(os.path.dirname(__file__), self.HEADER_FILE)
def get_bytes(self, upto: int = -1, after: int = 0) -> bytes:
with open(self.header_file_name, 'rb') as headers:
headers.seek(after, os.SEEK_SET)
return headers.read(upto)
async def get_headers(self, upto: int = -1):
h = MainHeaders(':memory:')
h.io.write(self.get_bytes(upto))
return h
class BasicHeadersTests(BitcoinHeadersTestCase):
async def test_serialization(self):
h = await self.get_headers()
self.assertDictEqual(h[0], {
'bits': 486604799,
'block_height': 0,
'merkle_root': b'4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b',
'nonce': 2083236893,
'prev_block_hash': b'0000000000000000000000000000000000000000000000000000000000000000',
'timestamp': 1231006505,
'version': 1
})
self.assertDictEqual(h[self.RETARGET_BLOCK-1], {
'bits': 486604799,
'block_height': 32255,
'merkle_root': b'89b4f223789e40b5b475af6483bb05bceda54059e17d2053334b358f6bb310ac',
'nonce': 312762301,
'prev_block_hash': b'000000006baebaa74cecde6c6787c26ee0a616a3c333261bff36653babdac149',
'timestamp': 1262152739,
'version': 1
})
self.assertDictEqual(h[self.RETARGET_BLOCK], {
'bits': 486594666,
'block_height': 32256,
'merkle_root': b'64b5e5f5a262f47af443a0120609206a3305877693edfe03e994f20a024ab627',
'nonce': 121087187,
'prev_block_hash': b'00000000984f962134a7291e3693075ae03e521f0ee33378ec30a334d860034b',
'timestamp': 1262153464,
'version': 1
})
self.assertDictEqual(h[self.RETARGET_BLOCK+1], {
'bits': 486594666,
'block_height': 32257,
'merkle_root': b'4d1488981f08b3037878193297dbac701a2054e0f803d4424fe6a4d763d62334',
'nonce': 274675219,
'prev_block_hash': b'000000004f2886a170adb7204cb0c7a824217dd24d11a74423d564c4e0904967',
'timestamp': 1262154352,
'version': 1
})
self.assertEqual(
h.serialize(h[0]),
h.get_raw_header(0)
)
self.assertEqual(
h.serialize(h[self.RETARGET_BLOCK]),
h.get_raw_header(self.RETARGET_BLOCK)
)
async def test_connect_from_genesis_to_3000_past_first_chunk_at_2016(self):
headers = MainHeaders(':memory:')
self.assertEqual(headers.height, -1)
await headers.connect(0, self.get_bytes(block_bytes(3001)))
self.assertEqual(headers.height, 3000)
async def test_connect_9_blocks_passing_a_retarget_at_32256(self):
retarget = block_bytes(self.RETARGET_BLOCK-5)
headers = await self.get_headers(upto=retarget)
remainder = self.get_bytes(after=retarget)
self.assertEqual(headers.height, 32250)
await headers.connect(len(headers), remainder)
self.assertEqual(headers.height, 32259)
async def test_bounds(self):
headers = MainHeaders(':memory:')
await headers.connect(0, self.get_bytes(block_bytes(3001)))
self.assertEqual(headers.height, 3000)
with self.assertRaises(IndexError):
_ = headers[3001]
with self.assertRaises(IndexError):
_ = headers[-1]
self.assertIsNotNone(headers[3000])
self.assertIsNotNone(headers[0])
async def test_repair(self):
headers = MainHeaders(':memory:')
await headers.connect(0, self.get_bytes(block_bytes(3001)))
self.assertEqual(headers.height, 3000)
await headers.repair()
self.assertEqual(headers.height, 3000)
# corrupt the middle of it
headers.io.seek(block_bytes(1500))
headers.io.write(b"wtf")
await headers.repair()
self.assertEqual(headers.height, 1499)
self.assertEqual(len(headers), 1500)
# corrupt by appending
headers.io.seek(block_bytes(len(headers)))
headers.io.write(b"appending")
await headers.repair()
self.assertEqual(headers.height, 1499)
await headers.connect(len(headers), self.get_bytes(block_bytes(3001 - 1500), after=block_bytes(1500)))
self.assertEqual(headers.height, 3000)
async def test_checkpointed_writer(self):
headers = MainHeaders(':memory:')
headers.checkpoint = 100, hexlify(sha256(self.get_bytes(block_bytes(100))))
genblocks = lambda start, end: self.get_bytes(block_bytes(end - start), block_bytes(start))
async with headers.checkpointed_connector() as buff:
buff.write(genblocks(0, 10))
self.assertEqual(len(headers), 10)
async with headers.checkpointed_connector() as buff:
buff.write(genblocks(10, 100))
self.assertEqual(len(headers), 100)
headers = MainHeaders(':memory:')
async with headers.checkpointed_connector() as buff:
buff.write(genblocks(0, 300))
self.assertEqual(len(headers), 300)
async def test_concurrency(self):
BLOCKS = 30
headers_temporary_file = tempfile.mktemp()
headers = MainHeaders(headers_temporary_file)
await headers.open()
self.addCleanup(os.remove, headers_temporary_file)
async def writer():
for block_index in range(BLOCKS):
await headers.connect(block_index, self.get_bytes(block_bytes(block_index + 1), block_bytes(block_index)))
async def reader():
for block_index in range(BLOCKS):
while len(headers) < block_index:
await asyncio.sleep(0.000001)
assert headers[block_index]['block_height'] == block_index
reader_task = asyncio.create_task(reader())
await writer()
await reader_task
class TestHeaders(AsyncioTestCase): class TestHeaders(AsyncioTestCase):
@ -201,9 +48,9 @@ class TestHeaders(AsyncioTestCase):
async def test_connect_from_middle(self): async def test_connect_from_middle(self):
h = Headers(':memory:') h = Headers(':memory:')
h.io.write(HEADERS[:10*Headers.header_size]) h.io.write(HEADERS[:block_bytes(10)])
self.assertEqual(h.height, 9) self.assertEqual(h.height, 9)
await h.connect(len(h), HEADERS[10*Headers.header_size:20*Headers.header_size]) await h.connect(len(h), HEADERS[block_bytes(10):block_bytes(20)])
self.assertEqual(h.height, 19) self.assertEqual(h.height, 19)
def test_target_calculation(self): def test_target_calculation(self):
@ -260,6 +107,70 @@ class TestHeaders(AsyncioTestCase):
b"74044747b7c1ff867eb09a84d026b02d8dc539fb6adcec3536f3dfa9266495d9" b"74044747b7c1ff867eb09a84d026b02d8dc539fb6adcec3536f3dfa9266495d9"
) )
async def test_bounds(self):
headers = Headers(':memory:')
await headers.connect(0, HEADERS)
self.assertEqual(19, headers.height)
with self.assertRaises(IndexError):
_ = headers[3001]
with self.assertRaises(IndexError):
_ = headers[-1]
self.assertIsNotNone(headers[19])
self.assertIsNotNone(headers[0])
async def test_repair(self):
headers = Headers(':memory:')
await headers.connect(0, HEADERS[:block_bytes(11)])
self.assertEqual(10, headers.height)
await headers.repair()
self.assertEqual(10, headers.height)
# corrupt the middle of it
headers.io.seek(block_bytes(8))
headers.io.write(b"wtf")
await headers.repair()
self.assertEqual(7, headers.height)
self.assertEqual(8, len(headers))
# corrupt by appending
headers.io.seek(block_bytes(len(headers)))
headers.io.write(b"appending")
await headers.repair()
self.assertEqual(7, headers.height)
await headers.connect(len(headers), HEADERS[block_bytes(8):])
self.assertEqual(19, headers.height)
async def test_checkpointed_writer(self):
headers = Headers(':memory:')
getblocks = lambda start, end: HEADERS[block_bytes(start):block_bytes(end)]
headers.checkpoint = 10, hexlify(sha256(getblocks(10, 11)))
async with headers.checkpointed_connector() as buff:
buff.write(getblocks(0, 10))
self.assertEqual(len(headers), 10)
async with headers.checkpointed_connector() as buff:
buff.write(getblocks(10, 19))
self.assertEqual(len(headers), 19)
headers = Headers(':memory:')
async with headers.checkpointed_connector() as buff:
buff.write(getblocks(0, 19))
self.assertEqual(len(headers), 19)
async def test_concurrency(self):
BLOCKS = 19
headers_temporary_file = tempfile.mktemp()
headers = Headers(headers_temporary_file)
await headers.open()
self.addCleanup(os.remove, headers_temporary_file)
async def writer():
for block_index in range(BLOCKS):
await headers.connect(block_index, HEADERS[block_bytes(block_index):block_bytes(block_index + 1)])
async def reader():
for block_index in range(BLOCKS):
while len(headers) < block_index:
await asyncio.sleep(0.000001)
assert headers[block_index]['block_height'] == block_index
reader_task = asyncio.create_task(reader())
await writer()
await reader_task
HEADERS = unhexlify( HEADERS = unhexlify(
b'010000000000000000000000000000000000000000000000000000000000000000000000cc59e59ff97ac092b55e4' b'010000000000000000000000000000000000000000000000000000000000000000000000cc59e59ff97ac092b55e4'

View file

@ -1,18 +1,15 @@
import os import os
from binascii import hexlify from binascii import hexlify
from torba.coin.bitcoinsegwit import MainNetLedger from lbry.wallet.testcase import AsyncioTestCase
from torba.client.wallet import Wallet from lbry.wallet.client.wallet import Wallet
from client_tests.unit.test_transaction import get_transaction, get_output
from client_tests.unit.test_headers import BitcoinHeadersTestCase, block_bytes
from torba.testcase import AsyncioTestCase
from torba.client.wallet import Wallet
from lbry.wallet.account import Account from lbry.wallet.account import Account
from lbry.wallet.transaction import Transaction, Output, Input from lbry.wallet.transaction import Transaction, Output, Input
from lbry.wallet.ledger import MainNetLedger from lbry.wallet.ledger import MainNetLedger
from tests.unit.wallet.test_transaction import get_transaction, get_output
from tests.unit.wallet.test_headers import HEADERS, block_bytes
class MockNetwork: class MockNetwork:
@ -61,6 +58,7 @@ class LedgerTestCase(AsyncioTestCase):
'nonce': 2083236893, 'nonce': 2083236893,
'prev_block_hash': b'0000000000000000000000000000000000000000000000000000000000000000', 'prev_block_hash': b'0000000000000000000000000000000000000000000000000000000000000000',
'timestamp': 1231006505, 'timestamp': 1231006505,
'claim_trie_root': b'4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b',
'version': 1 'version': 1
} }
header.update(kwargs) header.update(kwargs)
@ -144,37 +142,37 @@ class BlockchainReorganizationTests(LedgerTestCase):
async def test_1_block_reorganization(self): async def test_1_block_reorganization(self):
self.ledger.network = MocHeaderNetwork({ self.ledger.network = MocHeaderNetwork({
20: {'height': 20, 'count': 5, 'hex': hexlify( 10: {'height': 10, 'count': 5, 'hex': hexlify(
self.get_bytes(after=block_bytes(20), upto=block_bytes(5)) HEADERS[block_bytes(10):block_bytes(15)]
)}, )},
25: {'height': 25, 'count': 0, 'hex': b''} 15: {'height': 15, 'count': 0, 'hex': b''}
}) })
headers = self.ledger.headers headers = self.ledger.headers
await headers.connect(0, self.get_bytes(upto=block_bytes(20))) await headers.connect(0, HEADERS[:block_bytes(10)])
self.add_header(block_height=len(headers)) self.add_header(block_height=len(headers))
self.assertEqual(headers.height, 20) self.assertEqual(10, headers.height)
await self.ledger.receive_header([{ await self.ledger.receive_header([{
'height': 21, 'hex': hexlify(self.make_header(block_height=21)) 'height': 11, 'hex': hexlify(self.make_header(block_height=11))
}]) }])
async def test_3_block_reorganization(self): async def test_3_block_reorganization(self):
self.ledger.network = MocHeaderNetwork({ self.ledger.network = MocHeaderNetwork({
20: {'height': 20, 'count': 5, 'hex': hexlify( 10: {'height': 10, 'count': 5, 'hex': hexlify(
self.get_bytes(after=block_bytes(20), upto=block_bytes(5)) HEADERS[block_bytes(10):block_bytes(15)]
)}, )},
21: {'height': 21, 'count': 1, 'hex': hexlify(self.make_header(block_height=21))}, 11: {'height': 11, 'count': 1, 'hex': hexlify(self.make_header(block_height=11))},
22: {'height': 22, 'count': 1, 'hex': hexlify(self.make_header(block_height=22))}, 12: {'height': 12, 'count': 1, 'hex': hexlify(self.make_header(block_height=12))},
25: {'height': 25, 'count': 0, 'hex': b''} 15: {'height': 15, 'count': 0, 'hex': b''}
}) })
headers = self.ledger.headers headers = self.ledger.headers
await headers.connect(0, self.get_bytes(upto=block_bytes(20))) await headers.connect(0, HEADERS[:block_bytes(10)])
self.add_header(block_height=len(headers)) self.add_header(block_height=len(headers))
self.add_header(block_height=len(headers)) self.add_header(block_height=len(headers))
self.add_header(block_height=len(headers)) self.add_header(block_height=len(headers))
self.assertEqual(headers.height, 22) self.assertEqual(headers.height, 12)
await self.ledger.receive_header(({ await self.ledger.receive_header([{
'height': 23, 'hex': hexlify(self.make_header(block_height=23)) 'height': 13, 'hex': hexlify(self.make_header(block_height=13))
},)) }])
class BasicAccountingTests(LedgerTestCase): class BasicAccountingTests(LedgerTestCase):

View file

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

View file

@ -1,9 +1,7 @@
from binascii import unhexlify from binascii import unhexlify
from cryptography.exceptions import InvalidSignature from lbry.wallet.testcase import AsyncioTestCase
from lbry.wallet.client.constants import CENT, NULL_HASH32
from torba.testcase import AsyncioTestCase
from torba.client.constants import CENT, NULL_HASH32
from lbry.wallet.ledger import MainNetLedger from lbry.wallet.ledger import MainNetLedger
from lbry.wallet.transaction import Transaction, Input, Output from lbry.wallet.transaction import Transaction, Input, Output

View file

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

View file

@ -1,6 +1,6 @@
from torba.stream import StreamController from lbry.wallet.stream import StreamController
from torba.tasks import TaskGroup from lbry.wallet.tasks import TaskGroup
from torba.testcase import AsyncioTestCase from lbry.wallet.testcase import AsyncioTestCase
class StreamControllerTestCase(AsyncioTestCase): class StreamControllerTestCase(AsyncioTestCase):

View file

@ -1,26 +1,28 @@
import unittest import unittest
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
from itertools import cycle
from torba.testcase import AsyncioTestCase from lbry.wallet.testcase import AsyncioTestCase
from torba.client.constants import CENT, COIN, NULL_HASH32 from lbry.wallet.client.constants import CENT, COIN, NULL_HASH32
from torba.client.wallet import Wallet from lbry.wallet.client.wallet import Wallet
from lbry.wallet.ledger import MainNetLedger from lbry.wallet.ledger import MainNetLedger
from lbry.wallet.transaction import Transaction, Output, Input from lbry.wallet.transaction import Transaction, Output, Input
NULL_HASH = b'\x00'*32
FEE_PER_BYTE = 50 FEE_PER_BYTE = 50
FEE_PER_CHAR = 200000 FEE_PER_CHAR = 200000
def get_output(amount=CENT, pubkey_hash=NULL_HASH32): def get_output(amount=CENT, pubkey_hash=NULL_HASH32, height=-2):
return Transaction() \ return Transaction(height=height) \
.add_outputs([Output.pay_pubkey_hash(amount, pubkey_hash)]) \ .add_outputs([Output.pay_pubkey_hash(amount, pubkey_hash)]) \
.outputs[0] .outputs[0]
def get_input(): def get_input(amount=CENT, pubkey_hash=NULL_HASH):
return Input.spend(get_output()) return Input.spend(get_output(amount, pubkey_hash))
def get_transaction(txo=None): def get_transaction(txo=None):
@ -95,7 +97,7 @@ class TestAccountBalanceImpactFromTransaction(unittest.TestCase):
_ = tx.net_account_balance _ = tx.net_account_balance
def test_paying_from_my_account_to_other_account(self): def test_paying_from_my_account_to_other_account(self):
tx = ledger_class.transaction_class() \ tx = Transaction() \
.add_inputs([get_input(300*CENT)]) \ .add_inputs([get_input(300*CENT)]) \
.add_outputs([get_output(190*CENT, NULL_HASH), .add_outputs([get_output(190*CENT, NULL_HASH),
get_output(100*CENT, NULL_HASH)]) get_output(100*CENT, NULL_HASH)])
@ -105,7 +107,7 @@ class TestAccountBalanceImpactFromTransaction(unittest.TestCase):
self.assertEqual(tx.net_account_balance, -200*CENT) self.assertEqual(tx.net_account_balance, -200*CENT)
def test_paying_from_other_account_to_my_account(self): def test_paying_from_other_account_to_my_account(self):
tx = ledger_class.transaction_class() \ tx = Transaction() \
.add_inputs([get_input(300*CENT)]) \ .add_inputs([get_input(300*CENT)]) \
.add_outputs([get_output(190*CENT, NULL_HASH), .add_outputs([get_output(190*CENT, NULL_HASH),
get_output(100*CENT, NULL_HASH)]) get_output(100*CENT, NULL_HASH)])
@ -115,7 +117,7 @@ class TestAccountBalanceImpactFromTransaction(unittest.TestCase):
self.assertEqual(tx.net_account_balance, 190*CENT) self.assertEqual(tx.net_account_balance, 190*CENT)
def test_paying_from_my_account_to_my_account(self): def test_paying_from_my_account_to_my_account(self):
tx = ledger_class.transaction_class() \ tx = Transaction() \
.add_inputs([get_input(300*CENT)]) \ .add_inputs([get_input(300*CENT)]) \
.add_outputs([get_output(190*CENT, NULL_HASH), .add_outputs([get_output(190*CENT, NULL_HASH),
get_output(100*CENT, NULL_HASH)]) get_output(100*CENT, NULL_HASH)])
@ -303,9 +305,9 @@ class TestTransactionSigning(AsyncioTestCase):
class TransactionIOBalancing(AsyncioTestCase): class TransactionIOBalancing(AsyncioTestCase):
async def asyncSetUp(self): async def asyncSetUp(self):
self.ledger = ledger_class({ self.ledger = MainNetLedger({
'db': ledger_class.database_class(':memory:'), 'db': MainNetLedger.database_class(':memory:'),
'headers': ledger_class.headers_class(':memory:'), 'headers': MainNetLedger.headers_class(':memory:')
}) })
await self.ledger.db.open() await self.ledger.db.open()
self.account = self.ledger.account_class.from_dict( self.account = self.ledger.account_class.from_dict(
@ -326,15 +328,15 @@ class TransactionIOBalancing(AsyncioTestCase):
return get_output(int(amount*COIN), address or next(self.hash_cycler)) return get_output(int(amount*COIN), address or next(self.hash_cycler))
def txi(self, txo): def txi(self, txo):
return ledger_class.transaction_class.input_class.spend(txo) return Transaction.input_class.spend(txo)
def tx(self, inputs, outputs): def tx(self, inputs, outputs):
return ledger_class.transaction_class.create(inputs, outputs, [self.account], self.account) return Transaction.create(inputs, outputs, [self.account], self.account)
async def create_utxos(self, amounts): async def create_utxos(self, amounts):
utxos = [self.txo(amount) for amount in amounts] utxos = [self.txo(amount) for amount in amounts]
self.funding_tx = ledger_class.transaction_class(is_verified=True) \ self.funding_tx = Transaction(is_verified=True) \
.add_inputs([self.txi(self.txo(sum(amounts)+0.1))]) \ .add_inputs([self.txi(self.txo(sum(amounts)+0.1))]) \
.add_outputs(utxos) .add_outputs(utxos)

View file

@ -1,7 +1,7 @@
import unittest import unittest
from torba.client.util import ArithUint256 from lbry.wallet.client.util import ArithUint256
from torba.client.util import coins_to_satoshis as c2s, satoshis_to_coins as s2c from lbry.wallet.client.util import coins_to_satoshis as c2s, satoshis_to_coins as s2c
class TestCoinValueParsing(unittest.TestCase): class TestCoinValueParsing(unittest.TestCase):

View file

@ -2,12 +2,11 @@ import tempfile
from binascii import hexlify from binascii import hexlify
from unittest import TestCase, mock from unittest import TestCase, mock
from torba.testcase import AsyncioTestCase from lbry.wallet.testcase import AsyncioTestCase
from torba.coin.bitcoinsegwit import MainNetLedger as BTCLedger from lbry.wallet.ledger import MainNetLedger, RegTestLedger
from torba.coin.bitcoincash import MainNetLedger as BCHLedger from lbry.wallet.client.basemanager import BaseWalletManager
from torba.client.basemanager import BaseWalletManager from lbry.wallet.client.wallet import Wallet, WalletStorage, TimestampedPreferences
from torba.client.wallet import Wallet, WalletStorage, TimestampedPreferences
class TestWalletCreation(AsyncioTestCase): class TestWalletCreation(AsyncioTestCase):
@ -15,17 +14,17 @@ class TestWalletCreation(AsyncioTestCase):
async def asyncSetUp(self): async def asyncSetUp(self):
self.manager = BaseWalletManager() self.manager = BaseWalletManager()
config = {'data_path': '/tmp/wallet'} config = {'data_path': '/tmp/wallet'}
self.btc_ledger = self.manager.get_or_create_ledger(BTCLedger.get_id(), config) self.main_ledger = self.manager.get_or_create_ledger(MainNetLedger.get_id(), config)
self.bch_ledger = self.manager.get_or_create_ledger(BCHLedger.get_id(), config) self.test_ledger = self.manager.get_or_create_ledger(RegTestLedger.get_id(), config)
def test_create_wallet_and_accounts(self): def test_create_wallet_and_accounts(self):
wallet = Wallet() wallet = Wallet()
self.assertEqual(wallet.name, 'Wallet') self.assertEqual(wallet.name, 'Wallet')
self.assertListEqual(wallet.accounts, []) self.assertListEqual(wallet.accounts, [])
account1 = wallet.generate_account(self.btc_ledger) account1 = wallet.generate_account(self.main_ledger)
wallet.generate_account(self.btc_ledger) wallet.generate_account(self.main_ledger)
wallet.generate_account(self.bch_ledger) wallet.generate_account(self.test_ledger)
self.assertEqual(wallet.default_account, account1) self.assertEqual(wallet.default_account, account1)
self.assertEqual(len(wallet.accounts), 3) self.assertEqual(len(wallet.accounts), 3)
@ -36,19 +35,20 @@ class TestWalletCreation(AsyncioTestCase):
'preferences': {}, 'preferences': {},
'accounts': [ 'accounts': [
{ {
'certificates': {},
'name': 'An Account', 'name': 'An Account',
'ledger': 'btc_mainnet', 'ledger': 'lbc_mainnet',
'modified_on': 123.456, 'modified_on': 123.456,
'seed': 'seed':
"carbon smart garage balance margin twelve chest sword toast envelope bottom stomac" "carbon smart garage balance margin twelve chest sword toast envelope bottom stomac"
"h absent", "h absent",
'encrypted': False, 'encrypted': False,
'private_key': 'private_key':
'xprv9s21ZrQH143K3TsAz5efNV8K93g3Ms3FXcjaWB9fVUsMwAoE3Z' 'xprv9s21ZrQH143K42ovpZygnjfHdAqSd9jo7zceDfPRogM7bkkoNVv7'
'T4vYymkp5BxKKfnpz8J6sHDFriX1SnpvjNkzcks8XBnxjGLS83BTyfpna', 'DRNLEoB8HoirMgH969NrgL8jNzLEegqFzPRWM37GXd4uE8uuRkx4LAe',
'public_key': 'public_key':
'xpub661MyMwAqRbcFwwe67Bfjd53h5WXmKm6tqfBJZZH3pQLoy8Nb6' 'xpub661MyMwAqRbcGWtPvbWh9sc2BCfw2cTeVDYF23o3N1t6UZ5wv3EMm'
'mKUMJFc7UbpVNzmwFPN2evn3YHnig1pkKVYcvCV8owTd2yAcEkJfCX53g', 'Dgp66FxHuDtWdft3B5eL5xQtyzAtkdmhhC95gjRjLzSTdkho95asu9',
'address_generator': { 'address_generator': {
'name': 'deterministic-chain', 'name': 'deterministic-chain',
'receiving': {'gap': 17, 'maximum_uses_per_address': 3}, 'receiving': {'gap': 17, 'maximum_uses_per_address': 3},
@ -62,11 +62,11 @@ class TestWalletCreation(AsyncioTestCase):
wallet = Wallet.from_storage(storage, self.manager) wallet = Wallet.from_storage(storage, self.manager)
self.assertEqual(wallet.name, 'Main Wallet') self.assertEqual(wallet.name, 'Main Wallet')
self.assertEqual( self.assertEqual(
hexlify(wallet.hash), b'1bd61fbe18875cb7828c466022af576104ed861c8a1fdb1dadf5e39417a68483' hexlify(wallet.hash), b'a75913d2e7339c1a9ac0c89d621a4e10fd3a40dc3560dc01f4cf4ada0a0b05b8'
) )
self.assertEqual(len(wallet.accounts), 1) self.assertEqual(len(wallet.accounts), 1)
account = wallet.default_account account = wallet.default_account
self.assertIsInstance(account, BTCLedger.account_class) self.assertIsInstance(account, MainNetLedger.account_class)
self.maxDiff = None self.maxDiff = None
self.assertDictEqual(wallet_dict, wallet.to_dict()) self.assertDictEqual(wallet_dict, wallet.to_dict())
@ -77,7 +77,7 @@ class TestWalletCreation(AsyncioTestCase):
def test_read_write(self): def test_read_write(self):
manager = BaseWalletManager() manager = BaseWalletManager()
config = {'data_path': '/tmp/wallet'} config = {'data_path': '/tmp/wallet'}
ledger = manager.get_or_create_ledger(BTCLedger.get_id(), config) ledger = manager.get_or_create_ledger(MainNetLedger.get_id(), config)
with tempfile.NamedTemporaryFile(suffix='.json') as wallet_file: with tempfile.NamedTemporaryFile(suffix='.json') as wallet_file:
wallet_file.write(b'{"version": 1}') wallet_file.write(b'{"version": 1}')
@ -98,11 +98,11 @@ class TestWalletCreation(AsyncioTestCase):
wallet1 = Wallet() wallet1 = Wallet()
wallet1.preferences['one'] = 1 wallet1.preferences['one'] = 1
wallet1.preferences['conflict'] = 1 wallet1.preferences['conflict'] = 1
wallet1.generate_account(self.btc_ledger) wallet1.generate_account(self.main_ledger)
wallet2 = Wallet() wallet2 = Wallet()
wallet2.preferences['two'] = 2 wallet2.preferences['two'] = 2
wallet2.preferences['conflict'] = 2 # will be more recent wallet2.preferences['conflict'] = 2 # will be more recent
wallet2.generate_account(self.btc_ledger) wallet2.generate_account(self.main_ledger)
self.assertEqual(len(wallet1.accounts), 1) self.assertEqual(len(wallet1.accounts), 1)
self.assertEqual(wallet1.preferences, {'one': 1, 'conflict': 1}) self.assertEqual(wallet1.preferences, {'one': 1, 'conflict': 1})