Skip to content

Commit 1fe9e92

Browse files
committed
Add CMerkleBlock class
1 parent edae39f commit 1fe9e92

File tree

2 files changed

+143
-0
lines changed

2 files changed

+143
-0
lines changed

bitcoin/core/__init__.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,123 @@ def GetHash(self):
539539
object.__setattr__(self, '_cached_GetHash', _cached_GetHash)
540540
return _cached_GetHash
541541

542+
class CMerkleBlock(CBlockHeader):
543+
"""
544+
The merkle block returned to spv clients when a filter is set on the remote peer.
545+
"""
546+
547+
__slots__ = ['nTX', 'vHashes', 'vFlags']
548+
549+
def __init__(self, nVersion=3, hashPrevBlock=b'\x00'*32, hashMerkleRoot=b'\x00'*32, nTime=0, nBits=0, nNonce=0, nTX=0, vHashes=(), vFlags=()):
550+
"""Create a new block"""
551+
super(CMerkleBlock, self).__init__(nVersion, hashPrevBlock, hashMerkleRoot, nTime, nBits, nNonce)
552+
553+
object.__setattr__(self, 'nTX', nTX)
554+
object.__setattr__(self, 'vHashes', vHashes)
555+
object.__setattr__(self, 'vFlags', vFlags)
556+
557+
@classmethod
558+
def stream_deserialize(cls, f):
559+
560+
def bits(f, n):
561+
ret = []
562+
bytes = (ord(b) for b in f.read(n))
563+
for b in bytes:
564+
for i in xrange(8):
565+
ret.append((b >> i) & 1)
566+
return ret
567+
568+
self = super(CMerkleBlock, cls).stream_deserialize(f)
569+
570+
nTX = struct.unpack('<L', ser_read(f, 4))[0]
571+
nHashes = VarIntSerializer.stream_deserialize(f)
572+
vHashes = []
573+
for i in range(nHashes):
574+
vHashes.append(ser_read(f, 32))
575+
nFlags = VarIntSerializer.stream_deserialize(f)
576+
vFlags = bits(f, nFlags)
577+
object.__setattr__(self, 'nTX', nTX)
578+
object.__setattr__(self, 'vHashes', vHashes)
579+
object.__setattr__(self, 'vFlags', vFlags)
580+
581+
return self
582+
583+
def stream_serialize(self, f):
584+
super(CMerkleBlock, self).stream_serialize(f)
585+
f.write(struct.pack('<L', self.nTX))
586+
VarIntSerializer.stream_serialize(len(self.vHashes), f)
587+
for hash in self.vHashes:
588+
f.write(hash)
589+
VarIntSerializer.stream_serialize(int(len(self.vFlags)/8), f)
590+
bin_string = ""
591+
for bit in self.vFlags:
592+
bin_string += str(bit)
593+
if len(bin_string) == 8:
594+
f.write(struct.pack('B', int(bin_string[::-1], 2)))
595+
bin_string = ""
596+
597+
def get_matched_txs(self):
598+
"""
599+
Return a list of transaction hashes that matched the filter. These txs
600+
have been validated against the merkle tree structure and are definitely
601+
in the block. However, the block hash still needs to be checked against
602+
the best chain.
603+
"""
604+
def getTreeWidth(transaction_count, height):
605+
return (transaction_count + (1 << height) - 1) >> height
606+
607+
matched_hashes = []
608+
609+
def recursive_extract_hashes(height, pos):
610+
parent_of_match = bool(self.vFlags.pop(0))
611+
if height == 0 or not parent_of_match:
612+
hash = self.vHashes.pop(0)
613+
if height == 0 and parent_of_match:
614+
matched_hashes.append(hash)
615+
return hash
616+
else:
617+
left = recursive_extract_hashes(height - 1, pos * 2)
618+
if pos * 2 + 1 < getTreeWidth(self.nTX, height-1):
619+
right = recursive_extract_hashes(height - 1, pos * 2 + 1)
620+
if left == right:
621+
raise Exception("Invalid Merkle Tree")
622+
else:
623+
right = left
624+
return sha256(sha256(left+right).digest()).digest()
625+
626+
height = 0
627+
while getTreeWidth(self.nTX, height) > 1:
628+
height += 1
629+
630+
calculated_root = recursive_extract_hashes(height, 0)
631+
if calculated_root == self.get_header().hashMerkleRoot:
632+
return matched_hashes
633+
else:
634+
return None
635+
636+
def get_header(self):
637+
"""Return the block header
638+
Returned header is a new object.
639+
"""
640+
return CBlockHeader(nVersion=self.nVersion,
641+
hashPrevBlock=self.hashPrevBlock,
642+
hashMerkleRoot=self.hashMerkleRoot,
643+
nTime=self.nTime,
644+
nBits=self.nBits,
645+
nNonce=self.nNonce)
646+
647+
def GetHash(self):
648+
"""Return the block hash
649+
Note that this is the hash of the header, not the entire serialized
650+
block.
651+
"""
652+
try:
653+
return self._cached_GetHash
654+
except AttributeError:
655+
_cached_GetHash = self.get_header().GetHash()
656+
object.__setattr__(self, '_cached_GetHash', _cached_GetHash)
657+
return _cached_GetHash
658+
542659
class CoreChainParams(object):
543660
"""Define consensus-critical parameters of a given instance of the Bitcoin system"""
544661
MAX_MONEY = None
@@ -766,6 +883,7 @@ def CheckBlock(block, fCheckPoW = True, fCheckMerkleRoot = True, cur_time=None):
766883
'CMutableTransaction',
767884
'CBlockHeader',
768885
'CBlock',
886+
'CMerkleBlock',
769887
'CoreChainParams',
770888
'CoreMainParams',
771889
'CoreTestNetParams',

bitcoin/tests/test_core.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,28 @@ def test_calc_merkle_root(self):
120120
# 99993 four transactions
121121
block = CBlock.deserialize(x('01000000acda3db591d5c2c63e8c09e7523a5b0581707ef3e3520d6ca180000000000000701179cb9a9e0fe709cc96261b6b943b31362b61dacba94b03f9b71a06cc2eff7d1c1b4d4c86041b75962f880401000000010000000000000000000000000000000000000000000000000000000000000000ffffffff07044c86041b0152ffffffff014034152a01000000434104216220ab283b5e2871c332de670d163fb1b7e509fd67db77997c5568e7c25afd988f19cd5cc5aec6430866ec64b5214826b28e0f7a86458073ff933994b47a5cac0000000001000000042a40ae58b06c3a61ae55dbee05cab546e80c508f71f24ef0cdc9749dac91ea5f000000004a49304602210089c685b37903c4aa62d984929afeaca554d1641f9a668398cd228fb54588f06b0221008a5cfbc5b0a38ba78c4f4341e53272b9cd0e377b2fb740106009b8d7fa693f0b01ffffffff7b999491e30af112b11105cb053bc3633a8a87f44740eb158849a76891ff228b00000000494830450221009a4aa8663ff4017063d2020519f2eade5b4e3e30be69bf9a62b4e6472d1747b2022021ee3b3090b8ce439dbf08a5df31e2dc23d68073ebda45dc573e8a4f74f5cdfc01ffffffffdea82ec2f9e88e0241faa676c13d093030b17c479770c6cc83239436a4327d49000000004a493046022100c29d9de71a34707c52578e355fa0fdc2bb69ce0a957e6b591658a02b1e039d69022100f82c8af79c166a822d305f0832fb800786d831aea419069b3aed97a6edf8f02101fffffffff3e7987da9981c2ae099f97a551783e1b21669ba0bf3aca8fe12896add91a11a0000000049483045022100e332c81781b281a3b35cf75a5a204a2be451746dad8147831255291ebac2604d02205f889a2935270d1bf1ef47db773d68c4d5c6a51bb51f082d3e1c491de63c345601ffffffff0100c817a8040000001976a91420420e56079150b50fb0617dce4c374bd61eccea88ac00000000010000000265a7293b2d69ba51d554cd32ac7586f7fbeaeea06835f26e03a2feab6aec375f000000004a493046022100922361eaafe316003087d355dd3c0ef3d9f44edae661c212a28a91e020408008022100c9b9c84d53d82c0ba9208f695c79eb42a453faea4d19706a8440e1d05e6cff7501fffffffff6971f00725d17c1c531088144b45ed795a307a22d51ca377c6f7f93675bb03a000000008b483045022100d060f2b2f4122edac61a25ea06396fe9135affdabc66d350b5ae1813bc6bf3f302205d8363deef2101fc9f3d528a8b3907e9d29c40772e587dcea12838c574cb80f801410449fce4a25c972a43a6bc67456407a0d4ced782d4cf8c0a35a130d5f65f0561e9f35198349a7c0b4ec79a15fead66bd7642f17cc8c40c5df95f15ac7190c76442ffffffff0200f2052a010000001976a914c3f537bc307c7eda43d86b55695e46047b770ea388ac00cf7b05000000001976a91407bef290008c089a60321b21b1df2d7f2202f40388ac0000000001000000014ab7418ecda2b2531eef0145d4644a4c82a7da1edd285d1aab1ec0595ac06b69000000008c493046022100a796490f89e0ef0326e8460edebff9161da19c36e00c7408608135f72ef0e03e0221009e01ef7bc17cddce8dfda1f1a6d3805c51f9ab2f8f2145793d8e85e0dd6e55300141043e6d26812f24a5a9485c9d40b8712215f0c3a37b0334d76b2c24fcafa587ae5258853b6f49ceeb29cd13ebb76aa79099fad84f516bbba47bd170576b121052f1ffffffff0200a24a04000000001976a9143542e17b6229a25d5b76909f9d28dd6ed9295b2088ac003fab01000000001976a9149cea2b6e3e64ad982c99ebba56a882b9e8a816fe88ac00000000'))
122122
self.assertEqual(block.calc_merkle_root(), lx('ff2ecc061ab7f9034ba9cbda612b36313b946b1b2696cc09e70f9e9acb791170'))
123+
124+
class Test_CMerkleBlock(unittest.TestCase):
125+
def test_serialization(self):
126+
initial_serialized = x('0100000082bb869cf3a793432a66e826e05a6fc37469f8efb7421dc880670100000000007f16c5962e8bd963659c793ce370d95f093bc7e367117b3c30c1f8fdd0d9728776381b4d4c86041b554b852907000000043612262624047ee87660be1a707519a443b1c1ce3d248cbfc6c15870f6c5daa2019f5b01d4195ecbc9398fbf3c3b1fa9bb3183301d7a1fb3bd174fcfa40a2b6541ed70551dd7e841883ab8f0b16bf04176b7d1480e4f0af9f3d4c3595768d06820d2a7bc994987302e5b1ac80fc425fe25f8b63169ea78e68fbaaefa59379bbf011d')
127+
block = CMerkleBlock.deserialize(initial_serialized)
128+
self.assertEqual(block.nTX, 7)
129+
self.assertEqual(len(block.vHashes), 4)
130+
self.assertEqual(len(block.vHashes), 4)
131+
self.assertEqual(len(block.vFlags), 8)
132+
serialized = block.serialize()
133+
self.assertEqual(Hash(serialized[0:80]), Hash(initial_serialized[:80]))
134+
self.assertEqual(serialized, initial_serialized)
135+
136+
def test_GetHash(self):
137+
block = CMerkleBlock.deserialize(x('0100000082bb869cf3a793432a66e826e05a6fc37469f8efb7421dc880670100000000007f16c5962e8bd963659c793ce370d95f093bc7e367117b3c30c1f8fdd0d9728776381b4d4c86041b554b852907000000043612262624047ee87660be1a707519a443b1c1ce3d248cbfc6c15870f6c5daa2019f5b01d4195ecbc9398fbf3c3b1fa9bb3183301d7a1fb3bd174fcfa40a2b6541ed70551dd7e841883ab8f0b16bf04176b7d1480e4f0af9f3d4c3595768d06820d2a7bc994987302e5b1ac80fc425fe25f8b63169ea78e68fbaaefa59379bbf011d'))
138+
self.assertEqual(block.GetHash(), lx('000000000000b731f2eef9e8c63173adfb07e41bd53eb0ef0a6b720d6cb6dea4'))
139+
140+
def test_extract_hashes(self):
141+
initial_serialized = x('0100000082bb869cf3a793432a66e826e05a6fc37469f8efb7421dc880670100000000007f16c5962e8bd963659c793ce370d95f093bc7e367117b3c30c1f8fdd0d9728776381b4d4c86041b554b852907000000043612262624047ee87660be1a707519a443b1c1ce3d248cbfc6c15870f6c5daa2019f5b01d4195ecbc9398fbf3c3b1fa9bb3183301d7a1fb3bd174fcfa40a2b6541ed70551dd7e841883ab8f0b16bf04176b7d1480e4f0af9f3d4c3595768d06820d2a7bc994987302e5b1ac80fc425fe25f8b63169ea78e68fbaaefa59379bbf011d')
142+
block = CMerkleBlock.deserialize(initial_serialized)
143+
matches = block.get_matched_txs()
144+
self.assertEqual(len(matches), 1)
145+
self.assertEqual(matches[0], x("019f5b01d4195ecbc9398fbf3c3b1fa9bb3183301d7a1fb3bd174fcfa40a2b65"))
146+
147+

0 commit comments

Comments
 (0)