forked from LBRYCommunity/lbry-sdk
updated files and scripts post torba merge
This commit is contained in:
parent
ea5322af82
commit
f0d7ea4cc6
98 changed files with 927 additions and 1520 deletions
|
@ -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:
|
||||||
|
|
17
.travis.yml
17
.travis.yml
|
@ -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
|
||||||
|
|
7
Makefile
7
Makefile
|
@ -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
|
||||||
|
|
|
@ -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('.')))
|
||||||
|
|
||||||
|
|
|
@ -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
2
lbry/lbry/constants.py
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
CENT = 1000000
|
||||||
|
COIN = 100*CENT
|
0
lbry/lbry/crypto/__init__.py
Normal file
0
lbry/lbry/crypto/__init__.py
Normal file
86
lbry/lbry/crypto/base58.py
Normal file
86
lbry/lbry/crypto/base58.py
Normal 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
69
lbry/lbry/crypto/crypt.py
Normal 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
47
lbry/lbry/crypto/hash.py
Normal 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
13
lbry/lbry/crypto/util.py
Normal 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))
|
|
@ -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
|
||||||
|
|
|
@ -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__)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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":
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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__)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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__)
|
||||||
|
|
||||||
|
|
|
@ -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__)
|
||||||
|
|
||||||
|
|
|
@ -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__)
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,2 @@
|
||||||
class InvalidPasswordError(Exception):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__("Password is invalid.")
|
|
||||||
|
|
||||||
|
|
||||||
class InsufficientFundsError(Exception):
|
class InsufficientFundsError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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__)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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__)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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})
|
||||||
|
|
Loading…
Reference in a new issue