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):
|
||||
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):
|
||||
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
|
||||
def get_transaction(self, txhash):
|
||||
result = yield self.db.runQuery(
|
||||
|
@ -258,15 +264,22 @@ class BaseDatabase(SQLiteMixin):
|
|||
defer.returnValue(0)
|
||||
|
||||
@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(
|
||||
"""
|
||||
SELECT amount, script, txhash, txo.position, txoid
|
||||
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)
|
||||
""",
|
||||
{'account': sqlite3.Binary(account.public_key.address)}
|
||||
"""+extra_sql, values
|
||||
)
|
||||
output_class = account.ledger.transaction_class.output_class
|
||||
defer.returnValue([
|
||||
output_class(
|
||||
values[0],
|
||||
|
@ -335,7 +348,7 @@ class BaseDatabase(SQLiteMixin):
|
|||
return self.db.runInteraction(lambda t: self._set_address_history(t, address, history))
|
||||
|
||||
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(
|
||||
account, chain, '=', 0
|
||||
))
|
||||
|
|
|
@ -133,15 +133,12 @@ class BaseLedger(six.with_metaclass(LedgerRegistry)):
|
|||
if bytes(match['account']) == account.public_key.address:
|
||||
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
|
||||
def get_effective_amount_estimators(self, funding_accounts):
|
||||
# type: (Iterable[baseaccount.BaseAccount]) -> defer.Deferred
|
||||
estimators = []
|
||||
for account in funding_accounts:
|
||||
utxos = yield self.get_unspent_outputs(account)
|
||||
utxos = yield account.get_unspent_outputs()
|
||||
for utxo in utxos:
|
||||
estimators.append(utxo.get_estimator(self))
|
||||
defer.returnValue(estimators)
|
||||
|
@ -266,41 +263,41 @@ class BaseLedger(six.with_metaclass(LedgerRegistry)):
|
|||
|
||||
yield lock.acquire()
|
||||
|
||||
#try:
|
||||
# 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])
|
||||
save_tx = None
|
||||
if raw is None:
|
||||
_raw = yield self.network.get_transaction(hex_id)
|
||||
tx = self.transaction_class(unhexlify(_raw))
|
||||
save_tx = 'insert'
|
||||
else:
|
||||
tx = self.transaction_class(raw)
|
||||
try:
|
||||
# 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])
|
||||
save_tx = None
|
||||
if raw is None:
|
||||
_raw = yield self.network.get_transaction(hex_id)
|
||||
tx = self.transaction_class(unhexlify(_raw))
|
||||
save_tx = 'insert'
|
||||
else:
|
||||
tx = self.transaction_class(raw)
|
||||
|
||||
if remote_height > 0 and not is_verified:
|
||||
is_verified = yield self.is_valid_transaction(tx, remote_height)
|
||||
is_verified = 1 if is_verified else 0
|
||||
if save_tx is None:
|
||||
save_tx = 'update'
|
||||
if remote_height > 0 and not is_verified:
|
||||
is_verified = yield self.is_valid_transaction(tx, remote_height)
|
||||
is_verified = 1 if is_verified else 0
|
||||
if save_tx is None:
|
||||
save_tx = 'update'
|
||||
|
||||
yield self.db.save_transaction_io(
|
||||
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)
|
||||
)
|
||||
yield self.db.save_transaction_io(
|
||||
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)
|
||||
)
|
||||
|
||||
log.debug("{}: sync'ed tx {} for address: {}, height: {}, verified: {}".format(
|
||||
self.get_id(), hex_id, address, remote_height, is_verified
|
||||
))
|
||||
self._on_transaction_controller.add(TransactionEvent(address, tx, remote_height, is_verified))
|
||||
log.debug("{}: sync'ed tx {} for address: {}, height: {}, verified: {}".format(
|
||||
self.get_id(), hex_id, address, remote_height, is_verified
|
||||
))
|
||||
self._on_transaction_controller.add(TransactionEvent(address, tx, remote_height, is_verified))
|
||||
|
||||
# except:
|
||||
# log.exception('Failed to synchronize transaction:')
|
||||
# raise
|
||||
#
|
||||
# finally:
|
||||
lock.release()
|
||||
if not lock.locked:
|
||||
del self._transaction_processing_locks[hex_id]
|
||||
except:
|
||||
log.exception('Failed to synchronize transaction:')
|
||||
raise
|
||||
|
||||
finally:
|
||||
lock.release()
|
||||
if not lock.locked:
|
||||
del self._transaction_processing_locks[hex_id]
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def subscribe_history(self, address):
|
||||
|
|
|
@ -331,9 +331,40 @@ class BaseTransaction:
|
|||
defer.returnValue(tx)
|
||||
|
||||
@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. """
|
||||
|
||||
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):
|
||||
return hash_type
|
||||
|
||||
|
|
|
@ -97,11 +97,11 @@ class PubKey(_KeyBase):
|
|||
raise TypeError('pubkey must be raw bytes')
|
||||
if len(pubkey) != 33:
|
||||
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')
|
||||
curve = cls.CURVE.curve
|
||||
|
||||
is_odd = byte2int(pubkey[0]) == 3
|
||||
is_odd = indexbytes(pubkey, 0) == 3
|
||||
x = bytes_to_int(pubkey[1:])
|
||||
|
||||
# p is the finite field order
|
||||
|
|
Loading…
Add table
Reference in a new issue