URI: 
       tMerge pull request #3574 from SomberNight/coinchooser_uses_weights - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit fe41c61be482303013df797efbdd8b1977513ba7
   DIR parent efc837b4aae2015028dd66e1928c05bf8430bd67
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Thu, 21 Dec 2017 11:56:44 +0100
       
       Merge pull request #3574 from SomberNight/coinchooser_uses_weights
       
       more precise fee calculation
       Diffstat:
         M lib/coinchooser.py                  |      59 +++++++++++++++++++++++--------
         M lib/transaction.py                  |       7 +++----
       
       2 files changed, 48 insertions(+), 18 deletions(-)
       ---
   DIR diff --git a/lib/coinchooser.py b/lib/coinchooser.py
       t@@ -68,7 +68,13 @@ class PRNG:
                    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'''
       t@@ -91,12 +97,14 @@ class CoinChooserBase(PrintError):
                    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()))
        
       t@@ -169,10 +177,13 @@ class CoinChooserBase(PrintError):
        
            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]
       t@@ -180,16 +191,36 @@ class CoinChooserBase(PrintError):
        
                # 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)
       t@@ -197,11 +228,11 @@ class CoinChooserBase(PrintError):
                                              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)
        
   DIR diff --git a/lib/transaction.py b/lib/transaction.py
       t@@ -863,17 +863,16 @@ class Transaction:
                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