2 # Copyright (c) 2010 ArtForz -- public domain half-a-node
3 # Copyright (c) 2012 Jeff Garzik
4 # Copyright (c) 2010-2016 The Bitcoin Core developers
5 # Distributed under the MIT software license, see the accompanying
6 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
7 """Bitcoin P2P network half-a-node.
9 This python code was modified from ArtForz' public domain half-a-node, as
10 found in the mini-node branch of http://github.com/jgarzik/pynode.
12 NodeConn: an object which manages p2p connectivity to a bitcoin node
13 NodeConnCB: a base class that describes the interface for receiving
14 callbacks with network messages from a NodeConn
15 CBlock, CTransaction, CBlockHeader, CTxIn, CTxOut, etc....:
16 data structures that should map to corresponding structures in
18 msg_block, msg_tx, msg_headers, etc.:
19 data structures that represent network messages
20 ser_*, deser_*: functions that handle serialization/deserialization
24 from codecs
import encode
25 from collections
import defaultdict
28 from io
import BytesIO
35 from threading
import RLock
, Thread
37 from test_framework
.siphash
import siphash256
38 from test_framework
.util
import hex_str_to_bytes
, bytes_to_hex_str
, wait_until
40 MY_VERSION
= 70014 # past bip-31 for ping/pong
41 MY_SUBVERSION
= b
"/python-mininode-tester:0.0.3/"
42 MY_RELAY
= 1 # from version 70001 onwards, fRelay should be appended to version messages (BIP37)
45 MAX_BLOCK_BASE_SIZE
= 1000000
47 COIN
= 100000000 # 1 btc in satoshis
49 NODE_NETWORK
= (1 << 0)
50 # NODE_GETUTXO = (1 << 1)
51 # NODE_BLOOM = (1 << 2)
52 NODE_WITNESS
= (1 << 3)
53 NODE_UNSUPPORTED_SERVICE_BIT_5
= (1 << 5)
54 NODE_UNSUPPORTED_SERVICE_BIT_7
= (1 << 7)
56 logger
= logging
.getLogger("TestFramework.mininode")
58 # Keep our own socket map for asyncore, so that we can track disconnects
59 # ourselves (to workaround an issue with closing an asyncore socket when
61 mininode_socket_map
= dict()
63 # One lock for synchronizing all data access between the networking thread (see
64 # NetworkThread below) and the thread running the test logic. For simplicity,
65 # NodeConn acquires this lock whenever delivering a message to a NodeConnCB,
66 # and whenever adding anything to the send buffer (in send_message()). This
67 # lock should be acquired in the thread running the test logic to synchronize
68 # access to any data shared with the NodeConnCB or NodeConn.
69 mininode_lock
= RLock()
71 # Serialization/deserialization tools
73 return hashlib
.new('sha256', s
).digest()
76 return hashlib
.new('ripemd160', s
).digest()
79 return sha256(sha256(s
))
81 def ser_compact_size(l
):
84 r
= struct
.pack("B", l
)
86 r
= struct
.pack("<BH", 253, l
)
88 r
= struct
.pack("<BI", 254, l
)
90 r
= struct
.pack("<BQ", 255, l
)
93 def deser_compact_size(f
):
94 nit
= struct
.unpack("<B", f
.read(1))[0]
96 nit
= struct
.unpack("<H", f
.read(2))[0]
98 nit
= struct
.unpack("<I", f
.read(4))[0]
100 nit
= struct
.unpack("<Q", f
.read(8))[0]
104 nit
= deser_compact_size(f
)
108 return ser_compact_size(len(s
)) + s
110 def deser_uint256(f
):
113 t
= struct
.unpack("<I", f
.read(4))[0]
121 rs
+= struct
.pack("<I", u
& 0xFFFFFFFF)
126 def uint256_from_str(s
):
128 t
= struct
.unpack("<IIIIIIII", s
[:32])
130 r
+= t
[i
] << (i
* 32)
134 def uint256_from_compact(c
):
135 nbytes
= (c
>> 24) & 0xFF
136 v
= (c
& 0xFFFFFF) << (8 * (nbytes
- 3))
140 def deser_vector(f
, c
):
141 nit
= deser_compact_size(f
)
150 # ser_function_name: Allow for an alternate serialization function on the
151 # entries in the vector (we use this for serializing the vector of transactions
152 # for a witness block).
153 def ser_vector(l
, ser_function_name
=None):
154 r
= ser_compact_size(len(l
))
156 if ser_function_name
:
157 r
+= getattr(i
, ser_function_name
)()
163 def deser_uint256_vector(f
):
164 nit
= deser_compact_size(f
)
172 def ser_uint256_vector(l
):
173 r
= ser_compact_size(len(l
))
179 def deser_string_vector(f
):
180 nit
= deser_compact_size(f
)
188 def ser_string_vector(l
):
189 r
= ser_compact_size(len(l
))
195 def deser_int_vector(f
):
196 nit
= deser_compact_size(f
)
199 t
= struct
.unpack("<i", f
.read(4))[0]
204 def ser_int_vector(l
):
205 r
= ser_compact_size(len(l
))
207 r
+= struct
.pack("<i", i
)
210 # Deserialize from a hex string representation (eg from RPC)
211 def FromHex(obj
, hex_string
):
212 obj
.deserialize(BytesIO(hex_str_to_bytes(hex_string
)))
215 # Convert a binary-serializable object to hex (eg for submission via RPC)
217 return bytes_to_hex_str(obj
.serialize())
219 # Objects that map to bitcoind objects, which can be serialized/deserialized
224 self
.pchReserved
= b
"\x00" * 10 + b
"\xff" * 2
228 def deserialize(self
, f
):
229 self
.nServices
= struct
.unpack("<Q", f
.read(8))[0]
230 self
.pchReserved
= f
.read(12)
231 self
.ip
= socket
.inet_ntoa(f
.read(4))
232 self
.port
= struct
.unpack(">H", f
.read(2))[0]
236 r
+= struct
.pack("<Q", self
.nServices
)
237 r
+= self
.pchReserved
238 r
+= socket
.inet_aton(self
.ip
)
239 r
+= struct
.pack(">H", self
.port
)
243 return "CAddress(nServices=%i ip=%s port=%i)" % (self
.nServices
,
246 MSG_WITNESS_FLAG
= 1<<30
253 1|MSG_WITNESS_FLAG
: "WitnessTx",
254 2|MSG_WITNESS_FLAG
: "WitnessBlock",
258 def __init__(self
, t
=0, h
=0):
262 def deserialize(self
, f
):
263 self
.type = struct
.unpack("<i", f
.read(4))[0]
264 self
.hash = deser_uint256(f
)
268 r
+= struct
.pack("<i", self
.type)
269 r
+= ser_uint256(self
.hash)
273 return "CInv(type=%s hash=%064x)" \
274 % (self
.typemap
[self
.type], self
.hash)
277 class CBlockLocator():
279 self
.nVersion
= MY_VERSION
282 def deserialize(self
, f
):
283 self
.nVersion
= struct
.unpack("<i", f
.read(4))[0]
284 self
.vHave
= deser_uint256_vector(f
)
288 r
+= struct
.pack("<i", self
.nVersion
)
289 r
+= ser_uint256_vector(self
.vHave
)
293 return "CBlockLocator(nVersion=%i vHave=%s)" \
294 % (self
.nVersion
, repr(self
.vHave
))
298 def __init__(self
, hash=0, n
=0):
302 def deserialize(self
, f
):
303 self
.hash = deser_uint256(f
)
304 self
.n
= struct
.unpack("<I", f
.read(4))[0]
308 r
+= ser_uint256(self
.hash)
309 r
+= struct
.pack("<I", self
.n
)
313 return "COutPoint(hash=%064x n=%i)" % (self
.hash, self
.n
)
317 def __init__(self
, outpoint
=None, scriptSig
=b
"", nSequence
=0):
319 self
.prevout
= COutPoint()
321 self
.prevout
= outpoint
322 self
.scriptSig
= scriptSig
323 self
.nSequence
= nSequence
325 def deserialize(self
, f
):
326 self
.prevout
= COutPoint()
327 self
.prevout
.deserialize(f
)
328 self
.scriptSig
= deser_string(f
)
329 self
.nSequence
= struct
.unpack("<I", f
.read(4))[0]
333 r
+= self
.prevout
.serialize()
334 r
+= ser_string(self
.scriptSig
)
335 r
+= struct
.pack("<I", self
.nSequence
)
339 return "CTxIn(prevout=%s scriptSig=%s nSequence=%i)" \
340 % (repr(self
.prevout
), bytes_to_hex_str(self
.scriptSig
),
345 def __init__(self
, nValue
=0, scriptPubKey
=b
""):
347 self
.scriptPubKey
= scriptPubKey
349 def deserialize(self
, f
):
350 self
.nValue
= struct
.unpack("<q", f
.read(8))[0]
351 self
.scriptPubKey
= deser_string(f
)
355 r
+= struct
.pack("<q", self
.nValue
)
356 r
+= ser_string(self
.scriptPubKey
)
360 return "CTxOut(nValue=%i.%08i scriptPubKey=%s)" \
361 % (self
.nValue
// COIN
, self
.nValue
% COIN
,
362 bytes_to_hex_str(self
.scriptPubKey
))
365 class CScriptWitness():
367 # stack is a vector of strings
371 return "CScriptWitness(%s)" % \
372 (",".join([bytes_to_hex_str(x
) for x
in self
.stack
]))
380 class CTxInWitness():
382 self
.scriptWitness
= CScriptWitness()
384 def deserialize(self
, f
):
385 self
.scriptWitness
.stack
= deser_string_vector(f
)
388 return ser_string_vector(self
.scriptWitness
.stack
)
391 return repr(self
.scriptWitness
)
394 return self
.scriptWitness
.is_null()
401 def deserialize(self
, f
):
402 for i
in range(len(self
.vtxinwit
)):
403 self
.vtxinwit
[i
].deserialize(f
)
407 # This is different than the usual vector serialization --
408 # we omit the length of the vector, which is required to be
409 # the same length as the transaction's vin vector.
410 for x
in self
.vtxinwit
:
415 return "CTxWitness(%s)" % \
416 (';'.join([repr(x
) for x
in self
.vtxinwit
]))
419 for x
in self
.vtxinwit
:
425 class CTransaction():
426 def __init__(self
, tx
=None):
431 self
.wit
= CTxWitness()
436 self
.nVersion
= tx
.nVersion
437 self
.vin
= copy
.deepcopy(tx
.vin
)
438 self
.vout
= copy
.deepcopy(tx
.vout
)
439 self
.nLockTime
= tx
.nLockTime
440 self
.sha256
= tx
.sha256
442 self
.wit
= copy
.deepcopy(tx
.wit
)
444 def deserialize(self
, f
):
445 self
.nVersion
= struct
.unpack("<i", f
.read(4))[0]
446 self
.vin
= deser_vector(f
, CTxIn
)
448 if len(self
.vin
) == 0:
449 flags
= struct
.unpack("<B", f
.read(1))[0]
450 # Not sure why flags can't be zero, but this
451 # matches the implementation in bitcoind
453 self
.vin
= deser_vector(f
, CTxIn
)
454 self
.vout
= deser_vector(f
, CTxOut
)
456 self
.vout
= deser_vector(f
, CTxOut
)
458 self
.wit
.vtxinwit
= [CTxInWitness() for i
in range(len(self
.vin
))]
459 self
.wit
.deserialize(f
)
460 self
.nLockTime
= struct
.unpack("<I", f
.read(4))[0]
464 def serialize_without_witness(self
):
466 r
+= struct
.pack("<i", self
.nVersion
)
467 r
+= ser_vector(self
.vin
)
468 r
+= ser_vector(self
.vout
)
469 r
+= struct
.pack("<I", self
.nLockTime
)
472 # Only serialize with witness when explicitly called for
473 def serialize_with_witness(self
):
475 if not self
.wit
.is_null():
478 r
+= struct
.pack("<i", self
.nVersion
)
481 r
+= ser_vector(dummy
)
482 r
+= struct
.pack("<B", flags
)
483 r
+= ser_vector(self
.vin
)
484 r
+= ser_vector(self
.vout
)
486 if (len(self
.wit
.vtxinwit
) != len(self
.vin
)):
487 # vtxinwit must have the same length as vin
488 self
.wit
.vtxinwit
= self
.wit
.vtxinwit
[:len(self
.vin
)]
489 for i
in range(len(self
.wit
.vtxinwit
), len(self
.vin
)):
490 self
.wit
.vtxinwit
.append(CTxInWitness())
491 r
+= self
.wit
.serialize()
492 r
+= struct
.pack("<I", self
.nLockTime
)
495 # Regular serialization is without witness -- must explicitly
496 # call serialize_with_witness to include witness data.
498 return self
.serialize_without_witness()
500 # Recalculate the txid (transaction hash without witness)
505 # We will only cache the serialization without witness in
506 # self.sha256 and self.hash -- those are expected to be the txid.
507 def calc_sha256(self
, with_witness
=False):
509 # Don't cache the result, just return it
510 return uint256_from_str(hash256(self
.serialize_with_witness()))
512 if self
.sha256
is None:
513 self
.sha256
= uint256_from_str(hash256(self
.serialize_without_witness()))
514 self
.hash = encode(hash256(self
.serialize())[::-1], 'hex_codec').decode('ascii')
518 for tout
in self
.vout
:
519 if tout
.nValue
< 0 or tout
.nValue
> 21000000 * COIN
:
524 return "CTransaction(nVersion=%i vin=%s vout=%s wit=%s nLockTime=%i)" \
525 % (self
.nVersion
, repr(self
.vin
), repr(self
.vout
), repr(self
.wit
), self
.nLockTime
)
528 class CBlockHeader():
529 def __init__(self
, header
=None):
533 self
.nVersion
= header
.nVersion
534 self
.hashPrevBlock
= header
.hashPrevBlock
535 self
.hashMerkleRoot
= header
.hashMerkleRoot
536 self
.nTime
= header
.nTime
537 self
.nBits
= header
.nBits
538 self
.nNonce
= header
.nNonce
539 self
.sha256
= header
.sha256
540 self
.hash = header
.hash
545 self
.hashPrevBlock
= 0
546 self
.hashMerkleRoot
= 0
553 def deserialize(self
, f
):
554 self
.nVersion
= struct
.unpack("<i", f
.read(4))[0]
555 self
.hashPrevBlock
= deser_uint256(f
)
556 self
.hashMerkleRoot
= deser_uint256(f
)
557 self
.nTime
= struct
.unpack("<I", f
.read(4))[0]
558 self
.nBits
= struct
.unpack("<I", f
.read(4))[0]
559 self
.nNonce
= struct
.unpack("<I", f
.read(4))[0]
565 r
+= struct
.pack("<i", self
.nVersion
)
566 r
+= ser_uint256(self
.hashPrevBlock
)
567 r
+= ser_uint256(self
.hashMerkleRoot
)
568 r
+= struct
.pack("<I", self
.nTime
)
569 r
+= struct
.pack("<I", self
.nBits
)
570 r
+= struct
.pack("<I", self
.nNonce
)
573 def calc_sha256(self
):
574 if self
.sha256
is None:
576 r
+= struct
.pack("<i", self
.nVersion
)
577 r
+= ser_uint256(self
.hashPrevBlock
)
578 r
+= ser_uint256(self
.hashMerkleRoot
)
579 r
+= struct
.pack("<I", self
.nTime
)
580 r
+= struct
.pack("<I", self
.nBits
)
581 r
+= struct
.pack("<I", self
.nNonce
)
582 self
.sha256
= uint256_from_str(hash256(r
))
583 self
.hash = encode(hash256(r
)[::-1], 'hex_codec').decode('ascii')
591 return "CBlockHeader(nVersion=%i hashPrevBlock=%064x hashMerkleRoot=%064x nTime=%s nBits=%08x nNonce=%08x)" \
592 % (self
.nVersion
, self
.hashPrevBlock
, self
.hashMerkleRoot
,
593 time
.ctime(self
.nTime
), self
.nBits
, self
.nNonce
)
596 class CBlock(CBlockHeader
):
597 def __init__(self
, header
=None):
598 super(CBlock
, self
).__init
__(header
)
601 def deserialize(self
, f
):
602 super(CBlock
, self
).deserialize(f
)
603 self
.vtx
= deser_vector(f
, CTransaction
)
605 def serialize(self
, with_witness
=False):
607 r
+= super(CBlock
, self
).serialize()
609 r
+= ser_vector(self
.vtx
, "serialize_with_witness")
611 r
+= ser_vector(self
.vtx
)
614 # Calculate the merkle root given a vector of transaction hashes
616 def get_merkle_root(cls
, hashes
):
617 while len(hashes
) > 1:
619 for i
in range(0, len(hashes
), 2):
620 i2
= min(i
+1, len(hashes
)-1)
621 newhashes
.append(hash256(hashes
[i
] + hashes
[i2
]))
623 return uint256_from_str(hashes
[0])
625 def calc_merkle_root(self
):
629 hashes
.append(ser_uint256(tx
.sha256
))
630 return self
.get_merkle_root(hashes
)
632 def calc_witness_merkle_root(self
):
633 # For witness root purposes, the hash of the
634 # coinbase, with witness, is defined to be 0...0
635 hashes
= [ser_uint256(0)]
637 for tx
in self
.vtx
[1:]:
638 # Calculate the hashes with witness data
639 hashes
.append(ser_uint256(tx
.calc_sha256(True)))
641 return self
.get_merkle_root(hashes
)
645 target
= uint256_from_compact(self
.nBits
)
646 if self
.sha256
> target
:
649 if not tx
.is_valid():
651 if self
.calc_merkle_root() != self
.hashMerkleRoot
:
657 target
= uint256_from_compact(self
.nBits
)
658 while self
.sha256
> target
:
663 return "CBlock(nVersion=%i hashPrevBlock=%064x hashMerkleRoot=%064x nTime=%s nBits=%08x nNonce=%08x vtx=%s)" \
664 % (self
.nVersion
, self
.hashPrevBlock
, self
.hashMerkleRoot
,
665 time
.ctime(self
.nTime
), self
.nBits
, self
.nNonce
, repr(self
.vtx
))
668 class PrefilledTransaction():
669 def __init__(self
, index
=0, tx
= None):
673 def deserialize(self
, f
):
674 self
.index
= deser_compact_size(f
)
675 self
.tx
= CTransaction()
676 self
.tx
.deserialize(f
)
678 def serialize(self
, with_witness
=False):
680 r
+= ser_compact_size(self
.index
)
682 r
+= self
.tx
.serialize_with_witness()
684 r
+= self
.tx
.serialize_without_witness()
687 def serialize_with_witness(self
):
688 return self
.serialize(with_witness
=True)
691 return "PrefilledTransaction(index=%d, tx=%s)" % (self
.index
, repr(self
.tx
))
693 # This is what we send on the wire, in a cmpctblock message.
694 class P2PHeaderAndShortIDs():
696 self
.header
= CBlockHeader()
698 self
.shortids_length
= 0
700 self
.prefilled_txn_length
= 0
701 self
.prefilled_txn
= []
703 def deserialize(self
, f
):
704 self
.header
.deserialize(f
)
705 self
.nonce
= struct
.unpack("<Q", f
.read(8))[0]
706 self
.shortids_length
= deser_compact_size(f
)
707 for i
in range(self
.shortids_length
):
708 # shortids are defined to be 6 bytes in the spec, so append
709 # two zero bytes and read it in as an 8-byte number
710 self
.shortids
.append(struct
.unpack("<Q", f
.read(6) + b
'\x00\x00')[0])
711 self
.prefilled_txn
= deser_vector(f
, PrefilledTransaction
)
712 self
.prefilled_txn_length
= len(self
.prefilled_txn
)
714 # When using version 2 compact blocks, we must serialize with_witness.
715 def serialize(self
, with_witness
=False):
717 r
+= self
.header
.serialize()
718 r
+= struct
.pack("<Q", self
.nonce
)
719 r
+= ser_compact_size(self
.shortids_length
)
720 for x
in self
.shortids
:
721 # We only want the first 6 bytes
722 r
+= struct
.pack("<Q", x
)[0:6]
724 r
+= ser_vector(self
.prefilled_txn
, "serialize_with_witness")
726 r
+= ser_vector(self
.prefilled_txn
)
730 return "P2PHeaderAndShortIDs(header=%s, nonce=%d, shortids_length=%d, shortids=%s, prefilled_txn_length=%d, prefilledtxn=%s" % (repr(self
.header
), self
.nonce
, self
.shortids_length
, repr(self
.shortids
), self
.prefilled_txn_length
, repr(self
.prefilled_txn
))
732 # P2P version of the above that will use witness serialization (for compact
734 class P2PHeaderAndShortWitnessIDs(P2PHeaderAndShortIDs
):
736 return super(P2PHeaderAndShortWitnessIDs
, self
).serialize(with_witness
=True)
738 # Calculate the BIP 152-compact blocks shortid for a given transaction hash
739 def calculate_shortid(k0
, k1
, tx_hash
):
740 expected_shortid
= siphash256(k0
, k1
, tx_hash
)
741 expected_shortid
&= 0x0000ffffffffffff
742 return expected_shortid
744 # This version gets rid of the array lengths, and reinterprets the differential
745 # encoding into indices that can be used for lookup.
746 class HeaderAndShortIDs():
747 def __init__(self
, p2pheaders_and_shortids
= None):
748 self
.header
= CBlockHeader()
751 self
.prefilled_txn
= []
752 self
.use_witness
= False
754 if p2pheaders_and_shortids
!= None:
755 self
.header
= p2pheaders_and_shortids
.header
756 self
.nonce
= p2pheaders_and_shortids
.nonce
757 self
.shortids
= p2pheaders_and_shortids
.shortids
759 for x
in p2pheaders_and_shortids
.prefilled_txn
:
760 self
.prefilled_txn
.append(PrefilledTransaction(x
.index
+ last_index
+ 1, x
.tx
))
761 last_index
= self
.prefilled_txn
[-1].index
765 ret
= P2PHeaderAndShortWitnessIDs()
767 ret
= P2PHeaderAndShortIDs()
768 ret
.header
= self
.header
769 ret
.nonce
= self
.nonce
770 ret
.shortids_length
= len(self
.shortids
)
771 ret
.shortids
= self
.shortids
772 ret
.prefilled_txn_length
= len(self
.prefilled_txn
)
773 ret
.prefilled_txn
= []
775 for x
in self
.prefilled_txn
:
776 ret
.prefilled_txn
.append(PrefilledTransaction(x
.index
- last_index
- 1, x
.tx
))
780 def get_siphash_keys(self
):
781 header_nonce
= self
.header
.serialize()
782 header_nonce
+= struct
.pack("<Q", self
.nonce
)
783 hash_header_nonce_as_str
= sha256(header_nonce
)
784 key0
= struct
.unpack("<Q", hash_header_nonce_as_str
[0:8])[0]
785 key1
= struct
.unpack("<Q", hash_header_nonce_as_str
[8:16])[0]
786 return [ key0
, key1
]
788 # Version 2 compact blocks use wtxid in shortids (rather than txid)
789 def initialize_from_block(self
, block
, nonce
=0, prefill_list
= [0], use_witness
= False):
790 self
.header
= CBlockHeader(block
)
792 self
.prefilled_txn
= [ PrefilledTransaction(i
, block
.vtx
[i
]) for i
in prefill_list
]
794 self
.use_witness
= use_witness
795 [k0
, k1
] = self
.get_siphash_keys()
796 for i
in range(len(block
.vtx
)):
797 if i
not in prefill_list
:
798 tx_hash
= block
.vtx
[i
].sha256
800 tx_hash
= block
.vtx
[i
].calc_sha256(with_witness
=True)
801 self
.shortids
.append(calculate_shortid(k0
, k1
, tx_hash
))
804 return "HeaderAndShortIDs(header=%s, nonce=%d, shortids=%s, prefilledtxn=%s" % (repr(self
.header
), self
.nonce
, repr(self
.shortids
), repr(self
.prefilled_txn
))
807 class BlockTransactionsRequest():
809 def __init__(self
, blockhash
=0, indexes
= None):
810 self
.blockhash
= blockhash
811 self
.indexes
= indexes
if indexes
!= None else []
813 def deserialize(self
, f
):
814 self
.blockhash
= deser_uint256(f
)
815 indexes_length
= deser_compact_size(f
)
816 for i
in range(indexes_length
):
817 self
.indexes
.append(deser_compact_size(f
))
821 r
+= ser_uint256(self
.blockhash
)
822 r
+= ser_compact_size(len(self
.indexes
))
823 for x
in self
.indexes
:
824 r
+= ser_compact_size(x
)
827 # helper to set the differentially encoded indexes from absolute ones
828 def from_absolute(self
, absolute_indexes
):
831 for x
in absolute_indexes
:
832 self
.indexes
.append(x
-last_index
-1)
835 def to_absolute(self
):
836 absolute_indexes
= []
838 for x
in self
.indexes
:
839 absolute_indexes
.append(x
+last_index
+1)
840 last_index
= absolute_indexes
[-1]
841 return absolute_indexes
844 return "BlockTransactionsRequest(hash=%064x indexes=%s)" % (self
.blockhash
, repr(self
.indexes
))
847 class BlockTransactions():
849 def __init__(self
, blockhash
=0, transactions
= None):
850 self
.blockhash
= blockhash
851 self
.transactions
= transactions
if transactions
!= None else []
853 def deserialize(self
, f
):
854 self
.blockhash
= deser_uint256(f
)
855 self
.transactions
= deser_vector(f
, CTransaction
)
857 def serialize(self
, with_witness
=False):
859 r
+= ser_uint256(self
.blockhash
)
861 r
+= ser_vector(self
.transactions
, "serialize_with_witness")
863 r
+= ser_vector(self
.transactions
)
867 return "BlockTransactions(hash=%064x transactions=%s)" % (self
.blockhash
, repr(self
.transactions
))
870 # Objects that correspond to messages on the wire
875 self
.nVersion
= MY_VERSION
876 self
.nServices
= NODE_NETWORK | NODE_WITNESS
877 self
.nTime
= int(time
.time())
878 self
.addrTo
= CAddress()
879 self
.addrFrom
= CAddress()
880 self
.nNonce
= random
.getrandbits(64)
881 self
.strSubVer
= MY_SUBVERSION
882 self
.nStartingHeight
= -1
883 self
.nRelay
= MY_RELAY
885 def deserialize(self
, f
):
886 self
.nVersion
= struct
.unpack("<i", f
.read(4))[0]
887 if self
.nVersion
== 10300:
889 self
.nServices
= struct
.unpack("<Q", f
.read(8))[0]
890 self
.nTime
= struct
.unpack("<q", f
.read(8))[0]
891 self
.addrTo
= CAddress()
892 self
.addrTo
.deserialize(f
)
894 if self
.nVersion
>= 106:
895 self
.addrFrom
= CAddress()
896 self
.addrFrom
.deserialize(f
)
897 self
.nNonce
= struct
.unpack("<Q", f
.read(8))[0]
898 self
.strSubVer
= deser_string(f
)
902 self
.strSubVer
= None
903 self
.nStartingHeight
= None
905 if self
.nVersion
>= 209:
906 self
.nStartingHeight
= struct
.unpack("<i", f
.read(4))[0]
908 self
.nStartingHeight
= None
910 if self
.nVersion
>= 70001:
911 # Relay field is optional for version 70001 onwards
913 self
.nRelay
= struct
.unpack("<b", f
.read(1))[0]
921 r
+= struct
.pack("<i", self
.nVersion
)
922 r
+= struct
.pack("<Q", self
.nServices
)
923 r
+= struct
.pack("<q", self
.nTime
)
924 r
+= self
.addrTo
.serialize()
925 r
+= self
.addrFrom
.serialize()
926 r
+= struct
.pack("<Q", self
.nNonce
)
927 r
+= ser_string(self
.strSubVer
)
928 r
+= struct
.pack("<i", self
.nStartingHeight
)
929 r
+= struct
.pack("<b", self
.nRelay
)
933 return 'msg_version(nVersion=%i nServices=%i nTime=%s addrTo=%s addrFrom=%s nNonce=0x%016X strSubVer=%s nStartingHeight=%i nRelay=%i)' \
934 % (self
.nVersion
, self
.nServices
, time
.ctime(self
.nTime
),
935 repr(self
.addrTo
), repr(self
.addrFrom
), self
.nNonce
,
936 self
.strSubVer
, self
.nStartingHeight
, self
.nRelay
)
945 def deserialize(self
, f
):
952 return "msg_verack()"
961 def deserialize(self
, f
):
962 self
.addrs
= deser_vector(f
, CAddress
)
965 return ser_vector(self
.addrs
)
968 return "msg_addr(addrs=%s)" % (repr(self
.addrs
))
974 def __init__(self
, inv
=None):
980 def deserialize(self
, f
):
981 self
.inv
= deser_vector(f
, CInv
)
984 return ser_vector(self
.inv
)
987 return "msg_inv(inv=%s)" % (repr(self
.inv
))
993 def __init__(self
, inv
=None):
994 self
.inv
= inv
if inv
!= None else []
996 def deserialize(self
, f
):
997 self
.inv
= deser_vector(f
, CInv
)
1000 return ser_vector(self
.inv
)
1003 return "msg_getdata(inv=%s)" % (repr(self
.inv
))
1006 class msg_getblocks():
1007 command
= b
"getblocks"
1010 self
.locator
= CBlockLocator()
1013 def deserialize(self
, f
):
1014 self
.locator
= CBlockLocator()
1015 self
.locator
.deserialize(f
)
1016 self
.hashstop
= deser_uint256(f
)
1018 def serialize(self
):
1020 r
+= self
.locator
.serialize()
1021 r
+= ser_uint256(self
.hashstop
)
1025 return "msg_getblocks(locator=%s hashstop=%064x)" \
1026 % (repr(self
.locator
), self
.hashstop
)
1032 def __init__(self
, tx
=CTransaction()):
1035 def deserialize(self
, f
):
1036 self
.tx
.deserialize(f
)
1038 def serialize(self
):
1039 return self
.tx
.serialize_without_witness()
1042 return "msg_tx(tx=%s)" % (repr(self
.tx
))
1044 class msg_witness_tx(msg_tx
):
1046 def serialize(self
):
1047 return self
.tx
.serialize_with_witness()
1053 def __init__(self
, block
=None):
1055 self
.block
= CBlock()
1059 def deserialize(self
, f
):
1060 self
.block
.deserialize(f
)
1062 def serialize(self
):
1063 return self
.block
.serialize()
1066 return "msg_block(block=%s)" % (repr(self
.block
))
1068 # for cases where a user needs tighter control over what is sent over the wire
1069 # note that the user must supply the name of the command, and the data
1070 class msg_generic():
1071 def __init__(self
, command
, data
=None):
1072 self
.command
= command
1075 def serialize(self
):
1079 return "msg_generic()"
1081 class msg_witness_block(msg_block
):
1083 def serialize(self
):
1084 r
= self
.block
.serialize(with_witness
=True)
1087 class msg_getaddr():
1088 command
= b
"getaddr"
1093 def deserialize(self
, f
):
1096 def serialize(self
):
1100 return "msg_getaddr()"
1106 def __init__(self
, nonce
=0):
1109 def deserialize(self
, f
):
1110 self
.nonce
= struct
.unpack("<Q", f
.read(8))[0]
1112 def serialize(self
):
1114 r
+= struct
.pack("<Q", self
.nonce
)
1118 return "msg_ping(nonce=%08x)" % self
.nonce
1124 def __init__(self
, nonce
=0):
1127 def deserialize(self
, f
):
1128 self
.nonce
= struct
.unpack("<Q", f
.read(8))[0]
1130 def serialize(self
):
1132 r
+= struct
.pack("<Q", self
.nonce
)
1136 return "msg_pong(nonce=%08x)" % self
.nonce
1139 class msg_mempool():
1140 command
= b
"mempool"
1145 def deserialize(self
, f
):
1148 def serialize(self
):
1152 return "msg_mempool()"
1154 class msg_sendheaders():
1155 command
= b
"sendheaders"
1160 def deserialize(self
, f
):
1163 def serialize(self
):
1167 return "msg_sendheaders()"
1170 # getheaders message has
1173 # hash_stop (hash of last desired block header, 0 to get as many as possible)
1174 class msg_getheaders():
1175 command
= b
"getheaders"
1178 self
.locator
= CBlockLocator()
1181 def deserialize(self
, f
):
1182 self
.locator
= CBlockLocator()
1183 self
.locator
.deserialize(f
)
1184 self
.hashstop
= deser_uint256(f
)
1186 def serialize(self
):
1188 r
+= self
.locator
.serialize()
1189 r
+= ser_uint256(self
.hashstop
)
1193 return "msg_getheaders(locator=%s, stop=%064x)" \
1194 % (repr(self
.locator
), self
.hashstop
)
1197 # headers message has
1198 # <count> <vector of block headers>
1199 class msg_headers():
1200 command
= b
"headers"
1202 def __init__(self
, headers
=None):
1203 self
.headers
= headers
if headers
is not None else []
1205 def deserialize(self
, f
):
1206 # comment in bitcoind indicates these should be deserialized as blocks
1207 blocks
= deser_vector(f
, CBlock
)
1209 self
.headers
.append(CBlockHeader(x
))
1211 def serialize(self
):
1212 blocks
= [CBlock(x
) for x
in self
.headers
]
1213 return ser_vector(blocks
)
1216 return "msg_headers(headers=%s)" % repr(self
.headers
)
1221 REJECT_MALFORMED
= 1
1229 def deserialize(self
, f
):
1230 self
.message
= deser_string(f
)
1231 self
.code
= struct
.unpack("<B", f
.read(1))[0]
1232 self
.reason
= deser_string(f
)
1233 if (self
.code
!= self
.REJECT_MALFORMED
and
1234 (self
.message
== b
"block" or self
.message
== b
"tx")):
1235 self
.data
= deser_uint256(f
)
1237 def serialize(self
):
1238 r
= ser_string(self
.message
)
1239 r
+= struct
.pack("<B", self
.code
)
1240 r
+= ser_string(self
.reason
)
1241 if (self
.code
!= self
.REJECT_MALFORMED
and
1242 (self
.message
== b
"block" or self
.message
== b
"tx")):
1243 r
+= ser_uint256(self
.data
)
1247 return "msg_reject: %s %d %s [%064x]" \
1248 % (self
.message
, self
.code
, self
.reason
, self
.data
)
1250 class msg_feefilter():
1251 command
= b
"feefilter"
1253 def __init__(self
, feerate
=0):
1254 self
.feerate
= feerate
1256 def deserialize(self
, f
):
1257 self
.feerate
= struct
.unpack("<Q", f
.read(8))[0]
1259 def serialize(self
):
1261 r
+= struct
.pack("<Q", self
.feerate
)
1265 return "msg_feefilter(feerate=%08x)" % self
.feerate
1267 class msg_sendcmpct():
1268 command
= b
"sendcmpct"
1271 self
.announce
= False
1274 def deserialize(self
, f
):
1275 self
.announce
= struct
.unpack("<?", f
.read(1))[0]
1276 self
.version
= struct
.unpack("<Q", f
.read(8))[0]
1278 def serialize(self
):
1280 r
+= struct
.pack("<?", self
.announce
)
1281 r
+= struct
.pack("<Q", self
.version
)
1285 return "msg_sendcmpct(announce=%s, version=%lu)" % (self
.announce
, self
.version
)
1287 class msg_cmpctblock():
1288 command
= b
"cmpctblock"
1290 def __init__(self
, header_and_shortids
= None):
1291 self
.header_and_shortids
= header_and_shortids
1293 def deserialize(self
, f
):
1294 self
.header_and_shortids
= P2PHeaderAndShortIDs()
1295 self
.header_and_shortids
.deserialize(f
)
1297 def serialize(self
):
1299 r
+= self
.header_and_shortids
.serialize()
1303 return "msg_cmpctblock(HeaderAndShortIDs=%s)" % repr(self
.header_and_shortids
)
1305 class msg_getblocktxn():
1306 command
= b
"getblocktxn"
1309 self
.block_txn_request
= None
1311 def deserialize(self
, f
):
1312 self
.block_txn_request
= BlockTransactionsRequest()
1313 self
.block_txn_request
.deserialize(f
)
1315 def serialize(self
):
1317 r
+= self
.block_txn_request
.serialize()
1321 return "msg_getblocktxn(block_txn_request=%s)" % (repr(self
.block_txn_request
))
1323 class msg_blocktxn():
1324 command
= b
"blocktxn"
1327 self
.block_transactions
= BlockTransactions()
1329 def deserialize(self
, f
):
1330 self
.block_transactions
.deserialize(f
)
1332 def serialize(self
):
1334 r
+= self
.block_transactions
.serialize()
1338 return "msg_blocktxn(block_transactions=%s)" % (repr(self
.block_transactions
))
1340 class msg_witness_blocktxn(msg_blocktxn
):
1341 def serialize(self
):
1343 r
+= self
.block_transactions
.serialize(with_witness
=True)
1347 """Callback and helper functions for P2P connection to a bitcoind node.
1349 Individual testcases should subclass this and override the on_* methods
1350 if they want to alter message handling behaviour."""
1352 # Track whether we have a P2P connection open to the node
1353 self
.connected
= False
1354 self
.connection
= None
1356 # Track number of messages of each type received and the most recent
1357 # message of each type
1358 self
.message_count
= defaultdict(int)
1359 self
.last_message
= {}
1361 # A count of the number of ping messages we've sent to the node
1362 self
.ping_counter
= 1
1364 # Message receiving methods
1366 def deliver(self
, conn
, message
):
1367 """Receive message and dispatch message to appropriate callback.
1369 We keep a count of how many of each message type has been received
1370 and the most recent message of each type."""
1373 command
= message
.command
.decode('ascii')
1374 self
.message_count
[command
] += 1
1375 self
.last_message
[command
] = message
1376 getattr(self
, 'on_' + command
)(conn
, message
)
1378 print("ERROR delivering %s (%s)" % (repr(message
),
1382 # Callback methods. Can be overridden by subclasses in individual test
1383 # cases to provide custom message handling behaviour.
1385 def on_open(self
, conn
):
1386 self
.connected
= True
1388 def on_close(self
, conn
):
1389 self
.connected
= False
1390 self
.connection
= None
1392 def on_addr(self
, conn
, message
): pass
1393 def on_block(self
, conn
, message
): pass
1394 def on_blocktxn(self
, conn
, message
): pass
1395 def on_cmpctblock(self
, conn
, message
): pass
1396 def on_feefilter(self
, conn
, message
): pass
1397 def on_getaddr(self
, conn
, message
): pass
1398 def on_getblocks(self
, conn
, message
): pass
1399 def on_getblocktxn(self
, conn
, message
): pass
1400 def on_getdata(self
, conn
, message
): pass
1401 def on_getheaders(self
, conn
, message
): pass
1402 def on_headers(self
, conn
, message
): pass
1403 def on_mempool(self
, conn
): pass
1404 def on_pong(self
, conn
, message
): pass
1405 def on_reject(self
, conn
, message
): pass
1406 def on_sendcmpct(self
, conn
, message
): pass
1407 def on_sendheaders(self
, conn
, message
): pass
1408 def on_tx(self
, conn
, message
): pass
1410 def on_inv(self
, conn
, message
):
1411 want
= msg_getdata()
1412 for i
in message
.inv
:
1416 conn
.send_message(want
)
1418 def on_ping(self
, conn
, message
):
1419 conn
.send_message(msg_pong(message
.nonce
))
1421 def on_verack(self
, conn
, message
):
1422 conn
.ver_recv
= conn
.ver_send
1423 self
.verack_received
= True
1425 def on_version(self
, conn
, message
):
1426 if message
.nVersion
>= 209:
1427 conn
.send_message(msg_verack())
1428 conn
.ver_send
= min(MY_VERSION
, message
.nVersion
)
1429 if message
.nVersion
< 209:
1430 conn
.ver_recv
= conn
.ver_send
1431 conn
.nServices
= message
.nServices
1433 # Connection helper methods
1435 def add_connection(self
, conn
):
1436 self
.connection
= conn
1438 def wait_for_disconnect(self
, timeout
=60):
1439 test_function
= lambda: not self
.connected
1440 wait_until(test_function
, timeout
=timeout
, lock
=mininode_lock
)
1442 # Message receiving helper methods
1444 def wait_for_block(self
, blockhash
, timeout
=60):
1445 test_function
= lambda: self
.last_message
.get("block") and self
.last_message
["block"].block
.rehash() == blockhash
1446 wait_until(test_function
, timeout
=timeout
, lock
=mininode_lock
)
1448 def wait_for_getdata(self
, timeout
=60):
1449 test_function
= lambda: self
.last_message
.get("getdata")
1450 wait_until(test_function
, timeout
=timeout
, lock
=mininode_lock
)
1452 def wait_for_getheaders(self
, timeout
=60):
1453 test_function
= lambda: self
.last_message
.get("getheaders")
1454 wait_until(test_function
, timeout
=timeout
, lock
=mininode_lock
)
1456 def wait_for_inv(self
, expected_inv
, timeout
=60):
1457 """Waits for an INV message and checks that the first inv object in the message was as expected."""
1458 if len(expected_inv
) > 1:
1459 raise NotImplementedError("wait_for_inv() will only verify the first inv object")
1460 test_function
= lambda: self
.last_message
.get("inv") and \
1461 self
.last_message
["inv"].inv
[0].type == expected_inv
[0].type and \
1462 self
.last_message
["inv"].inv
[0].hash == expected_inv
[0].hash
1463 wait_until(test_function
, timeout
=timeout
, lock
=mininode_lock
)
1465 def wait_for_verack(self
, timeout
=60):
1466 test_function
= lambda: self
.message_count
["verack"]
1467 wait_until(test_function
, timeout
=timeout
, lock
=mininode_lock
)
1469 # Message sending helper functions
1471 def send_message(self
, message
):
1473 self
.connection
.send_message(message
)
1475 logger
.error("Cannot send message. No connection to node!")
1477 def send_and_ping(self
, message
):
1478 self
.send_message(message
)
1479 self
.sync_with_ping()
1481 # Sync up with the node
1482 def sync_with_ping(self
, timeout
=60):
1483 self
.send_message(msg_ping(nonce
=self
.ping_counter
))
1484 test_function
= lambda: self
.last_message
.get("pong") and self
.last_message
["pong"].nonce
== self
.ping_counter
1485 wait_until(test_function
, timeout
=timeout
, lock
=mininode_lock
)
1486 self
.ping_counter
+= 1
1488 class NodeConn(asyncore
.dispatcher
):
1489 """The actual NodeConn class
1491 This class provides an interface for a p2p connection to a specified node."""
1493 b
"version": msg_version
,
1494 b
"verack": msg_verack
,
1497 b
"getdata": msg_getdata
,
1498 b
"getblocks": msg_getblocks
,
1500 b
"block": msg_block
,
1501 b
"getaddr": msg_getaddr
,
1504 b
"headers": msg_headers
,
1505 b
"getheaders": msg_getheaders
,
1506 b
"reject": msg_reject
,
1507 b
"mempool": msg_mempool
,
1508 b
"feefilter": msg_feefilter
,
1509 b
"sendheaders": msg_sendheaders
,
1510 b
"sendcmpct": msg_sendcmpct
,
1511 b
"cmpctblock": msg_cmpctblock
,
1512 b
"getblocktxn": msg_getblocktxn
,
1513 b
"blocktxn": msg_blocktxn
1516 "mainnet": b
"\xf9\xbe\xb4\xd9", # mainnet
1517 "testnet3": b
"\x0b\x11\x09\x07", # testnet3
1518 "regtest": b
"\xfa\xbf\xb5\xda", # regtest
1521 def __init__(self
, dstaddr
, dstport
, rpc
, callback
, net
="regtest", services
=NODE_NETWORK|NODE_WITNESS
, send_version
=True):
1522 asyncore
.dispatcher
.__init
__(self
, map=mininode_socket_map
)
1523 self
.dstaddr
= dstaddr
1524 self
.dstport
= dstport
1525 self
.create_socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
1526 self
.socket
.setsockopt(socket
.IPPROTO_TCP
, socket
.TCP_NODELAY
, 1)
1532 self
.state
= "connecting"
1535 self
.disconnect
= False
1539 # stuff version msg into sendbuf
1541 vt
.nServices
= services
1542 vt
.addrTo
.ip
= self
.dstaddr
1543 vt
.addrTo
.port
= self
.dstport
1544 vt
.addrFrom
.ip
= "0.0.0.0"
1545 vt
.addrFrom
.port
= 0
1546 self
.send_message(vt
, True)
1548 logger
.info('Connecting to Bitcoin Node: %s:%d' % (self
.dstaddr
, self
.dstport
))
1551 self
.connect((dstaddr
, dstport
))
1556 def handle_connect(self
):
1557 if self
.state
!= "connected":
1558 logger
.debug("Connected & Listening: %s:%d" % (self
.dstaddr
, self
.dstport
))
1559 self
.state
= "connected"
1560 self
.cb
.on_open(self
)
1562 def handle_close(self
):
1563 logger
.debug("Closing connection to: %s:%d" % (self
.dstaddr
, self
.dstport
))
1564 self
.state
= "closed"
1571 self
.cb
.on_close(self
)
1573 def handle_read(self
):
1584 pre_connection
= self
.state
== "connecting"
1585 length
= len(self
.sendbuf
)
1586 return (length
> 0 or pre_connection
)
1588 def handle_write(self
):
1590 # asyncore does not expose socket connection, only the first read/write
1591 # event, thus we must check connection manually here to know when we
1593 if self
.state
== "connecting":
1594 self
.handle_connect()
1595 if not self
.writable():
1599 sent
= self
.send(self
.sendbuf
)
1603 self
.sendbuf
= self
.sendbuf
[sent
:]
1608 if len(self
.recvbuf
) < 4:
1610 if self
.recvbuf
[:4] != self
.MAGIC_BYTES
[self
.network
]:
1611 raise ValueError("got garbage %s" % repr(self
.recvbuf
))
1612 if self
.ver_recv
< 209:
1613 if len(self
.recvbuf
) < 4 + 12 + 4:
1615 command
= self
.recvbuf
[4:4+12].split(b
"\x00", 1)[0]
1616 msglen
= struct
.unpack("<i", self
.recvbuf
[4+12:4+12+4])[0]
1618 if len(self
.recvbuf
) < 4 + 12 + 4 + msglen
:
1620 msg
= self
.recvbuf
[4+12+4:4+12+4+msglen
]
1621 self
.recvbuf
= self
.recvbuf
[4+12+4+msglen
:]
1623 if len(self
.recvbuf
) < 4 + 12 + 4 + 4:
1625 command
= self
.recvbuf
[4:4+12].split(b
"\x00", 1)[0]
1626 msglen
= struct
.unpack("<i", self
.recvbuf
[4+12:4+12+4])[0]
1627 checksum
= self
.recvbuf
[4+12+4:4+12+4+4]
1628 if len(self
.recvbuf
) < 4 + 12 + 4 + 4 + msglen
:
1630 msg
= self
.recvbuf
[4+12+4+4:4+12+4+4+msglen
]
1633 if checksum
!= h
[:4]:
1634 raise ValueError("got bad checksum " + repr(self
.recvbuf
))
1635 self
.recvbuf
= self
.recvbuf
[4+12+4+4+msglen
:]
1636 if command
in self
.messagemap
:
1638 t
= self
.messagemap
[command
]()
1642 logger
.warning("Received unknown command from %s:%d: '%s' %s" % (self
.dstaddr
, self
.dstport
, command
, repr(msg
)))
1643 raise ValueError("Unknown command: '%s'" % (command
))
1644 except Exception as e
:
1645 logger
.exception('got_data:', repr(e
))
1648 def send_message(self
, message
, pushbuf
=False):
1649 if self
.state
!= "connected" and not pushbuf
:
1650 raise IOError('Not connected, no pushbuf')
1651 self
._log
_message
("send", message
)
1652 command
= message
.command
1653 data
= message
.serialize()
1654 tmsg
= self
.MAGIC_BYTES
[self
.network
]
1656 tmsg
+= b
"\x00" * (12 - len(command
))
1657 tmsg
+= struct
.pack("<I", len(data
))
1658 if self
.ver_send
>= 209:
1664 if (len(self
.sendbuf
) == 0 and not pushbuf
):
1666 sent
= self
.send(tmsg
)
1667 self
.sendbuf
= tmsg
[sent
:]
1668 except BlockingIOError
:
1671 self
.sendbuf
+= tmsg
1672 self
.last_sent
= time
.time()
1674 def got_message(self
, message
):
1675 if self
.last_sent
+ 30 * 60 < time
.time():
1676 self
.send_message(self
.messagemap
[b
'ping']())
1677 self
._log
_message
("receive", message
)
1678 self
.cb
.deliver(self
, message
)
1680 def _log_message(self
, direction
, msg
):
1681 if direction
== "send":
1682 log_message
= "Send message to "
1683 elif direction
== "receive":
1684 log_message
= "Received message from "
1685 log_message
+= "%s:%d: %s" % (self
.dstaddr
, self
.dstport
, repr(msg
)[:500])
1686 if len(log_message
) > 500:
1687 log_message
+= "... (msg truncated)"
1688 logger
.debug(log_message
)
1690 def disconnect_node(self
):
1691 self
.disconnect
= True
1694 class NetworkThread(Thread
):
1696 while mininode_socket_map
:
1697 # We check for whether to disconnect outside of the asyncore
1698 # loop to workaround the behavior of asyncore when using
1701 for fd
, obj
in mininode_socket_map
.items():
1703 disconnected
.append(obj
)
1704 [ obj
.handle_close() for obj
in disconnected
]
1705 asyncore
.loop(0.1, use_poll
=True, map=mininode_socket_map
, count
=1)
1706 logger
.debug("Network thread closing")