forked from LBRYCommunity/lbry-sdk
+ Transaction.liquidate, balance/get_utxos support constraints
This commit is contained in:
parent
2006ad68ee
commit
5eb276655d
5 changed files with 86 additions and 42 deletions
|
@ -175,3 +175,6 @@ class BaseAccount(object):
|
||||||
|
|
||||||
def get_balance(self, **constraints):
|
def get_balance(self, **constraints):
|
||||||
return self.ledger.db.get_balance_for_account(self, **constraints)
|
return self.ledger.db.get_balance_for_account(self, **constraints)
|
||||||
|
|
||||||
|
def get_unspent_outputs(self, **constraints):
|
||||||
|
return self.ledger.db.get_utxos_for_account(self, **constraints)
|
||||||
|
|
|
@ -225,6 +225,12 @@ class BaseDatabase(SQLiteMixin):
|
||||||
def release_reserved_outputs(self, txoids):
|
def release_reserved_outputs(self, txoids):
|
||||||
return self.reserve_spent_outputs(txoids, is_reserved=False)
|
return self.reserve_spent_outputs(txoids, is_reserved=False)
|
||||||
|
|
||||||
|
def get_txoid_for_txo(self, txo):
|
||||||
|
return self.query_one_value(
|
||||||
|
"SELECT txoid FROM txo WHERE txhash = ? AND position = ?",
|
||||||
|
(sqlite3.Binary(txo.transaction.hash), txo.index)
|
||||||
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def get_transaction(self, txhash):
|
def get_transaction(self, txhash):
|
||||||
result = yield self.db.runQuery(
|
result = yield self.db.runQuery(
|
||||||
|
@ -258,15 +264,22 @@ class BaseDatabase(SQLiteMixin):
|
||||||
defer.returnValue(0)
|
defer.returnValue(0)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def get_utxos(self, account, output_class):
|
def get_utxos_for_account(self, account, **constraints):
|
||||||
|
extra_sql = ""
|
||||||
|
if constraints:
|
||||||
|
extra_sql = ' AND ' + ' AND '.join(
|
||||||
|
'{} = :{}'.format(c, c) for c in constraints.keys()
|
||||||
|
)
|
||||||
|
values = {'account': sqlite3.Binary(account.public_key.address)}
|
||||||
|
values.update(constraints)
|
||||||
utxos = yield self.db.runQuery(
|
utxos = yield self.db.runQuery(
|
||||||
"""
|
"""
|
||||||
SELECT amount, script, txhash, txo.position, txoid
|
SELECT amount, script, txhash, txo.position, txoid
|
||||||
FROM txo JOIN pubkey_address ON pubkey_address.address=txo.address
|
FROM txo JOIN pubkey_address ON pubkey_address.address=txo.address
|
||||||
WHERE account=:account AND txo.is_reserved=0 AND txoid NOT IN (SELECT txoid FROM txi)
|
WHERE account=:account AND txo.is_reserved=0 AND txoid NOT IN (SELECT txoid FROM txi)
|
||||||
""",
|
"""+extra_sql, values
|
||||||
{'account': sqlite3.Binary(account.public_key.address)}
|
|
||||||
)
|
)
|
||||||
|
output_class = account.ledger.transaction_class.output_class
|
||||||
defer.returnValue([
|
defer.returnValue([
|
||||||
output_class(
|
output_class(
|
||||||
values[0],
|
values[0],
|
||||||
|
@ -335,7 +348,7 @@ class BaseDatabase(SQLiteMixin):
|
||||||
return self.db.runInteraction(lambda t: self._set_address_history(t, address, history))
|
return self.db.runInteraction(lambda t: self._set_address_history(t, address, history))
|
||||||
|
|
||||||
def get_unused_addresses(self, account, chain):
|
def get_unused_addresses(self, account, chain):
|
||||||
# type: (torba.baseaccount.BaseAccount, int) -> defer.Deferred[List[str]]
|
# type: (torba.baseaccount.BaseAccount, Union[int,None]) -> defer.Deferred[List[str]]
|
||||||
return self.query_one_value_list(*self._used_address_sql(
|
return self.query_one_value_list(*self._used_address_sql(
|
||||||
account, chain, '=', 0
|
account, chain, '=', 0
|
||||||
))
|
))
|
||||||
|
|
|
@ -133,15 +133,12 @@ class BaseLedger(six.with_metaclass(LedgerRegistry)):
|
||||||
if bytes(match['account']) == account.public_key.address:
|
if bytes(match['account']) == account.public_key.address:
|
||||||
defer.returnValue(account.get_private_key(match['chain'], match['position']))
|
defer.returnValue(account.get_private_key(match['chain'], match['position']))
|
||||||
|
|
||||||
def get_unspent_outputs(self, account):
|
|
||||||
return self.db.get_utxos(account, self.transaction_class.output_class)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def get_effective_amount_estimators(self, funding_accounts):
|
def get_effective_amount_estimators(self, funding_accounts):
|
||||||
# type: (Iterable[baseaccount.BaseAccount]) -> defer.Deferred
|
# type: (Iterable[baseaccount.BaseAccount]) -> defer.Deferred
|
||||||
estimators = []
|
estimators = []
|
||||||
for account in funding_accounts:
|
for account in funding_accounts:
|
||||||
utxos = yield self.get_unspent_outputs(account)
|
utxos = yield account.get_unspent_outputs()
|
||||||
for utxo in utxos:
|
for utxo in utxos:
|
||||||
estimators.append(utxo.get_estimator(self))
|
estimators.append(utxo.get_estimator(self))
|
||||||
defer.returnValue(estimators)
|
defer.returnValue(estimators)
|
||||||
|
@ -266,41 +263,41 @@ class BaseLedger(six.with_metaclass(LedgerRegistry)):
|
||||||
|
|
||||||
yield lock.acquire()
|
yield lock.acquire()
|
||||||
|
|
||||||
#try:
|
try:
|
||||||
# see if we have a local copy of transaction, otherwise fetch it from server
|
# see if we have a local copy of transaction, otherwise fetch it from server
|
||||||
raw, local_height, is_verified = yield self.db.get_transaction(unhexlify(hex_id)[::-1])
|
raw, local_height, is_verified = yield self.db.get_transaction(unhexlify(hex_id)[::-1])
|
||||||
save_tx = None
|
save_tx = None
|
||||||
if raw is None:
|
if raw is None:
|
||||||
_raw = yield self.network.get_transaction(hex_id)
|
_raw = yield self.network.get_transaction(hex_id)
|
||||||
tx = self.transaction_class(unhexlify(_raw))
|
tx = self.transaction_class(unhexlify(_raw))
|
||||||
save_tx = 'insert'
|
save_tx = 'insert'
|
||||||
else:
|
else:
|
||||||
tx = self.transaction_class(raw)
|
tx = self.transaction_class(raw)
|
||||||
|
|
||||||
if remote_height > 0 and not is_verified:
|
if remote_height > 0 and not is_verified:
|
||||||
is_verified = yield self.is_valid_transaction(tx, remote_height)
|
is_verified = yield self.is_valid_transaction(tx, remote_height)
|
||||||
is_verified = 1 if is_verified else 0
|
is_verified = 1 if is_verified else 0
|
||||||
if save_tx is None:
|
if save_tx is None:
|
||||||
save_tx = 'update'
|
save_tx = 'update'
|
||||||
|
|
||||||
yield self.db.save_transaction_io(
|
yield self.db.save_transaction_io(
|
||||||
save_tx, tx, remote_height, is_verified, address, self.address_to_hash160(address),
|
save_tx, tx, remote_height, is_verified, address, self.address_to_hash160(address),
|
||||||
''.join('{}:{}:'.format(tx_id.decode(), tx_height) for tx_id, tx_height in synced_history)
|
''.join('{}:{}:'.format(tx_id.decode(), tx_height) for tx_id, tx_height in synced_history)
|
||||||
)
|
)
|
||||||
|
|
||||||
log.debug("{}: sync'ed tx {} for address: {}, height: {}, verified: {}".format(
|
log.debug("{}: sync'ed tx {} for address: {}, height: {}, verified: {}".format(
|
||||||
self.get_id(), hex_id, address, remote_height, is_verified
|
self.get_id(), hex_id, address, remote_height, is_verified
|
||||||
))
|
))
|
||||||
self._on_transaction_controller.add(TransactionEvent(address, tx, remote_height, is_verified))
|
self._on_transaction_controller.add(TransactionEvent(address, tx, remote_height, is_verified))
|
||||||
|
|
||||||
# except:
|
except:
|
||||||
# log.exception('Failed to synchronize transaction:')
|
log.exception('Failed to synchronize transaction:')
|
||||||
# raise
|
raise
|
||||||
#
|
|
||||||
# finally:
|
finally:
|
||||||
lock.release()
|
lock.release()
|
||||||
if not lock.locked:
|
if not lock.locked:
|
||||||
del self._transaction_processing_locks[hex_id]
|
del self._transaction_processing_locks[hex_id]
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def subscribe_history(self, address):
|
def subscribe_history(self, address):
|
||||||
|
|
|
@ -331,9 +331,40 @@ class BaseTransaction:
|
||||||
defer.returnValue(tx)
|
defer.returnValue(tx)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def liquidate(cls, assets, funding_accounts, change_account):
|
@defer.inlineCallbacks
|
||||||
|
def liquidate(cls, assets, funding_accounts, change_account, reserve_outputs=True):
|
||||||
""" Spend assets (utxos) supplementing with funding_accounts if fee is higher than asset value. """
|
""" Spend assets (utxos) supplementing with funding_accounts if fee is higher than asset value. """
|
||||||
|
|
||||||
|
tx = cls().add_inputs([
|
||||||
|
cls.input_class.spend(utxo) for utxo in assets
|
||||||
|
])
|
||||||
|
ledger = cls.ensure_all_have_same_ledger(funding_accounts, change_account)
|
||||||
|
|
||||||
|
reserved_outputs = [utxo.txoid for utxo in assets]
|
||||||
|
if reserve_outputs:
|
||||||
|
yield ledger.db.reserve_spent_outputs(reserved_outputs)
|
||||||
|
|
||||||
|
try:
|
||||||
|
cost_of_change = (
|
||||||
|
ledger.get_transaction_base_fee(tx) +
|
||||||
|
ledger.get_input_output_fee(cls.output_class.pay_pubkey_hash(COIN, NULL_HASH))
|
||||||
|
)
|
||||||
|
liquidated_total = sum(utxo.amount for utxo in assets)
|
||||||
|
if liquidated_total > cost_of_change:
|
||||||
|
change_address = yield change_account.change.get_or_create_usable_address()
|
||||||
|
change_hash160 = change_account.ledger.address_to_hash160(change_address)
|
||||||
|
change_amount = liquidated_total - cost_of_change
|
||||||
|
tx.add_outputs([cls.output_class.pay_pubkey_hash(change_amount, change_hash160)])
|
||||||
|
|
||||||
|
yield tx.sign(funding_accounts)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
if reserve_outputs:
|
||||||
|
yield ledger.db.release_reserved_outputs(reserved_outputs)
|
||||||
|
raise
|
||||||
|
|
||||||
|
defer.returnValue(tx)
|
||||||
|
|
||||||
def signature_hash_type(self, hash_type):
|
def signature_hash_type(self, hash_type):
|
||||||
return hash_type
|
return hash_type
|
||||||
|
|
||||||
|
|
|
@ -97,11 +97,11 @@ class PubKey(_KeyBase):
|
||||||
raise TypeError('pubkey must be raw bytes')
|
raise TypeError('pubkey must be raw bytes')
|
||||||
if len(pubkey) != 33:
|
if len(pubkey) != 33:
|
||||||
raise ValueError('pubkey must be 33 bytes')
|
raise ValueError('pubkey must be 33 bytes')
|
||||||
if byte2int(pubkey[0]) not in (2, 3):
|
if indexbytes(pubkey, 0) not in (2, 3):
|
||||||
raise ValueError('invalid pubkey prefix byte')
|
raise ValueError('invalid pubkey prefix byte')
|
||||||
curve = cls.CURVE.curve
|
curve = cls.CURVE.curve
|
||||||
|
|
||||||
is_odd = byte2int(pubkey[0]) == 3
|
is_odd = indexbytes(pubkey, 0) == 3
|
||||||
x = bytes_to_int(pubkey[1:])
|
x = bytes_to_int(pubkey[1:])
|
||||||
|
|
||||||
# p is the finite field order
|
# p is the finite field order
|
||||||
|
|
Loading…
Add table
Reference in a new issue