Skip to content

Commit eca69b7

Browse files
author
Bob McElrath
committed
CTxWitness class, new message types
1 parent f4d1d2a commit eca69b7

File tree

4 files changed

+83
-21
lines changed

4 files changed

+83
-21
lines changed

bitcoin/core/__init__.py

Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -306,12 +306,15 @@ def from_txout(cls, txout):
306306

307307

308308
class CTxInWitness(ImmutableSerializable):
309-
"""Witness data for a transaction input. """
309+
"""Witness data for a single transaction input. """
310310
__slots__ = ['scriptWitness']
311311

312312
def __init__(self, scriptWitness=CScriptWitness()):
313313
object.__setattr__(self, 'scriptWitness', scriptWitness)
314314

315+
def is_null(self):
316+
return self.scriptWitness.is_null()
317+
315318
@classmethod
316319
def stream_deserialize(cls, f):
317320
scriptWitness = CScriptWitness.stream_deserialize(f)
@@ -336,12 +339,50 @@ def from_txinwitness(cls, txinwitness):
336339
else:
337340
return cls(txinwitness.scriptWitness)
338341

342+
class CTxWitness(ImmutableSerializable):
343+
"""Witness data for all inputs to a transaction."""
344+
__slots__ = ['vtxinwit']
345+
346+
def __init__(self, vtxinwit=()):
347+
object.__setattr__(self, 'vtxinwit', vtxinwit)
348+
349+
def is_null(self):
350+
for n in range(len(self.vtxinwit)):
351+
if not self.vtxinwit[n].is_null(): return False
352+
return True
353+
354+
# FIXME this cannot be a @classmethod like the others because we need to
355+
# know how many items to deserialize, which comes from len(vin)
356+
def stream_deserialize(self, f):
357+
vtxinwit = tuple(CTxInWitness.stream_deserialize(f) for dummy in
358+
range(len(self.vtxinwit)))
359+
return CTxWitness(vtxinwit)
360+
361+
def stream_serialize(self, f):
362+
for i in range(len(self.vtxinwit)):
363+
self.vtxinwit[i].stream_serialize(f)
364+
365+
def __repr__(self):
366+
return "CTxWitness(%s)" % (','.join(repr(w) for w in self.vtxinwit))
367+
368+
@classmethod
369+
def from_txwitness(cls, txwitness):
370+
"""Create an immutable copy of an existing TxWitness
371+
372+
If txwitness is already immutable (txwitness.__class__ is CTxWitness) it is returned
373+
directly.
374+
"""
375+
if txwitness.__class__ is CTxWitness:
376+
return txwitness
377+
else:
378+
return cls(txwitness.vtxinwit)
379+
339380

340381
class CTransaction(ImmutableSerializable):
341382
"""A transaction"""
342383
__slots__ = ['nVersion', 'vin', 'vout', 'nLockTime', 'wit']
343384

344-
def __init__(self, vin=(), vout=(), nLockTime=0, nVersion=1, witness=()):
385+
def __init__(self, vin=(), vout=(), nLockTime=0, nVersion=1, witness=CTxWitness()):
345386
"""Create a new transaction
346387
347388
vin and vout are iterables of transaction inputs and outputs
@@ -351,13 +392,10 @@ def __init__(self, vin=(), vout=(), nLockTime=0, nVersion=1, witness=()):
351392
if not (0 <= nLockTime <= 0xffffffff):
352393
raise ValueError('CTransaction: nLockTime must be in range 0x0 to 0xffffffff; got %x' % nLockTime)
353394
object.__setattr__(self, 'nLockTime', nLockTime)
354-
355395
object.__setattr__(self, 'nVersion', nVersion)
356396
object.__setattr__(self, 'vin', tuple(CTxIn.from_txin(txin) for txin in vin))
357397
object.__setattr__(self, 'vout', tuple(CTxOut.from_txout(txout) for txout in vout))
358-
object.__setattr__(self, 'wit',
359-
tuple(CTxInWitness.from_txinwitness(witness) for txinwitness in
360-
witness))
398+
object.__setattr__(self, 'wit', CTxWitness.from_txwitness(witness))
361399

362400
@classmethod
363401
def stream_deserialize(cls, f):
@@ -370,7 +408,8 @@ def stream_deserialize(cls, f):
370408
raise DeserializationFormatError
371409
vin = VectorSerializer.stream_deserialize(CTxIn, f)
372410
vout = VectorSerializer.stream_deserialize(CTxOut, f)
373-
wit = VectorSerializer.stream_deserialize(CTxInWitness, f)
411+
wit = CTxWitness(tuple(0 for dummy in range(len(vin))))
412+
wit = wit.stream_deserialize(f)
374413
nLockTime = struct.unpack(b"<I", ser_read(f,4))[0]
375414
return cls(vin, vout, nLockTime, nVersion, wit)
376415
else:
@@ -382,15 +421,15 @@ def stream_deserialize(cls, f):
382421

383422

384423
def stream_serialize(self, f):
385-
if self.wit:
386-
if len(self.wit) != len(self.vin):
424+
if not self.wit.is_null():
425+
if len(self.wit.vtxinwit) != len(self.vin):
387426
raise SerializationMissingWitnessError
388427
f.write(struct.pack(b"<i", self.nVersion))
389428
f.write(b'\x00') # Marker
390429
f.write(b'\x01') # Flag
391430
VectorSerializer.stream_serialize(CTxIn, self.vin, f)
392431
VectorSerializer.stream_serialize(CTxOut, self.vout, f)
393-
for w in self.wit: w.stream_serialize(f)
432+
self.wit.stream_serialize(f)
394433
f.write(struct.pack(b"<I", self.nLockTime))
395434
else:
396435
f.write(struct.pack(b"<i", self.nVersion))
@@ -422,11 +461,9 @@ def GetTxid(self):
422461
"""Get the transaction ID. This differs from the transactions hash as
423462
given by GetHash. GetTxid excludes witness data, while GetHash
424463
includes it. """
425-
if self.wit:
426-
wit = self.wit
427-
self.wit = b''
428-
txid = Hash(self.serialize())
429-
self.wit = wit
464+
if self.wit != CTxWitness():
465+
txid = Hash(CTransaction(self.vin, self.vout, self.nLockTime,
466+
self.nVersion).serialize())
430467
else:
431468
txid = Hash(self.serialize())
432469
return txid
@@ -452,6 +489,9 @@ def __init__(self, vin=None, vout=None, nLockTime=0, nVersion=1, witness=None):
452489
vout = []
453490
self.vout = vout
454491
self.nVersion = nVersion
492+
493+
if witness is None:
494+
witness = CTxWitness([CTxInWitness() for dummy in range(len(vin))])
455495
self.wit = witness
456496

457497
@classmethod

bitcoin/core/script.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -678,21 +678,28 @@ def is_p2sh(self):
678678
_bord(self[22]) == OP_EQUAL)
679679

680680
def is_witness_scriptpubkey(self):
681+
"""Returns true if this is a scriptpubkey signaling segregated witness
682+
data. """
681683
return 3 <= len(self) <= 42 and CScriptOp(self[0]).is_small_int()
682684

683685
def witness_version(self):
686+
"""Returns the witness version on [0,16]. """
684687
return next(iter(self))
685688

686689
def is_witness_v0_keyhash(self):
690+
"""Returns true if this is a scriptpubkey for V0 P2WPKH. """
687691
return len(self) == 22 and self[0:2] == b'\x00\x14'
688692

689693
def is_witness_v0_nested_keyhash(self):
694+
"""Returns true if this is a scriptpubkey for V0 P2WPKH embedded in P2SH. """
690695
return len(self) == 23 and self[0:3] == b'\x16\x00\x14'
691696

692697
def is_witness_v0_scripthash(self):
698+
"""Returns true if this is a scriptpubkey for V0 P2WSH. """
693699
return len(self) == 34 and self[0:2] == b'\x00\x20'
694700

695701
def is_witness_v0_nested_scripthash(self):
702+
"""Returns true if this is a scriptpubkey for V0 P2WSH embedded in P2SH. """
696703
return len(self) == 23 and self[0:2] == b'\xa9\x14' and self[-1] == b'\x87'
697704

698705
def is_push_only(self):
@@ -812,15 +819,16 @@ def __iter__(self):
812819
return iter(self.stack)
813820

814821
def __repr__(self):
815-
816822
return 'CScriptWitness(' + ','.join("x('%s')" % bitcoin.core.b2x(s) for s in self.stack) + ')'
817823

824+
def is_null(self):
825+
return len(self.stack) == 0
826+
818827
@classmethod
819828
def stream_deserialize(cls, f):
820829
n = VarIntSerializer.stream_deserialize(f)
821-
self.stack = []
822-
for i in range(n):
823-
self.stack.append(BytesSerializer.stream_deserialize(f))
830+
stack = tuple(BytesSerializer.stream_deserialize(f) for i in range(n))
831+
return cls(stack)
824832

825833
def stream_serialize(self, f):
826834
VarIntSerializer.stream_serialize(len(self.stack), f)
@@ -948,7 +956,7 @@ def RawSignatureHash(script, txTo, inIdx, hashtype):
948956
txtmp.vin = []
949957
txtmp.vin.append(tmp)
950958

951-
txtmp.wit = ()
959+
txtmp.wit = bitcoin.core.CTxWitness()
952960
s = txtmp.serialize()
953961
s += struct.pack(b"<I", hashtype)
954962

bitcoin/messages.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,16 @@
3535
from bitcoin.net import *
3636
import bitcoin
3737

38+
MSG_WITNESS_FLAG = 1 << 30
39+
MSG_TYPE_MASK = 0xffffffff >> 2
40+
3841
MSG_TX = 1
3942
MSG_BLOCK = 2
4043
MSG_FILTERED_BLOCK = 3
44+
MSG_CMPCT_BLOCK = 4
45+
MSG_WITNESS_BLOCK = MSG_BLOCK | MSG_WITNESS_FLAG,
46+
MSG_WITNESS_TX = MSG_TX | MSG_WITNESS_FLAG,
47+
MSG_FILTERED_WITNESS_BLOCK = MSG_FILTERED_BLOCK | MSG_WITNESS_FLAG,
4148

4249

4350
class MsgSerializable(Serializable):
@@ -499,6 +506,12 @@ def __repr__(self):
499506
'MSG_TX',
500507
'MSG_BLOCK',
501508
'MSG_FILTERED_BLOCK',
509+
'MSG_CMPCT_BLOCK',
510+
'MSG_TYPE_MASK',
511+
'MSG_WITNESS_TX',
512+
'MSG_WITNESS_BLOCK',
513+
'MSG_WITNESS_FLAG',
514+
'MSG_FILTERED_WITNESS_BLOCK',
502515
'MsgSerializable',
503516
'msg_version',
504517
'msg_verack',

bitcoin/net.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ class CInv(Serializable):
7676
0: "Error",
7777
1: "TX",
7878
2: "Block",
79-
3: "FilteredBlock"}
79+
3: "FilteredBlock",
80+
4: "CompactBlock"}
8081

8182
def __init__(self):
8283
self.type = 0

0 commit comments

Comments
 (0)