This commit is contained in:
Lex Berezhny 2018-07-11 23:18:59 -04:00 committed by Jack Robison
parent f85e61d8ed
commit 70a7ca95fe
No known key found for this signature in database
GPG key ID: DF25C68FE0239BB2
8 changed files with 181 additions and 51 deletions

View file

@ -15,6 +15,6 @@ class LoopingCallManager(object):
self.calls[name].stop() self.calls[name].stop()
def shutdown(self): def shutdown(self):
for lcall in self.calls.itervalues(): for lcall in self.calls.values():
if lcall.running: if lcall.running:
lcall.stop() lcall.stop()

View file

@ -178,7 +178,9 @@ class WalletIsLocked(RequiredCondition):
@staticmethod @staticmethod
def evaluate(component): def evaluate(component):
return component.check_locked() d = component.check_locked()
d.addCallback(lambda r: not r)
return d
class Daemon(AuthJSONRPCServer): class Daemon(AuthJSONRPCServer):
@ -245,7 +247,7 @@ class Daemon(AuthJSONRPCServer):
def _stop_streams(self): def _stop_streams(self):
"""stop pending GetStream downloads""" """stop pending GetStream downloads"""
for sd_hash, stream in self.streams.iteritems(): for sd_hash, stream in self.streams.items():
stream.cancel(reason="daemon shutdown") stream.cancel(reason="daemon shutdown")
def _shutdown(self): def _shutdown(self):
@ -360,10 +362,10 @@ class Daemon(AuthJSONRPCServer):
defer.returnValue(result) defer.returnValue(result)
@defer.inlineCallbacks @defer.inlineCallbacks
def _publish_stream(self, name, bid, claim_dict, file_path=None, certificate_id=None, def _publish_stream(self, name, bid, claim_dict, file_path=None, certificate=None,
claim_address=None, change_address=None): claim_address=None, change_address=None):
publisher = Publisher( publisher = Publisher(
self.blob_manager, self.payment_rate_manager, self.storage, self.file_manager, self.wallet, certificate_id self.blob_manager, self.payment_rate_manager, self.storage, self.file_manager, self.wallet, certificate
) )
parse_lbry_uri(name) parse_lbry_uri(name)
if not file_path: if not file_path:
@ -1723,23 +1725,23 @@ class Daemon(AuthJSONRPCServer):
if bid <= 0.0: if bid <= 0.0:
raise ValueError("Bid value must be greater than 0.0") raise ValueError("Bid value must be greater than 0.0")
for address in [claim_address, change_address]: bid = int(bid * COIN)
if address is not None:
# raises an error if the address is invalid
decode_address(address)
yield self.wallet.update_balance() available = yield self.wallet.default_account.get_balance()
if bid >= self.wallet.get_balance(): if bid >= available:
balance = yield self.wallet.get_max_usable_balance_for_claim(name) # TODO: add check for existing claim balance
max_bid_amount = balance - MAX_UPDATE_FEE_ESTIMATE #balance = yield self.wallet.get_max_usable_balance_for_claim(name)
if balance <= MAX_UPDATE_FEE_ESTIMATE: #max_bid_amount = balance - MAX_UPDATE_FEE_ESTIMATE
raise InsufficientFundsError( #if balance <= MAX_UPDATE_FEE_ESTIMATE:
"Insufficient funds, please deposit additional LBC. Minimum additional LBC needed {}" raise InsufficientFundsError(
.format(MAX_UPDATE_FEE_ESTIMATE - balance)) "Insufficient funds, please deposit additional LBC. Minimum additional LBC needed {}"
elif bid > max_bid_amount: .format(round((bid - available)/COIN + 0.01, 2))
raise InsufficientFundsError( )
"Please lower the bid value, the maximum amount you can specify for this claim is {}." # .format(MAX_UPDATE_FEE_ESTIMATE - balance))
.format(max_bid_amount)) #elif bid > max_bid_amount:
# raise InsufficientFundsError(
# "Please lower the bid value, the maximum amount you can specify for this claim is {}."
# .format(max_bid_amount))
metadata = metadata or {} metadata = metadata or {}
if fee is not None: if fee is not None:
@ -1777,7 +1779,7 @@ class Daemon(AuthJSONRPCServer):
log.warning("Stripping empty fee from published metadata") log.warning("Stripping empty fee from published metadata")
del metadata['fee'] del metadata['fee']
elif 'address' not in metadata['fee']: elif 'address' not in metadata['fee']:
address = yield self.wallet.get_least_used_address() address = yield self.wallet.default_account.receiving.get_or_create_usable_address()
metadata['fee']['address'] = address metadata['fee']['address'] = address
if 'fee' in metadata and 'version' not in metadata['fee']: if 'fee' in metadata and 'version' not in metadata['fee']:
metadata['fee']['version'] = '_0_0_1' metadata['fee']['version'] = '_0_0_1'
@ -1830,20 +1832,15 @@ class Daemon(AuthJSONRPCServer):
}) })
if channel_id: if channel_id:
certificate_id = channel_id certificate = self.wallet.default_account.get_certificate(by_claim_id=channel_id)
elif channel_name: elif channel_name:
certificate_id = None certificate = self.wallet.default_account.get_certificate(by_name=channel_name)
my_certificates = yield self.wallet.channel_list() if not certificate:
for certificate in my_certificates:
if channel_name == certificate['name']:
certificate_id = certificate['claim_id']
break
if not certificate_id:
raise Exception("Cannot publish using channel %s" % channel_name) raise Exception("Cannot publish using channel %s" % channel_name)
else: else:
certificate_id = None certificate = None
result = yield self._publish_stream(name, bid, claim_dict, file_path, certificate_id, result = yield self._publish_stream(name, bid, claim_dict, file_path, certificate,
claim_address, change_address) claim_address, change_address)
response = yield self._render_response(result) response = yield self._render_response(result)
defer.returnValue(response) defer.returnValue(response)
@ -2756,7 +2753,7 @@ class Daemon(AuthJSONRPCServer):
if sd_hash in self.blob_manager.blobs: if sd_hash in self.blob_manager.blobs:
blobs = [self.blob_manager.blobs[sd_hash]] + blobs blobs = [self.blob_manager.blobs[sd_hash]] + blobs
else: else:
blobs = self.blob_manager.blobs.itervalues() blobs = self.blob_manager.blobs.values()
if needed: if needed:
blobs = [blob for blob in blobs if not blob.get_is_verified()] blobs = [blob for blob in blobs if not blob.get_is_verified()]

View file

@ -11,13 +11,13 @@ log = logging.getLogger(__name__)
class Publisher(object): class Publisher(object):
def __init__(self, blob_manager, payment_rate_manager, storage, lbry_file_manager, wallet, certificate_id): def __init__(self, blob_manager, payment_rate_manager, storage, lbry_file_manager, wallet, certificate):
self.blob_manager = blob_manager self.blob_manager = blob_manager
self.payment_rate_manager = payment_rate_manager self.payment_rate_manager = payment_rate_manager
self.storage = storage self.storage = storage
self.lbry_file_manager = lbry_file_manager self.lbry_file_manager = lbry_file_manager
self.wallet = wallet self.wallet = wallet
self.certificate_id = certificate_id self.certificate = certificate
self.lbry_file = None self.lbry_file = None
@defer.inlineCallbacks @defer.inlineCallbacks
@ -74,7 +74,7 @@ class Publisher(object):
@defer.inlineCallbacks @defer.inlineCallbacks
def make_claim(self, name, bid, claim_dict, claim_address=None, change_address=None): def make_claim(self, name, bid, claim_dict, claim_address=None, change_address=None):
claim_out = yield self.wallet.claim_name(name, bid, claim_dict, claim_out = yield self.wallet.claim_name(name, bid, claim_dict,
certificate_id=self.certificate_id, certificate=self.certificate,
claim_address=claim_address, claim_address=claim_address,
change_address=change_address) change_address=change_address)
defer.returnValue(claim_out) defer.returnValue(claim_out)

View file

@ -1,7 +1,12 @@
from binascii import hexlify
from twisted.internet import defer
from torba.baseaccount import BaseAccount
from lbryschema.claim import ClaimDict from lbryschema.claim import ClaimDict
from lbryschema.signer import SECP256k1, get_signer from lbryschema.signer import SECP256k1, get_signer
from torba.baseaccount import BaseAccount from .transaction import Transaction
def generate_certificate(): def generate_certificate():
@ -9,19 +14,32 @@ def generate_certificate():
return ClaimDict.generate_certificate(secp256k1_private_key, curve=SECP256k1), secp256k1_private_key return ClaimDict.generate_certificate(secp256k1_private_key, curve=SECP256k1), secp256k1_private_key
def get_certificate_lookup(tx_or_hash, nout):
if isinstance(tx_or_hash, Transaction):
return '{}:{}'.format(tx_or_hash.hex_id.decode(), nout)
else:
return '{}:{}'.format(hexlify(tx_or_hash[::-1]).decode(), nout)
class Account(BaseAccount): class Account(BaseAccount):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(Account, self).__init__(*args, **kwargs) super(Account, self).__init__(*args, **kwargs)
self.certificates = {} self.certificates = {}
def add_certificate(self, claim_id, key): def add_certificate(self, tx, nout, private_key):
assert claim_id not in self.certificates, 'Trying to add a duplicate certificate.' lookup_key = '{}:{}'.format(tx.hex_id.decode(), nout)
self.certificates[claim_id] = key assert lookup_key not in self.certificates, 'Trying to add a duplicate certificate.'
self.certificates[lookup_key] = private_key
def get_certificate(self, claim_id): def get_certificate_private_key(self, tx_or_hash, nout):
return self.certificates[claim_id] return self.certificates.get(get_certificate_lookup(tx_or_hash, nout))
@defer.inlineCallbacks
def maybe_migrate_certificates(self):
for lookup_key in self.certificates.keys():
if ':' not in lookup_key:
claim = self.ledger.
def get_balance(self, include_claims=False): def get_balance(self, include_claims=False):
if include_claims: if include_claims:
return super(Account, self).get_balance() return super(Account, self).get_balance()

View file

@ -1,4 +1,7 @@
import sqlite3
from twisted.internet import defer
from torba.basedatabase import BaseDatabase from torba.basedatabase import BaseDatabase
from .certificate import Certificate
class WalletDatabase(BaseDatabase): class WalletDatabase(BaseDatabase):
@ -12,7 +15,8 @@ class WalletDatabase(BaseDatabase):
amount integer not null, amount integer not null,
script blob not null, script blob not null,
is_reserved boolean not null default 0, is_reserved boolean not null default 0,
claim_id blob,
claim_name text, claim_name text,
is_claim boolean not null default 0, is_claim boolean not null default 0,
is_update boolean not null default 0, is_update boolean not null default 0,
@ -37,3 +41,43 @@ class WalletDatabase(BaseDatabase):
if txo.script.is_claim_involved: if txo.script.is_claim_involved:
row['claim_name'] = txo.script.values['claim_name'] row['claim_name'] = txo.script.values['claim_name']
return row return row
@defer.inlineCallbacks
def get_certificates(self, name, private_key_accounts=None, exclude_without_key=False):
txos = yield self.db.runQuery(
"""
SELECT tx.hash, txo.position, txo.claim_id
FROM txo JOIN tx ON tx.txhash=txo.txhash
WHERE claim_name=:claim AND (is_claim=1 OR is_update=1)
ORDER BY tx.height DESC
GROUP BY txo.claim_id
""", {'name': name}
)
certificates = [
Certificate(
values[0],
values[1],
values[2],
name,
None
) for values in txos
]
# Lookup private keys for each certificate.
if private_key_accounts is not None:
for cert in certificates:
for account in private_key_accounts:
private_key = account.get_certificate_private_key(
cert.txhash, cert.nout
)
if private_key is not None:
cert.private_key = private_key
break
if exclude_without_key:
defer.returnValue([
c for c in certificates if c.private_key is not None
])
defer.returnValue(certificates)

View file

@ -2,6 +2,8 @@ import struct
from six import int2byte from six import int2byte
from binascii import unhexlify from binascii import unhexlify
from twisted.internet import defer
from torba.baseledger import BaseLedger from torba.baseledger import BaseLedger
from torba.baseheader import BaseHeaders, _ArithUint256 from torba.baseheader import BaseHeaders, _ArithUint256
from torba.util import int_to_hex, rev_hex, hash_encode from torba.util import int_to_hex, rev_hex, hash_encode
@ -132,6 +134,13 @@ class MainNetLedger(BaseLedger):
self.headers.hash(), *uris self.headers.hash(), *uris
) )
@defer.inlineCallbacks
def start(self):
yield super(MainNetLedger, self).start()
yield defer.DeferredList([
a.maybe_migrate_certificates() for a in self.accounts
])
class TestNetLedger(MainNetLedger): class TestNetLedger(MainNetLedger):
network_name = 'testnet' network_name = 'testnet'

View file

@ -1,7 +1,9 @@
import os import os
import json
from twisted.internet import defer from twisted.internet import defer
from torba.manager import WalletManager as BaseWalletManager from torba.manager import WalletManager as BaseWalletManager
from torba.wallet import WalletStorage
from lbryschema.uri import parse_lbry_uri from lbryschema.uri import parse_lbry_uri
from lbryschema.error import URIParseError from lbryschema.error import URIParseError
@ -45,7 +47,7 @@ class LbryWalletManager(BaseWalletManager):
return defer.succeed(False) return defer.succeed(False)
@classmethod @classmethod
def from_old_config(cls, settings): def from_lbrynet_config(cls, settings, db):
ledger_id = { ledger_id = {
'lbrycrd_main': 'lbc_mainnet', 'lbrycrd_main': 'lbc_mainnet',
@ -57,12 +59,45 @@ class LbryWalletManager(BaseWalletManager):
'auto_connect': True, 'auto_connect': True,
'default_servers': settings['lbryum_servers'], 'default_servers': settings['lbryum_servers'],
'data_path': settings['lbryum_wallet_dir'], 'data_path': settings['lbryum_wallet_dir'],
'use_keyring': settings['use_keyring'] 'use_keyring': settings['use_keyring'],
'db': db
} }
wallet_file_path = os.path.join(settings['lbryum_wallet_dir'], 'default_wallet')
if os.path.exists(wallet_file_path):
with open(wallet_file_path, 'r') as f:
json_data = f.read()
json_dict = json.loads(json_data)
# TODO: After several public releases of new torba based wallet, we can delete
# this lbryum->torba conversion code and require that users who still
# have old structured wallets install one of the earlier releases that
# still has the below conversion code.
if 'master_public_keys' in json_dict:
json_data = json.dumps({
'version': 1,
'name': 'My Wallet',
'accounts': [{
'version': 1,
'name': 'Main Account',
'ledger': 'lbc_mainnet',
'encrypted': json_dict['use_encryption'],
'seed': json_dict['seed'],
'seed_version': json_dict['seed_version'],
'private_key': json_dict['master_private_keys']['x/'],
'public_key': json_dict['master_public_keys']['x/'],
'certificates': json_dict['claim_certificates'],
'receiving_gap': 20,
'change_gap': 6,
'receiving_maximum_use_per_address': 2,
'change_maximum_use_per_address': 2
}]
}, indent=4, sort_keys=True)
with open(wallet_file_path, 'w') as f:
f.write(json_data)
return cls.from_config({ return cls.from_config({
'ledgers': {ledger_id: ledger_config}, 'ledgers': {ledger_id: ledger_config},
'wallets': [os.path.join(settings['lbryum_wallet_dir'], 'default_wallet')] 'wallets': [wallet_file_path]
}) })
def get_best_blockhash(self): def get_best_blockhash(self):
@ -101,8 +136,19 @@ class LbryWalletManager(BaseWalletManager):
def get_history(self): def get_history(self):
return defer.succeed([]) return defer.succeed([])
def claim_name(self, name, amount, claim): @defer.inlineCallbacks
pass def claim_name(self, name, amount, claim, certificate=None, claim_address=None):
account = self.default_account
if not claim_address:
claim_address = yield account.receiving.get_or_create_usable_address()
if certificate:
claim = claim.sign(
certificate['private_key'], claim_address, certificate['claim_id']
)
tx = yield Transaction.claim(name.encode(), claim, amount, claim_address, [account], account)
yield account.ledger.broadcast(tx)
# TODO: release reserved tx outputs in case anything fails by this point
defer.returnValue(tx)
@defer.inlineCallbacks @defer.inlineCallbacks
def claim_new_channel(self, channel_name, amount): def claim_new_channel(self, channel_name, amount):
@ -121,7 +167,7 @@ class LbryWalletManager(BaseWalletManager):
cert, key = generate_certificate() cert, key = generate_certificate()
tx = yield Transaction.claim(channel_name.encode(), cert, amount, address, [account], account) tx = yield Transaction.claim(channel_name.encode(), cert, amount, address, [account], account)
yield account.ledger.broadcast(tx) yield account.ledger.broadcast(tx)
account.add_certificate(tx.get_claim_id(0), key) account.add_certificate(tx, 0, tx.get_claim_id(0), channel_name, key)
# TODO: release reserved tx outputs in case anything fails by this point # TODO: release reserved tx outputs in case anything fails by this point
defer.returnValue(tx) defer.returnValue(tx)

View file

@ -10,13 +10,21 @@ lbryschema.BLOCKCHAIN_NAME = 'lbrycrd_regtest'
from lbrynet import conf as lbry_conf from lbrynet import conf as lbry_conf
from lbrynet.daemon.Daemon import Daemon from lbrynet.daemon.Daemon import Daemon
from lbrynet.wallet.manager import LbryWalletManager from lbrynet.wallet.manager import LbryWalletManager
from lbrynet.daemon.Components import WalletComponent, FileManager from lbrynet.daemon.Components import WalletComponent, FileManager, SessionComponent
from lbrynet.file_manager.EncryptedFileManager import EncryptedFileManager
class FakeAnalytics: class FakeAnalytics:
def send_new_channel(self): def send_new_channel(self):
pass pass
def shutdown(self):
pass
class FakeSession:
storage = None
class CommandTestCase(IntegrationTestCase): class CommandTestCase(IntegrationTestCase):
@ -48,11 +56,19 @@ class CommandTestCase(IntegrationTestCase):
wallet_component.wallet = self.manager wallet_component.wallet = self.manager
wallet_component._running = True wallet_component._running = True
self.daemon.component_manager.components.add(wallet_component) self.daemon.component_manager.components.add(wallet_component)
session_component = SessionComponent(self.daemon.component_manager)
session_component.session = FakeSession()
session_component._running = True
self.daemon.component_manager.components.add(session_component)
file_manager = FileManager(self.daemon.component_manager)
file_manager.file_manager = EncryptedFileManager(session_component.session, True)
file_manager._running = True
self.daemon.component_manager.components.add(file_manager)
class ChannelNewCommandTests(CommandTestCase): class ChannelNewCommandTests(CommandTestCase):
VERBOSE = False VERBOSE = True
@defer.inlineCallbacks @defer.inlineCallbacks
def test_new_channel(self): def test_new_channel(self):