Skip to content

Commit

Permalink
Merge aaf5e32 into 4bab8b6
Browse files Browse the repository at this point in the history
  • Loading branch information
SomberNight committed Dec 21, 2017
2 parents 4bab8b6 + aaf5e32 commit 7d4d644
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 18 deletions.
59 changes: 45 additions & 14 deletions lib/coinchooser.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,13 @@ def shuffle(self, x):
x[i], x[j] = x[j], x[i]


Bucket = namedtuple('Bucket', ['desc', 'size', 'value', 'coins', 'min_height'])
Bucket = namedtuple('Bucket',
['desc',
'weight', # as in BIP-141
'value', # in satoshis
'coins', # UTXOs
'min_height', # min block height where a coin was confirmed
'witness']) # whether any coin uses segwit

def strip_unneeded(bkts, sufficient_funds):
'''Remove buckets that are unnecessary in achieving the spend amount'''
Expand All @@ -91,12 +97,14 @@ def bucketize_coins(self, coins):
buckets[key].append(coin)

def make_Bucket(desc, coins):
weight = sum(Transaction.estimated_input_weight(coin)
for coin in coins)
size = Transaction.virtual_size_from_weight(weight)
witness = any(Transaction.is_segwit_input(coin) for coin in coins)
# note that we're guessing whether the tx uses segwit based
# on this single bucket
weight = sum(Transaction.estimated_input_weight(coin, witness)
for coin in coins)
value = sum(coin['value'] for coin in coins)
min_height = min(coin['height'] for coin in coins)
return Bucket(desc, size, value, coins, min_height)
return Bucket(desc, weight, value, coins, min_height, witness)

return list(map(make_Bucket, buckets.keys(), buckets.values()))

Expand Down Expand Up @@ -169,39 +177,62 @@ def change_outputs(self, tx, change_addrs, fee_estimator, dust_threshold):

def make_tx(self, coins, outputs, change_addrs, fee_estimator,
dust_threshold):
'''Select unspent coins to spend to pay outputs. If the change is
"""Select unspent coins to spend to pay outputs. If the change is
greater than dust_threshold (after adding the change output to
the transaction) it is kept, otherwise none is sent and it is
added to the transaction fee.'''
added to the transaction fee.
Note: fee_estimator expects virtual bytes
"""

# Deterministic randomness from coins
utxos = [c['prevout_hash'] + str(c['prevout_n']) for c in coins]
self.p = PRNG(''.join(sorted(utxos)))

# Copy the ouputs so when adding change we don't modify "outputs"
tx = Transaction.from_io([], outputs[:])
# Size of the transaction with no inputs and no change
base_size = tx.estimated_size()
# Weight of the transaction with no inputs and no change
# Note: this will use legacy tx serialization as the need for "segwit"
# would be detected from inputs. The only side effect should be that the
# marker and flag are excluded, which is compensated in get_tx_weight()
base_weight = tx.estimated_weight()
spent_amount = tx.output_value()

def fee_estimator_w(weight):
return fee_estimator(Transaction.virtual_size_from_weight(weight))

def get_tx_weight(buckets):
total_weight = base_weight + sum(bucket.weight for bucket in buckets)
is_segwit_tx = any(bucket.witness for bucket in buckets)
if is_segwit_tx:
total_weight += 2 # marker and flag
# non-segwit inputs were previously assumed to have
# a witness of '' instead of '00' (hex)
# note that mixed legacy/segwit buckets are already ok
num_legacy_inputs = sum((not bucket.witness) * len(bucket.coins)
for bucket in buckets)
total_weight += num_legacy_inputs

return total_weight

def sufficient_funds(buckets):
'''Given a list of buckets, return True if it has enough
value to pay for the transaction'''
total_input = sum(bucket.value for bucket in buckets)
total_size = sum(bucket.size for bucket in buckets) + base_size
return total_input >= spent_amount + fee_estimator(total_size)
total_weight = get_tx_weight(buckets)
return total_input >= spent_amount + fee_estimator_w(total_weight)

# Collect the coins into buckets, choose a subset of the buckets
buckets = self.bucketize_coins(coins)
buckets = self.choose_buckets(buckets, sufficient_funds,
self.penalty_func(tx))

tx.add_inputs([coin for b in buckets for coin in b.coins])
tx_size = base_size + sum(bucket.size for bucket in buckets)
tx_weight = get_tx_weight(buckets)

# This takes a count of change outputs and returns a tx fee
output_size = Transaction.estimated_output_size(change_addrs[0])
fee = lambda count: fee_estimator(tx_size + count * output_size)
output_weight = 4 * Transaction.estimated_output_size(change_addrs[0])
fee = lambda count: fee_estimator_w(tx_weight + count * output_weight)
change = self.change_outputs(tx, change_addrs, fee, dust_threshold)
tx.add_outputs(change)

Expand Down
7 changes: 3 additions & 4 deletions lib/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -863,17 +863,16 @@ def estimated_size(self):
return self.virtual_size_from_weight(weight)

@classmethod
def estimated_input_weight(cls, txin):
def estimated_input_weight(cls, txin, is_segwit_tx):
'''Return an estimate of serialized input weight in weight units.'''
script = cls.input_script(txin, True)
input_size = len(cls.serialize_input(txin, script)) // 2

# note: we should actually branch based on tx.is_segwit()
# only if none of the inputs have a witness, is the size actually 0
if cls.is_segwit_input(txin):
assert is_segwit_tx
witness_size = len(cls.serialize_witness(txin, True)) // 2
else:
witness_size = 0
witness_size = 1 if is_segwit_tx else 0

return 4 * input_size + witness_size

Expand Down

0 comments on commit 7d4d644

Please sign in to comment.