tMerge pull request #3351 from SomberNight/segwit_tx_size_estimation - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit ac5cbdaca2a6ed144550dbc97593fdd0058d3d01 DIR parent 8a200aaf33711c728665b6cfb0c519d05c419848 HTML Author: ThomasV <thomasv@electrum.org> Date: Thu, 23 Nov 2017 09:41:02 +0100 Merge pull request #3351 from SomberNight/segwit_tx_size_estimation better segwit tx size estimation Diffstat: M lib/coinchooser.py | 3 ++- M lib/transaction.py | 37 ++++++++++++++++++++++--------- 2 files changed, 29 insertions(+), 11 deletions(-) --- DIR diff --git a/lib/coinchooser.py b/lib/coinchooser.py t@@ -91,8 +91,9 @@ class CoinChooserBase(PrintError): buckets[key].append(coin) def make_Bucket(desc, coins): - size = sum(Transaction.estimated_input_size(coin) + weight = sum(Transaction.estimated_input_weight(coin) for coin in coins) + size = Transaction.virtual_size_from_weight(weight) value = sum(coin['value'] for coin in coins) return Bucket(desc, size, value, coins) DIR diff --git a/lib/transaction.py b/lib/transaction.py t@@ -635,11 +635,11 @@ class Transaction: return pk_list, sig_list @classmethod - def serialize_witness(self, txin): + def serialize_witness(self, txin, estimate_size=False): add_w = lambda x: var_int(len(x)//2) + x if not self.is_segwit_input(txin): return '00' - pubkeys, sig_list = self.get_siglist(txin) + pubkeys, sig_list = self.get_siglist(txin, estimate_size) if txin['type'] in ['p2wpkh', 'p2wpkh-p2sh']: witness = var_int(2) + add_w(sig_list[0]) + add_w(pubkeys[0]) elif txin['type'] in ['p2wsh', 'p2wsh-p2sh']: t@@ -648,7 +648,10 @@ class Transaction: witness = var_int(n) + '00' + ''.join(add_w(x) for x in sig_list) + add_w(witness_script) else: raise BaseException('wrong txin type') - value_field = '' if self.is_txin_complete(txin) else var_int(0xffffffff) + int_to_hex(txin['value'], 8) + if self.is_txin_complete(txin) or estimate_size: + value_field = '' + else: + value_field = var_int(0xffffffff) + int_to_hex(txin['value'], 8) return value_field + witness @classmethod t@@ -781,7 +784,7 @@ class Transaction: if witness and self.is_segwit(): marker = '00' flag = '01' - witness = ''.join(self.serialize_witness(x) for x in inputs) + witness = ''.join(self.serialize_witness(x, estimate_size) for x in inputs) return nVersion + marker + flag + txins + txouts + witness + nLocktime else: return nVersion + txins + txouts + nLocktime t@@ -830,13 +833,26 @@ class Transaction: weights, but for simplicity we approximate that with (virtual_size)x4 """ weight = self.estimated_weight() - return weight // 4 + (weight % 4 > 0) + return self.virtual_size_from_weight(weight) + + @classmethod + def estimated_input_weight(cls, txin): + '''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): + witness_size = len(cls.serialize_witness(txin, True)) // 2 + else: + witness_size = 0 + + return 4 * input_size + witness_size @classmethod - def estimated_input_size(self, txin): - '''Return an estimated of serialized input size in bytes.''' - script = self.input_script(txin, True) - return len(self.serialize_input(txin, script)) // 2 + def virtual_size_from_weight(cls, weight): + return weight // 4 + (weight % 4 > 0) def estimated_total_size(self): """Return an estimated total transaction size in bytes.""" t@@ -847,7 +863,8 @@ class Transaction: if not self.is_segwit(): return 0 inputs = self.inputs() - witness = ''.join(self.serialize_witness(x) for x in inputs) + estimate = not self.is_complete() + witness = ''.join(self.serialize_witness(x, estimate) for x in inputs) witness_size = len(witness) // 2 + 2 # include marker and flag return witness_size