mirror of
https://github.com/alexgo-io/stacks-puppet-node.git
synced 2026-04-10 16:56:34 +08:00
Calculate the set of UTXOs that will be used over a sequence of transactions, including the ones that will be generated by transactions within the sequence. Do not assume that the whole set of UTXOs will be used by each transaction.
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
|
||||
import os
|
||||
import threading
|
||||
import functools
|
||||
|
||||
from ..constants import *
|
||||
from ..keys import *
|
||||
@@ -232,6 +233,7 @@ class ScatterGather(object):
|
||||
else:
|
||||
# for testing purposes
|
||||
for task_name, task_call in self.tasks.items():
|
||||
log.debug("Run task '{}'".format(task_name))
|
||||
res = ScatterGatherThread.do_work(task_call)
|
||||
self.results[task_name] = res
|
||||
|
||||
@@ -472,6 +474,12 @@ def operation_sanity_checks(fqu_or_ns, operations, scatter_gather, payment_privk
|
||||
_cleanup()
|
||||
return {'error': 'The first imported name must have the namespace reveal address as its owner'}
|
||||
|
||||
# is this address usable?
|
||||
res = _is_address_usable(import_addr)
|
||||
if 'error' in res:
|
||||
_cleanup()
|
||||
return res
|
||||
|
||||
return {'status': True}
|
||||
|
||||
|
||||
@@ -553,6 +561,45 @@ def interpret_operation_sanity_checks( operations, scatter_gather ):
|
||||
return reply
|
||||
|
||||
|
||||
def estimate_transaction_inputs(operations, inputs, owner_address=None, payment_address=None):
|
||||
"""
|
||||
Estimate the inputs that will be consumed by each
|
||||
operation's transaction. These inputs do *not* correspond to the inputs
|
||||
that will actually be used, but are represenatitive of the *quantity*
|
||||
of inputs consumed by an operation and the *type* consumed. It does *not*
|
||||
reflect the *value* consumed by the inputs.
|
||||
|
||||
The first operation consumes all inputs, and emits one output
|
||||
that will be consumed by other inputs.
|
||||
|
||||
@owner_address or @payment_address must be given (but not both)
|
||||
|
||||
Returns {'status': True, 'inputs': [ [utxos], [utxos], ... ]}
|
||||
"""
|
||||
|
||||
op_inputs = []
|
||||
|
||||
def _consolidate(inps):
|
||||
ret = inps[0]
|
||||
ret['value'] += sum([inp['value'] for inp in inps])
|
||||
return ret
|
||||
|
||||
# one_input: what if all inputs were consolidated to a single input of the same value
|
||||
one_input = _consolidate(inputs)
|
||||
|
||||
for i in xrange(0, len(operations)):
|
||||
if i == 0:
|
||||
# the first operation will consume everything, and emit a single output
|
||||
# to be consumed by the next operation
|
||||
op_inputs.append(inputs[:])
|
||||
|
||||
else:
|
||||
# operations consume one input
|
||||
op_inputs.append([one_input])
|
||||
|
||||
return {'status': True, 'inputs': op_inputs}
|
||||
|
||||
|
||||
def get_operation_fees(name_or_ns, operations, scatter_gather, payment_privkey_info, owner_privkey_info, tx_fee_per_byte,
|
||||
proxy=None, config_path=CONFIG_PATH, payment_address=None,
|
||||
min_payment_confs=TX_MIN_CONFIRMATIONS, owner_address=None, transfer_address=None):
|
||||
@@ -629,22 +676,46 @@ def get_operation_fees(name_or_ns, operations, scatter_gather, payment_privkey_i
|
||||
if len(set(operations).intersection(set(name_operations))) > 0 and len(set(operations).intersection(set(namespace_operations))) > 0:
|
||||
return {'error': 'Cannot mix name and namespace operations'}
|
||||
|
||||
if len(operations) != len(set(operations)):
|
||||
return {'error': 'No duplicate operations allowed at this time'}
|
||||
|
||||
# first things first: get UTXOs for owner and payment addresses
|
||||
utxo_client = get_utxo_provider_client(config_path=config_path, min_confirmations=min_payment_confs)
|
||||
|
||||
log.debug("Getting UTXOs for {}".format(owner_address))
|
||||
owner_utxos = get_utxos(owner_address, utxo_client=utxo_client, config_path=config_path, min_confirmations=min_payment_confs)
|
||||
if 'error' in owner_utxos:
|
||||
log.error("Failed to get UTXOs for {}: {}".format(owner_address, owner_utxos['error']))
|
||||
return {'error': 'Failed to get UTXOs for {}'.format(owner_address)}
|
||||
|
||||
payment_utxos = get_utxos(payment_address, utxo_client=utxo_client, config_path=config_path, min_confirmations=min_payment_confs)
|
||||
if 'error' in payment_utxos:
|
||||
log.error("Failed to get UTXOs for {}: {}".format(payment_address, payment_utxos['error']))
|
||||
return {'error': 'Failed to get UTXOs for {}'.format(payment_address)}
|
||||
|
||||
balance = sum([utxo.get('value', None) for utxo in payment_utxos])
|
||||
log.debug("Balance of {} is {} satoshis".format(payment_address, balance))
|
||||
|
||||
# find out what our UTXOs will look like for each operation
|
||||
estimated_payment_inputs = estimate_transaction_inputs(operations, payment_utxos, payment_address=payment_address)['inputs']
|
||||
estimated_owner_inputs = estimate_transaction_inputs(operations, owner_utxos, owner_address=owner_address)['inputs']
|
||||
|
||||
log.debug("Get total operation fees for running '{}' on {} owned by {} paid by {}".format(','.join(operations), name_or_ns, owner_address, payment_address))
|
||||
|
||||
for i in xrange(0, len(operations)):
|
||||
if BLOCKSTACK_TEST:
|
||||
log.debug("Operation {} may consume owner inputs\n{}".format(operations[i], json.dumps(estimated_owner_inputs[i], indent=4, sort_keys=True)))
|
||||
log.debug("Operation {} may consume payment inputs\n{}".format(operations[i], json.dumps(estimated_payment_inputs[i], indent=4, sort_keys=True)))
|
||||
else:
|
||||
log.debug("Operation {} may consume up to {} owner inputs and {} payment inputs".format(operations[i], len(estimated_owner_inputs[i]), len(estimated_payment_inputs[i])))
|
||||
|
||||
def _get_balance():
|
||||
"""
|
||||
get payment address balance (scatter/gather worker)
|
||||
"""
|
||||
utxo_client = get_utxo_provider_client(config_path=config_path, min_confirmations=min_payment_confs)
|
||||
balance = get_balance(payment_address, config_path=config_path, utxo_client=utxo_client)
|
||||
if balance is None:
|
||||
msg = 'Failed to get balance'
|
||||
return {'error': msg}
|
||||
else:
|
||||
return {'status': balance}
|
||||
return {'status': balance, 'balance': balance}
|
||||
|
||||
|
||||
def _estimate_preorder_tx():
|
||||
def _estimate_preorder_tx( operation_index ):
|
||||
"""
|
||||
Estimate preorder tx cost
|
||||
Return {'status': True, 'name_cost': ..., 'tx_fee': ..., 'insufficient': ...} on success
|
||||
@@ -662,13 +733,12 @@ def get_operation_fees(name_or_ns, operations, scatter_gather, payment_privkey_i
|
||||
return {'error': 'Could not get name price'}
|
||||
|
||||
try:
|
||||
owner_privkey_params = get_privkey_info_params(owner_privkey_info)
|
||||
utxo_client = get_utxo_provider_client(config_path=config_path, min_confirmations=min_payment_confs)
|
||||
|
||||
insufficient_funds = False
|
||||
preorder_tx_fee = estimate_preorder_tx_fee(
|
||||
name_or_ns, name_cost, owner_address, payment_address, tx_fee_per_byte, utxo_client,
|
||||
owner_privkey_params=owner_privkey_params,
|
||||
name_or_ns, name_cost, payment_privkey_info, owner_privkey_info, tx_fee_per_byte, utxo_client,
|
||||
payment_utxos=estimated_payment_inputs[operation_index],
|
||||
config_path=config_path, include_dust=True
|
||||
)
|
||||
|
||||
@@ -689,7 +759,7 @@ def get_operation_fees(name_or_ns, operations, scatter_gather, payment_privkey_i
|
||||
return {'error': 'Failed to query UTXO provider for NAME_PREORDER fee estimation. Please try again.'}
|
||||
|
||||
|
||||
def _estimate_register_tx():
|
||||
def _estimate_register_tx( operation_index ):
|
||||
"""
|
||||
Estimate register tx cost
|
||||
Return {'status': True, 'tx_fee': ..., 'insufficient': ...} on success
|
||||
@@ -697,13 +767,12 @@ def get_operation_fees(name_or_ns, operations, scatter_gather, payment_privkey_i
|
||||
"""
|
||||
|
||||
try:
|
||||
owner_privkey_params = get_privkey_info_params(owner_privkey_info)
|
||||
utxo_client = get_utxo_provider_client(config_path=config_path, min_confirmations=min_payment_confs)
|
||||
|
||||
insufficient_funds = False
|
||||
register_tx_fee = estimate_register_tx_fee(
|
||||
name_or_ns, owner_address, payment_address, tx_fee_per_byte, utxo_client,
|
||||
owner_privkey_params=owner_privkey_params,
|
||||
name_or_ns, payment_privkey_info, owner_privkey_info, tx_fee_per_byte, utxo_client,
|
||||
payment_utxos=estimated_payment_inputs[operation_index],
|
||||
config_path=config_path, include_dust=True
|
||||
)
|
||||
|
||||
@@ -723,22 +792,21 @@ def get_operation_fees(name_or_ns, operations, scatter_gather, payment_privkey_i
|
||||
return {'error': 'Failed to query UTXO provider for NAME_REGISTRATION fee estimation. Please try again.'}
|
||||
|
||||
|
||||
def _estimate_update_tx():
|
||||
def _estimate_update_tx( operation_index ):
|
||||
"""
|
||||
Estimate update tx fee
|
||||
Return {'status': True, 'tx_fee': ..., 'insufficient': ...} on success
|
||||
Return {'error': ...} on failure
|
||||
"""
|
||||
try:
|
||||
owner_privkey_params = get_privkey_info_params(owner_privkey_info)
|
||||
utxo_client = get_utxo_provider_client(config_path=config_path, min_confirmations=min_payment_confs)
|
||||
|
||||
insufficient_funds = False
|
||||
estimate = False
|
||||
update_tx_fee = estimate_update_tx_fee(
|
||||
name_or_ns, payment_privkey_info, owner_address, tx_fee_per_byte, utxo_client,
|
||||
owner_privkey_params=owner_privkey_params,
|
||||
config_path=config_path, payment_address=payment_address, include_dust=True
|
||||
name_or_ns, payment_privkey_info, owner_privkey_info, tx_fee_per_byte, utxo_client,
|
||||
payment_utxos=estimated_payment_inputs[operation_index], owner_utxos=estimated_owner_inputs[operation_index],
|
||||
config_path=config_path, include_dust=True
|
||||
)
|
||||
|
||||
if update_tx_fee is not None:
|
||||
@@ -761,7 +829,7 @@ def get_operation_fees(name_or_ns, operations, scatter_gather, payment_privkey_i
|
||||
return {'error': 'Failed to query UTXO provider for NAME_UPDATE fee estimation. Please try again.'}
|
||||
|
||||
|
||||
def _estimate_transfer_tx():
|
||||
def _estimate_transfer_tx( operation_index ):
|
||||
"""
|
||||
Estiamte transfer tx fee
|
||||
Return {'status': True, 'tx_fee': ..., 'insufficient': ...} on success
|
||||
@@ -773,16 +841,15 @@ def get_operation_fees(name_or_ns, operations, scatter_gather, payment_privkey_i
|
||||
try:
|
||||
|
||||
if transfer_address is not None:
|
||||
owner_privkey_params = get_privkey_info_params(owner_privkey_info)
|
||||
utxo_client = get_utxo_provider_client(config_path=config_path, min_confirmations=min_payment_confs)
|
||||
|
||||
insufficient_funds = False
|
||||
estimate = False
|
||||
|
||||
transfer_tx_fee = estimate_transfer_tx_fee(
|
||||
name_or_ns, payment_privkey_info, owner_address, tx_fee_per_byte, utxo_client,
|
||||
owner_privkey_params=owner_privkey_params,
|
||||
config_path=config_path, payment_address=payment_address, include_dust=True
|
||||
name_or_ns, payment_privkey_info, owner_privkey_info, tx_fee_per_byte, utxo_client,
|
||||
payment_utxos=estimated_payment_inputs[operation_index], owner_utxos=estimated_owner_inputs[operation_index],
|
||||
config_path=config_path, include_dust=True
|
||||
)
|
||||
|
||||
if transfer_tx_fee is not None:
|
||||
@@ -808,22 +875,21 @@ def get_operation_fees(name_or_ns, operations, scatter_gather, payment_privkey_i
|
||||
return {'error': 'Failed to query UTXO provider for NAME_TRANSFER fee estimation. Please try again.'}
|
||||
|
||||
|
||||
def _estimate_revoke_tx():
|
||||
def _estimate_revoke_tx( operation_index ):
|
||||
"""
|
||||
Estimate revoke tx fee
|
||||
Return {'status': True, 'tx_fee': ..., 'insufficient': ...} on success
|
||||
Return {'error': ...} on failure
|
||||
"""
|
||||
try:
|
||||
owner_privkey_params = get_privkey_info_params(owner_privkey_info)
|
||||
utxo_client = get_utxo_provider_client(config_path=config_path, min_confirmations=min_payment_confs)
|
||||
|
||||
insufficient_funds = False
|
||||
estimate = False
|
||||
|
||||
tx_fee = estimate_revoke_tx_fee(
|
||||
name_or_ns, payment_privkey_info, owner_address, tx_fee_per_byte, utxo_client,
|
||||
owner_privkey_params=owner_privkey_params,
|
||||
name_or_ns, payment_privkey_info, owner_privkey_info, tx_fee_per_byte, utxo_client,
|
||||
payment_utxos=estimated_payment_inputs[operation_index], owner_utxos=estimated_owner_inputs[operation_index],
|
||||
config_path=config_path, include_dust=True
|
||||
)
|
||||
|
||||
@@ -847,7 +913,7 @@ def get_operation_fees(name_or_ns, operations, scatter_gather, payment_privkey_i
|
||||
return {'error': 'Failed to query UTXO provider for NAME_REVOKE fee estimation. Please try again.'}
|
||||
|
||||
|
||||
def _estimate_renewal_tx():
|
||||
def _estimate_renewal_tx( operation_index ):
|
||||
"""
|
||||
Estimate renewal tx fee
|
||||
Return {'status': True, 'name_cost': ..., 'tx_fee': ..., 'insufficient': ...} on success
|
||||
@@ -862,7 +928,6 @@ def get_operation_fees(name_or_ns, operations, scatter_gather, payment_privkey_i
|
||||
return {'error': 'Could not get name price'}
|
||||
|
||||
try:
|
||||
owner_privkey_params = get_privkey_info_params(owner_privkey_info)
|
||||
utxo_client = get_utxo_provider_client(config_path=config_path, min_confirmations=min_payment_confs)
|
||||
|
||||
insufficient_funds = False
|
||||
@@ -870,6 +935,7 @@ def get_operation_fees(name_or_ns, operations, scatter_gather, payment_privkey_i
|
||||
|
||||
tx_fee = estimate_renewal_tx_fee(
|
||||
name_or_ns, name_cost, payment_privkey_info, owner_privkey_info, tx_fee_per_byte, utxo_client,
|
||||
payment_utxos=estimated_payment_inputs[operation_index], owner_utxos=estimated_owner_inputs[operation_index],
|
||||
config_path=config_path, include_dust=True
|
||||
)
|
||||
|
||||
@@ -893,7 +959,7 @@ def get_operation_fees(name_or_ns, operations, scatter_gather, payment_privkey_i
|
||||
return {'error': 'Failed to query UTXO provider for NAME_RENEWAL fee estimation. Please try again.'}
|
||||
|
||||
|
||||
def _estimate_namespace_preorder_tx():
|
||||
def _estimate_namespace_preorder_tx( operation_index ):
|
||||
"""
|
||||
Estimate namespace preorder tx fee
|
||||
Return {'status': True, 'namespace_cost': ..., 'tx_fee': ..., 'insufficient': ..., 'estimate': ...} on success
|
||||
@@ -913,7 +979,11 @@ def get_operation_fees(name_or_ns, operations, scatter_gather, payment_privkey_i
|
||||
|
||||
try:
|
||||
utxo_client = get_utxo_provider_client(config_path=config_path, min_confirmations=min_payment_confs)
|
||||
tx_fee = estimate_namespace_preorder_tx_fee( name_or_ns, namespace_cost, payment_address, tx_fee_per_byte, utxo_client, config_path=config_path, include_dust=True )
|
||||
|
||||
log.debug("Estimate namespace preorder TX fee (from {})".format(payment_address))
|
||||
tx_fee = estimate_namespace_preorder_tx_fee( name_or_ns, namespace_cost, payment_privkey_info, tx_fee_per_byte, utxo_client,
|
||||
payment_utxos=estimated_payment_inputs[operation_index],
|
||||
config_path=config_path, include_dust=True )
|
||||
|
||||
if tx_fee is not None:
|
||||
tx_fee = int(tx_fee)
|
||||
@@ -932,7 +1002,7 @@ def get_operation_fees(name_or_ns, operations, scatter_gather, payment_privkey_i
|
||||
return {'error': 'Failed to query UTXO provider for NAMESPACE_PREORDER fee estimation. Please try again.'}
|
||||
|
||||
|
||||
def _estimate_namespace_reveal_tx():
|
||||
def _estimate_namespace_reveal_tx( operation_index ):
|
||||
"""
|
||||
Estimate namespace reveal tx fee
|
||||
payment_address here corresponds to the NAMESPACE_PREORDER key's address
|
||||
@@ -945,7 +1015,11 @@ def get_operation_fees(name_or_ns, operations, scatter_gather, payment_privkey_i
|
||||
|
||||
try:
|
||||
utxo_client = get_utxo_provider_client(config_path=config_path, min_confirmations=min_payment_confs)
|
||||
tx_fee = estimate_namespace_reveal_tx_fee( name_or_ns, payment_address, tx_fee_per_byte, utxo_client, config_path=config_path, include_dust=True )
|
||||
|
||||
log.debug("Estimate namespace reveal TX fee (from {})".format(payment_address))
|
||||
tx_fee = estimate_namespace_reveal_tx_fee( name_or_ns, payment_privkey_info, tx_fee_per_byte, utxo_client,
|
||||
payment_utxos=estimated_payment_inputs[operation_index],
|
||||
config_path=config_path, include_dust=True )
|
||||
|
||||
if tx_fee is not None:
|
||||
tx_fee = int(tx_fee)
|
||||
@@ -964,7 +1038,7 @@ def get_operation_fees(name_or_ns, operations, scatter_gather, payment_privkey_i
|
||||
return {'error': 'Failed to query UTXO provider for NAMESPACE_REVEAL fee estimation. Please try again.'}
|
||||
|
||||
|
||||
def _estimate_namespace_ready_tx():
|
||||
def _estimate_namespace_ready_tx( operation_index ):
|
||||
"""
|
||||
Estimate namespace ready tx fee
|
||||
payment_address here corresponds to the reveal address (i.e. the reveal key pays for the NAMESPACE_READY)
|
||||
@@ -977,7 +1051,11 @@ def get_operation_fees(name_or_ns, operations, scatter_gather, payment_privkey_i
|
||||
|
||||
try:
|
||||
utxo_client = get_utxo_provider_client(config_path=config_path, min_confirmations=min_payment_confs)
|
||||
tx_fee = estimate_namespace_ready_tx_fee( name_or_ns, payment_address, tx_fee_per_byte, utxo_client, config_path=config_path, include_dust=True )
|
||||
|
||||
log.debug("Estimate namespace ready TX fee (from {})".format(payment_address))
|
||||
tx_fee = estimate_namespace_ready_tx_fee( name_or_ns, payment_privkey_info, tx_fee_per_byte, utxo_client,
|
||||
revealer_utxos=estimated_payment_inputs[operation_index],
|
||||
config_path=config_path, include_dust=True )
|
||||
|
||||
if tx_fee is not None:
|
||||
tx_fee = int(tx_fee)
|
||||
@@ -996,7 +1074,7 @@ def get_operation_fees(name_or_ns, operations, scatter_gather, payment_privkey_i
|
||||
return {'error': 'Failed to query UTXO provider for NAMESPACE_READY fee estimation. Please try again'}
|
||||
|
||||
|
||||
def _estimate_name_import_tx():
|
||||
def _estimate_name_import_tx( operation_index ):
|
||||
"""
|
||||
Estimate name import tx fee
|
||||
payment_address is address of the derived reveal key used to issue the import
|
||||
@@ -1012,7 +1090,9 @@ def get_operation_fees(name_or_ns, operations, scatter_gather, payment_privkey_i
|
||||
|
||||
try:
|
||||
utxo_client = get_utxo_provider_client(config_path=config_path, min_confirmations=min_payment_confs)
|
||||
tx_fee = estimate_name_import_tx_fee( name_or_ns, payment_address, transfer_address, tx_fee_per_byte, utxo_client, config_path=config_path, include_dust=True )
|
||||
tx_fee = estimate_name_import_tx_fee( name_or_ns, payment_privkey_info, transfer_address, tx_fee_per_byte, utxo_client,
|
||||
importer_utxos=estimated_payment_inputs[operation_index],
|
||||
config_path=config_path, include_dust=True )
|
||||
|
||||
if tx_fee is not None:
|
||||
tx_fee = int(tx_fee)
|
||||
@@ -1050,14 +1130,18 @@ def get_operation_fees(name_or_ns, operations, scatter_gather, payment_privkey_i
|
||||
sg.add_task('get_balance', _get_balance)
|
||||
|
||||
# queue each operation
|
||||
for op in set(operations):
|
||||
|
||||
for i in xrange(0, len(operations)):
|
||||
op = operations[i]
|
||||
if op not in tx_fee_checks.keys():
|
||||
log.error("Unrecognized operation {}".format(op))
|
||||
return {'error': 'Unrecognized operation'}
|
||||
|
||||
check_name = '{}_tx_fee'.format(op)
|
||||
sg.add_task( check_name, tx_fee_checks[op] )
|
||||
|
||||
log.debug("Add task {} ({})".format(check_name, tx_fee_checks[op]))
|
||||
|
||||
check_func = functools.partial(tx_fee_checks[op], i)
|
||||
sg.add_task( check_name, check_func )
|
||||
|
||||
# scatter/gather primed!
|
||||
return {'status': True}
|
||||
@@ -1103,7 +1187,7 @@ def interpret_operation_fees( operations, scatter_gather, balance=None ):
|
||||
log.error("Failed to get balance")
|
||||
|
||||
else:
|
||||
balance = results['get_balance']['status']
|
||||
balance = results['get_balance']['balance']
|
||||
log.debug("Balance is {} satoshis".format(balance))
|
||||
|
||||
failed_tasks = []
|
||||
@@ -1227,7 +1311,7 @@ def check_operations( fqu_or_ns, operations, owner_privkey_info, payment_privkey
|
||||
'opchecks': opchecks,
|
||||
'tx_fee_per_byte': tx_fee_per_byte}
|
||||
|
||||
balance = sg.get_result('get_balance')['status']
|
||||
balance = sg.get_result('get_balance')['balance']
|
||||
|
||||
if balance < opchecks['total_estimated_cost']:
|
||||
msg = 'Address {} does not have enough balance (need {}, have {}).'
|
||||
@@ -1394,5 +1478,6 @@ def check_name_import(name, importer_privkey_info, recipient_address, min_paymen
|
||||
Verify that a name import can go through
|
||||
@importer_privkey_info is the private key information derived from the reveal private key. It will be used as the "payment" key by the op-checker
|
||||
"""
|
||||
required_checks = ['is_namespace_still_revealed', 'is_payment_address_usable', 'is_name_import_key', 'owner_can_receive']
|
||||
return _check_op(name, 'name_import', required_checks, None, importer_privkey_info, owner_address=recipient_address, min_payment_confs=min_payment_confs, config_path=config_path, proxy=proxy)
|
||||
required_checks = ['is_namespace_still_revealed', 'is_payment_address_usable', 'is_owner_address_usable', 'is_name_import_key', 'recipient_can_receive']
|
||||
return _check_op(name, 'name_import', required_checks, importer_privkey_info, importer_privkey_info, transfer_address=recipient_address, min_payment_confs=min_payment_confs, config_path=config_path, proxy=proxy)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user